1 /*
<lambda>null2 * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 @file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
6
7 package kotlinx.atomicfu.transformer
8
9 import org.objectweb.asm.*
10 import org.objectweb.asm.ClassReader.*
11 import org.objectweb.asm.Opcodes.*
12 import org.objectweb.asm.Type.*
13 import org.objectweb.asm.commons.*
14 import org.objectweb.asm.commons.InstructionAdapter.*
15 import org.objectweb.asm.tree.*
16 import java.io.*
17 import java.net.*
18 import java.util.*
19
20 class TypeInfo(val fuType: Type, val originalType: Type, val transformedType: Type)
21
22 private const val AFU_PKG = "kotlinx/atomicfu"
23 private const val JUCA_PKG = "java/util/concurrent/atomic"
24 private const val JLI_PKG = "java/lang/invoke"
25 private const val ATOMIC = "atomic"
26
27 private const val TRACE = "Trace"
28 private const val TRACE_BASE = "TraceBase"
29 private const val TRACE_FORMAT = "TraceFormat"
30
31 private val INT_ARRAY_TYPE = getType("[I")
32 private val LONG_ARRAY_TYPE = getType("[J")
33 private val BOOLEAN_ARRAY_TYPE = getType("[Z")
34 private val REF_ARRAY_TYPE = getType("[Ljava/lang/Object;")
35 private val REF_TYPE = getType("L$AFU_PKG/AtomicRef;")
36 private val ATOMIC_ARRAY_TYPE = getType("L$AFU_PKG/AtomicArray;")
37
38 private val AFU_CLASSES: Map<String, TypeInfo> = mapOf(
39 "$AFU_PKG/AtomicInt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), INT_TYPE, INT_TYPE),
40 "$AFU_PKG/AtomicLong" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongFieldUpdater"), LONG_TYPE, LONG_TYPE),
41 "$AFU_PKG/AtomicRef" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceFieldUpdater"), OBJECT_TYPE, OBJECT_TYPE),
42 "$AFU_PKG/AtomicBoolean" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), BOOLEAN_TYPE, INT_TYPE),
43
44 "$AFU_PKG/AtomicIntArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), INT_ARRAY_TYPE, INT_ARRAY_TYPE),
45 "$AFU_PKG/AtomicLongArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongArray"), LONG_ARRAY_TYPE, LONG_ARRAY_TYPE),
46 "$AFU_PKG/AtomicBooleanArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), BOOLEAN_ARRAY_TYPE, INT_ARRAY_TYPE),
47 "$AFU_PKG/AtomicArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE),
48 "$AFU_PKG/AtomicFU_commonKt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE)
49 )
50
51 private val WRAPPER: Map<Type, String> = mapOf(
52 INT_TYPE to "java/lang/Integer",
53 LONG_TYPE to "java/lang/Long",
54 BOOLEAN_TYPE to "java/lang/Boolean"
55 )
56
57 private val ARRAY_ELEMENT_TYPE: Map<Type, Int> = mapOf(
58 INT_ARRAY_TYPE to T_INT,
59 LONG_ARRAY_TYPE to T_LONG,
60 BOOLEAN_ARRAY_TYPE to T_BOOLEAN
61 )
62
63 private val AFU_TYPES: Map<Type, TypeInfo> = AFU_CLASSES.mapKeys { getObjectType(it.key) }
64
65 private val METHOD_HANDLES = "$JLI_PKG/MethodHandles"
66 private val LOOKUP = "$METHOD_HANDLES\$Lookup"
67 private val VH_TYPE = getObjectType("$JLI_PKG/VarHandle")
68
69 private val STRING_TYPE = getObjectType("java/lang/String")
70 private val CLASS_TYPE = getObjectType("java/lang/Class")
71
prettyStrnull72 private fun String.prettyStr() = replace('/', '.')
73
74 data class MethodId(val owner: String, val name: String, val desc: String, val invokeOpcode: Int) {
75 override fun toString(): String = "${owner.prettyStr()}::$name"
76 }
77
78 private const val GET_VALUE = "getValue"
79 private const val SET_VALUE = "setValue"
80 private const val GET_SIZE = "getSize"
81
82 private const val AFU_CLS = "$AFU_PKG/AtomicFU"
83 private const val TRACE_KT = "$AFU_PKG/TraceKt"
84 private const val TRACE_BASE_CLS = "$AFU_PKG/$TRACE_BASE"
85
86 private val TRACE_BASE_TYPE = getObjectType(TRACE_BASE_CLS)
87
88 private val TRACE_APPEND = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
89 private val TRACE_APPEND_2 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
90 private val TRACE_APPEND_3 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
91 private val TRACE_APPEND_4 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
92 private val TRACE_DEFAULT_ARGS = "I${OBJECT_TYPE.descriptor}"
93 private const val DEFAULT = "\$default"
94
95 private val TRACE_FACTORY = MethodId(TRACE_KT, TRACE, "(IL$AFU_PKG/$TRACE_FORMAT;)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
96 private val TRACE_PARTIAL_ARGS_FACTORY = MethodId(TRACE_KT, "$TRACE$DEFAULT", "(IL$AFU_PKG/$TRACE_FORMAT;$TRACE_DEFAULT_ARGS)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
97
98 private val FACTORIES: Set<MethodId> = setOf(
99 MethodId(AFU_CLS, ATOMIC, "(Ljava/lang/Object;)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
100 MethodId(AFU_CLS, ATOMIC, "(I)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
101 MethodId(AFU_CLS, ATOMIC, "(J)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
102 MethodId(AFU_CLS, ATOMIC, "(Z)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC),
103
104 MethodId("$AFU_PKG/AtomicIntArray", "<init>", "(I)V", INVOKESPECIAL),
105 MethodId("$AFU_PKG/AtomicLongArray", "<init>", "(I)V", INVOKESPECIAL),
106 MethodId("$AFU_PKG/AtomicBooleanArray", "<init>", "(I)V", INVOKESPECIAL),
107 MethodId("$AFU_PKG/AtomicArray", "<init>", "(I)V", INVOKESPECIAL),
108 MethodId("$AFU_PKG/AtomicFU_commonKt", "atomicArrayOfNulls", "(I)L$AFU_PKG/AtomicArray;", INVOKESTATIC),
109
110 MethodId(AFU_CLS, ATOMIC, "(Ljava/lang/Object;L$TRACE_BASE_CLS;)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
111 MethodId(AFU_CLS, ATOMIC, "(IL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
112 MethodId(AFU_CLS, ATOMIC, "(JL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
113 MethodId(AFU_CLS, ATOMIC, "(ZL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC),
114
115 MethodId(AFU_CLS, ATOMIC + DEFAULT, "(Ljava/lang/Object;L$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
116 MethodId(AFU_CLS, ATOMIC + DEFAULT, "(IL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
117 MethodId(AFU_CLS, ATOMIC + DEFAULT, "(JL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
118 MethodId(AFU_CLS, ATOMIC + DEFAULT, "(ZL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC)
119 )
120
containsnull121 private operator fun Int.contains(bit: Int) = this and bit != 0
122
123 private inline fun code(mv: MethodVisitor, block: InstructionAdapter.() -> Unit) {
124 block(InstructionAdapter(mv))
125 }
126
insnsnull127 private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
128 val node = MethodNode(ASM9)
129 block(InstructionAdapter(node))
130 return node.instructions
131 }
132
133 data class FieldId(val owner: String, val name: String, val desc: String) {
toStringnull134 override fun toString(): String = "${owner.prettyStr()}::$name"
135 }
136
137 class FieldInfo(
138 val fieldId: FieldId,
139 val fieldType: Type,
140 val isStatic: Boolean = false
141 ) {
142 val owner = fieldId.owner
143 val ownerType: Type = getObjectType(owner)
144 val typeInfo = AFU_CLASSES.getValue(fieldType.internalName)
145 val fuType = typeInfo.fuType
146 val isArray = typeInfo.originalType.sort == ARRAY
147
148 // state: updated during analysis
149 val accessors = mutableSetOf<MethodId>() // set of accessor method that read the corresponding atomic
150 var hasExternalAccess = false // accessed from different package
151 var hasAtomicOps = false // has atomic operations operations other than getValue/setValue
152
153 val name: String
154 get() = if (hasExternalAccess) mangleInternal(fieldId.name) else fieldId.name
155 val fuName: String
156 get() {
157 val fuName = fieldId.name + '$' + "FU"
158 return if (hasExternalAccess) mangleInternal(fuName) else fuName
159 }
160
161 val refVolatileClassName = "${owner.replace('.', '/')}$${name.capitalize()}RefVolatile"
162 val staticRefVolatileField = refVolatileClassName.substringAfterLast("/").decapitalize()
163
164 fun getPrimitiveType(vh: Boolean): Type = if (vh) typeInfo.originalType else typeInfo.transformedType
165
166 private fun mangleInternal(fieldName: String): String = "$fieldName\$internal"
167
168 override fun toString(): String = "${owner.prettyStr()}::$name"
169 }
170
171 enum class JvmVariant { FU, VH, BOTH }
172
173 class AtomicFUTransformer(
174 classpath: List<String>,
175 inputDir: File,
176 outputDir: File = inputDir,
177 var jvmVariant: JvmVariant = JvmVariant.FU
178 ) : AtomicFUTransformerBase(inputDir, outputDir) {
179
180 private val classPathLoader = URLClassLoader(
<lambda>null181 (listOf(inputDir) + (classpath.map { File(it) } - outputDir))
<lambda>null182 .map { it.toURI().toURL() }.toTypedArray()
183 )
184
185 private val fields = mutableMapOf<FieldId, FieldInfo>()
186 private val accessors = mutableMapOf<MethodId, FieldInfo>()
187 private val traceFields = mutableSetOf<FieldId>()
188 private val traceAccessors = mutableSetOf<MethodId>()
189 private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
190 private val delegatedPropertiesAccessors = mutableMapOf<FieldId, MethodId>()
191 private val removeMethods = mutableSetOf<MethodId>()
192
transformnull193 override fun transform() {
194 info("Analyzing in $inputDir")
195 val files = inputDir.walk().filter { it.isFile }.toList()
196 val needTransform = analyzeFilesForFields(files)
197 if (needTransform || outputDir == inputDir) {
198 val vh = jvmVariant == JvmVariant.VH
199 // visit method bodies for external references to fields, runs all logic, fails if anything is wrong
200 val needsTransform = analyzeFilesForRefs(files, vh)
201 // perform transformation
202 info("Transforming to $outputDir")
203 files.forEach { file ->
204 val bytes = file.readBytes()
205 val outBytes = if (file.isClassFile() && file in needsTransform) transformFile(file, bytes, vh) else bytes
206 val outFile = file.toOutputFile()
207 outFile.mkdirsAndWrite(outBytes)
208 if (jvmVariant == JvmVariant.BOTH && outBytes !== bytes) {
209 val vhBytes = transformFile(file, bytes, true)
210 val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
211 vhFile.mkdirsAndWrite(vhBytes)
212 }
213 }
214 } else {
215 info("Nothing to transform -- all classes are up to date")
216 }
217 }
218
219 // Phase 1: visit methods and fields, register all accessors, collect times
220 // Returns 'true' if any files are out of date
analyzeFilesForFieldsnull221 private fun analyzeFilesForFields(files: List<File>): Boolean {
222 var needTransform = false
223 files.forEach { file ->
224 val inpTime = file.lastModified()
225 val outTime = file.toOutputFile().lastModified()
226 if (inpTime > outTime) needTransform = true
227 if (file.isClassFile()) analyzeFileForFields(file)
228 }
229 if (lastError != null) throw TransformerException("Encountered errors while analyzing fields", lastError)
230 return needTransform
231 }
232
analyzeFileForFieldsnull233 private fun analyzeFileForFields(file: File) {
234 file.inputStream().use { ClassReader(it).accept(FieldsCollectorCV(), SKIP_FRAMES) }
235 }
236
237 // Phase2: visit method bodies for external references to fields and
238 // run method analysis in "analysisMode" to see which fields need AU/VH generated for them
239 // Returns a set of files that need transformation
analyzeFilesForRefsnull240 private fun analyzeFilesForRefs(files: List<File>, vh: Boolean): Set<File> {
241 val result = HashSet<File>()
242 files.forEach { file ->
243 if (file.isClassFile() && analyzeFileForRefs(file, vh)) result += file
244 }
245 // Batch analyze all files, report all errors, bail out only at the end
246 if (lastError != null) throw TransformerException("Encountered errors while analyzing references", lastError)
247 return result
248 }
249
analyzeFileForRefsnull250 private fun analyzeFileForRefs(file: File, vh: Boolean): Boolean =
251 file.inputStream().use { input ->
252 transformed = false // clear global "transformed" flag
253 val cv = TransformerCV(null, vh, analyzePhase2 = true)
254 try {
255 ClassReader(input).accept(cv, SKIP_FRAMES)
256 } catch (e: Exception) {
257 error("Failed to analyze: $e", cv.sourceInfo)
258 e.printStackTrace(System.out)
259 if (lastError == null) lastError = e
260 }
261 transformed // true for classes that need transformation
262 }
263
264 // Phase 3: Transform file (only called for files that need to be transformed)
265 // Returns updated byte array for class data
transformFilenull266 private fun transformFile(file: File, bytes: ByteArray, vh: Boolean): ByteArray {
267 transformed = false // clear global "transformed" flag
268 val cw = CW()
269 val cv = TransformerCV(cw, vh, analyzePhase2 = false)
270 try {
271 ClassReader(ByteArrayInputStream(bytes)).accept(cv, SKIP_FRAMES)
272 } catch (e: Throwable) {
273 error("Failed to transform: $e", cv.sourceInfo)
274 e.printStackTrace(System.out)
275 if (lastError == null) lastError = e
276 }
277 if (!transformed) error("Invoked transformFile on a file that does not need transformation: $file")
278 if (lastError != null) throw TransformerException("Encountered errors while transforming: $file", lastError)
279 info("Transformed $file")
280 return cw.toByteArray() // write transformed bytes
281 }
282
283 private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM9, cv) {
284 lateinit var className: String
285
visitnull286 override fun visit(
287 version: Int,
288 access: Int,
289 name: String,
290 signature: String?,
291 superName: String?,
292 interfaces: Array<out String>?
293 ) {
294 className = name
295 super.visit(version, access, name, signature, superName, interfaces)
296 }
297 }
298
registerFieldnull299 private fun registerField(field: FieldId, fieldType: Type, isStatic: Boolean): FieldInfo {
300 val result = fields.getOrPut(field) { FieldInfo(field, fieldType, isStatic) }
301 if (result.fieldType != fieldType) abort("$field type mismatch between $fieldType and ${result.fieldType}")
302 return result
303 }
304
305 private inner class FieldsCollectorCV : CV(null) {
visitFieldnull306 override fun visitField(
307 access: Int,
308 name: String,
309 desc: String,
310 signature: String?,
311 value: Any?
312 ): FieldVisitor? {
313 val fieldType = getType(desc)
314 if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
315 val field = FieldId(className, name, desc)
316 info("$field field found")
317 if (ACC_PUBLIC in access) error("$field field cannot be public")
318 if (ACC_FINAL !in access) error("$field field must be final")
319 registerField(field, fieldType, (ACC_STATIC in access))
320 }
321 return null
322 }
323
visitMethodnull324 override fun visitMethod(
325 access: Int,
326 name: String,
327 desc: String,
328 signature: String?,
329 exceptions: Array<out String>?
330 ): MethodVisitor? {
331 val methodType = getMethodType(desc)
332 if (methodType.argumentTypes.any { it in AFU_TYPES }) {
333 val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
334 info("$methodId method to be removed")
335 removeMethods += methodId
336 }
337 getPotentialAccessorType(access, className, methodType)?.let { onType ->
338 return AccessorCollectorMV(onType.internalName, access, name, desc, signature, exceptions)
339 }
340 if (name == "<init>" || name == "<clinit>") {
341 // check for copying atomic values into delegate fields and register potential delegate fields
342 return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
343 }
344 // collect accessors of potential delegated properties
345 if (methodType.argumentTypes.isEmpty()) {
346 return DelegatedFieldAccessorCollectorMV(className, methodType.returnType, access, name, desc, signature, exceptions)
347 }
348 return null
349 }
350 }
351
352 private inner class AccessorCollectorMV(
353 private val className: String,
354 access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
355 ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
visitEndnull356 override fun visitEnd() {
357 val insns = instructions.listUseful(4)
358 if (insns.size == 3 &&
359 insns[0].isAload(0) &&
360 insns[1].isGetField(className) &&
361 insns[2].isAreturn() ||
362 insns.size == 2 &&
363 insns[0].isGetStatic(className) &&
364 insns[1].isAreturn()
365 ) {
366 val isStatic = insns.size == 2
367 val fi = (if (isStatic) insns[0] else insns[1]) as FieldInsnNode
368 val fieldName = fi.name
369 val field = FieldId(className, fieldName, fi.desc)
370 val fieldType = getType(fi.desc)
371 val accessorMethod = MethodId(className, name, desc, accessToInvokeOpcode(access))
372 info("$field accessor $name found")
373 if (fieldType == TRACE_BASE_TYPE) {
374 traceAccessors.add(accessorMethod)
375 } else {
376 val fieldInfo = registerField(field, fieldType, isStatic)
377 fieldInfo.accessors += accessorMethod
378 accessors[accessorMethod] = fieldInfo
379 }
380 }
381 }
382 }
383
384 // returns a type on which this is a potential accessor
getPotentialAccessorTypenull385 private fun getPotentialAccessorType(access: Int, className: String, methodType: Type): Type? {
386 if (methodType.returnType !in AFU_TYPES && methodType.returnType != TRACE_BASE_TYPE) return null
387 return if (access and ACC_STATIC != 0) {
388 if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty()) {
389 // accessor for top-level atomic
390 getObjectType(className)
391 } else {
392 // accessor for top-level atomic
393 if (methodType.argumentTypes.size == 1 && methodType.argumentTypes[0].sort == OBJECT)
394 methodType.argumentTypes[0] else null
395 }
396 } else {
397 // if it not static, then it must be final
398 if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty())
399 getObjectType(className) else null
400 }
401 }
402
403 private inner class DelegatedFieldAccessorCollectorMV(
404 private val className: String, private val returnType: Type,
405 access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
406 ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
visitEndnull407 override fun visitEnd() {
408 // check for pattern of a delegated property getter
409 // getfield/getstatic a$delegate: Atomic*
410 // astore_i ...
411 // aload_i
412 // invokevirtual Atomic*.getValue()
413 // ireturn
414 var cur = instructions.first
415 while (cur != null && !(cur.isGetFieldOrGetStatic() && getType((cur as FieldInsnNode).desc) in AFU_TYPES)) {
416 cur = cur.next
417 }
418 if (cur != null && cur.next.opcode == ASTORE) {
419 val fi = cur as FieldInsnNode
420 val fieldDelegate = FieldId(className, fi.name, fi.desc)
421 val atomicType = getType(fi.desc)
422 val v = (cur.next as VarInsnNode).`var`
423 while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
424 cur = cur.next
425 }
426 val invokeVirtual = cur.next
427 if (invokeVirtual.opcode == INVOKEVIRTUAL && (invokeVirtual as MethodInsnNode).name == GET_VALUE && invokeVirtual.owner == atomicType.internalName) {
428 // followed by RETURN operation
429 val next = invokeVirtual.nextUseful
430 val ret = if (next?.opcode == CHECKCAST) next.nextUseful else next
431 if (ret != null && ret.isTypeReturn(returnType)) {
432 // register delegated property accessor
433 delegatedPropertiesAccessors[fieldDelegate] = MethodId(className, name, desc, accessToInvokeOpcode(access))
434 }
435 }
436 }
437 }
438 }
439
440 private inner class DelegateFieldsCollectorMV(
441 access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
442 ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
visitEndnull443 override fun visitEnd() {
444 // register delegate field and the corresponding original atomic field
445 // getfield a: *Atomic
446 // putfield a$delegate: *Atomic
447 instructions.forEach { insn ->
448 if (insn is FieldInsnNode) {
449 insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
450 val next = insn.next
451 (next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
452 if (getfieldId in fields && delegateFieldId in fields) {
453 // original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
454 val originalField = fields[getfieldId]!!
455 fieldDelegates[delegateFieldId] = originalField
456 }
457 }
458 }
459 }
460 if (insn is MethodInsnNode) {
461 val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
462 if (methodId in FACTORIES) {
463 (insn.nextUseful as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
464 val fieldType = getType(insn.desc).returnType
465 if (fieldType in AFU_TYPES) {
466 val isStatic = insn.nextUseful!!.opcode == PUTSTATIC
467 // delegate field is initialized by a factory invocation
468 // for volatile delegated properties store FieldInfo of the delegate field itself
469 fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType, isStatic)
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477
descToNamenull478 private fun descToName(desc: String): String = desc.drop(1).dropLast(1)
479
480 private fun FieldInsnNode.checkPutFieldOrPutStatic(): FieldId? {
481 if (opcode != PUTFIELD && opcode != PUTSTATIC) return null
482 val fieldId = FieldId(owner, name, desc)
483 return if (fieldId in fields) fieldId else null
484 }
485
checkGetFieldOrGetStaticnull486 private fun FieldInsnNode.checkGetFieldOrGetStatic(): FieldId? {
487 if (opcode != GETFIELD && opcode != GETSTATIC) return null
488 val fieldId = FieldId(owner, name, desc)
489 return if (fieldId in fields) fieldId else null
490 }
491
FieldIdnull492 private fun FieldId.isFieldDelegate() = this in fieldDelegates && delegatedPropertiesAccessors.contains(this)
493
494 private inner class TransformerCV(
495 cv: ClassVisitor?,
496 private val vh: Boolean,
497 private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
498 ) : CV(cv) {
499 private var source: String? = null
500 var sourceInfo: SourceInfo? = null
501
502 private var metadata: AnnotationNode? = null
503
504 private var originalClinit: MethodNode? = null
505 private var newClinit: MethodNode? = null
506
507 private fun newClinit() = MethodNode(ASM9, ACC_STATIC, "<clinit>", "()V", null, null)
508 fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
509
510 override fun visitSource(source: String?, debug: String?) {
511 this.source = source
512 super.visitSource(source, debug)
513 }
514
515 override fun visitField(
516 access: Int,
517 name: String,
518 desc: String,
519 signature: String?,
520 value: Any?
521 ): FieldVisitor? {
522 val fieldType = getType(desc)
523 if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
524 val fieldId = FieldId(className, name, desc)
525 // skip field delegates except volatile delegated properties (e.g. val a: Int by atomic(0))
526 if (fieldId.isFieldDelegate() && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
527 transformed = true
528 return null
529 }
530 val f = fields[fieldId]!!
531 val visibility = when {
532 f.hasExternalAccess -> ACC_PUBLIC
533 f.accessors.isEmpty() -> ACC_PRIVATE
534 else -> 0
535 }
536 val protection = ACC_SYNTHETIC or visibility or when {
537 // reference to wrapper class (primitive atomics) or reference to to j.u.c.a.Atomic*Array (atomic array)
538 f.isStatic && !vh -> ACC_STATIC or ACC_FINAL
539 // primitive type field
540 f.isStatic && vh -> ACC_STATIC
541 else -> 0
542 }
543 val primitiveType = f.getPrimitiveType(vh)
544 val fv = when {
545 // replace (top-level) Atomic*Array with (static) j.u.c.a/Atomic*Array field
546 f.isArray && !vh -> super.visitField(protection, f.name, f.fuType.descriptor, null, null)
547 // replace top-level primitive atomics with static instance of the corresponding wrapping *RefVolatile class
548 f.isStatic && !vh -> super.visitField(
549 protection,
550 f.staticRefVolatileField,
551 getObjectType(f.refVolatileClassName).descriptor,
552 null,
553 null
554 )
555 // volatile primitive type field
556 else -> super.visitField(protection or ACC_VOLATILE, f.name, primitiveType.descriptor, null, null)
557 }
558 if (vh) {
559 // VarHandle is needed for all array element accesses and for regular fields with atomic ops
560 if (f.hasAtomicOps || f.isArray) vhField(protection, f)
561 } else {
562 // FieldUpdater is not needed for arrays (they use AtomicArrays)
563 if (f.hasAtomicOps && !f.isArray) fuField(protection, f)
564 }
565 transformed = true
566 return fv
567 }
568 // skip trace field
569 if (fieldType == TRACE_BASE_TYPE) {
570 traceFields += FieldId(className, name, desc)
571 transformed = true
572 return null
573 }
574 return super.visitField(access, name, desc, signature, value)
575 }
576
577 // Generates static VarHandle field
578 private fun vhField(protection: Int, f: FieldInfo) {
579 super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, VH_TYPE.descriptor, null, null)
580 code(getOrCreateNewClinit()) {
581 if (!f.isArray) {
582 invokestatic(METHOD_HANDLES, "lookup", "()L$LOOKUP;", false)
583 aconst(getObjectType(className))
584 aconst(f.name)
585 val primitiveType = f.getPrimitiveType(vh)
586 if (primitiveType.sort == OBJECT) {
587 aconst(primitiveType)
588 } else {
589 val wrapper = WRAPPER.getValue(primitiveType)
590 getstatic(wrapper, "TYPE", CLASS_TYPE.descriptor)
591 }
592 val findVHName = if (f.isStatic) "findStaticVarHandle" else "findVarHandle"
593 invokevirtual(
594 LOOKUP, findVHName,
595 getMethodDescriptor(VH_TYPE, CLASS_TYPE, STRING_TYPE, CLASS_TYPE), false
596 )
597 putstatic(className, f.fuName, VH_TYPE.descriptor)
598 } else {
599 // create VarHandle for array
600 aconst(f.getPrimitiveType(vh))
601 invokestatic(
602 METHOD_HANDLES,
603 "arrayElementVarHandle",
604 getMethodDescriptor(VH_TYPE, CLASS_TYPE),
605 false
606 )
607 putstatic(className, f.fuName, VH_TYPE.descriptor)
608 }
609 }
610 }
611
612 // Generates static AtomicXXXFieldUpdater field
613 private fun fuField(protection: Int, f: FieldInfo) {
614 super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, f.fuType.descriptor, null, null)
615 code(getOrCreateNewClinit()) {
616 val params = mutableListOf<Type>()
617 params += CLASS_TYPE
618 if (!f.isStatic) aconst(getObjectType(className)) else aconst(getObjectType(f.refVolatileClassName))
619 val primitiveType = f.getPrimitiveType(vh)
620 if (primitiveType.sort == OBJECT) {
621 params += CLASS_TYPE
622 aconst(primitiveType)
623 }
624 params += STRING_TYPE
625 aconst(f.name)
626 invokestatic(
627 f.fuType.internalName,
628 "newUpdater",
629 getMethodDescriptor(f.fuType, *params.toTypedArray()),
630 false
631 )
632 putstatic(className, f.fuName, f.fuType.descriptor)
633 }
634 }
635
636 override fun visitMethod(
637 access: Int,
638 name: String,
639 desc: String,
640 signature: String?,
641 exceptions: Array<out String>?
642 ): MethodVisitor? {
643 val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
644 if (methodId in accessors || methodId in traceAccessors || methodId in removeMethods) {
645 // drop and skip the methods that were found in Phase 1
646 // todo: should remove those methods from kotlin metadata, too
647 transformed = true
648 return null // drop accessor
649 }
650 val sourceInfo = SourceInfo(methodId, source)
651 val superMV = if (name == "<clinit>" && desc == "()V") {
652 if (access and ACC_STATIC == 0) abort("<clinit> method not marked as static")
653 // defer writing class initialization method
654 val node = MethodNode(ASM9, access, name, desc, signature, exceptions)
655 if (originalClinit != null) abort("Multiple <clinit> methods found")
656 originalClinit = node
657 node
658 } else {
659 // write transformed method to class right away
660 super.visitMethod(access, name, desc, signature, exceptions)
661 }
662 val mv = TransformerMV(
663 sourceInfo, access, name, desc, signature, exceptions, superMV,
664 className.ownerPackageName, vh, analyzePhase2
665 )
666 this.sourceInfo = mv.sourceInfo
667 return mv
668 }
669
670 override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? {
671 if (desc == KOTLIN_METADATA_DESC) {
672 check(visible) { "Expected run-time visible $KOTLIN_METADATA_DESC annotation" }
673 check(metadata == null) { "Only one $KOTLIN_METADATA_DESC annotation is expected" }
674 return AnnotationNode(desc).also { metadata = it }
675 }
676 return super.visitAnnotation(desc, visible)
677 }
678
679 override fun visitEnd() {
680 // remove unused methods from metadata
681 metadata?.let {
682 val mt = MetadataTransformer(
683 removeFields = fields.keys + traceFields,
684 removeMethods = accessors.keys + traceAccessors + removeMethods
685 )
686 if (mt.transformMetadata(it)) transformed = true
687 if (cv != null) it.accept(cv.visitAnnotation(KOTLIN_METADATA_DESC, true))
688 }
689 if (analyzePhase2) return // nop in analyze phase
690 // collect class initialization
691 if (originalClinit != null || newClinit != null) {
692 val newClinit = newClinit
693 if (newClinit == null) {
694 // dump just original clinit
695 originalClinit!!.accept(cv)
696 } else {
697 // create dummy base code if needed
698 val originalClinit = originalClinit ?: newClinit().also {
699 code(it) { visitInsn(RETURN) }
700 }
701 // makes sure return is last useful instruction
702 val last = originalClinit.instructions.last
703 val ret = last.thisOrPrevUseful
704 if (ret == null || !ret.isReturn()) abort("Last instruction in <clinit> shall be RETURN", ret)
705 originalClinit.instructions.insertBefore(ret, newClinit.instructions)
706 originalClinit.accept(cv)
707 }
708 }
709 super.visitEnd()
710 }
711 }
712
713 private inner class TransformerMV(
714 sourceInfo: SourceInfo,
715 access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?,
716 mv: MethodVisitor?,
717 private val packageName: String,
718 private val vh: Boolean,
719 private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
720 ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
721 init {
722 this.mv = mv
723 }
724
725 val sourceInfo = sourceInfo.copy(insnList = instructions)
726
727 private var tempLocal = 0
728 private var bumpedLocals = 0
729
bumpLocalsnull730 private fun bumpLocals(n: Int) {
731 if (bumpedLocals == 0) tempLocal = maxLocals
732 while (n > bumpedLocals) bumpedLocals = n
733 maxLocals = tempLocal + bumpedLocals
734 }
735
visitMethodInsnnull736 override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
737 val methodId = MethodId(owner, name, desc, opcode)
738 val fieldInfo = accessors[methodId]
739 // compare owner packages
740 if (fieldInfo != null && methodId.owner.ownerPackageName != packageName) {
741 if (analyzePhase2) {
742 fieldInfo.hasExternalAccess = true
743 } else {
744 check(fieldInfo.hasExternalAccess) // should have been set on previous phase
745 }
746 }
747 super.visitMethodInsn(opcode, owner, name, desc, itf)
748 }
749
visitEndnull750 override fun visitEnd() {
751 // transform instructions list
752 var hasErrors = false
753 var i = instructions.first
754 while (i != null)
755 try {
756 i = transform(i)
757 } catch (e: AbortTransform) {
758 error(e.message!!, sourceInfo.copy(i = e.i))
759 i = i.next
760 hasErrors = true
761 }
762 // make sure all kotlinx/atomicfu references removed
763 removeAtomicReferencesFromLVT()
764 // save transformed method if not in analysis phase
765 if (!hasErrors && !analyzePhase2)
766 accept(mv)
767 }
768
removeAtomicReferencesFromLVTnull769 private fun removeAtomicReferencesFromLVT() =
770 localVariables?.removeIf { getType(it.desc) in AFU_TYPES }
771
checkCopyToDelegatenull772 private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
773 val fieldId = FieldId(owner, name, desc)
774 if (fieldId.isFieldDelegate()) {
775 // original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
776 val originalField = fieldDelegates[fieldId]!!
777 val getField = previous as FieldInsnNode
778 val next = this.next
779 if (!originalField.isStatic) instructions.remove(getField.previous) // no aload for static field
780 instructions.remove(getField)
781 instructions.remove(this)
782 return next
783 }
784 return null
785 }
786
787 // ld: instruction that loads atomic field (already changed to getstatic)
788 // iv: invoke virtual on the loaded atomic field (to be fixed)
fixupInvokeVirtualnull789 private fun fixupInvokeVirtual(
790 ld: FieldInsnNode,
791 onArrayElement: Boolean, // true when fixing invokeVirtual on loaded array element
792 iv: MethodInsnNode,
793 f: FieldInfo
794 ): AbstractInsnNode? {
795 check(f.isArray || !onArrayElement) { "Cannot fix array element access on non array fields" }
796 val typeInfo = if (onArrayElement) f.typeInfo else AFU_CLASSES.getValue(iv.owner)
797 if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
798 check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
799 val setInsn = iv.name == SET_VALUE
800 if (!onArrayElement) return getPureTypeField(ld, f, iv)
801 var methodType = getMethodType(iv.desc)
802 if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
803 val ret = f.typeInfo.transformedType.elementType
804 iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
805 methodType = getMethodType(iv.desc)
806 }
807 iv.name = iv.name.substring(0, 3)
808 if (!vh) {
809 // map to j.u.c.a.Atomic*Array get or set
810 iv.owner = descToName(f.fuType.descriptor)
811 iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
812 } else {
813 // map to VarHandle get or set
814 iv.owner = descToName(VH_TYPE.descriptor)
815 iv.desc = getMethodDescriptor(
816 methodType.returnType,
817 f.getPrimitiveType(vh),
818 INT_TYPE,
819 *methodType.argumentTypes
820 )
821 }
822 return iv
823 }
824 if (f.isArray && iv.name == GET_SIZE) {
825 if (!vh) {
826 // map to j.u.c.a.Atomic*Array length()
827 iv.owner = descToName(f.fuType.descriptor)
828 iv.name = "length"
829 } else {
830 // replace with arraylength of the primitive type array
831 val arrayLength = InsnNode(ARRAYLENGTH)
832 instructions.insert(ld, arrayLength)
833 // do not need varhandle
834 if (!f.isStatic) {
835 instructions.remove(ld.previous.previous)
836 instructions.remove(ld.previous)
837 } else {
838 instructions.remove(ld.previous)
839 }
840 instructions.remove(iv)
841 return arrayLength
842 }
843 return iv
844 }
845 // An operation other than getValue/setValue is used
846 if (f.isArray && iv.name == "get") { // "operator get" that retrieves array element, further ops apply to it
847 // fixup atomic operation on this array element
848 return fixupLoadedArrayElement(f, ld, iv)
849 }
850 // non-trivial atomic operation
851 check(f.isArray == onArrayElement) { "Atomic operations can be performed on atomic elements only" }
852 if (analyzePhase2) {
853 f.hasAtomicOps = true // mark the fact that non-trivial atomic op is used here
854 } else {
855 check(f.hasAtomicOps) // should have been set on previous phase
856 }
857 // update method invocation
858 if (vh) {
859 vhOperation(iv, typeInfo, f)
860 } else {
861 fuOperation(iv, typeInfo, f)
862 }
863 if (f.isStatic && !onArrayElement) {
864 if (!vh) {
865 // getstatic *RefVolatile class
866 val aload = FieldInsnNode(
867 GETSTATIC,
868 f.owner,
869 f.staticRefVolatileField,
870 getObjectType(f.refVolatileClassName).descriptor
871 )
872 instructions.insert(ld, aload)
873 }
874 return iv.next
875 }
876 if (!onArrayElement) {
877 // insert swap after field load
878 val swap = InsnNode(SWAP)
879 instructions.insert(ld, swap)
880 return swap.next
881 }
882 return iv.next
883 }
884
getPureTypeFieldnull885 private fun getPureTypeField(ld: FieldInsnNode, f: FieldInfo, iv: MethodInsnNode): AbstractInsnNode? {
886 val primitiveType = f.getPrimitiveType(vh)
887 val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
888 if (!vh && f.isStatic) {
889 val getOwnerClass = FieldInsnNode(
890 GETSTATIC,
891 f.owner,
892 f.staticRefVolatileField,
893 getObjectType(owner).descriptor
894 )
895 instructions.insert(ld, getOwnerClass)
896 }
897 instructions.remove(ld) // drop getfield/getstatic of the atomic field
898 val j = FieldInsnNode(
899 when {
900 iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
901 else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
902 }, owner, f.name, primitiveType.descriptor
903 )
904 instructions.set(iv, j) // replace invokevirtual with get/setfield
905 return j.next
906 }
907
vhOperationnull908 private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
909 val methodType = getMethodType(iv.desc)
910 val args = methodType.argumentTypes
911 iv.owner = VH_TYPE.internalName
912 val params = if (!f.isArray && !f.isStatic) mutableListOf<Type>(
913 OBJECT_TYPE,
914 *args
915 ) else if (!f.isArray && f.isStatic) mutableListOf<Type>(*args) else mutableListOf(
916 typeInfo.originalType,
917 INT_TYPE,
918 *args
919 )
920 val elementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
921 val long = elementType == LONG_TYPE
922 when (iv.name) {
923 "lazySet" -> iv.name = "setRelease"
924 "getAndIncrement" -> {
925 instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
926 params += elementType
927 iv.name = "getAndAdd"
928 }
929 "getAndDecrement" -> {
930 instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
931 params += elementType
932 iv.name = "getAndAdd"
933 }
934 "addAndGet" -> {
935 bumpLocals(if (long) 2 else 1)
936 instructions.insertBefore(iv, insns {
937 if (long) dup2() else dup()
938 store(tempLocal, elementType)
939 })
940 iv.name = "getAndAdd"
941 instructions.insert(iv, insns {
942 load(tempLocal, elementType)
943 add(elementType)
944 })
945 }
946 "incrementAndGet" -> {
947 instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
948 params += elementType
949 iv.name = "getAndAdd"
950 instructions.insert(iv, insns {
951 if (long) lconst(1) else iconst(1)
952 add(elementType)
953 })
954 }
955 "decrementAndGet" -> {
956 instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
957 params += elementType
958 iv.name = "getAndAdd"
959 instructions.insert(iv, insns {
960 if (long) lconst(-1) else iconst(-1)
961 add(elementType)
962 })
963 }
964 }
965 iv.desc = getMethodDescriptor(methodType.returnType, *params.toTypedArray())
966 }
967
fuOperationnull968 private fun fuOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
969 val methodType = getMethodType(iv.desc)
970 val originalElementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
971 val transformedElementType =
972 if (f.isArray) typeInfo.transformedType.elementType else typeInfo.transformedType
973 val trans = originalElementType != transformedElementType
974 val args = methodType.argumentTypes
975 var ret = methodType.returnType
976 if (trans) {
977 args.forEachIndexed { i, type -> if (type == originalElementType) args[i] = transformedElementType }
978 if (iv.name == "getAndSet") ret = transformedElementType
979 }
980 if (f.isArray) {
981 // map to j.u.c.a.AtomicIntegerArray method
982 iv.owner = typeInfo.fuType.internalName
983 // add int argument as element index
984 iv.desc = getMethodDescriptor(ret, INT_TYPE, *args)
985 return // array operation in this mode does not use FU field
986 }
987 iv.owner = typeInfo.fuType.internalName
988 iv.desc = getMethodDescriptor(ret, OBJECT_TYPE, *args)
989 }
990
tryEraseUncheckedCastnull991 private fun tryEraseUncheckedCast(getter: AbstractInsnNode) {
992 if (getter.next.opcode == DUP && getter.next.next.opcode == IFNONNULL) {
993 // unchecked cast upon AtomicRef var is performed
994 // erase compiler check for this var being not null:
995 // (remove all insns from ld till the non null branch label)
996 val ifnonnull = (getter.next.next as JumpInsnNode)
997 var i: AbstractInsnNode = getter.next
998 while (!(i is LabelNode && i.label == ifnonnull.label.label)) {
999 val next = i.next
1000 instructions.remove(i)
1001 i = next
1002 }
1003 }
1004 // fix for languageVersion 1.7: check if there is checkNotNull invocation
1005 var startInsn: AbstractInsnNode = getter
1006 val checkNotNull = when {
1007 getter.next?.opcode == DUP && getter.next?.next?.opcode == LDC -> FlowAnalyzer(getter.next?.next).getUncheckedCastInsn()
1008 getter.next?.opcode == ASTORE -> {
1009 startInsn = getter.next
1010 val v = (getter.next as VarInsnNode).`var`
1011 var aload: AbstractInsnNode = getter.next
1012 while (!(aload is VarInsnNode && aload.opcode == ALOAD && aload.`var` == v)) {
1013 aload = aload.next
1014 }
1015 if (aload.next.opcode == LDC) {
1016 FlowAnalyzer(aload.next).getUncheckedCastInsn()
1017 } else null
1018 }
1019 else -> null
1020 }
1021 if (checkNotNull != null) {
1022 var i: AbstractInsnNode = checkNotNull
1023 while (i != startInsn) {
1024 val prev = i.previous
1025 instructions.remove(i)
1026 i = prev
1027 }
1028 }
1029 }
1030
fixupLoadedAtomicVarnull1031 private fun fixupLoadedAtomicVar(f: FieldInfo, ld: FieldInsnNode): AbstractInsnNode? {
1032 if (f.fieldType == REF_TYPE) tryEraseUncheckedCast(ld)
1033 val j = FlowAnalyzer(ld.next).execute()
1034 return fixupOperationOnAtomicVar(j, f, ld, null)
1035 }
1036
fixupLoadedArrayElementnull1037 private fun fixupLoadedArrayElement(f: FieldInfo, ld: FieldInsnNode, getter: MethodInsnNode): AbstractInsnNode? {
1038 if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(getter)
1039 // contains array field load (in vh case: + swap and pure type array load) and array element index
1040 // this array element information is only used in case the reference to this element is stored (copied and inserted at the point of loading)
1041 val arrayElementInfo = mutableListOf<AbstractInsnNode>()
1042 if (vh) {
1043 if (!f.isStatic) {
1044 arrayElementInfo.add(ld.previous.previous) // getstatic VarHandle field
1045 arrayElementInfo.add(ld.previous) // swap
1046 } else {
1047 arrayElementInfo.add(ld.previous) // getstatic VarHandle field
1048 }
1049 }
1050 var i: AbstractInsnNode = ld
1051 while (i != getter) {
1052 arrayElementInfo.add(i)
1053 i = i.next
1054 }
1055 // start of array element operation arguments
1056 val args = getter.next
1057 // remove array element getter
1058 instructions.remove(getter)
1059 val arrayElementOperation = FlowAnalyzer(args).execute()
1060 return fixupOperationOnAtomicVar(arrayElementOperation, f, ld, arrayElementInfo)
1061 }
1062
fixupOperationOnAtomicVarnull1063 private fun fixupOperationOnAtomicVar(operation: AbstractInsnNode, f: FieldInfo, ld: FieldInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? {
1064 when (operation) {
1065 is MethodInsnNode -> {
1066 // invoked virtual method on atomic var -- fixup & done with it
1067 debug("invoke $f.${operation.name}", sourceInfo.copy(i = operation))
1068 return fixupInvokeVirtual(ld, arrayElementInfo != null, operation, f)
1069 }
1070 is VarInsnNode -> {
1071 val onArrayElement = arrayElementInfo != null
1072 check(f.isArray == onArrayElement)
1073 // was stored to local -- needs more processing:
1074 // for class fields store owner ref into the variable instead
1075 // for static fields store nothing, remove the local var
1076 val v = operation.`var`
1077 val next = operation.next
1078 if (onArrayElement) {
1079 // leave just owner class load insn on stack
1080 arrayElementInfo!!.forEach { instructions.remove(it) }
1081 } else {
1082 instructions.remove(ld)
1083 }
1084 val lv = localVar(v, operation)
1085 if (f.isStatic) instructions.remove(operation) // remove astore operation
1086 if (lv != null) {
1087 // Stored to a local variable with an entry in LVT (typically because of inline function)
1088 if (lv.desc != f.fieldType.descriptor && !onArrayElement)
1089 abort("field $f was stored to a local variable #$v \"${lv.name}\" with unexpected type: ${lv.desc}")
1090 // correct local variable descriptor
1091 lv.desc = f.ownerType.descriptor
1092 lv.signature = null
1093 // process all loads of this variable in the corresponding local variable range
1094 forVarLoads(v, lv.start, lv.end) { otherLd ->
1095 fixupLoad(f, ld, otherLd, arrayElementInfo)
1096 }
1097 } else {
1098 // Spilled temporarily to a local variable w/o an entry in LVT -> fixup only one load
1099 fixupLoad(f, ld, nextVarLoad(v, next), arrayElementInfo)
1100 }
1101 return next
1102 }
1103 else -> abort("cannot happen")
1104 }
1105 }
1106
fixupLoadnull1107 private fun fixupLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? {
1108 val next = if (arrayElementInfo != null) {
1109 fixupArrayElementLoad(f, ld, otherLd, arrayElementInfo)
1110 } else {
1111 fixupVarLoad(f, ld, otherLd)
1112 }
1113 if (f.isStatic) instructions.remove(otherLd) // remove aload instruction for static fields, nothing is stored there
1114 return next
1115 }
1116
fixupVarLoadnull1117 private fun fixupVarLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode): AbstractInsnNode? {
1118 val ldCopy = ld.clone(null) as FieldInsnNode
1119 instructions.insert(otherLd, ldCopy)
1120 return fixupLoadedAtomicVar(f, ldCopy)
1121 }
1122
fixupArrayElementLoadnull1123 private fun fixupArrayElementLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>): AbstractInsnNode? {
1124 if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(otherLd)
1125 // index instructions from array element info: drop owner class load instruction (in vh case together with preceding getting VH + swap)
1126 val index = arrayElementInfo.drop(if (vh) 3 else 1)
1127 // previously stored array element reference is loaded -> arrayElementInfo should be cloned and inserted at the point of this load
1128 // before cloning make sure that index instructions contain just loads and simple arithmetic, without any invocations and complex data flow
1129 for (indexInsn in index) {
1130 checkDataFlowComplexity(indexInsn)
1131 }
1132 // start of atomic operation arguments
1133 val args = otherLd.next
1134 val operationOnArrayElement = FlowAnalyzer(args).execute()
1135 val arrayElementInfoCopy = mutableListOf<AbstractInsnNode>()
1136 arrayElementInfo.forEach { arrayElementInfoCopy.add(it.clone(null)) }
1137 arrayElementInfoCopy.forEach { instructions.insertBefore(args, it) }
1138 return fixupOperationOnAtomicVar(operationOnArrayElement, f, ld, arrayElementInfo)
1139 }
1140
checkDataFlowComplexitynull1141 fun checkDataFlowComplexity(i: AbstractInsnNode) {
1142 when (i) {
1143 is MethodInsnNode -> {
1144 abort("No method invocations are allowed for calculation of an array element index " +
1145 "at the point of loading the reference to this element.\n" +
1146 "Extract index calculation to the local variable.", i)
1147 }
1148 is LdcInsnNode -> { /* ok loading const */ }
1149 else -> {
1150 when(i.opcode) {
1151 IADD, ISUB, IMUL, IDIV, IREM, IAND, IOR, IXOR, ISHL, ISHR, IUSHR -> { /* simple arithmetics */ }
1152 ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, ILOAD, IALOAD -> { /* int loads */ }
1153 GETFIELD, GETSTATIC -> { /* getting fields */ }
1154 else -> {
1155 abort("Complex data flow is not allowed for calculation of an array element index " +
1156 "at the point of loading the reference to this element.\n" +
1157 "Extract index calculation to the local variable.", i)
1158 }
1159 }
1160 }
1161 }
1162 }
1163
putPrimitiveTypeWrappernull1164 private fun putPrimitiveTypeWrapper(
1165 factoryInsn: MethodInsnNode,
1166 initStart: AbstractInsnNode,
1167 f: FieldInfo,
1168 next: FieldInsnNode
1169 ): AbstractInsnNode? {
1170 // generate wrapper class for static fields of primitive type
1171 val factoryArg = getMethodType(factoryInsn.desc).argumentTypes[0]
1172 generateRefVolatileClass(f, factoryArg)
1173 // remove calling atomic factory for static field and following putstatic
1174 val afterPutStatic = next.next
1175 instructions.remove(factoryInsn)
1176 instructions.remove(next)
1177 initRefVolatile(f, factoryArg, initStart, afterPutStatic)
1178 return afterPutStatic
1179 }
1180
putJucaAtomicArraynull1181 private fun putJucaAtomicArray(
1182 arrayfactoryInsn: MethodInsnNode,
1183 initStart: AbstractInsnNode,
1184 f: FieldInfo,
1185 next: FieldInsnNode
1186 ): AbstractInsnNode? {
1187 // replace with invoking j.u.c.a.Atomic*Array constructor
1188 val jucaAtomicArrayDesc = f.typeInfo.fuType.descriptor
1189 if (initStart.opcode == NEW) {
1190 // change descriptor of NEW instruction
1191 (initStart as TypeInsnNode).desc = descToName(jucaAtomicArrayDesc)
1192 arrayfactoryInsn.owner = descToName(jucaAtomicArrayDesc)
1193 } else {
1194 // array initialisation starts from bipush size, then static array factory was called (atomicArrayOfNulls)
1195 // add NEW j.u.c.a.Atomic*Array instruction
1196 val newInsn = TypeInsnNode(NEW, descToName(jucaAtomicArrayDesc))
1197 instructions.insert(initStart.previous, newInsn)
1198 instructions.insert(newInsn, InsnNode(DUP))
1199 val jucaArrayFactory =
1200 MethodInsnNode(INVOKESPECIAL, descToName(jucaAtomicArrayDesc), "<init>", "(I)V", false)
1201 instructions.set(arrayfactoryInsn, jucaArrayFactory)
1202 }
1203 //fix the following putfield
1204 next.desc = jucaAtomicArrayDesc
1205 next.name = f.name
1206 transformed = true
1207 return next.next
1208 }
1209
putPureVhArraynull1210 private fun putPureVhArray(
1211 arrayFactoryInsn: MethodInsnNode,
1212 initStart: AbstractInsnNode,
1213 f: FieldInfo,
1214 next: FieldInsnNode
1215 ): AbstractInsnNode? {
1216 if (initStart.opcode == NEW) {
1217 // remove dup
1218 instructions.remove(initStart.next)
1219 // remove NEW AFU_PKG/Atomic*Array instruction
1220 instructions.remove(initStart)
1221 }
1222 // create pure array of given size and put it
1223 val primitiveType = f.getPrimitiveType(vh)
1224 val primitiveElementType = ARRAY_ELEMENT_TYPE[f.typeInfo.originalType]
1225 val newArray =
1226 if (primitiveElementType != null) IntInsnNode(NEWARRAY, primitiveElementType)
1227 else TypeInsnNode(ANEWARRAY, descToName(primitiveType.elementType.descriptor))
1228 instructions.set(arrayFactoryInsn, newArray)
1229 next.desc = primitiveType.descriptor
1230 next.name = f.name
1231 transformed = true
1232 return next.next
1233 }
1234
1235 // removes pushing atomic factory trace arguments
1236 // returns the first value argument push
removeTraceInitnull1237 private fun removeTraceInit(atomicFactory: MethodInsnNode, isArrayFactory: Boolean): AbstractInsnNode {
1238 val initStart = FlowAnalyzer(atomicFactory).getInitStart(1)
1239 if (isArrayFactory) return initStart
1240 var lastArg = atomicFactory.previous
1241 val valueArgInitLast = FlowAnalyzer(atomicFactory).getValueArgInitLast()
1242 while (lastArg != valueArgInitLast) {
1243 val prev = lastArg.previous
1244 instructions.remove(lastArg)
1245 lastArg = prev
1246 }
1247 return initStart
1248 }
1249
removeTraceAppendnull1250 private fun removeTraceAppend(append: AbstractInsnNode): AbstractInsnNode {
1251 // remove append trace instructions: from append invocation up to getfield Trace or accessor to Trace field
1252 val afterAppend = append.next
1253 var start = append
1254 val isGetFieldTrace = { insn: AbstractInsnNode ->
1255 insn.opcode == GETFIELD && (start as FieldInsnNode).desc == getObjectType(TRACE_BASE_CLS).descriptor }
1256 val isTraceAccessor = { insn: AbstractInsnNode ->
1257 if (insn is MethodInsnNode) {
1258 val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
1259 methodId in traceAccessors
1260 } else false
1261 }
1262 while (!(isGetFieldTrace(start) || isTraceAccessor(start))) {
1263 start = start.previous
1264 }
1265 // now start contains Trace getfield insn or Trace accessor
1266 if (isTraceAccessor(start)) {
1267 instructions.remove(start.previous.previous)
1268 instructions.remove(start.previous)
1269 } else {
1270 instructions.remove(start.previous)
1271 }
1272 while (start != afterAppend) {
1273 if (start is VarInsnNode) {
1274 // remove all local store instructions
1275 localVariables.removeIf { it.index == (start as VarInsnNode).`var` }
1276 }
1277 val next = start.next
1278 instructions.remove(start)
1279 start = next
1280 }
1281 return afterAppend
1282 }
1283
transformnull1284 private fun transform(i: AbstractInsnNode): AbstractInsnNode? {
1285 when (i) {
1286 is MethodInsnNode -> {
1287 val methodId = MethodId(i.owner, i.name, i.desc, i.opcode)
1288 when {
1289 methodId in FACTORIES -> {
1290 if (name != "<init>" && name != "<clinit>") abort("factory $methodId is used outside of constructor or class initialisation")
1291 val next = i.nextUseful
1292 val fieldId = (next as? FieldInsnNode)?.checkPutFieldOrPutStatic()
1293 ?: abort("factory $methodId invocation must be followed by putfield")
1294 val f = fields[fieldId]!!
1295 val isArray = AFU_CLASSES[i.owner]?.let { it.originalType.sort == ARRAY } ?: false
1296 // erase pushing arguments for trace initialisation
1297 val newInitStart = removeTraceInit(i, isArray)
1298 // in FU mode wrap values of top-level primitive atomics into corresponding *RefVolatile class
1299 if (!vh && f.isStatic && !f.isArray) {
1300 return putPrimitiveTypeWrapper(i, newInitStart, f, next)
1301 }
1302 if (f.isArray) {
1303 return if (vh) {
1304 putPureVhArray(i, newInitStart, f, next)
1305 } else {
1306 putJucaAtomicArray(i, newInitStart, f, next)
1307 }
1308 }
1309 instructions.remove(i)
1310 transformed = true
1311 val primitiveType = f.getPrimitiveType(vh)
1312 next.desc = primitiveType.descriptor
1313 next.name = f.name
1314 return next.next
1315 }
1316 methodId in accessors -> {
1317 // replace INVOKESTATIC/VIRTUAL to accessor with GETSTATIC on var handle / field updater
1318 val f = accessors[methodId]!!
1319 val j = FieldInsnNode(
1320 GETSTATIC, f.owner, f.fuName,
1321 if (vh) VH_TYPE.descriptor else f.fuType.descriptor
1322 )
1323 // set original name for an array in FU mode
1324 if (!vh && f.isArray) {
1325 j.opcode = if (!f.isStatic) GETFIELD else GETSTATIC
1326 j.name = f.name
1327 }
1328 instructions.set(i, j)
1329 if (vh && f.isArray) {
1330 return insertPureVhArray(j, f)
1331 }
1332 transformed = true
1333 return fixupLoadedAtomicVar(f, j)
1334 }
1335 methodId == TRACE_FACTORY || methodId == TRACE_PARTIAL_ARGS_FACTORY -> {
1336 if (methodId == TRACE_FACTORY) {
1337 // remove trace format initialization
1338 var checkcastTraceFormat = i
1339 while (checkcastTraceFormat.opcode != CHECKCAST) checkcastTraceFormat = checkcastTraceFormat.previous
1340 val astoreTraceFormat = checkcastTraceFormat.next
1341 val tranceFormatInitStart = FlowAnalyzer(checkcastTraceFormat.previous).getInitStart(1).previous
1342 var initInsn = checkcastTraceFormat
1343 while (initInsn != tranceFormatInitStart) {
1344 val prev = initInsn.previous
1345 instructions.remove(initInsn)
1346 initInsn = prev
1347 }
1348 instructions.insertBefore(astoreTraceFormat, InsnNode(ACONST_NULL))
1349 }
1350 // remove trace factory and following putfield
1351 val argsSize = getMethodType(methodId.desc).argumentTypes.size
1352 val putfield = i.next
1353 val next = putfield.next
1354 val depth = if (i.opcode == INVOKESPECIAL) 2 else argsSize
1355 val initStart = FlowAnalyzer(i.previous).getInitStart(depth).previous
1356 var lastArg = i
1357 while (lastArg != initStart) {
1358 val prev = lastArg.previous
1359 instructions.remove(lastArg)
1360 lastArg = prev
1361 }
1362 instructions.remove(initStart) // aload of the parent class
1363 instructions.remove(putfield)
1364 return next
1365 }
1366 methodId == TRACE_APPEND || methodId == TRACE_APPEND_2 || methodId == TRACE_APPEND_3 || methodId == TRACE_APPEND_4 -> {
1367 return removeTraceAppend(i)
1368 }
1369 methodId in removeMethods -> {
1370 abort(
1371 "invocation of method $methodId on atomic types. " +
1372 "Make the latter method 'inline' to use it", i
1373 )
1374 }
1375 i.opcode == INVOKEVIRTUAL && i.owner in AFU_CLASSES -> {
1376 abort("standalone invocation of $methodId that was not traced to previous field load", i)
1377 }
1378 }
1379 }
1380 is FieldInsnNode -> {
1381 val fieldId = FieldId(i.owner, i.name, i.desc)
1382 if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
1383 if (fieldId.isFieldDelegate() && i.next.opcode == ASTORE) {
1384 return transformDelegatedFieldAccessor(i, fieldId)
1385 }
1386 (i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
1387 // Convert GETFIELD to GETSTATIC on var handle / field updater
1388 val f = fields[fieldId]!!
1389 val isArray = f.getPrimitiveType(vh).sort == ARRAY
1390 // GETSTATIC for all fields except FU arrays
1391 if (!isArray || vh) {
1392 if (i.desc != f.fieldType.descriptor) return i.next // already converted get/setfield
1393 i.opcode = GETSTATIC
1394 i.name = f.fuName
1395 }
1396 // for FU arrays with external access change name to mangled one
1397 if (!vh && isArray && f.hasExternalAccess) {
1398 i.name = f.name
1399 }
1400 i.desc = if (vh) VH_TYPE.descriptor else f.fuType.descriptor
1401 val prev = i.previous
1402 if (vh && f.getPrimitiveType(vh).sort == ARRAY) {
1403 return getInsnOrNull(from = prev, to = insertPureVhArray(i, f)) { it.isAtomicGetFieldOrGetStatic() }
1404 }
1405 transformed = true
1406 // in order not to skip the transformation of atomic field loads
1407 // check if there are any nested between the current atomic field load instruction i and it's transformed operation
1408 // and return the first one
1409 return getInsnOrNull(from = prev, to = fixupLoadedAtomicVar(f, i)) { it.isAtomicGetFieldOrGetStatic() }
1410 }
1411 }
1412 }
1413 return i.next
1414 }
1415
transformDelegatedFieldAccessornull1416 private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode? {
1417 val f = fieldDelegates[fieldId]!!
1418 val v = (i.next as VarInsnNode).`var`
1419 // remove instructions [astore_v .. aload_v]
1420 var cur: AbstractInsnNode = i.next
1421 while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
1422 val next = cur.next
1423 instructions.remove(cur)
1424 cur = next
1425 }
1426 val iv = FlowAnalyzer(cur.next).execute()
1427 check(iv.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
1428 val isGetter = (iv as MethodInsnNode).name == GET_VALUE
1429 instructions.remove(cur) // remove aload_v
1430 localVariables.removeIf {
1431 !(getType(it.desc).internalName == f.owner ||
1432 (!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
1433 }
1434 return getPureTypeField(i, f, iv)
1435 }
1436
isAtomicGetFieldOrGetStaticnull1437 private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
1438 this is FieldInsnNode && (opcode == GETFIELD || opcode == GETSTATIC) &&
1439 FieldId(owner, name, desc) in fields
1440
1441 private fun AbstractInsnNode.isAtomicGetValueOrSetValue() =
1442 isInvokeVirtual() && (getObjectType((this as MethodInsnNode).owner) in AFU_TYPES) &&
1443 (name == GET_VALUE || name == SET_VALUE)
1444
1445 private fun insertPureVhArray(getVarHandleInsn: FieldInsnNode, f: FieldInfo): AbstractInsnNode? {
1446 val getPureArray = FieldInsnNode(GETFIELD, f.owner, f.name, f.getPrimitiveType(vh).descriptor)
1447 if (!f.isStatic) {
1448 // swap className reference and VarHandle
1449 val swap = InsnNode(SWAP)
1450 instructions.insert(getVarHandleInsn, swap)
1451 instructions.insert(swap, getPureArray)
1452 } else {
1453 getPureArray.opcode = GETSTATIC
1454 instructions.insert(getVarHandleInsn, getPureArray)
1455 }
1456 transformed = true
1457 return fixupLoadedAtomicVar(f, getPureArray)
1458 }
1459
1460 // generates a ref class with volatile field of primitive type inside
generateRefVolatileClassnull1461 private fun generateRefVolatileClass(f: FieldInfo, arg: Type) {
1462 if (analyzePhase2) return // nop
1463 val cw = ClassWriter(0)
1464 val visibility = if (f.hasExternalAccess) ACC_PUBLIC else 0
1465 cw.visit(V1_6, visibility or ACC_SYNTHETIC, f.refVolatileClassName, null, "java/lang/Object", null)
1466 //creating class constructor
1467 val cons = cw.visitMethod(ACC_PUBLIC, "<init>", "(${arg.descriptor})V", null, null)
1468 code(cons) {
1469 visitVarInsn(ALOAD, 0)
1470 invokespecial("java/lang/Object", "<init>", "()V", false)
1471 visitVarInsn(ALOAD, 0)
1472 load(1, arg)
1473 putfield(f.refVolatileClassName, f.name, f.getPrimitiveType(vh).descriptor)
1474 visitInsn(RETURN)
1475 // stack size to fit long type
1476 visitMaxs(3, 3)
1477 }
1478 //declaring volatile field of primitive type
1479 cw.visitField(visibility or ACC_VOLATILE, f.name, f.getPrimitiveType(vh).descriptor, null, null)
1480 val genFile = outputDir / "${f.refVolatileClassName}.class"
1481 genFile.mkdirsAndWrite(cw.toByteArray())
1482 }
1483
1484 // Initializes static instance of generated *RefVolatile class
initRefVolatilenull1485 private fun initRefVolatile(
1486 f: FieldInfo,
1487 argType: Type,
1488 firstInitInsn: AbstractInsnNode,
1489 lastInitInsn: AbstractInsnNode
1490 ) {
1491 val new = TypeInsnNode(NEW, f.refVolatileClassName)
1492 val dup = InsnNode(DUP)
1493 instructions.insertBefore(firstInitInsn, new)
1494 instructions.insertBefore(firstInitInsn, dup)
1495 val invokespecial =
1496 MethodInsnNode(INVOKESPECIAL, f.refVolatileClassName, "<init>", "(${argType.descriptor})V", false)
1497 val putstatic = FieldInsnNode(
1498 PUTSTATIC,
1499 f.owner,
1500 f.staticRefVolatileField,
1501 getObjectType(f.refVolatileClassName).descriptor
1502 )
1503 instructions.insertBefore(lastInitInsn, invokespecial)
1504 instructions.insert(invokespecial, putstatic)
1505 }
1506 }
1507
1508 private inner class CW : ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES) {
getCommonSuperClassnull1509 override fun getCommonSuperClass(type1: String, type2: String): String {
1510 var c: Class<*> = loadClass(type1)
1511 val d: Class<*> = loadClass(type2)
1512 if (c.isAssignableFrom(d)) return type1
1513 if (d.isAssignableFrom(c)) return type2
1514 return if (c.isInterface || d.isInterface) {
1515 "java/lang/Object"
1516 } else {
1517 do {
1518 c = c.superclass
1519 } while (!c.isAssignableFrom(d))
1520 c.name.replace('.', '/')
1521 }
1522 }
1523 }
1524
loadClassnull1525 private fun loadClass(type: String): Class<*> =
1526 try {
1527 Class.forName(type.replace('/', '.'), false, classPathLoader)
1528 } catch (e: Exception) {
1529 throw TransformerException("Failed to load class for '$type'", e)
1530 }
1531 }
1532
mainnull1533 fun main(args: Array<String>) {
1534 if (args.size !in 1..3) {
1535 println("Usage: AtomicFUTransformerKt <dir> [<output>] [<variant>]")
1536 return
1537 }
1538 val t = AtomicFUTransformer(emptyList(), File(args[0]))
1539 if (args.size > 1) t.outputDir = File(args[1])
1540 if (args.size > 2) t.jvmVariant = enumValueOf(args[2].toUpperCase(Locale.US))
1541 t.verbose = true
1542 t.transform()
1543 }
1544