10
10
11
11
package org.jetbrains.kotlinx.lincheck.transformation.transformers
12
12
13
+ import sun.nio.ch.lincheck.*
14
+ import org.jetbrains.kotlinx.lincheck.transformation.*
15
+ import org.jetbrains.kotlinx.lincheck.*
13
16
import org.objectweb.asm.Opcodes.*
17
+ import org.objectweb.asm.Type
14
18
import org.objectweb.asm.commons.GeneratorAdapter
15
19
import org.objectweb.asm.commons.InstructionAdapter.OBJECT_TYPE
16
- import org.jetbrains.kotlinx.lincheck.canonicalClassName
17
- import org.jetbrains.kotlinx.lincheck.transformation.*
18
- import sun.nio.ch.lincheck.*
19
20
20
21
/* *
21
22
* [ObjectCreationTransformer] tracks creation of new objects,
@@ -28,26 +29,84 @@ internal class ObjectCreationTransformer(
28
29
adapter : GeneratorAdapter
29
30
) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) {
30
31
31
- override fun visitMethodInsn (opcode : Int , owner : String , name : String , desc : String , itf : Boolean ) =
32
- adapter.run {
33
- if (name == " <init>" && owner == " java/lang/Object" ) {
34
- invokeIfInTestingCode(
35
- original = {
36
- visitMethodInsn(opcode, owner, name, desc, itf)
37
- },
38
- code = {
39
- val objectLocal = newLocal(OBJECT_TYPE )
40
- dup()
41
- storeLocal(objectLocal)
42
- visitMethodInsn(opcode, owner, name, desc, itf)
43
- loadLocal(objectLocal)
44
- invokeStatic(Injections ::afterNewObjectCreation)
45
- }
46
- )
47
- } else {
48
- visitMethodInsn(opcode, owner, name, desc, itf)
49
- }
32
+ /* To track object creation, this transformer inserts `Injections::afterNewObjectCreation` calls
33
+ * after an object is allocated and initialized.
34
+ * The created object is passed into the injected function as an argument.
35
+ *
36
+ * In order to achieve this, this transformer tracks the following instructions:
37
+ * `NEW`, `NEWARRAY`, `ANEWARRAY`, and `MULTIANEWARRAY`;
38
+ *
39
+ * It is possible to inject the injection call right after array objects creation
40
+ * (i.e., after all instructions listed above except `NEW`),
41
+ * since the array is in initialized state right after its allocation.
42
+ * However, when an object is allocated via `NEW` it is first in uninitialized state,
43
+ * until its constructor (i.e., `<init>` method) is called.
44
+ * Trying to pass the object in uninitialized into the injected function would result
45
+ * in a bytecode verification error.
46
+ * Thus, we postpone the injection up after the constructor call (i.e., `<init>`).
47
+ *
48
+ * Another difficulty is that because of the inheritance, there could exist several
49
+ * constructor calls (i.e., `<init>`) for the same object.
50
+ * We need to distinguish between the base class constructor call inside the derived class constructor,
51
+ * and the actual initializing constructor call from the object creation call size.
52
+ *
53
+ * Therefore, to tackle these issues, we maintain a counter of allocated, but not yet initialized objects.
54
+ * Whenever we encounter a constructor call (i.e., `<init>`) we check for the counter
55
+ * and inject the object creation tracking method if the counter is not null.
56
+ *
57
+ * The solution with allocated objects counter is inspired by:
58
+ * https://github.com/google/allocation-instrumenter
59
+ *
60
+ * TODO: keeping just a counter might be not reliable in some cases,
61
+ * perhaps we need more robust solution, checking for particular bytecode instructions sequence, e.g.:
62
+ * `NEW; DUP; INVOKESPECIAL <init>`
63
+ */
64
+ private var uninitializedObjects = 0
65
+
66
+ override fun visitMethodInsn (opcode : Int , owner : String , name : String , desc : String , itf : Boolean ) = adapter.run {
67
+ // special handling for a common case of `Object` constructor
68
+ if (name == " <init>" && owner == " java/lang/Object" && uninitializedObjects > 0 ) {
69
+ invokeIfInTestingCode(
70
+ original = {
71
+ visitMethodInsn(opcode, owner, name, desc, itf)
72
+ },
73
+ code = {
74
+ val objectLocal = newLocal(OBJECT_TYPE )
75
+ copyLocal(objectLocal)
76
+ visitMethodInsn(opcode, owner, name, desc, itf)
77
+ loadLocal(objectLocal)
78
+ invokeStatic(Injections ::afterNewObjectCreation)
79
+ }
80
+ )
81
+ uninitializedObjects--
82
+ return
50
83
}
84
+ if (name == " <init>" && uninitializedObjects > 0 ) {
85
+ invokeIfInTestingCode(
86
+ original = {
87
+ visitMethodInsn(opcode, owner, name, desc, itf)
88
+ },
89
+ code = {
90
+ val objectLocal = newLocal(OBJECT_TYPE )
91
+ // save and pop the constructor parameters from the stack
92
+ val constructorType = Type .getType(desc)
93
+ val params = storeLocals(constructorType.argumentTypes)
94
+ // copy the object on which we call the constructor
95
+ copyLocal(objectLocal)
96
+ // push constructor parameters back on the stack
97
+ params.forEach { loadLocal(it) }
98
+ // call the constructor
99
+ visitMethodInsn(opcode, owner, name, desc, itf)
100
+ // call the injected method
101
+ loadLocal(objectLocal)
102
+ invokeStatic(Injections ::afterNewObjectCreation)
103
+ }
104
+ )
105
+ uninitializedObjects--
106
+ return
107
+ }
108
+ visitMethodInsn(opcode, owner, name, desc, itf)
109
+ }
51
110
52
111
override fun visitIntInsn (opcode : Int , operand : Int ) = adapter.run {
53
112
adapter.visitIntInsn(opcode, operand)
@@ -71,6 +130,7 @@ internal class ObjectCreationTransformer(
71
130
invokeStatic(Injections ::beforeNewObjectCreation)
72
131
}
73
132
)
133
+ uninitializedObjects++
74
134
}
75
135
visitTypeInsn(opcode, type)
76
136
if (opcode == ANEWARRAY ) {
0 commit comments