Skip to content

Commit dacf5d9

Browse files
authored
Fix the illegal lookupClass issue when using MethodHandles.Lookup on JDK 8 (#594)
* Fix MethodHandles. * Clarify tryCatchFinally docs * Add a test for custom `MethodHandles.lookup` * Enhance logging
1 parent 718d5e4 commit dacf5d9

File tree

6 files changed

+123
-5
lines changed

6 files changed

+123
-5
lines changed

bootstrap/src/sun/nio/ch/lincheck/Injections.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
package sun.nio.ch.lincheck;
1212

1313
import java.lang.invoke.CallSite;
14+
import java.lang.invoke.MethodHandles;
15+
import java.lang.reflect.Constructor;
1416

1517
import static sun.nio.ch.lincheck.Types.convertAsmMethodType;
1618

@@ -634,4 +636,46 @@ public static int getNextEventId(String type) {
634636
public static void setLastMethodCallEventId() {
635637
getEventTracker().setLastMethodCallEventId();
636638
}
639+
640+
/**
641+
* Attempts to retrieve the Class object associated with the given class name.
642+
* If the class is not found, it returns null instead of throwing an exception.
643+
*
644+
* @param name the fully qualified name of the desired class
645+
* @return the Class object for the class with the specified name,
646+
* or null if the class cannot be located
647+
*/
648+
public static Class<?> getClassForNameOrNull(String name) {
649+
try {
650+
return Class.forName(name);
651+
} catch (ClassNotFoundException e) {
652+
return null;
653+
}
654+
}
655+
656+
657+
private static Constructor<MethodHandles.Lookup> lookUpPrivateConstructor = null;
658+
659+
/**
660+
* Provides a trusted MethodHandles.Lookup for the given class,
661+
* bypassing JDK 8-specific restrictions written in the {@link MethodHandles} implementation.
662+
* <p>
663+
* In JDK 8 there are additional <a href="https://github.com/frohoff/jdk8u-jdk/blob/da0da73ab82ed714dc5be94acd2f0d00fbdfe2e9/src/share/classes/java/lang/invoke/MethodHandles.java#L681">restrictions</a>,
664+
* breaking {@code MethodHandles.lookup()} from being called from the Java standard library.
665+
* Calling it is necessary for {@code invokedynamic} handling in the trace debugger.
666+
* <p>
667+
* The function instead calls a private constructor with the TRUSTED mode via reflection.
668+
*
669+
* @param callerClass the class for which the trusted {@link MethodHandles.Lookup} is to be created
670+
* @return a trusted {@link MethodHandles.Lookup} instance for the specified class
671+
* @throws Exception if an error occurs while creating the trusted lookup instance
672+
*/
673+
public static MethodHandles.Lookup trustedLookup(Class<?> callerClass) throws Exception {
674+
if (lookUpPrivateConstructor == null) {
675+
Constructor<MethodHandles.Lookup> declaredConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
676+
declaredConstructor.setAccessible(true);
677+
lookUpPrivateConstructor = declaredConstructor;
678+
}
679+
return lookUpPrivateConstructor.newInstance(callerClass, -1);
680+
}
637681
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,10 @@ abstract class ManagedStrategy(
14771477
params: Array<Any?>,
14781478
result: Any?
14791479
) = runInsideIgnoredSection {
1480+
if (deterministicMethodDescriptor != null) {
1481+
Logger.debug { "On method return with descriptor $deterministicMethodDescriptor: $result" }
1482+
}
1483+
14801484
require(deterministicMethodDescriptor is DeterministicMethodDescriptor<*, *>?)
14811485
// process intrinsic candidate methods
14821486
if (MethodIds.isIntrinsicMethod(methodId)) {
@@ -1531,6 +1535,9 @@ abstract class ManagedStrategy(
15311535
params: Array<Any?>,
15321536
throwable: Throwable
15331537
) = runInsideIgnoredSection {
1538+
if (deterministicMethodDescriptor != null) {
1539+
Logger.debug { "On method exception with descriptor $deterministicMethodDescriptor:\n${throwable.stackTraceToString()}" }
1540+
}
15341541
require(deterministicMethodDescriptor is DeterministicMethodDescriptor<*, *>?)
15351542
if (isInTraceDebuggerMode && isFirstReplay && deterministicMethodDescriptor != null) {
15361543
deterministicMethodDescriptor.saveFirstResult(receiver, params, KResult.failure(throwable)) {

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.objectweb.asm.Opcodes.*
1616
import org.objectweb.asm.commons.*
1717
import org.jetbrains.kotlinx.lincheck.transformation.InstrumentationMode.*
1818
import org.jetbrains.kotlinx.lincheck.transformation.transformers.*
19+
import org.jetbrains.kotlinx.lincheck.util.Logger
1920
import sun.nio.ch.lincheck.*
2021

2122
internal class LincheckClassVisitor(
@@ -69,7 +70,10 @@ internal class LincheckClassVisitor(
6970
exceptions: Array<String>?
7071
): MethodVisitor {
7172
var mv = super.visitMethod(access, methodName, desc, signature, exceptions)
72-
if (access and ACC_NATIVE != 0) return mv
73+
if (access and ACC_NATIVE != 0) {
74+
Logger.debug { "Skipping transformation of the native method $className.$methodName" }
75+
return mv
76+
}
7377
if (instrumentationMode == STRESS) {
7478
return if (methodName != "<clinit>" && methodName != "<init>") {
7579
CoroutineCancellabilitySupportTransformer(mv, access, className, methodName, desc)
@@ -140,7 +144,9 @@ internal class LincheckClassVisitor(
140144
if (isInTraceDebuggerMode) {
141145
// Lincheck does not support true identity hash codes (it always uses zeroes),
142146
// so there is no need for the `DeterministicInvokeDynamicTransformer` there.
143-
mv = DeterministicInvokeDynamicTransformer(fileName, className, methodName, mv.newAdapter())
147+
mv = DeterministicInvokeDynamicTransformer(
148+
fileName, className, methodName, classVersion, mv.newAdapter()
149+
)
144150
}
145151
mv = run {
146152
val st = ConstructorArgumentsSnapshotTrackerTransformer(fileName, className, methodName, mv.newAdapter(), classVisitor::isInstanceOf)
@@ -177,7 +183,7 @@ internal class LincheckClassVisitor(
177183
if (isInTraceDebuggerMode) {
178184
// Lincheck does not support true identity hash codes (it always uses zeroes),
179185
// so there is no need for the `DeterministicInvokeDynamicTransformer` there.
180-
mv = DeterministicInvokeDynamicTransformer(fileName, className, methodName, mv.newAdapter())
186+
mv = DeterministicInvokeDynamicTransformer(fileName, className, methodName, classVersion, mv.newAdapter())
181187
} else {
182188
// In trace debugger mode we record hash codes of tracked objects and substitute them on re-run,
183189
// otherwise, we track all hash code calls in the instrumented code

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ internal fun GeneratorAdapter.storeArguments(methodDescriptor: String): IntArray
184184
/**
185185
* Executes a try-catch-finally block within the context of the GeneratorAdapter.
186186
*
187-
* Attention: this method does not insert `finally` blocks before inner return and throw statements.
187+
* **Attention**:
188+
* * This method does not insert `finally` blocks before inner return and throw statements.
189+
* * It is forbidden to jump from the blocks outside and between them.
190+
* * The operand stack must be empty by the beginning of the [tryCatchFinally].
188191
*
189192
* @param tryBlock The code block to be executed in the try section.
190193
* @param exceptionType The type of exception to be caught in the `catch` section, or null to catch all exceptions.

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ internal class DeterministicInvokeDynamicTransformer(
4040
fileName: String,
4141
className: String,
4242
methodName: String,
43+
private val classVersion: Int,
4344
adapter: GeneratorAdapter
4445
) : ManagedStrategyMethodVisitor(fileName, className, methodName, adapter) {
4546
override fun visitInvokeDynamicInsn(
@@ -208,7 +209,24 @@ internal class DeterministicInvokeDynamicTransformer(
208209
require(bootstrapMethodParameterTypes[2] == methodTypeType)
209210

210211
// pushing predefined arguments manually
211-
invokeStatic(MethodHandles::lookup)
212+
if (classVersion == 52 /* Java 8 */) {
213+
invokeInsideIgnoredSection {
214+
push(this@DeterministicInvokeDynamicTransformer.className.replace('/', '.'))
215+
invokeStatic(Injections::getClassForNameOrNull)
216+
dup()
217+
val endLabel = newLabel()
218+
val ifNonNullLabel = newLabel()
219+
ifNonNull(ifNonNullLabel)
220+
pop()
221+
invokeStatic(MethodHandles::lookup)
222+
goTo(endLabel)
223+
visitLabel(ifNonNullLabel)
224+
invokeStatic(Injections::trustedLookup)
225+
visitLabel(endLabel)
226+
}
227+
} else {
228+
invokeStatic(MethodHandles::lookup)
229+
}
212230
visitLdcInsn(name)
213231
visitLdcInsn(Type.getMethodType(descriptor))
214232
val jvmPredefinedParametersCount = 3
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Lincheck
3+
*
4+
* Copyright (C) 2019 - 2025 JetBrains s.r.o.
5+
*
6+
* This Source Code Form is subject to the terms of the
7+
* Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed
8+
* with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
package org.jetbrains.kotlinx.lincheck_test.trace_debugger;
12+
13+
import org.jetbrains.kotlinx.lincheck.annotations.Operation;
14+
15+
import java.lang.invoke.MethodHandles;
16+
import java.util.Arrays;
17+
import java.util.Collection;
18+
import java.util.List;
19+
import java.util.stream.Collectors;
20+
21+
/**
22+
* This test is intended to check that {@link MethodHandles} is successfully created even on Java 8.
23+
* It is necessary for invokedynamic handling in the trace debugger.
24+
* <p>
25+
* However, due to workaround for <a href="https://github.com/JetBrains/lincheck/issues/500">the issue</a>,
26+
* the tests are actually run on the JDK 17.
27+
* Nevertheless, when the issue is resolved,
28+
* the tests will actually check the correct {@link MethodHandles.Lookup} creation.
29+
*/
30+
public class CollectorsTest extends AbstractDeterministicTest {
31+
@Operation
32+
public Object operation() {
33+
return Arrays.stream(
34+
new List[] {
35+
Arrays.stream(new String[]{"Hello ", "w"}).collect(Collectors.toList()),
36+
Arrays.stream(new String[]{"Hello ", "w"}).collect(Collectors.toList())
37+
}
38+
).flatMap(Collection::stream).collect(Collectors.toList());
39+
}
40+
}

0 commit comments

Comments
 (0)