/
githubmirror
/
galahad
Обзор
Документация
Войти
/
githubmirror
/
galahad
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java
276 строк
10 KB
Adam Sotona
8366926: Unexpected exception occurs when executing code in a "local" JShell environment
23 сен 2025, 12:09
23 сен 2025, 12:09
d316d3f
Код
Авторство
О чём код?
/* * Copyright (c) 2016, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package jdk.jshell.execution; import java.io.ByteArrayInputStream; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashSet; 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; import java.lang.classfile.CodeTransform; import java.lang.classfile.Label; import java.lang.classfile.instruction.BranchInstruction; import java.lang.classfile.instruction.LabelTarget; /** * An implementation of {@link jdk.jshell.spi.ExecutionControl} which executes * in the same JVM as the JShell-core. * * @author Grigory Ptashko * @since 9 */ public class LocalExecutionControl extends DirectExecutionControl { private final Object STOP_LOCK = new Object(); private boolean userCodeRunning = false; private ThreadGroup execThreadGroup; private Field allStop = null; /** * Creates an instance, delegating loader operations to the specified * delegate. * * @param loaderDelegate the delegate to handle loading classes */ public LocalExecutionControl(LoaderDelegate loaderDelegate) { super(loaderDelegate); } /** * Create an instance using the default class loading, which delegates to the system class loader. */ public LocalExecutionControl() { } /** * Create an instance using the default class loading, but delegating to the specified parent class loader. * * @param parent parent class loader * @since 22 */ public LocalExecutionControl(ClassLoader parent) { super(new DefaultLoaderDelegate(parent)); } @Override public void load(ClassBytecodes[] cbcs) throws ClassInstallException, NotImplementedException, EngineTerminationException { super.load(instrument(cbcs)); } private static final String CANCEL_CLASS = "REPL.$Cancel$"; private static final ClassDesc CD_Cancel = ClassDesc.of(CANCEL_CLASS); private static final String STOP_CHECK = "stopCheck"; private static final ClassDesc CD_ThreadDeath = ClassDesc.of("java.lang.ThreadDeath"); 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))); } private static ClassBytecodes genCancelClass() { return new ClassBytecodes(CANCEL_CLASS, ClassFile.of().build(CD_Cancel, clb -> clb.withFlags(ClassFile.ACC_PUBLIC) .withField("allStop", ConstantDescs.CD_boolean, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC | ClassFile.ACC_VOLATILE) .withMethodBody(STOP_CHECK, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, cob -> cob.getstatic(CD_Cancel, "allStop", ConstantDescs.CD_boolean) .ifThenElse(tb -> tb.new_(CD_ThreadDeath) .dup() .invokespecial(CD_ThreadDeath, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void) .athrow(), eb -> eb.return_())))); } // This inserts calls to REPL.$Cancel$.stopCheck() at method start and prior to any backward branch private static class StopCheckWeaver implements CodeTransform { private final Set<Label> priorLabels = new HashSet<>(); @Override public void atStart(CodeBuilder builder) { stopCheck(builder); } @Override public void accept(CodeBuilder builder, CodeElement element) { switch (element) { case LabelTarget target -> priorLabels.add(target.label()); case BranchInstruction branch when priorLabels.contains(branch.target()) -> stopCheck(builder); default -> { } } builder.with(element); }; private void stopCheck(CodeBuilder builder) { builder.invokestatic(CD_Cancel, STOP_CHECK, ConstantDescs.MTD_void); } } @Override @SuppressWarnings("removal") protected String invoke(Method doitMethod) throws Exception { if (allStop == null) { super.load(new ClassBytecodes[]{ genCancelClass() }); allStop = findClass(CANCEL_CLASS).getDeclaredField("allStop"); } allStop.set(null, false); AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>(); AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>(); AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>(); AtomicReference<Boolean> stopped = new AtomicReference<>(false); execThreadGroup = new ThreadGroup("JShell process local execution") { @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof InvocationTargetException) { if (e.getCause() instanceof ThreadDeath) { stopped.set(true); } else { iteEx.set((InvocationTargetException) e); } } else if (e instanceof IllegalAccessException) { iaeEx.set((IllegalAccessException) e); } else if (e instanceof NoSuchMethodException) { nmeEx.set((NoSuchMethodException) e); } else if (e instanceof ThreadDeath) { stopped.set(true); } } }; final Object[] res = new Object[1]; Thread snippetThread = new Thread(execThreadGroup, () -> { try { res[0] = doitMethod.invoke(null, new Object[0]); } catch (InvocationTargetException e) { if (e.getCause() instanceof ThreadDeath) { stopped.set(true); } else { iteEx.set(e); } } catch (IllegalAccessException e) { iaeEx.set(e); } catch (ThreadDeath e) { stopped.set(true); } }); snippetThread.start(); Thread[] threadList = new Thread[execThreadGroup.activeCount()]; execThreadGroup.enumerate(threadList); for (Thread thread : threadList) { if (thread != null) { thread.join(); } } if (stopped.get()) { throw new StoppedException(); } if (iteEx.get() != null) { throw iteEx.get(); } else if (nmeEx.get() != null) { throw nmeEx.get(); } else if (iaeEx.get() != null) { throw iaeEx.get(); } return valueString(res[0]); } @Override public void stop() throws EngineTerminationException, InternalException { synchronized (STOP_LOCK) { if (!userCodeRunning) { return; } if (execThreadGroup == null) { throw new InternalException("Process-local code snippets thread group is null. Aborting stop."); } try { allStop.set(null, true); } catch (IllegalArgumentException | IllegalAccessException ex) { throw new InternalException("Exception on local stop: " + ex); } Thread[] threads; int len, threadCount; do { len = execThreadGroup.activeCount() + 4; threads = new Thread[len]; threadCount = execThreadGroup.enumerate(threads); } while (threadCount == len); for (int i = 0; i < threadCount; i++) { threads[i].interrupt(); } } } @Override protected void clientCodeEnter() { synchronized (STOP_LOCK) { userCodeRunning = true; } } @Override protected void clientCodeLeave() { synchronized (STOP_LOCK) { userCodeRunning = false; } } }