Skip to content

Commit 3cbce9b

Browse files
committed
fix ObjectCreationTransformer: track all created objects
Before this commit, `ObjectCreationTransformer` tracked only the objects on which `Object::<init>` constructor was called. However, if the instrumented code created a new instance of some `Derived` class, such that its `Base` class is not instrumented, then no `Object::<init>` constructor call would have been tracked. To fix this issue, we now track any `::<init>` constructor call, and keep a counter of uninitialized objects to distringuish between actual constructor calls, and the calls of the base class constructor from the derived class constructor. Signed-off-by: Evgeniy Moiseenko <[email protected]>
1 parent bdfc241 commit 3cbce9b

File tree

1 file changed

+79
-22
lines changed

1 file changed

+79
-22
lines changed

src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/ObjectCreationTransformer.kt

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010

1111
package org.jetbrains.kotlinx.lincheck.transformation.transformers
1212

13+
import sun.nio.ch.lincheck.*
14+
import org.jetbrains.kotlinx.lincheck.transformation.*
15+
import org.jetbrains.kotlinx.lincheck.*
1316
import org.objectweb.asm.Opcodes.*
17+
import org.objectweb.asm.Type
1418
import org.objectweb.asm.commons.GeneratorAdapter
1519
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.*
1920

2021
/**
2122
* [ObjectCreationTransformer] tracks creation of new objects,
@@ -28,26 +29,81 @@ internal class ObjectCreationTransformer(
2829
adapter: GeneratorAdapter
2930
) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) {
3031

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
5080
}
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+
}
51107

52108
override fun visitIntInsn(opcode: Int, operand: Int) = adapter.run {
53109
adapter.visitIntInsn(opcode, operand)
@@ -71,6 +127,7 @@ internal class ObjectCreationTransformer(
71127
invokeStatic(Injections::beforeNewObjectCreation)
72128
}
73129
)
130+
uninitializedObjects++
74131
}
75132
visitTypeInsn(opcode, type)
76133
if (opcode == ANEWARRAY) {

0 commit comments

Comments
 (0)