8366926: Unexpected exception occurs when executing code in a "local" JShell environment

Reviewed-by: liach, jlahoda
This commit is contained in:
Adam Sotona 2025-09-23 09:09:46 +00:00
parent 2e99ed6422
commit d316d3f74f
2 changed files with 87 additions and 5 deletions

View File

@ -24,6 +24,7 @@
*/
package jdk.jshell.execution;
import java.io.ByteArrayInputStream;
import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.lang.reflect.Field;
@ -34,6 +35,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassHierarchyResolver;
import java.lang.classfile.ClassTransform;
import java.lang.classfile.CodeBuilder;
import java.lang.classfile.CodeElement;
@ -85,9 +87,7 @@ public class LocalExecutionControl extends DirectExecutionControl {
@Override
public void load(ClassBytecodes[] cbcs)
throws ClassInstallException, NotImplementedException, EngineTerminationException {
super.load(Stream.of(cbcs)
.map(cbc -> new ClassBytecodes(cbc.name(), instrument(cbc.bytecodes())))
.toArray(ClassBytecodes[]::new));
super.load(instrument(cbcs));
}
private static final String CANCEL_CLASS = "REPL.$Cancel$";
@ -95,8 +95,26 @@ public class LocalExecutionControl extends DirectExecutionControl {
private static final String STOP_CHECK = "stopCheck";
private static final ClassDesc CD_ThreadDeath = ClassDesc.of("java.lang.ThreadDeath");
private static byte[] instrument(byte[] classFile) {
var cc = ClassFile.of();
private static ClassBytecodes[] instrument(ClassBytecodes[] cbcs) {
var cc = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(
ClassHierarchyResolver.defaultResolver().orElse(
ClassHierarchyResolver.ofResourceParsing(cd -> {
String cName = cd.descriptorString();
cName = cName.substring(1, cName.length() - 1).replace('/', '.');
for (ClassBytecodes cbc : cbcs) {
if (cName.equals(cbc.name())) {
return new ByteArrayInputStream(cbc.bytecodes());
}
}
return null;
}))));
return Stream.of(cbcs)
.map(cbc -> new ClassBytecodes(cbc.name(), instrument(cc, cbc.bytecodes())))
.toArray(ClassBytecodes[]::new);
}
private static byte[] instrument(ClassFile cc, byte[] classFile) {
return cc.transformClass(cc.parse(classFile),
ClassTransform.transformingMethodBodies(
CodeTransform.ofStateful(StopCheckWeaver::new)));

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8366926
* @summary Verify the instrumenation class hierarchy resolution works properly in local execution mode
* @library /tools/lib
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build KullaTesting
* @run junit/othervm LocalExecutionInstrumentationCHRTest
*/
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class LocalExecutionInstrumentationCHRTest extends ReplToolTesting {
@Test
public void verifyMyClassFoundOnClassPath() {
test(new String[] { "--execution", "local" },
a -> assertCommand(a, "public interface TestInterface {}", "| created interface TestInterface"),
a -> assertCommand(a,
"public class TestClass {"
+ "public TestInterface foo(boolean b) {"
+ "TestInterface test; "
+ "if (b) {"
+ "test = new TestInterfaceImpl1();"
+ "} else {"
+ "test = new TestInterfaceImpl2();"
+ "}"
+ "return test;"
+ "}"
+ "private class TestInterfaceImpl1 implements TestInterface {}"
+ "private class TestInterfaceImpl2 implements TestInterface {}"
+ "}", "| created class TestClass"),
a -> assertCommand(a, "new TestClass().foo(true).getClass();", "$3 ==> class TestClass$TestInterfaceImpl1"),
a -> assertCommand(a, "new TestClass().foo(false).getClass();", "$4 ==> class TestClass$TestInterfaceImpl2")
);
}
}