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