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