• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2018 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 val INT_ARRAY_TYPE = getType("[I")
28 private val LONG_ARRAY_TYPE = getType("[J")
29 private val BOOLEAN_ARRAY_TYPE = getType("[Z")
30 private val REF_ARRAY_TYPE = getType("[Ljava/lang/Object;")
31 private val REF_TYPE = getType("L$AFU_PKG/AtomicRef;")
32 private val ATOMIC_ARRAY_TYPE = getType("L$AFU_PKG/AtomicArray;")
33 
34 private val AFU_CLASSES: Map<String, TypeInfo> = mapOf(
35     "$AFU_PKG/AtomicInt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), INT_TYPE, INT_TYPE),
36     "$AFU_PKG/AtomicLong" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongFieldUpdater"), LONG_TYPE, LONG_TYPE),
37     "$AFU_PKG/AtomicRef" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceFieldUpdater"), OBJECT_TYPE, OBJECT_TYPE),
38     "$AFU_PKG/AtomicBoolean" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), BOOLEAN_TYPE, INT_TYPE),
39 
40     "$AFU_PKG/AtomicIntArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), INT_ARRAY_TYPE, INT_ARRAY_TYPE),
41     "$AFU_PKG/AtomicLongArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongArray"), LONG_ARRAY_TYPE, LONG_ARRAY_TYPE),
42     "$AFU_PKG/AtomicBooleanArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), BOOLEAN_ARRAY_TYPE, INT_ARRAY_TYPE),
43     "$AFU_PKG/AtomicArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE),
44     "$AFU_PKG/AtomicFU_commonKt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE)
45 )
46 
47 private val WRAPPER: Map<Type, String> = mapOf(
48     INT_TYPE to "java/lang/Integer",
49     LONG_TYPE to "java/lang/Long",
50     BOOLEAN_TYPE to "java/lang/Boolean"
51 )
52 
53 private val ARRAY_ELEMENT_TYPE: Map<Type, Int> = mapOf(
54     INT_ARRAY_TYPE to T_INT,
55     LONG_ARRAY_TYPE to T_LONG,
56     BOOLEAN_ARRAY_TYPE to T_BOOLEAN
57 )
58 
59 private val AFU_TYPES: Map<Type, TypeInfo> = AFU_CLASSES.mapKeys { getObjectType(it.key) }
60 
61 private val METHOD_HANDLES = "$JLI_PKG/MethodHandles"
62 private val LOOKUP = "$METHOD_HANDLES\$Lookup"
63 private val VH_TYPE = getObjectType("$JLI_PKG/VarHandle")
64 
65 private val STRING_TYPE = getObjectType("java/lang/String")
66 private val CLASS_TYPE = getObjectType("java/lang/Class")
67 
prettyStrnull68 private fun String.prettyStr() = replace('/', '.')
69 
70 data class MethodId(val owner: String, val name: String, val desc: String, val invokeOpcode: Int) {
71     override fun toString(): String = "${owner.prettyStr()}::$name"
72 }
73 
74 private const val GET_VALUE = "getValue"
75 private const val SET_VALUE = "setValue"
76 
77 private const val AFU_CLS = "$AFU_PKG/AtomicFU"
78 
79 private val FACTORIES: Set<MethodId> = setOf(
80     MethodId(AFU_CLS, ATOMIC, "(Ljava/lang/Object;)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
81     MethodId(AFU_CLS, ATOMIC, "(I)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
82     MethodId(AFU_CLS, ATOMIC, "(J)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
83     MethodId(AFU_CLS, ATOMIC, "(Z)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC),
84 
85     MethodId("$AFU_PKG/AtomicIntArray", "<init>", "(I)V", INVOKESPECIAL),
86     MethodId("$AFU_PKG/AtomicLongArray", "<init>", "(I)V", INVOKESPECIAL),
87     MethodId("$AFU_PKG/AtomicBooleanArray", "<init>", "(I)V", INVOKESPECIAL),
88     MethodId("$AFU_PKG/AtomicArray", "<init>", "(I)V", INVOKESPECIAL),
89     MethodId("$AFU_PKG/AtomicFU_commonKt", "atomicArrayOfNulls", "(I)L$AFU_PKG/AtomicArray;", INVOKESTATIC)
90 )
91 
containsnull92 private operator fun Int.contains(bit: Int) = this and bit != 0
93 
94 private inline fun code(mv: MethodVisitor, block: InstructionAdapter.() -> Unit) {
95     block(InstructionAdapter(mv))
96 }
97 
insnsnull98 private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
99     val node = MethodNode(ASM5)
100     block(InstructionAdapter(node))
101     return node.instructions
102 }
103 
104 data class FieldId(val owner: String, val name: String, val desc: String) {
toStringnull105     override fun toString(): String = "${owner.prettyStr()}::$name"
106 }
107 
108 class FieldInfo(
109     val fieldId: FieldId,
110     val fieldType: Type,
111     val isStatic: Boolean = false
112 ) {
113     val owner = fieldId.owner
114     val ownerType: Type = getObjectType(owner)
115     val typeInfo = AFU_CLASSES.getValue(fieldType.internalName)
116     val fuType = typeInfo.fuType
117     val isArray = typeInfo.originalType.sort == ARRAY
118 
119     // state: updated during analysis
120     val accessors = mutableSetOf<MethodId>() // set of accessor method that read the corresponding atomic
121     var hasExternalAccess = false // accessed from different package
122     var hasAtomicOps = false // has atomic operations operations other than getValue/setValue
123 
124     val name: String
125         get() = if (hasExternalAccess) mangleInternal(fieldId.name) else fieldId.name
126     val fuName: String
127         get() {
128             val fuName = fieldId.name + '$' + "FU"
129             return if (hasExternalAccess) mangleInternal(fuName) else fuName
130         }
131 
132     val refVolatileClassName = "${owner.replace('.', '/')}${name.capitalize()}RefVolatile"
133     val staticRefVolatileField = refVolatileClassName.substringAfterLast("/").decapitalize()
134 
135     fun getPrimitiveType(vh: Boolean): Type = if (vh) typeInfo.originalType else typeInfo.transformedType
136 
137     private fun mangleInternal(fieldName: String): String = "$fieldName\$internal"
138 
139     override fun toString(): String = "${owner.prettyStr()}::$name"
140 }
141 
142 enum class Variant { FU, VH, BOTH }
143 
144 class AtomicFUTransformer(
145     classpath: List<String>,
146     inputDir: File,
147     outputDir: File = inputDir,
148     var variant: Variant = Variant.FU
149 ) : AtomicFUTransformerBase(inputDir, outputDir) {
150 
151     private val classPathLoader = URLClassLoader(
<lambda>null152         (listOf(inputDir) + (classpath.map { File(it) } - outputDir))
<lambda>null153             .map { it.toURI().toURL() }.toTypedArray()
154     )
155 
156     private val fields = mutableMapOf<FieldId, FieldInfo>()
157     private val accessors = mutableMapOf<MethodId, FieldInfo>()
158     private val removeMethods = mutableSetOf<MethodId>()
159 
transformnull160     override fun transform() {
161         info("Analyzing in $inputDir")
162         val files = inputDir.walk().filter { it.isFile }.toList()
163         val needTransform = analyzeFilesForFields(files)
164         if (needTransform || outputDir == inputDir) {
165             val vh = variant == Variant.VH
166             // visit method bodies for external references to fields, runs all logic, fails if anything is wrong
167             val needsTransform = analyzeFilesForRefs(files, vh)
168             // perform transformation
169             info("Transforming to $outputDir")
170             files.forEach { file ->
171                 val bytes = file.readBytes()
172                 val outBytes = if (file.isClassFile() && file in needsTransform) transformFile(file, bytes, vh) else bytes
173                 val outFile = file.toOutputFile()
174                 outFile.mkdirsAndWrite(outBytes)
175                 if (variant == Variant.BOTH && outBytes !== bytes) {
176                     val vhBytes = transformFile(file, bytes, true)
177                     val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
178                     vhFile.mkdirsAndWrite(vhBytes)
179                 }
180             }
181         } else {
182             info("Nothing to transform -- all classes are up to date")
183         }
184     }
185 
186     // Phase 1: visit methods and fields, register all accessors, collect times
187     // Returns 'true' if any files are out of date
analyzeFilesForFieldsnull188     private fun analyzeFilesForFields(files: List<File>): Boolean {
189         var needTransform = false
190         files.forEach { file ->
191             val inpTime = file.lastModified()
192             val outTime = file.toOutputFile().lastModified()
193             if (inpTime > outTime) needTransform = true
194             if (file.isClassFile()) analyzeFileForFields(file)
195         }
196         if (lastError != null) throw TransformerException("Encountered errors while analyzing fields", lastError)
197         return needTransform
198     }
199 
analyzeFileForFieldsnull200     private fun analyzeFileForFields(file: File) {
201         file.inputStream().use { ClassReader(it).accept(FieldsCollectorCV(), SKIP_FRAMES) }
202     }
203 
204     // Phase2: visit method bodies for external references to fields and
205     //          run method analysis in "analysisMode" to see which fields need AU/VH generated for them
206     // Returns a set of files that need transformation
analyzeFilesForRefsnull207     private fun analyzeFilesForRefs(files: List<File>, vh: Boolean): Set<File> {
208         val result = HashSet<File>()
209         files.forEach { file ->
210             if (file.isClassFile() && analyzeFileForRefs(file, vh)) result += file
211         }
212         // Batch analyze all files, report all errors, bail out only at the end
213         if (lastError != null) throw TransformerException("Encountered errors while analyzing references", lastError)
214         return result
215     }
216 
analyzeFileForRefsnull217     private fun analyzeFileForRefs(file: File, vh: Boolean): Boolean =
218         file.inputStream().use { input ->
219             transformed = false // clear global "transformed" flag
220             val cv = TransformerCV(null, vh, analyzePhase2 = true)
221             try {
222                 ClassReader(input).accept(cv, SKIP_FRAMES)
223             } catch (e: Exception) {
224                 error("Failed to analyze: $e", cv.sourceInfo)
225                 e.printStackTrace(System.out)
226                 if (lastError == null) lastError = e
227             }
228             transformed // true for classes that need transformation
229         }
230 
231     // Phase 3: Transform file (only called for files that need to be transformed)
232     // Returns updated byte array for class data
transformFilenull233     private fun transformFile(file: File, bytes: ByteArray, vh: Boolean): ByteArray {
234         transformed = false // clear global "transformed" flag
235         val cw = CW()
236         val cv = TransformerCV(cw, vh, analyzePhase2 = false)
237         try {
238             ClassReader(ByteArrayInputStream(bytes)).accept(cv, SKIP_FRAMES)
239         } catch (e: Exception) {
240             error("Failed to transform: $e", cv.sourceInfo)
241             e.printStackTrace(System.out)
242             if (lastError == null) lastError = e
243         }
244         if (!transformed) error("Invoked transformFile on a file that does not need transformation: $file")
245         if (lastError != null) throw TransformerException("Encountered errors while transforming: $file", lastError)
246         info("Transformed $file")
247         return cw.toByteArray() // write transformed bytes
248     }
249 
250     private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM5, cv) {
251         lateinit var className: String
252 
visitnull253         override fun visit(
254             version: Int,
255             access: Int,
256             name: String,
257             signature: String?,
258             superName: String?,
259             interfaces: Array<out String>?
260         ) {
261             className = name
262             super.visit(version, access, name, signature, superName, interfaces)
263         }
264     }
265 
registerFieldnull266     private fun registerField(field: FieldId, fieldType: Type, isStatic: Boolean): FieldInfo {
267         val result = fields.getOrPut(field) { FieldInfo(field, fieldType, isStatic) }
268         if (result.fieldType != fieldType) abort("$field type mismatch between $fieldType and ${result.fieldType}")
269         return result
270     }
271 
272     private inner class FieldsCollectorCV : CV(null) {
visitFieldnull273         override fun visitField(
274             access: Int,
275             name: String,
276             desc: String,
277             signature: String?,
278             value: Any?
279         ): FieldVisitor? {
280             val fieldType = getType(desc)
281             if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
282                 val field = FieldId(className, name, desc)
283                 info("$field field found")
284                 if (ACC_PUBLIC in access) error("$field field cannot be public")
285                 if (ACC_FINAL !in access) error("$field field must be final")
286                 registerField(field, fieldType, (ACC_STATIC in access))
287             }
288             return null
289         }
290 
visitMethodnull291         override fun visitMethod(
292             access: Int,
293             name: String,
294             desc: String,
295             signature: String?,
296             exceptions: Array<out String>?
297         ): MethodVisitor? {
298             val methodType = getMethodType(desc)
299             if (methodType.argumentTypes.any { it in AFU_TYPES }) {
300                 val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
301                 info("$methodId method to be removed")
302                 removeMethods += methodId
303             }
304             getPotentialAccessorType(access, className, methodType)?.let { onType ->
305                 return AccessorCollectorMV(onType.internalName, access, name, desc, signature, exceptions)
306             }
307             return null
308         }
309     }
310 
311     private inner class AccessorCollectorMV(
312         private val className: String,
313         access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
314     ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
visitEndnull315         override fun visitEnd() {
316             val insns = instructions.listUseful(4)
317             if (insns.size == 3 &&
318                 insns[0].isAload(0) &&
319                 insns[1].isGetField(className) &&
320                 insns[2].isAreturn() ||
321                 insns.size == 2 &&
322                 insns[0].isGetStatic(className) &&
323                 insns[1].isAreturn()
324             ) {
325                 val isStatic = insns.size == 2
326                 val fi = (if (isStatic) insns[0] else insns[1]) as FieldInsnNode
327                 val fieldName = fi.name
328                 val field = FieldId(className, fieldName, fi.desc)
329                 val fieldType = getType(fi.desc)
330                 val accessorMethod = MethodId(className, name, desc, accessToInvokeOpcode(access))
331                 info("$field accessor $name found")
332                 val fieldInfo = registerField(field, fieldType, isStatic)
333                 fieldInfo.accessors += accessorMethod
334                 accessors[accessorMethod] = fieldInfo
335             }
336         }
337     }
338 
339     // returns a type on which this is a potential accessor
getPotentialAccessorTypenull340     private fun getPotentialAccessorType(access: Int, className: String, methodType: Type): Type? {
341         if (methodType.returnType !in AFU_TYPES) return null
342         return if (access and ACC_STATIC != 0) {
343             if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty()) {
344                 // accessor for top-level atomic
345                 getObjectType(className)
346             } else {
347                 // accessor for top-level atomic
348                 if (methodType.argumentTypes.size == 1 && methodType.argumentTypes[0].sort == OBJECT)
349                     methodType.argumentTypes[0] else null
350             }
351         } else {
352             // if it not static, then it must be final
353             if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty())
354                 getObjectType(className) else null
355         }
356     }
357 
descToNamenull358     private fun descToName(desc: String): String = desc.drop(1).dropLast(1)
359 
360     private inner class TransformerCV(
361         cv: ClassVisitor?,
362         private val vh: Boolean,
363         private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
364     ) : CV(cv) {
365         private var source: String? = null
366         var sourceInfo: SourceInfo? = null
367 
368         private var metadata: AnnotationNode? = null
369 
370         private var originalClinit: MethodNode? = null
371         private var newClinit: MethodNode? = null
372 
373         private fun newClinit() = MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null)
374         fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
375 
376         override fun visitSource(source: String?, debug: String?) {
377             this.source = source
378             super.visitSource(source, debug)
379         }
380 
381         override fun visitField(
382             access: Int,
383             name: String,
384             desc: String,
385             signature: String?,
386             value: Any?
387         ): FieldVisitor? {
388             val fieldType = getType(desc)
389             if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
390                 val fieldId = FieldId(className, name, desc)
391                 val f = fields[fieldId]!!
392                 val protection = when {
393                     // reference to wrapper class (primitive atomics) or reference to to j.u.c.a.Atomic*Array (atomic array)
394                     f.isStatic && !vh -> ACC_STATIC or ACC_FINAL or ACC_SYNTHETIC
395                     // primitive type field
396                     f.isStatic && vh -> ACC_STATIC or ACC_SYNTHETIC
397                     f.hasExternalAccess -> ACC_PUBLIC or ACC_SYNTHETIC
398                     f.accessors.isEmpty() -> ACC_PRIVATE
399                     else -> 0
400                 }
401                 val primitiveType = f.getPrimitiveType(vh)
402                 val fv = when {
403                     // replace (top-level) Atomic*Array with (static) j.u.c.a/Atomic*Array field
404                     f.isArray && !vh -> super.visitField(protection, f.name, f.fuType.descriptor, null, null)
405                     // replace top-level primitive atomics with static instance of the corresponding wrapping *RefVolatile class
406                     f.isStatic && !vh -> super.visitField(
407                         protection,
408                         f.staticRefVolatileField,
409                         getObjectType(f.refVolatileClassName).descriptor,
410                         null,
411                         null
412                     )
413                     // volatile primitive type field
414                     else -> super.visitField(protection or ACC_VOLATILE, f.name, primitiveType.descriptor, null, null)
415                 }
416                 if (vh) {
417                     // VarHandle is needed for all array element accesses and for regular fields with atomic ops
418                     if (f.hasAtomicOps || f.isArray) vhField(protection, f)
419                 } else {
420                     // FieldUpdater is not needed for arrays (they use AtomicArrays)
421                     if (f.hasAtomicOps && !f.isArray) fuField(protection, f)
422                 }
423                 transformed = true
424                 return fv
425             }
426             return super.visitField(access, name, desc, signature, value)
427         }
428 
429         // Generates static VarHandle field
430         private fun vhField(protection: Int, f: FieldInfo) {
431             super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, VH_TYPE.descriptor, null, null)
432             code(getOrCreateNewClinit()) {
433                 if (!f.isArray) {
434                     invokestatic(METHOD_HANDLES, "lookup", "()L$LOOKUP;", false)
435                     aconst(getObjectType(className))
436                     aconst(f.name)
437                     val primitiveType = f.getPrimitiveType(vh)
438                     if (primitiveType.sort == OBJECT) {
439                         aconst(primitiveType)
440                     } else {
441                         val wrapper = WRAPPER.getValue(primitiveType)
442                         getstatic(wrapper, "TYPE", CLASS_TYPE.descriptor)
443                     }
444                     val findVHName = if (f.isStatic) "findStaticVarHandle" else "findVarHandle"
445                     invokevirtual(
446                         LOOKUP, findVHName,
447                         getMethodDescriptor(VH_TYPE, CLASS_TYPE, STRING_TYPE, CLASS_TYPE), false
448                     )
449                     putstatic(className, f.fuName, VH_TYPE.descriptor)
450                 } else {
451                     // create VarHandle for array
452                     aconst(f.getPrimitiveType(vh))
453                     invokestatic(
454                         METHOD_HANDLES,
455                         "arrayElementVarHandle",
456                         getMethodDescriptor(VH_TYPE, CLASS_TYPE),
457                         false
458                     )
459                     putstatic(className, f.fuName, VH_TYPE.descriptor)
460                 }
461             }
462         }
463 
464         // Generates static AtomicXXXFieldUpdater field
465         private fun fuField(protection: Int, f: FieldInfo) {
466             super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, f.fuType.descriptor, null, null)
467             code(getOrCreateNewClinit()) {
468                 val params = mutableListOf<Type>()
469                 params += CLASS_TYPE
470                 if (!f.isStatic) aconst(getObjectType(className)) else aconst(getObjectType(f.refVolatileClassName))
471                 val primitiveType = f.getPrimitiveType(vh)
472                 if (primitiveType.sort == OBJECT) {
473                     params += CLASS_TYPE
474                     aconst(primitiveType)
475                 }
476                 params += STRING_TYPE
477                 aconst(f.name)
478                 invokestatic(
479                     f.fuType.internalName,
480                     "newUpdater",
481                     getMethodDescriptor(f.fuType, *params.toTypedArray()),
482                     false
483                 )
484                 putstatic(className, f.fuName, f.fuType.descriptor)
485             }
486         }
487 
488         override fun visitMethod(
489             access: Int,
490             name: String,
491             desc: String,
492             signature: String?,
493             exceptions: Array<out String>?
494         ): MethodVisitor? {
495             val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
496             if (methodId in accessors || methodId in removeMethods) {
497                 // drop and skip the methods that were found in Phase 1
498                 // todo: should remove those methods from kotlin metadata, too
499                 transformed = true
500                 return null
501             }
502             val sourceInfo = SourceInfo(methodId, source)
503             val superMV = if (name == "<clinit>" && desc == "()V") {
504                 if (access and ACC_STATIC == 0) abort("<clinit> method not marked as static")
505                 // defer writing class initialization method
506                 val node = MethodNode(ASM5, access, name, desc, signature, exceptions)
507                 if (originalClinit != null) abort("Multiple <clinit> methods found")
508                 originalClinit = node
509                 node
510             } else {
511                 // write transformed method to class right away
512                 super.visitMethod(access, name, desc, signature, exceptions)
513             }
514             val mv = TransformerMV(
515                 sourceInfo, access, name, desc, signature, exceptions, superMV,
516                 className.ownerPackageName, vh, analyzePhase2
517             )
518             this.sourceInfo = mv.sourceInfo
519             return mv
520         }
521 
522         override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? {
523             if (desc == KOTLIN_METADATA_DESC) {
524                 check(visible) { "Expected run-time visible $KOTLIN_METADATA_DESC annotation" }
525                 check(metadata == null) { "Only one $KOTLIN_METADATA_DESC annotation is expected" }
526                 return AnnotationNode(desc).also { metadata = it }
527             }
528             return super.visitAnnotation(desc, visible)
529         }
530 
531         override fun visitEnd() {
532             // remove unused methods from metadata
533             metadata?.let {
534                 val mt = MetadataTransformer(
535                     removeFields = fields.keys,
536                     removeMethods = accessors.keys + removeMethods
537                 )
538                 if (mt.transformMetadata(it)) transformed = true
539                 if (cv != null) it.accept(cv.visitAnnotation(KOTLIN_METADATA_DESC, true))
540             }
541             if (analyzePhase2) return // nop in analyze phase
542             // collect class initialization
543             if (originalClinit != null || newClinit != null) {
544                 val newClinit = newClinit
545                 if (newClinit == null) {
546                     // dump just original clinit
547                     originalClinit!!.accept(cv)
548                 } else {
549                     // create dummy base code if needed
550                     val originalClinit = originalClinit ?: newClinit().also {
551                         code(it) { visitInsn(RETURN) }
552                     }
553                     // makes sure return is last useful instruction
554                     val last = originalClinit.instructions.last
555                     val ret = last.thisOrPrevUseful
556                     if (ret == null || !ret.isReturn()) abort("Last instruction in <clinit> shall be RETURN", ret)
557                     originalClinit.instructions.insertBefore(ret, newClinit.instructions)
558                     originalClinit.accept(cv)
559                 }
560             }
561             super.visitEnd()
562         }
563     }
564 
565     private inner class TransformerMV(
566         sourceInfo: SourceInfo,
567         access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?,
568         mv: MethodVisitor?,
569         private val packageName: String,
570         private val vh: Boolean,
571         private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
572     ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
573         init {
574             this.mv = mv
575         }
576 
577         val sourceInfo = sourceInfo.copy(insnList = instructions)
578 
579         private var tempLocal = 0
580         private var bumpedLocals = 0
581 
bumpLocalsnull582         private fun bumpLocals(n: Int) {
583             if (bumpedLocals == 0) tempLocal = maxLocals
584             while (n > bumpedLocals) bumpedLocals = n
585             maxLocals = tempLocal + bumpedLocals
586         }
587 
visitMethodInsnnull588         override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
589             val methodId = MethodId(owner, name, desc, opcode)
590             val fieldInfo = accessors[methodId]
591             // compare owner packages
592             if (fieldInfo != null && methodId.owner.ownerPackageName != packageName) {
593                 if (analyzePhase2) {
594                     fieldInfo.hasExternalAccess = true
595                 } else {
596                     check(fieldInfo.hasExternalAccess) // should have been set on previous phase
597                 }
598             }
599             super.visitMethodInsn(opcode, owner, name, desc, itf)
600         }
601 
visitEndnull602         override fun visitEnd() {
603             // transform instructions list
604             var hasErrors = false
605             var i = instructions.first
606             while (i != null)
607                 try {
608                     i = transform(i)
609                 } catch (e: AbortTransform) {
610                     error(e.message!!, sourceInfo.copy(i = e.i))
611                     i = i.next
612                     hasErrors = true
613                 }
614             // save transformed method if not in analysis phase
615             if (!hasErrors && !analyzePhase2)
616                 accept(mv)
617         }
618 
FieldInsnNodenull619         private fun FieldInsnNode.checkPutFieldOrPutStatic(): FieldId? {
620             if (opcode != PUTFIELD && opcode != PUTSTATIC) return null
621             val fieldId = FieldId(owner, name, desc)
622             return if (fieldId in fields) fieldId else null
623         }
624 
625         // ld: instruction that loads atomic field (already changed to getstatic)
626         // iv: invoke virtual on the loaded atomic field (to be fixed)
fixupInvokeVirtualnull627         private fun fixupInvokeVirtual(
628             ld: FieldInsnNode,
629             onArrayElement: Boolean, // true when fixing invokeVirtual on loaded array element
630             iv: MethodInsnNode,
631             f: FieldInfo
632         ): AbstractInsnNode? {
633             check(f.isArray || !onArrayElement) { "Cannot fix array element access on non array fields" }
634             val typeInfo = if (onArrayElement) f.typeInfo else AFU_CLASSES.getValue(iv.owner)
635             if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
636                 check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
637                 val setInsn = iv.name == SET_VALUE
638                 if (!onArrayElement) {
639                     instructions.remove(ld) // drop getstatic (we don't need field updater)
640                     val primitiveType = f.getPrimitiveType(vh)
641                     val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
642                     if (!vh && f.isStatic) {
643                         val getOwnerClass = FieldInsnNode(
644                             GETSTATIC,
645                             f.owner,
646                             f.staticRefVolatileField,
647                             getObjectType(owner).descriptor
648                         )
649                         instructions.insertBefore(iv, getOwnerClass)
650                     }
651                     val j = FieldInsnNode(
652                         when {
653                             iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
654                             else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
655                         }, owner, f.name, primitiveType.descriptor
656                     )
657                     instructions.set(iv, j) // replace invokevirtual with get/setfield
658                     return j.next
659                 } else {
660                     var methodType = getMethodType(iv.desc)
661                     if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
662                         val ret = f.typeInfo.transformedType.elementType
663                         iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
664                         methodType = getMethodType(iv.desc)
665                     }
666                     iv.name = iv.name.substring(0, 3)
667                     if (!vh) {
668                         // map to j.u.c.a.Atomic*Array get or set
669                         iv.owner = descToName(f.fuType.descriptor)
670                         iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
671                     } else {
672                         // map to VarHandle get or set
673                         iv.owner = descToName(VH_TYPE.descriptor)
674                         iv.desc = getMethodDescriptor(
675                             methodType.returnType,
676                             f.getPrimitiveType(vh),
677                             INT_TYPE,
678                             *methodType.argumentTypes
679                         )
680                     }
681                     return iv
682                 }
683             }
684             // An operation other than getValue/setValue is used
685             if (f.isArray && iv.name == "get") { // "operator get" that retrieves array element, further ops apply to it
686                 // fixup atomic operation on this array element
687                 return fixupLoadedArrayElement(f, ld, iv)
688             } else {
689                 // non-trivial atomic operation
690                 check(f.isArray == onArrayElement) { "Atomic operations can be performed on atomic elements only" }
691                 if (analyzePhase2) {
692                     f.hasAtomicOps = true // mark the fact that non-trivial atomic op is used here
693                 } else {
694                     check(f.hasAtomicOps) // should have been set on previous phase
695                 }
696                 // update method invocation
697                 if (vh) {
698                     vhOperation(iv, typeInfo, f)
699                 } else {
700                     fuOperation(iv, typeInfo, f)
701                 }
702                 if (f.isStatic && !onArrayElement) {
703                     if (!vh) {
704                         // getstatic *RefVolatile class
705                         val aload = FieldInsnNode(
706                             GETSTATIC,
707                             f.owner,
708                             f.staticRefVolatileField,
709                             getObjectType(f.refVolatileClassName).descriptor
710                         )
711                         instructions.insert(ld, aload)
712                     }
713                     return iv.next
714                 }
715                 if (!onArrayElement) {
716                     // insert swap after field load
717                     val swap = InsnNode(SWAP)
718                     instructions.insert(ld, swap)
719                     return swap.next
720                 }
721                 return iv.next
722             }
723         }
724 
vhOperationnull725         private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
726             val methodType = getMethodType(iv.desc)
727             val args = methodType.argumentTypes
728             iv.owner = VH_TYPE.internalName
729             val params = if (!f.isArray && !f.isStatic) mutableListOf<Type>(
730                 OBJECT_TYPE,
731                 *args
732             ) else if (!f.isArray && f.isStatic) mutableListOf<Type>(*args) else mutableListOf(
733                 typeInfo.originalType,
734                 INT_TYPE,
735                 *args
736             )
737             val elementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
738             val long = elementType == LONG_TYPE
739             when (iv.name) {
740                 "lazySet" -> iv.name = "setRelease"
741                 "getAndIncrement" -> {
742                     instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
743                     params += elementType
744                     iv.name = "getAndAdd"
745                 }
746                 "getAndDecrement" -> {
747                     instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
748                     params += elementType
749                     iv.name = "getAndAdd"
750                 }
751                 "addAndGet" -> {
752                     bumpLocals(if (long) 2 else 1)
753                     instructions.insertBefore(iv, insns {
754                         if (long) dup2() else dup()
755                         store(tempLocal, elementType)
756                     })
757                     iv.name = "getAndAdd"
758                     instructions.insert(iv, insns {
759                         load(tempLocal, elementType)
760                         add(elementType)
761                     })
762                 }
763                 "incrementAndGet" -> {
764                     instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
765                     params += elementType
766                     iv.name = "getAndAdd"
767                     instructions.insert(iv, insns {
768                         if (long) lconst(1) else iconst(1)
769                         add(elementType)
770                     })
771                 }
772                 "decrementAndGet" -> {
773                     instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
774                     params += elementType
775                     iv.name = "getAndAdd"
776                     instructions.insert(iv, insns {
777                         if (long) lconst(-1) else iconst(-1)
778                         add(elementType)
779                     })
780                 }
781             }
782             iv.desc = getMethodDescriptor(methodType.returnType, *params.toTypedArray())
783         }
784 
fuOperationnull785         private fun fuOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
786             val methodType = getMethodType(iv.desc)
787             val originalElementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
788             val transformedElementType =
789                 if (f.isArray) typeInfo.transformedType.elementType else typeInfo.transformedType
790             val trans = originalElementType != transformedElementType
791             val args = methodType.argumentTypes
792             var ret = methodType.returnType
793             if (trans) {
794                 args.forEachIndexed { i, type -> if (type == originalElementType) args[i] = transformedElementType }
795                 if (iv.name == "getAndSet") ret = transformedElementType
796             }
797             if (f.isArray) {
798                 // map to j.u.c.a.AtomicIntegerArray method
799                 iv.owner = typeInfo.fuType.internalName
800                 // add int argument as element index
801                 iv.desc = getMethodDescriptor(ret, INT_TYPE, *args)
802                 return // array operation in this mode does not use FU field
803             }
804             iv.owner = typeInfo.fuType.internalName
805             iv.desc = getMethodDescriptor(ret, OBJECT_TYPE, *args)
806         }
807 
tryEraseUncheckedCastnull808         private fun tryEraseUncheckedCast(getter: AbstractInsnNode) {
809             if (getter.next.opcode == DUP && getter.next.next.opcode == IFNONNULL) {
810                 // unchecked cast upon AtomicRef var is performed
811                 // erase compiler check for this var being not null:
812                 // (remove all insns from ld till the non null branch label)
813                 val ifnonnull = (getter.next.next as JumpInsnNode)
814                 var i: AbstractInsnNode = getter.next
815                 while (!(i is LabelNode && i.label == ifnonnull.label.label)) {
816                     val next = i.next
817                     instructions.remove(i)
818                     i = next
819                 }
820             }
821         }
822 
fixupLoadedAtomicVarnull823         private fun fixupLoadedAtomicVar(f: FieldInfo, ld: FieldInsnNode): AbstractInsnNode? {
824             if (f.fieldType == REF_TYPE) tryEraseUncheckedCast(ld)
825             val j = FlowAnalyzer(ld.next).execute()
826             return fixupOperationOnAtomicVar(j, f, ld, null)
827         }
828 
fixupLoadedArrayElementnull829         private fun fixupLoadedArrayElement(f: FieldInfo, ld: FieldInsnNode, getter: MethodInsnNode): AbstractInsnNode? {
830             if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(getter)
831             // contains array field load (in vh case: + swap and pure type array load) and array element index
832             // this array element information is only used in case the reference to this element is stored (copied and inserted at the point of loading)
833             val arrayElementInfo = mutableListOf<AbstractInsnNode>()
834             if (vh) {
835                 arrayElementInfo.add(ld.previous.previous) // getstatic VarHandle field
836                 arrayElementInfo.add(ld.previous) // swap
837             }
838             var i: AbstractInsnNode = ld
839             while (i != getter) {
840                 arrayElementInfo.add(i)
841                 i = i.next
842             }
843             // start of array element operation arguments
844             val args = getter.next
845             // remove array element getter
846             instructions.remove(getter)
847             val arrayElementOperation = FlowAnalyzer(args).execute()
848             return fixupOperationOnAtomicVar(arrayElementOperation, f, ld, arrayElementInfo)
849         }
850 
fixupOperationOnAtomicVarnull851         private fun fixupOperationOnAtomicVar(operation: AbstractInsnNode, f: FieldInfo, ld: FieldInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? {
852             when (operation) {
853                 is MethodInsnNode -> {
854                     // invoked virtual method on atomic var -- fixup & done with it
855                     debug("invoke $f.${operation.name}", sourceInfo.copy(i = operation))
856                     return fixupInvokeVirtual(ld, arrayElementInfo != null, operation, f)
857                 }
858                 is VarInsnNode -> {
859                     val onArrayElement = arrayElementInfo != null
860                     check(f.isArray == onArrayElement)
861                     // was stored to local -- needs more processing:
862                     // store owner ref into the variable instead
863                     val v = operation.`var`
864                     val next = operation.next
865                     if (onArrayElement) {
866                         // leave just owner class load insn on stack
867                         arrayElementInfo!!.forEach { instructions.remove(it) }
868                     } else {
869                         instructions.remove(ld)
870                     }
871                     val lv = localVar(v, operation)
872                     if (lv != null) {
873                         // Stored to a local variable with an entry in LVT (typically because of inline function)
874                         if (lv.desc != f.fieldType.descriptor && !onArrayElement)
875                             abort("field $f was stored to a local variable #$v \"${lv.name}\" with unexpected type: ${lv.desc}")
876                         // correct local variable descriptor
877                         lv.desc = f.ownerType.descriptor
878                         lv.signature = null
879                         // process all loads of this variable in the corresponding local variable range
880                         forVarLoads(v, lv.start, lv.end) { otherLd ->
881                             fixupLoad(f, ld, otherLd, arrayElementInfo)
882                         }
883                     } else {
884                         // Spilled temporarily to a local variable w/o an entry in LVT -> fixup only one load
885                         fixupLoad(f, ld, nextVarLoad(v, next), arrayElementInfo)
886                     }
887                     return next
888                 }
889                 else -> abort("cannot happen")
890             }
891         }
892 
fixupLoadnull893         private fun fixupLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? =
894             if (arrayElementInfo != null) {
895                 fixupArrayElementLoad(f, ld, otherLd, arrayElementInfo)
896             } else {
897                 fixupVarLoad(f, ld, otherLd)
898             }
899 
fixupVarLoadnull900         private fun fixupVarLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode): AbstractInsnNode? {
901             val ldCopy = ld.clone(null) as FieldInsnNode
902             instructions.insert(otherLd, ldCopy)
903             return fixupLoadedAtomicVar(f, ldCopy)
904         }
905 
fixupArrayElementLoadnull906         private fun fixupArrayElementLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>): AbstractInsnNode? {
907             if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(otherLd)
908             // index instructions from array element info: drop owner class load instruction (in vh case together with preceding getting VH + swap)
909             val index = arrayElementInfo.drop(if (vh) 3 else 1)
910             // previously stored array element reference is loaded -> arrayElementInfo should be cloned and inserted at the point of this load
911             // before cloning make sure that index instructions contain just loads and simple arithmetic, without any invocations and complex data flow
912             for (indexInsn in index) {
913                 checkDataFlowComplexity(indexInsn)
914             }
915             // start of atomic operation arguments
916             val args = otherLd.next
917             val operationOnArrayElement = FlowAnalyzer(args).execute()
918             val arrayElementInfoCopy = mutableListOf<AbstractInsnNode>()
919             arrayElementInfo.forEach { arrayElementInfoCopy.add(it.clone(null)) }
920             arrayElementInfoCopy.forEach { instructions.insertBefore(args, it) }
921             return fixupOperationOnAtomicVar(operationOnArrayElement, f, ld, arrayElementInfo)
922         }
923 
checkDataFlowComplexitynull924         fun checkDataFlowComplexity(i: AbstractInsnNode) {
925             when (i) {
926                 is MethodInsnNode -> {
927                     abort("No method invocations are allowed for calculation of an array element index " +
928                         "at the point of loading the reference to this element.\n" +
929                         "Extract index calculation to the local variable.", i)
930                 }
931                 is LdcInsnNode -> { /* ok loading const */ }
932                 else -> {
933                     when(i.opcode) {
934                         IADD, ISUB, IMUL, IDIV, IREM, IAND, IOR, IXOR, ISHL, ISHR, IUSHR -> { /* simple arithmetics */ }
935                         ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, ILOAD, IALOAD -> { /* int loads */ }
936                         GETFIELD, GETSTATIC -> { /* getting fields */ }
937                         else -> {
938                             abort("Complex data flow is not allowed for calculation of an array element index " +
939                                 "at the point of loading the reference to this element.\n" +
940                                 "Extract index calculation to the local variable.", i)
941                         }
942                     }
943                 }
944             }
945         }
946 
putPrimitiveTypeWrappernull947         private fun putPrimitiveTypeWrapper(
948             factoryInsn: MethodInsnNode,
949             f: FieldInfo,
950             next: FieldInsnNode
951         ): AbstractInsnNode? {
952             // generate wrapper class for static fields of primitive type
953             val factoryArg = getMethodType(factoryInsn.desc).argumentTypes[0]
954             generateRefVolatileClass(f, factoryArg)
955             val firstInitInsn = FlowAnalyzer(next).getInitStart()
956             // remove calling atomic factory for static field and following putstatic
957             val afterPutStatic = next.next
958             instructions.remove(factoryInsn)
959             instructions.remove(next)
960             initRefVolatile(f, factoryArg, firstInitInsn, afterPutStatic)
961             return afterPutStatic
962         }
963 
putJucaAtomicArraynull964         private fun putJucaAtomicArray(
965             arrayfactoryInsn: MethodInsnNode,
966             f: FieldInfo,
967             next: FieldInsnNode
968         ): AbstractInsnNode? {
969             // replace with invoking j.u.c.a.Atomic*Array constructor
970             val jucaAtomicArrayDesc = f.typeInfo.fuType.descriptor
971             val initStart = FlowAnalyzer(next).getInitStart().next
972             if (initStart.opcode == NEW) {
973                 // change descriptor of NEW instruction
974                 (initStart as TypeInsnNode).desc = descToName(jucaAtomicArrayDesc)
975                 arrayfactoryInsn.owner = descToName(jucaAtomicArrayDesc)
976             } else {
977                 // array initialisation starts from bipush size, then static array factory was called (atomicArrayOfNulls)
978                 // add NEW j.u.c.a.Atomic*Array instruction
979                 val newInsn = TypeInsnNode(NEW, descToName(jucaAtomicArrayDesc))
980                 instructions.insert(initStart.previous, newInsn)
981                 instructions.insert(newInsn, InsnNode(DUP))
982                 val jucaArrayFactory =
983                     MethodInsnNode(INVOKESPECIAL, descToName(jucaAtomicArrayDesc), "<init>", "(I)V", false)
984                 instructions.set(arrayfactoryInsn, jucaArrayFactory)
985             }
986             //fix the following putfield
987             next.desc = jucaAtomicArrayDesc
988             next.name = f.name
989             transformed = true
990             return next.next
991         }
992 
putPureVhArraynull993         private fun putPureVhArray(
994             arrayFactoryInsn: MethodInsnNode,
995             f: FieldInfo,
996             next: FieldInsnNode
997         ): AbstractInsnNode? {
998             val initStart = FlowAnalyzer(next).getInitStart().next
999             if (initStart.opcode == NEW) {
1000                 // remove dup
1001                 instructions.remove(initStart.next)
1002                 // remove NEW AFU_PKG/Atomic*Array instruction
1003                 instructions.remove(initStart)
1004             }
1005             // create pure array of given size and put it
1006             val primitiveType = f.getPrimitiveType(vh)
1007             val primitiveElementType = ARRAY_ELEMENT_TYPE[f.typeInfo.originalType]
1008             val newArray =
1009                 if (primitiveElementType != null) IntInsnNode(NEWARRAY, primitiveElementType)
1010                 else TypeInsnNode(ANEWARRAY, descToName(primitiveType.elementType.descriptor))
1011             instructions.set(arrayFactoryInsn, newArray)
1012             next.desc = primitiveType.descriptor
1013             next.name = f.name
1014             transformed = true
1015             return next.next
1016         }
1017 
transformnull1018         private fun transform(i: AbstractInsnNode): AbstractInsnNode? {
1019             when (i) {
1020                 is MethodInsnNode -> {
1021                     val methodId = MethodId(i.owner, i.name, i.desc, i.opcode)
1022                     when {
1023                         methodId in FACTORIES -> {
1024                             if (name != "<init>" && name != "<clinit>") abort("factory $methodId is used outside of constructor or class initialisation")
1025                             val next = i.nextUseful
1026                             val fieldId = (next as? FieldInsnNode)?.checkPutFieldOrPutStatic()
1027                                 ?: abort("factory $methodId invocation must be followed by putfield")
1028                             val f = fields[fieldId]!!
1029                             // in FU mode wrap values of top-level primitive atomics into corresponding *RefVolatile class
1030                             if (!vh && f.isStatic && !f.isArray) {
1031                                 return putPrimitiveTypeWrapper(i, f, next)
1032                             }
1033                             if (f.isArray) {
1034                                 return if (vh) {
1035                                     putPureVhArray(i, f, next)
1036                                 } else {
1037                                     putJucaAtomicArray(i, f, next)
1038                                 }
1039                             }
1040                             instructions.remove(i)
1041                             transformed = true
1042                             val primitiveType = f.getPrimitiveType(vh)
1043                             next.desc = primitiveType.descriptor
1044                             next.name = f.name
1045                             return next.next
1046                         }
1047                         methodId in accessors -> {
1048                             // replace INVOKESTATIC/VIRTUAL to accessor with GETSTATIC on var handle / field updater
1049                             val f = accessors[methodId]!!
1050                             val j = FieldInsnNode(
1051                                 GETSTATIC, f.owner, f.fuName,
1052                                 if (vh) VH_TYPE.descriptor else f.fuType.descriptor
1053                             )
1054                             // set original name for an array in FU mode
1055                             if (!vh && f.isArray) {
1056                                 j.opcode = if (!f.isStatic) GETFIELD else GETSTATIC
1057                                 j.name = f.name
1058                             }
1059                             instructions.set(i, j)
1060                             if (vh && f.isArray) {
1061                                 return insertPureVhArray(j, f)
1062                             }
1063                             transformed = true
1064                             return fixupLoadedAtomicVar(f, j)
1065                         }
1066                         methodId in removeMethods -> {
1067                             abort(
1068                                 "invocation of method $methodId on atomic types. " +
1069                                         "Make the later method 'inline' to use it", i
1070                             )
1071                         }
1072                         i.opcode == INVOKEVIRTUAL && i.owner in AFU_CLASSES -> {
1073                             abort("standalone invocation of $methodId that was not traced to previous field load", i)
1074                         }
1075                     }
1076                 }
1077                 is FieldInsnNode -> {
1078                     val fieldId = FieldId(i.owner, i.name, i.desc)
1079                     if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
1080                         // Convert GETFIELD to GETSTATIC on var handle / field updater
1081                         val f = fields[fieldId]!!
1082                         val isArray = f.getPrimitiveType(vh).sort == ARRAY
1083                         // GETSTATIC for all fields except FU arrays
1084                         if (!isArray || vh) {
1085                             if (i.desc != f.fieldType.descriptor) return i.next // already converted get/setfield
1086                             i.opcode = GETSTATIC
1087                             i.name = f.fuName
1088                         }
1089                         // for FU arrays with external access change name to mangled one
1090                         if (!vh && isArray && f.hasExternalAccess) {
1091                             i.name = f.name
1092                         }
1093                         i.desc = if (vh) VH_TYPE.descriptor else f.fuType.descriptor
1094                         if (vh && f.getPrimitiveType(vh).sort == ARRAY) {
1095                             return insertPureVhArray(i, f)
1096                         }
1097                         transformed = true
1098                         return fixupLoadedAtomicVar(f, i)
1099                     }
1100                 }
1101             }
1102             return i.next
1103         }
1104 
insertPureVhArraynull1105         private fun insertPureVhArray(getVarHandleInsn: FieldInsnNode, f: FieldInfo): AbstractInsnNode? {
1106             val getPureArray = FieldInsnNode(GETFIELD, f.owner, f.name, f.getPrimitiveType(vh).descriptor)
1107             if (!f.isStatic) {
1108                 // swap className reference and VarHandle
1109                 val swap = InsnNode(SWAP)
1110                 instructions.insert(getVarHandleInsn, swap)
1111                 instructions.insert(swap, getPureArray)
1112             } else {
1113                 getPureArray.opcode = GETSTATIC
1114                 instructions.insert(getVarHandleInsn, getPureArray)
1115             }
1116             transformed = true
1117             return fixupLoadedAtomicVar(f, getPureArray)
1118         }
1119 
1120         // generates a ref class with volatile field of primitive type inside
generateRefVolatileClassnull1121         private fun generateRefVolatileClass(f: FieldInfo, arg: Type) {
1122             if (analyzePhase2) return // nop
1123             val cw = ClassWriter(0)
1124             cw.visit(V1_6, ACC_PUBLIC or ACC_SYNTHETIC, f.refVolatileClassName, null, "java/lang/Object", null)
1125             //creating class constructor
1126             val cons = cw.visitMethod(ACC_PUBLIC, "<init>", "(${arg.descriptor})V", null, null)
1127             code(cons) {
1128                 visitVarInsn(ALOAD, 0)
1129                 invokespecial("java/lang/Object", "<init>", "()V", false)
1130                 visitVarInsn(ALOAD, 0)
1131                 load(1, arg)
1132                 putfield(f.refVolatileClassName, f.name, f.getPrimitiveType(vh).descriptor)
1133                 visitInsn(RETURN)
1134                 // stack size to fit long type
1135                 visitMaxs(3, 3)
1136             }
1137             //declaring volatile field of primitive type
1138             val protection = ACC_VOLATILE
1139             cw.visitField(protection, f.name, f.getPrimitiveType(vh).descriptor, null, null)
1140             val genFile = outputDir / "${f.refVolatileClassName}.class"
1141             genFile.mkdirsAndWrite(cw.toByteArray())
1142         }
1143 
1144         // Initializes static instance of generated *RefVolatile class
initRefVolatilenull1145         private fun initRefVolatile(
1146             f: FieldInfo,
1147             argType: Type,
1148             firstInitInsn: AbstractInsnNode,
1149             lastInitInsn: AbstractInsnNode
1150         ) {
1151             val new = TypeInsnNode(NEW, f.refVolatileClassName)
1152             val dup = InsnNode(DUP)
1153             instructions.insertBefore(firstInitInsn, new)
1154             instructions.insertBefore(firstInitInsn, dup)
1155             val invokespecial =
1156                 MethodInsnNode(INVOKESPECIAL, f.refVolatileClassName, "<init>", "(${argType.descriptor})V", false)
1157             val putstatic = FieldInsnNode(
1158                 PUTSTATIC,
1159                 f.owner,
1160                 f.staticRefVolatileField,
1161                 getObjectType(f.refVolatileClassName).descriptor
1162             )
1163             instructions.insertBefore(lastInitInsn, invokespecial)
1164             instructions.insert(invokespecial, putstatic)
1165         }
1166     }
1167 
1168     private inner class CW : ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES) {
getCommonSuperClassnull1169         override fun getCommonSuperClass(type1: String, type2: String): String {
1170             var c: Class<*> = loadClass(type1)
1171             val d: Class<*> = loadClass(type2)
1172             if (c.isAssignableFrom(d)) return type1
1173             if (d.isAssignableFrom(c)) return type2
1174             return if (c.isInterface || d.isInterface) {
1175                 "java/lang/Object"
1176             } else {
1177                 do {
1178                     c = c.superclass
1179                 } while (!c.isAssignableFrom(d))
1180                 c.name.replace('.', '/')
1181             }
1182         }
1183     }
1184 
loadClassnull1185     private fun loadClass(type: String): Class<*> =
1186         try {
1187             Class.forName(type.replace('/', '.'), false, classPathLoader)
1188         } catch (e: Exception) {
1189             throw TransformerException("Failed to load class for '$type'", e)
1190         }
1191 }
1192 
mainnull1193 fun main(args: Array<String>) {
1194     if (args.size !in 1..3) {
1195         println("Usage: AtomicFUTransformerKt <dir> [<output>] [<variant>]")
1196         return
1197     }
1198     val t = AtomicFUTransformer(emptyList(), File(args[0]))
1199     if (args.size > 1) t.outputDir = File(args[1])
1200     if (args.size > 2) t.variant = enumValueOf(args[2].toUpperCase(Locale.US))
1201     t.verbose = true
1202     t.transform()
1203 }