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 }