Skip to content

Commit 86e5f47

Browse files
committed
8366926: Unexpected exception occurs when executing code in a "local" JShell environment
Backport-of: d316d3f74fd951613eef3870ee3da2c2dc5b719c
1 parent 8fd86ab commit 86e5f47

2 files changed

Lines changed: 87 additions & 5 deletions

File tree

src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package jdk.jshell.execution;
2626

27+
import java.io.ByteArrayInputStream;
2728
import java.lang.constant.ClassDesc;
2829
import java.lang.constant.ConstantDescs;
2930
import java.lang.reflect.Field;
@@ -34,6 +35,7 @@
3435
import java.util.concurrent.atomic.AtomicReference;
3536
import java.util.stream.Stream;
3637
import java.lang.classfile.ClassFile;
38+
import java.lang.classfile.ClassHierarchyResolver;
3739
import java.lang.classfile.ClassTransform;
3840
import java.lang.classfile.CodeBuilder;
3941
import java.lang.classfile.CodeElement;
@@ -85,18 +87,34 @@ public LocalExecutionControl(ClassLoader parent) {
8587
@Override
8688
public void load(ClassBytecodes[] cbcs)
8789
throws ClassInstallException, NotImplementedException, EngineTerminationException {
88-
super.load(Stream.of(cbcs)
89-
.map(cbc -> new ClassBytecodes(cbc.name(), instrument(cbc.bytecodes())))
90-
.toArray(ClassBytecodes[]::new));
90+
super.load(instrument(cbcs));
9191
}
9292

9393
private static final String CANCEL_CLASS = "REPL.$Cancel$";
9494
private static final ClassDesc CD_Cancel = ClassDesc.of(CANCEL_CLASS);
9595
private static final String STOP_CHECK = "stopCheck";
9696
private static final ClassDesc CD_ThreadDeath = ClassDesc.of("java.lang.ThreadDeath");
9797

98-
private static byte[] instrument(byte[] classFile) {
99-
var cc = ClassFile.of();
98+
private static ClassBytecodes[] instrument(ClassBytecodes[] cbcs) {
99+
var cc = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
100+
ClassHierarchyResolver.defaultResolver().orElse(
101+
ClassHierarchyResolver.ofResourceParsing(cd -> {
102+
String cName = cd.descriptorString();
103+
cName = cName.substring(1, cName.length() - 1).replace('/', '.');
104+
for (ClassBytecodes cbc : cbcs) {
105+
if (cName.equals(cbc.name())) {
106+
return new ByteArrayInputStream(cbc.bytecodes());
107+
}
108+
}
109+
return null;
110+
}))));
111+
112+
return Stream.of(cbcs)
113+
.map(cbc -> new ClassBytecodes(cbc.name(), instrument(cc, cbc.bytecodes())))
114+
.toArray(ClassBytecodes[]::new);
115+
}
116+
117+
private static byte[] instrument(ClassFile cc, byte[] classFile) {
100118
return cc.transformClass(cc.parse(classFile),
101119
ClassTransform.transformingMethodBodies(
102120
CodeTransform.ofStateful(StopCheckWeaver::new)));
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8366926
27+
* @summary Verify the instrumenation class hierarchy resolution works properly in local execution mode
28+
* @library /tools/lib
29+
* @modules
30+
* jdk.compiler/com.sun.tools.javac.api
31+
* jdk.compiler/com.sun.tools.javac.main
32+
* @build KullaTesting
33+
* @run junit/othervm LocalExecutionInstrumentationCHRTest
34+
*/
35+
36+
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.TestInstance;
38+
39+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
40+
public class LocalExecutionInstrumentationCHRTest extends ReplToolTesting {
41+
42+
@Test
43+
public void verifyMyClassFoundOnClassPath() {
44+
test(new String[] { "--execution", "local" },
45+
a -> assertCommand(a, "public interface TestInterface {}", "| created interface TestInterface"),
46+
a -> assertCommand(a,
47+
"public class TestClass {"
48+
+ "public TestInterface foo(boolean b) {"
49+
+ "TestInterface test; "
50+
+ "if (b) {"
51+
+ "test = new TestInterfaceImpl1();"
52+
+ "} else {"
53+
+ "test = new TestInterfaceImpl2();"
54+
+ "}"
55+
+ "return test;"
56+
+ "}"
57+
+ "private class TestInterfaceImpl1 implements TestInterface {}"
58+
+ "private class TestInterfaceImpl2 implements TestInterface {}"
59+
+ "}", "| created class TestClass"),
60+
a -> assertCommand(a, "new TestClass().foo(true).getClass();", "$3 ==> class TestClass$TestInterfaceImpl1"),
61+
a -> assertCommand(a, "new TestClass().foo(false).getClass();", "$4 ==> class TestClass$TestInterfaceImpl2")
62+
);
63+
}
64+
}

0 commit comments

Comments
 (0)