Skip to content

Commit 91b72ad

Browse files
authored
Fix object creation tracking (#426)
1 parent bdfc241 commit 91b72ad

File tree

2 files changed

+94
-22
lines changed

2 files changed

+94
-22
lines changed

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

Lines changed: 82 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,84 @@ 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+
* 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
5083
}
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+
}
51110

52111
override fun visitIntInsn(opcode: Int, operand: Int) = adapter.run {
53112
adapter.visitIntInsn(opcode, operand)
@@ -71,6 +130,7 @@ internal class ObjectCreationTransformer(
71130
invokeStatic(Injections::beforeNewObjectCreation)
72131
}
73132
)
133+
uninitializedObjects++
74134
}
75135
visitTypeInsn(opcode, type)
76136
if (opcode == ANEWARRAY) {

src/jvm/test/org/jetbrains/kotlinx/lincheck_test/transformation/LocalObjectsTests.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ class LocalObjectEliminationTest {
5050
b.array[0] = it
5151
}
5252
a.any = b
53+
// check that closure object and captured `x: IntRef` object
54+
// are correctly classified as local objects;
55+
// note that these classes itself are not instrumented,
56+
// but the creation of their instances still should be tracked
57+
var x = 0
58+
val closure = {
59+
a.value += 1
60+
x += 1
61+
}
62+
repeat(20) {
63+
closure()
64+
}
5365
return (a.any as A).array.sum()
5466
}
5567

0 commit comments

Comments
 (0)