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,81 @@ 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
+ * TODO: keeping just a counter might be not reliable in some cases,
58
+ * perhaps we need more robust solution, checking for particular bytecode instructions sequence, e.g.:
59
+ * `NEW; DUP; INVOKESPECIAL <init>`
60
+ */
61
+ private var uninitializedObjects = 0
62
+
63
+ override fun visitMethodInsn (opcode : Int , owner : String , name : String , desc : String , itf : Boolean ) = adapter.run {
64
+ // special handling for a common case of `Object` constructor
65
+ if (name == " <init>" && owner == " java/lang/Object" && uninitializedObjects > 0 ) {
66
+ invokeIfInTestingCode(
67
+ original = {
68
+ visitMethodInsn(opcode, owner, name, desc, itf)
69
+ },
70
+ code = {
71
+ val objectLocal = newLocal(OBJECT_TYPE )
72
+ copyLocal(objectLocal)
73
+ visitMethodInsn(opcode, owner, name, desc, itf)
74
+ loadLocal(objectLocal)
75
+ invokeStatic(Injections ::afterNewObjectCreation)
76
+ }
77
+ )
78
+ uninitializedObjects--
79
+ return
50
80
}
81
+ if (name == " <init>" && uninitializedObjects > 0 ) {
82
+ invokeIfInTestingCode(
83
+ original = {
84
+ visitMethodInsn(opcode, owner, name, desc, itf)
85
+ },
86
+ code = {
87
+ val objectLocal = newLocal(OBJECT_TYPE )
88
+ // save and pop the constructor parameters from the stack
89
+ val constructorType = Type .getType(desc)
90
+ val params = storeLocals(constructorType.argumentTypes)
91
+ // copy the object on which we call the constructor
92
+ copyLocal(objectLocal)
93
+ // push constructor parameters back on the stack
94
+ params.forEach { loadLocal(it) }
95
+ // call the constructor
96
+ visitMethodInsn(opcode, owner, name, desc, itf)
97
+ // call the injected method
98
+ loadLocal(objectLocal)
99
+ invokeStatic(Injections ::afterNewObjectCreation)
100
+ }
101
+ )
102
+ uninitializedObjects--
103
+ return
104
+ }
105
+ visitMethodInsn(opcode, owner, name, desc, itf)
106
+ }
51
107
52
108
override fun visitIntInsn (opcode : Int , operand : Int ) = adapter.run {
53
109
adapter.visitIntInsn(opcode, operand)
@@ -71,6 +127,7 @@ internal class ObjectCreationTransformer(
71
127
invokeStatic(Injections ::beforeNewObjectCreation)
72
128
}
73
129
)
130
+ uninitializedObjects++
74
131
}
75
132
visitTypeInsn(opcode, type)
76
133
if (opcode == ANEWARRAY ) {
0 commit comments