Skip to content

Commit b03e042

Browse files
authored
Fix instrumentation of newly loaded classes (#525)
1 parent 552c2f6 commit b03e042

File tree

5 files changed

+117
-12
lines changed

5 files changed

+117
-12
lines changed

src/jvm/main/org/jetbrains/kotlinx/lincheck/strategy/managed/ObjectLabelFactory.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,23 @@ object ObjectLabelFactory {
7070
if (obj is Thread) {
7171
return "Thread#${getObjectNumber(Thread::class.java, obj)}"
7272
}
73-
if (obj.javaClass.isAnonymousClass) {
74-
return obj.javaClass.simpleNameForAnonymous
73+
runCatching {
74+
if (obj.javaClass.isAnonymousClass) {
75+
return obj.javaClass.simpleNameForAnonymous
76+
}
7577
}
76-
return objectName(obj) + "#" + getObjectNumber(obj.javaClass, obj)
78+
val objectName = runCatching {
79+
objectName(obj) + "#" + getObjectNumber(obj.javaClass, obj)
80+
}
81+
// There is a Kotlin compiler bug that leads to exception
82+
// `java.lang.InternalError: Malformed class name`
83+
// when trying to query for a class name of an anonymous class on JDK 8:
84+
// - https://youtrack.jetbrains.com/issue/KT-16727/
85+
// in such a case we fall back to returning `<unknown>` class name.
86+
.getOrElse {
87+
"<unknown>"
88+
}
89+
return objectName
7790
}
7891

7992
private fun objectName(obj: Any): String {

src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/LincheckJavaAgent.kt

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ internal object LincheckJavaAgent {
133133
// old classes that were already loaded before and have coroutine method calls inside
134134
canonicalClassName in coroutineCallingClasses
135135
}
136-
instrumentation.retransformClasses(*classes.toTypedArray())
137-
instrumentedClasses.addAll(classes.map { it.name })
136+
// for some reason, without `isNotEmpty()` check this code can throw NPE on JVM 8
137+
if (classes.isNotEmpty()) {
138+
instrumentation.retransformClasses(*classes.toTypedArray())
139+
instrumentedClasses.addAll(classes.map { it.name })
140+
}
138141
}
139142

140143
// In the model checking mode, Lincheck processes classes lazily, only when they are used.
@@ -192,8 +195,11 @@ internal object LincheckJavaAgent {
192195
canonicalClassName in instrumentedClasses
193196
}
194197
// `retransformClasses` uses initial (loaded in VM from disk) class bytecode and reapplies
195-
// transformations of all agents that did not remove their transformers to this moment
196-
instrumentation.retransformClasses(*classes.toTypedArray())
198+
// transformations of all agents that did not remove their transformers to this moment;
199+
// for some reason, without `isNotEmpty()` check this code can throw NPE on JVM 8
200+
if (classes.isNotEmpty()) {
201+
instrumentation.retransformClasses(*classes.toTypedArray())
202+
}
197203
// Clear the set of instrumented classes.
198204
instrumentedClasses.clear()
199205
}
@@ -351,12 +357,15 @@ internal object LincheckClassFileTransformer : ClassFileTransformer {
351357
protectionDomain: ProtectionDomain?,
352358
classBytes: ByteArray
353359
): ByteArray? = runInIgnoredSection {
354-
if (classBeingRedefined == null) {
355-
// No internal class name is expected if no class is provided.
356-
return null
357-
} else {
358-
require(internalClassName != null) { "Class name must not be null" }
360+
if (classBeingRedefined != null) {
361+
require(internalClassName != null) {
362+
"Internal class name of redefined class ${classBeingRedefined.name} must not be null"
363+
}
359364
}
365+
// Internal class name could be `null` in some cases (can be witnessed on JDK-8),
366+
// this can be related to the Kotlin compiler bug:
367+
// - https://youtrack.jetbrains.com/issue/KT-16727/
368+
if (internalClassName == null) return null
360369
// If the class should not be transformed, return immediately.
361370
if (!shouldTransform(internalClassName.canonicalClassName, instrumentationMode)) {
362371
return null

src/jvm/test/org/jetbrains/kotlinx/lincheck_test/representation/RunConcurrentRepresentationTests.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,33 @@ class VariableReadWriteRunConcurrentRepresentationTest : BaseRunConcurrentRepres
180180
}
181181
}
182182

183+
class AnonymousObjectRunConcurrentRepresentationTest : BaseRunConcurrentRepresentationTest<Unit>(
184+
if (isJdk8) "run_concurrent_test/anonymous_object_jdk8.txt" else "run_concurrent_test/anonymous_object.txt"
185+
) {
186+
// use static fields to avoid local object optimizations
187+
companion object {
188+
@JvmField var runnable: Runnable? = null
189+
@JvmField var x = 0
190+
}
191+
192+
// use the interface to additionally check that KT-16727 bug is handled:
193+
// https://youtrack.jetbrains.com/issue/KT-16727/
194+
interface I {
195+
fun test() = object : Runnable {
196+
override fun run() {
197+
x++
198+
}
199+
}
200+
}
201+
202+
@Suppress("UNUSED_VARIABLE")
203+
override fun block() {
204+
runnable = (object : I {}).test()
205+
runnable!!.run()
206+
check(false)
207+
}
208+
}
209+
183210
// TODO investigate difference for trace debugger (Evgeniy Moiseenko)
184211
class CustomThreadsRunConcurrentRepresentationTest : BaseRunConcurrentRepresentationTest<Unit>(
185212
if (isInTraceDebuggerMode) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
= Concurrent test failed =
2+
3+
java.lang.IllegalStateException: Check failed.
4+
at org.jetbrains.kotlinx.lincheck_test.representation.AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:206)
5+
at org.jetbrains.kotlinx.lincheck_test.representation.AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:183)
6+
at org.jetbrains.kotlinx.lincheck_test.representation.BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40)
7+
at java.base/java.lang.Thread.run(Thread.java:840)
8+
9+
The following interleaving leads to the error:
10+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11+
| Thread 1 |
12+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13+
| AnonymousObjectRunConcurrentRepresentationTest#1.block(): threw IllegalStateException at BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40) |
14+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15+
16+
Detailed trace:
17+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18+
| Thread 1 |
19+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20+
| AnonymousObjectRunConcurrentRepresentationTest#1.block(): threw IllegalStateException at BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40) |
21+
| block(): threw IllegalStateException at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:183) |
22+
| AnonymousObjectRunConcurrentRepresentationTest.runnable = test$1 at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:204) |
23+
| AnonymousObjectRunConcurrentRepresentationTest.runnable ➜ test$1 at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:205) |
24+
| test$1.run() at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:205) |
25+
| AnonymousObjectRunConcurrentRepresentationTest.x ➜ 0 at AnonymousObjectRunConcurrentRepresentationTest$I$test$1.run(RunConcurrentRepresentationTests.kt:197) |
26+
| AnonymousObjectRunConcurrentRepresentationTest.x = 1 at AnonymousObjectRunConcurrentRepresentationTest$I$test$1.run(RunConcurrentRepresentationTests.kt:197) |
27+
| result: IllegalStateException #1 |
28+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
= Concurrent test failed =
2+
3+
java.lang.IllegalStateException: Check failed.
4+
at org.jetbrains.kotlinx.lincheck_test.representation.AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:206)
5+
at org.jetbrains.kotlinx.lincheck_test.representation.AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:183)
6+
at org.jetbrains.kotlinx.lincheck_test.representation.BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40)
7+
at java.lang.Thread.run(Thread.java:750)
8+
9+
The following interleaving leads to the error:
10+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11+
| Thread 1 |
12+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13+
| AnonymousObjectRunConcurrentRepresentationTest#1.block(): threw IllegalStateException at BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40) |
14+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15+
16+
Detailed trace:
17+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18+
| Thread 1 |
19+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20+
| AnonymousObjectRunConcurrentRepresentationTest#1.block(): threw IllegalStateException at BaseRunConcurrentRepresentationTest$testRunWithModelChecker$result$1$1.invoke(RunConcurrentRepresentationTests.kt:40) |
21+
| block(): threw IllegalStateException at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:183) |
22+
| AnonymousObjectRunConcurrentRepresentationTest.runnable = <unknown> at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:204) |
23+
| AnonymousObjectRunConcurrentRepresentationTest.runnable ➜ <unknown> at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:205) |
24+
| <unknown>.run() at AnonymousObjectRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:205) |
25+
| AnonymousObjectRunConcurrentRepresentationTest.x ➜ 0 at AnonymousObjectRunConcurrentRepresentationTest$I$test$1.run(RunConcurrentRepresentationTests.kt:197) |
26+
| AnonymousObjectRunConcurrentRepresentationTest.x = 1 at AnonymousObjectRunConcurrentRepresentationTest$I$test$1.run(RunConcurrentRepresentationTests.kt:197) |
27+
| result: IllegalStateException #1 |
28+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

0 commit comments

Comments
 (0)