--- old/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java 2016-07-12 22:46:50.104815641 -0700 +++ new/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java 2016-07-12 22:46:50.016813377 -0700 @@ -64,6 +64,11 @@ public static final int DBG_DEP = 0b0001000; /** + * Execution control channel debugging. + */ + public static final int DBG_EC = 0b0010000; + + /** * Event debugging. */ public static final int DBG_EVNT = 0b0010000; @@ -125,7 +130,7 @@ * @param ex the fatal Exception * @param where additional context */ - public static void debug(JShell state, PrintStream err, Exception ex, String where) { + public static void debug(JShell state, PrintStream err, Throwable ex, String where) { if (isDebugEnabled(state, 0xFFFFFFFF)) { err.printf("Fatal error: %s: %s\n", where, ex.getMessage()); ex.printStackTrace(err); --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java 2016-07-12 22:46:50.422823818 -0700 +++ new/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java 2016-07-12 22:46:50.310820938 -0700 @@ -105,6 +105,7 @@ import static java.util.stream.Collectors.toMap; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP; +import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EC; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; @@ -1375,7 +1376,7 @@ boolean cmdDebug(String arg) { if (arg.isEmpty()) { debug = !debug; - InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0); + InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN | DBG_EC : 0); fluff("Debugging %s", debug ? "on" : "off"); } else { int flags = 0; @@ -1394,6 +1395,10 @@ flags |= DBG_GEN; fluff("General debugging on"); break; + case 'x': + flags |= DBG_EC; + fluff("Execution control debugging on"); + break; case 'f': flags |= DBG_FMGR; fluff("File manager debugging on"); @@ -1412,7 +1417,7 @@ break; default: hard("Unknown debugging option: %c", ch); - fluff("Use: 0 r g f c d"); + fluff("Use: 0 r g x f c d"); return false; } } --- old/src/jdk.jshell/share/classes/jdk/jshell/Eval.java 2016-07-12 22:46:50.756832408 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/Eval.java 2016-07-12 22:46:50.666830093 -0700 @@ -61,6 +61,13 @@ import jdk.jshell.TreeDissector.ExpressionInfo; import jdk.jshell.Wrap.Range; import jdk.jshell.Snippet.Status; +import jdk.jshell.spi.ExecutionControl.ClassBytecodes; +import jdk.jshell.spi.ExecutionControl.ClassInstallException; +import jdk.jshell.spi.ExecutionControl.EngineTerminationException; +import jdk.jshell.spi.ExecutionControl.InternalException; +import jdk.jshell.spi.ExecutionControl.ResolutionException; +import jdk.jshell.spi.ExecutionControl.RunException; +import jdk.jshell.spi.ExecutionControl.UserException; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static java.util.Collections.singletonList; @@ -541,15 +548,21 @@ if (si.status().isDefined()) { if (si.isExecutable()) { try { - value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME); + value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME); value = si.subKind().hasValue() ? expunge(value) : ""; - } catch (EvalException ex) { + } catch (ResolutionException ex) { + DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id()); + exception = new UnresolvedReferenceException(sn, ex.getStackTrace()); + } catch (UserException ex) { exception = translateExecutionException(ex); - } catch (JShellException ex) { - // UnresolvedReferenceException - exception = ex; + } catch (RunException ex) { + // StopException - no-op + } catch (InternalException ex) { + state.debug(ex, "invoke"); + } catch (EngineTerminationException ex) { + state.closeDown(); } } else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) { switch (((VarSnippet) si).typeName()) { @@ -700,15 +713,23 @@ /** * If there are classes to load, loads by calling the execution engine. - * @param classnames names of the classes to load. + * @param classbytecoes names of the classes to load. */ - private void load(Collection classnames) { - if (!classnames.isEmpty()) { - state.executionControl().load(classnames); + private void load(Collection classbytecoes) { + if (!classbytecoes.isEmpty()) { + ClassBytecodes[] cbcs = classbytecoes.toArray(new ClassBytecodes[classbytecoes.size()]); + try { + state.executionControl().load(cbcs); + state.classTracker.markLoaded(cbcs); + } catch (ClassInstallException ex) { + state.classTracker.markLoaded(cbcs, ex.installed()); + } catch (EngineTerminationException ex) { + state.closeDown(); + } } } - private EvalException translateExecutionException(EvalException ex) { + private EvalException translateExecutionException(UserException ex) { StackTraceElement[] raw = ex.getStackTrace(); int last = raw.length; do { @@ -739,7 +760,7 @@ if (msg.equals("")) { msg = null; } - return new EvalException(msg, ex.getExceptionClassName(), elems); + return new EvalException(msg, ex.causeExceptionClass(), elems); } private boolean isWrap(StackTraceElement ste) { --- old/src/jdk.jshell/share/classes/jdk/jshell/JShell.java 2016-07-12 22:46:51.113841589 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/JShell.java 2016-07-12 22:46:50.982838220 -0700 @@ -44,13 +44,15 @@ import java.util.function.Supplier; import jdk.internal.jshell.debug.InternalDebugControl; -import jdk.internal.jshell.jdi.FailOverExecutionControl; +import jdk.jshell.Snippet.Status; +import jdk.jshell.execution.JDIDefaultExecutionControl; +import jdk.jshell.spi.ExecutionControl.EngineTerminationException; +import jdk.jshell.spi.ExecutionControl.ExecutionControlException; +import jdk.jshell.spi.ExecutionEnv; +import static jdk.jshell.execution.Util.failOverExecutionControlGenerator; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import static jdk.jshell.Util.expunge; -import jdk.jshell.Snippet.Status; -import jdk.internal.jshell.jdi.JDIExecutionControl; -import jdk.jshell.spi.ExecutionEnv; /** * The JShell evaluation state engine. This is the central class in the JShell @@ -90,17 +92,17 @@ final BiFunction idGenerator; final List extraRemoteVMOptions; final List extraCompilerOptions; - final ExecutionControl executionControl; + final ExecutionControl.Generator executionControlGenerator; private int nextKeyIndex = 1; final Eval eval; - private final Map classnameToBytes = new HashMap<>(); + final ClassTracker classTracker; private final Map> shutdownListeners = new HashMap<>(); private final Map> keyStatusListeners = new HashMap<>(); private boolean closed = false; - private boolean executionControlLaunched = false; + private ExecutionControl executionControl = null; private SourceCodeAnalysisImpl sourceCodeAnalysis = null; private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n"; @@ -114,17 +116,18 @@ this.idGenerator = b.idGenerator; this.extraRemoteVMOptions = b.extraRemoteVMOptions; this.extraCompilerOptions = b.extraCompilerOptions; - this.executionControl = b.executionControl==null - ? new FailOverExecutionControl( - new JDIExecutionControl(), - new JDIExecutionControl(false)) - : b.executionControl; + this.executionControlGenerator = b.executionControlGenerator==null + ? failOverExecutionControlGenerator( + JDIDefaultExecutionControl.launch(), + JDIDefaultExecutionControl.listen()) + : b.executionControlGenerator; this.maps = new SnippetMaps(this); this.keyMap = new KeyMap(this); this.outerMap = new OuterWrapMap(this); this.taskFactory = new TaskFactory(this); this.eval = new Eval(this); + this.classTracker = new ClassTracker(); } /** @@ -154,7 +157,7 @@ BiFunction idGenerator = null; List extraRemoteVMOptions = new ArrayList<>(); List extraCompilerOptions = new ArrayList<>(); - ExecutionControl executionControl; + ExecutionControl.Generator executionControlGenerator; Builder() { } @@ -310,12 +313,12 @@ * Sets the custom engine for execution. Snippet execution will be * provided by the specified {@link ExecutionControl} instance. * - * @param execEngine the execution engine + * @param executionControlGenerator the execution engine generator * @return the {@code Builder} instance (for use in chained * initialization) */ - public Builder executionEngine(ExecutionControl execEngine) { - this.executionControl = execEngine; + public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) { + this.executionControlGenerator = executionControlGenerator; return this; } @@ -397,7 +400,8 @@ * be an event showing its status changed to OVERWRITTEN, this will not * occur for dropped, rejected, or already overwritten declarations. *

- * The execution environment is out of process. If the evaluated code + * If execution environment is out of process, as is the default case, then + * if the evaluated code * causes the execution environment to terminate, this {@code JShell} * instance will be closed but the calling process and VM remain valid. * @param input The input String to evaluate @@ -447,8 +451,14 @@ * @param path the path to add to the classpath. */ public void addToClasspath(String path) { - taskFactory.addToClasspath(path); // Compiler - executionControl().addToClasspath(path); // Runtime + // Compiler + taskFactory.addToClasspath(path); + // Runtime + try { + executionControl().addToClasspath(path); + } catch (ExecutionControlException ex) { + debug(ex, "on addToClasspath(" + path + ")"); + } if (sourceCodeAnalysis != null) { sourceCodeAnalysis.classpathChanged(); } @@ -468,8 +478,13 @@ * catching the {@link ThreadDeath} exception. */ public void stop() { - if (executionControl != null) - executionControl.stop(); + if (executionControl != null) { + try { + executionControl.stop(); + } catch (ExecutionControlException ex) { + debug(ex, "on stop()"); + } + } } /** @@ -622,7 +637,15 @@ throw new IllegalArgumentException( messageFormat("jshell.exc.var.not.valid", snippet, snippet.status())); } - String value = executionControl().varValue(snippet.classFullName(), snippet.name()); + String value; + try { + value = executionControl().varValue(snippet.classFullName(), snippet.name()); + } catch (EngineTerminationException ex) { + throw new IllegalStateException(ex.getMessage()); + } catch (ExecutionControlException ex) { + debug(ex, "In varValue()"); + return "[" + ex.getMessage() + "]"; + } return expunge(value); } @@ -706,33 +729,17 @@ } @Override - public byte[] getClassBytes(String classname) { - return classnameToBytes.get(classname); - } - - @Override - public EvalException createEvalException(String message, String exceptionClass, StackTraceElement[] stackElements) { - return new EvalException(message, exceptionClass, stackElements); - } - - @Override - public UnresolvedReferenceException createUnresolvedReferenceException(int id, StackTraceElement[] stackElements) { - DeclarationSnippet sn = (DeclarationSnippet) maps.getSnippetDeadOrAlive(id); - return new UnresolvedReferenceException(sn, stackElements); - } - - @Override public void closeDown() { JShell.this.closeDown(); } + } // --- private / package-private implementation support --- ExecutionControl executionControl() { - if (!executionControlLaunched) { + if (executionControl == null) { try { - executionControlLaunched = true; - executionControl.start(new ExecutionEnvImpl()); + executionControl = executionControlGenerator.generate(new ExecutionEnvImpl()); } catch (Throwable ex) { throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex); } @@ -740,10 +747,6 @@ return executionControl; } - void setClassnameToBytes(String classname, byte[] bytes) { - classnameToBytes.put(classname, bytes); - } - void debug(int flags, String format, Object... args) { InternalDebugControl.debug(this, err, flags, format, args); } --- old/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java 2016-07-12 22:46:51.555852955 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java 2016-07-12 22:46:51.443850075 -0700 @@ -223,7 +223,6 @@ new WrapSourceHandler(), Util.join(new String[] { "-Xshouldstop:at=FLOW", "-Xlint:unchecked", - "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none" }, extraArgs)); } @@ -267,7 +266,7 @@ CompileTask(final Collection wraps) { super(wraps.stream(), new WrapSourceHandler(), - "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none", "-parameters"); + "-Xlint:unchecked", "-proc:none", "-parameters"); } boolean compile() { @@ -286,7 +285,7 @@ } List list = new ArrayList<>(); for (OutputMemoryJavaFileObject fo : l) { - state.setClassnameToBytes(fo.getName(), fo.getBytes()); + state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes()); list.add(fo.getName()); } return list; --- old/src/jdk.jshell/share/classes/jdk/jshell/Unit.java 2016-07-12 22:46:51.867860979 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/Unit.java 2016-07-12 22:46:51.780858742 -0700 @@ -32,11 +32,15 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; +import jdk.jshell.ClassTracker.ClassInfo; import jdk.jshell.Snippet.Kind; import jdk.jshell.Snippet.Status; import jdk.jshell.Snippet.SubKind; import jdk.jshell.TaskFactory.AnalyzeTask; import jdk.jshell.TaskFactory.CompileTask; +import jdk.jshell.spi.ExecutionControl.ClassBytecodes; +import jdk.jshell.spi.ExecutionControl.ClassInstallException; +import jdk.jshell.spi.ExecutionControl.EngineTerminationException; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; @@ -75,7 +79,7 @@ private SnippetEvent replaceOldEvent; private List secondaryEvents; private boolean isAttemptingCorral; - private List toRedefine; + private List toRedefine; private boolean dependenciesNeeded; Unit(JShell state, Snippet si, Snippet causalSnippet, @@ -261,30 +265,29 @@ } /** - * Process the class information from the last compile. - * Requires loading of returned list. + * Process the class information from the last compile. Requires loading of + * returned list. + * * @return the list of classes to load */ - Stream classesToLoad(List classnames) { + Stream classesToLoad(List classnames) { toRedefine = new ArrayList<>(); - List toLoad = new ArrayList<>(); + List toLoad = new ArrayList<>(); if (status.isDefined() && !isImport()) { // Classes should only be loaded/redefined if the compile left them // in a defined state. Imports do not have code and are not loaded. for (String cn : classnames) { - switch (state.executionControl().getClassStatus(cn)) { - case UNKNOWN: - // If not loaded, add to the list of classes to load. - toLoad.add(cn); - dependenciesNeeded = true; - break; - case NOT_CURRENT: - // If loaded but out of date, add to the list of classes to attempt redefine. - toRedefine.add(cn); - break; - case CURRENT: - // Loaded and current, so nothing to do - break; + ClassInfo ci = state.classTracker.get(cn); + if (ci.isLoaded()) { + if (ci.isCurrent()) { + // nothing to do + } else { + toRedefine.add(ci); + } + } else { + // If not loaded, add to the list of classes to load. + toLoad.add(ci.toClassBytecodes()); + dependenciesNeeded = true; } } } @@ -292,14 +295,28 @@ } /** - * Redefine classes needing redefine. - * classesToLoad() must be called first. + * Redefine classes needing redefine. classesToLoad() must be called first. + * * @return true if all redefines succeeded (can be vacuously true) */ boolean doRedefines() { - return toRedefine.isEmpty() - ? true - : state.executionControl().redefine(toRedefine); + if (toRedefine.isEmpty()) { + return true; + } + ClassBytecodes[] cbcs = toRedefine.stream() + .map(ci -> ci.toClassBytecodes()) + .toArray(size -> new ClassBytecodes[size]); + try { + state.executionControl().redefine(cbcs); + state.classTracker.markLoaded(cbcs); + return true; + } catch (ClassInstallException ex) { + state.classTracker.markLoaded(cbcs, ex.installed()); + return false; + } catch (EngineTerminationException ex) { + state.closeDown(); + return false; + } } void markForReplacement() { --- old/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java 2016-07-12 22:46:52.129867717 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java 2016-07-12 22:46:52.043865505 -0700 @@ -22,133 +22,247 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jshell.spi; -import java.util.Collection; -import jdk.jshell.JShellException; +import java.io.Serializable; /** - * This interface specifies the functionality that must provided to implement - * a pluggable JShell execution engine. + * This interface specifies the functionality that must provided to implement a + * pluggable JShell execution engine. *

- * The audience for this Service Provider Interface is engineers - * wishing to implement their own version of the execution engine in support - * of the JShell API. This is NOT a part of the JShell API. + * The audience for this Service Provider Interface is engineers wishing to + * implement their own version of the execution engine in support of the JShell + * API. *

- * A Snippet is compiled into code wrapped in a 'wrapper class'. The execution - * engine is used by the core JShell implementation to load and, for - * executable Snippets, execute the Snippet. + * A Snippet is compiled into code wrapped in a 'wrapper class'. The execution + * engine is used by the core JShell implementation to load and, for executable + * Snippets, execute the Snippet. *

* Methods defined in this interface should only be called by the core JShell * implementation. *

- * To install an instance of ExecutionControl, it is passed to - * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }. + * To install an {@code ExecutionControl}, it's {@code Generator} is passed to + * {@link jdk.jshell.JShell.Builder#executionEngine(ExecutionControl.Generator) }. */ public interface ExecutionControl { + + public interface Generator { + ExecutionControl generate(ExecutionEnv env) throws Throwable; + } /** - * Represents the current status of a class in the execution engine. - */ - public enum ClassStatus { - /** - * Class is not known to the execution engine (not loaded). - */ - UNKNOWN, - - /** - * Class is loaded, but the loaded/redefined bytes do not match those - * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }. - */ - NOT_CURRENT, - - /** - * Class is loaded and loaded/redefined bytes match those - * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }. - */ - CURRENT - }; - - /** - * Initializes the instance. No methods in this interface can be called - * before this. + * Attempts to load new classes. * - * @param env the execution environment information provided by JShell - * @throws Exception if the instance is unable to initialize + * @param cbcs the class name and bytecodes to load + * @throws ClassInstallException exception occurred loading the classes, + * some or all were not loaded + * @throws EngineTerminationException the execution engine has terminated */ - void start(ExecutionEnv env) throws Exception; + void load(ClassBytecodes[] cbcs) + throws ClassInstallException, EngineTerminationException; /** - * Shuts down this execution engine. Implementation should free all - * resources held by this execution engine. - *

- * No calls to methods on this interface should be made after close. - */ - void close(); - - /** - * Adds the path to the execution class path. + * Attempts to redefine previously loaded classes. * - * @param path the path to add - * @return true if successful + * @param cbcs the class name and bytecodes to redefine + * @throws ClassInstallException exception occurred redefining the classes, + * some or all were not redefined + * @throws EngineTerminationException the execution engine has terminated */ - boolean addToClasspath(String path); + void redefine(ClassBytecodes[] cbcs) + throws ClassInstallException, EngineTerminationException; /** * Invokes an executable Snippet by calling a method on the specified * wrapper class. The method must have no arguments and return String. * - * @param classname the class whose method should be invoked - * @param methodname the name of method to invoke + * @param className the class whose method should be invoked + * @param methodName the name of method to invoke * @return the result of the execution or null if no result - * @throws JShellException if a user exception if thrown, - * {@link jdk.jshell.EvalException EvalException} will be thrown; if an - * unresolved reference is encountered, - * {@link jdk.jshell.UnresolvedReferenceException UnresolvedReferenceException} - * will be thrown - */ - String invoke(String classname, String methodname) throws JShellException; + * @throws UserException the invoke raised a user exception + * @throws ResolutionException the invoke attempted to directly or + * indirectly invoke an unresolved snippet + * @throws StoppedException if the {@code invoke()} was canceled by + * {@link ExecutionControl#stop} + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred + */ + String invoke(String className, String methodName) + throws RunException, + EngineTerminationException, InternalException; + + /** + * Returns the value of a variable. + * + * @param className the name of the wrapper class of the variable + * @param varName the name of the variable + * @return the value of the variable + * @throws UserException formatting the value raised a user exception + * @throws ResolutionException formatting the value attempted to directly or + * indirectly invoke an unresolved snippet + * @throws StoppedException if the formatting the value was canceled by + * {@link ExecutionControl#stop} + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred + */ + String varValue(String className, String varName) + throws RunException, + EngineTerminationException, InternalException; /** - * Attempts to load new classes. Class bytes are retrieved from - * {@link ExecutionEnv#getClassBytes(java.lang.String) } + * Adds the path to the execution class path. * - * @param classes list of class names to load - * @return true if load succeeded + * @param path the path to add + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred */ - boolean load(Collection classes); + void addToClasspath(String path) + throws EngineTerminationException, InternalException; /** - * Attempts to redefine previously loaded classes. Class bytes are retrieved - * from {@link ExecutionEnv#getClassBytes(java.lang.String) } + * Sets the execution class path to the specified path. * - * @param classes list of class names to redefine - * @return true if redefine succeeded + * @param path the path to add + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred */ - boolean redefine(Collection classes); + void setClasspath(String path) + throws EngineTerminationException, InternalException; /** - * Queries if the class is loaded and the class bytes are current. + * Interrupts a running invoke. * - * @param classname name of the wrapper class to query - * @return {@code UNKNOWN} if the class is not loaded; {@code CURRENT} if - * the loaded/redefined bytes are equal to the most recent bytes for this - * wrapper class; otherwise {@code NOT_CURRENT} + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred */ - ClassStatus getClassStatus(String classname); + void stop() + throws EngineTerminationException, InternalException; /** - * Interrupt a running invoke. + * Shuts down this execution engine. Implementation should free all + * resources held by this execution engine. + *

+ * No calls to methods on this interface should be made after close. */ - void stop(); + void close(); + + public static final class ClassBytecodes implements Serializable { + + private static final long serialVersionUID = 1L; + + public ClassBytecodes(String name, byte[] bytecodes) { + this.name = name; + this.bytecodes = bytecodes; + } + public final String name; + public final byte[] bytecodes; + } + + @SuppressWarnings("serial") + public static abstract class ExecutionControlException extends Exception { + + public ExecutionControlException(String message) { + super(message); + } + } /** - * Returns the value of a variable. - * - * @param classname the name of the wrapper class of the variable - * @param varname the name of the variable - * @return the value of the variable + * Unbidden execution engine termination has occurred. */ - String varValue(String classname, String varname); + @SuppressWarnings("serial") + public static class EngineTerminationException extends ExecutionControlException { + + public EngineTerminationException(String message) { + super(message); + System.err.printf("EngineTerminationException: %s\n", message); + } + } + + @SuppressWarnings("serial") + public static class InternalException extends ExecutionControlException { + + public InternalException(String message) { + super(message); + } + } + + @SuppressWarnings("serial") + public static class ClassInstallException extends ExecutionControlException { + + private final boolean[] installed; + + public ClassInstallException(String message, boolean[] installed) { + super(message); + this.installed = installed; + } + + public boolean[] installed() { + return installed; + } + } + + @SuppressWarnings("serial") // serialVersionUID intentionally omitted + public static abstract class RunException extends ExecutionControlException { + + private RunException(String message) { + super(message); + } + } + + @SuppressWarnings("serial") // serialVersionUID intentionally omitted + public static class UserException extends RunException { + + private final String causeExceptionClass; + + public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) { + super(message); + this.causeExceptionClass = causeExceptionClass; + this.setStackTrace(stackElements); + } + + public String causeExceptionClass() { + return causeExceptionClass; + } + } + + @SuppressWarnings("serial") // serialVersionUID intentionally omitted + public static class ResolutionException extends RunException { + + private final int id; + + /** + * Constructs an exception indicating that a + * {@code DeclarationSnippet} with unresolved references has been + * encountered. The throw of this exception is generated into the body + * of a + * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} + * method. + * + * @param id An internal identifier of the specific method + * @param stackElements the stack trace + */ + public ResolutionException(int id, StackTraceElement[] stackElements) { + super("resolution exception: " + id); + this.id = id; + this.setStackTrace(stackElements); + } + + /** + * Retrieves the internal identifier of the unresolved identifier. + * + * @return the internal identifier + */ + public int id() { + return id; + } + } + + @SuppressWarnings("serial") // serialVersionUID intentionally omitted + public static class StoppedException extends RunException { + + public StoppedException() { + super("stopped by stop()"); + } + } + } --- old/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java 2016-07-12 22:46:52.391874454 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java 2016-07-12 22:46:52.304872217 -0700 @@ -28,14 +28,11 @@ import java.io.InputStream; import java.io.PrintStream; import java.util.List; -import jdk.jshell.EvalException; import jdk.jshell.JShell; -import jdk.jshell.UnresolvedReferenceException; /** * Functionality made available to a pluggable JShell execution engine. It is - * provided to the execution engine by the core JShell implementation calling - * {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }. + * provided to the execution engine by the core JShell implementation. *

* This interface is designed to provide the access to core JShell functionality * needed to implement ExecutionControl. @@ -81,47 +78,8 @@ List extraRemoteVMOptions(); /** - * Retrieves the class file bytes for the specified wrapper class. - * - * @param className the name of the wrapper class - * @return the current class file bytes as a byte array - */ - byte[] getClassBytes(String className); - - /** - * Creates an {@code EvalException} corresponding to a user exception. An - * user exception thrown during - * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) } - * should be converted to an {@code EvalException} using this method. - * - * @param message the exception message to use (from the user exception) - * @param exceptionClass the class name of the user exception - * @param stackElements the stack trace elements to install - * @return a user API EvalException for the user exception - */ - EvalException createEvalException(String message, String exceptionClass, - StackTraceElement[] stackElements); - - /** - * Creates an {@code UnresolvedReferenceException} for the Snippet identifed - * by the specified identifier. An {@link SPIResolutionException} thrown - * during {@link ExecutionControl#invoke(java.lang.String, java.lang.String) } - * should be converted to an {@code UnresolvedReferenceException} using - * this method. - *

- * The identifier is an internal id, different from the id in the API. This - * internal id is returned by {@link SPIResolutionException#id()}. - * - * @param id the internal integer identifier - * @param stackElements the stack trace elements to install - * @return an {@code UnresolvedReferenceException} for the unresolved - * reference - */ - UnresolvedReferenceException createUnresolvedReferenceException(int id, - StackTraceElement[] stackElements); - - /** * Reports that the execution engine has shutdown. */ void closeDown(); + } --- old/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java 2016-07-12 22:46:52.645880986 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java 2016-07-12 22:46:52.557878723 -0700 @@ -33,9 +33,6 @@ *

* This exception is seen by the execution engine, but not seen by * the end user nor through the JShell API. - * - * @see ExecutionEnv#createUnresolvedReferenceException(int, - * java.lang.StackTraceElement[]) */ @SuppressWarnings("serial") // serialVersionUID intentionally omitted public class SPIResolutionException extends RuntimeException { @@ -60,8 +57,6 @@ * Retrieves the internal identifer of the unresolved identifer. * * @return the internal identifer - * @see ExecutionEnv#createUnresolvedReferenceException(int, - * java.lang.StackTraceElement[]) */ public int id() { return id; --- old/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java 2016-07-12 22:46:52.901887570 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java 2016-07-12 22:46:52.812885281 -0700 @@ -31,9 +31,9 @@ * implementation includes a default execution engine (currently a remote * process which is JDI controlled). By implementing the * {@link jdk.jshell.spi.ExecutionControl} interface and installing it with - * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) } + * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl.Generator) } * other execution engines can be used. * - * @see jdk.jshell.execution jdk.jshell.execution for execution implementation support + * @see jdk.jshell.execution for execution implementation support */ package jdk.jshell.spi; --- old/test/jdk/jshell/ComputeFQNsTest.java 2016-07-12 22:46:53.155894102 -0700 +++ new/test/jdk/jshell/ComputeFQNsTest.java 2016-07-12 22:46:53.067891839 -0700 @@ -76,7 +76,7 @@ assertInferredFQNs("class X { ArrayList", "ArrayList".length(), false, "java.util.ArrayList"); } - @Test + @Test(enabled = false) //TODO 8161165 public void testSuspendIndexing() throws Throwable { compiler.compile(outDir, "package test; public class FQNTest { }"); String jarName = "test.jar"; --- old/test/jdk/jshell/FailOverExecutionControlTest.java 2016-07-12 22:46:53.410900660 -0700 +++ new/test/jdk/jshell/FailOverExecutionControlTest.java 2016-07-12 22:46:53.321898371 -0700 @@ -23,23 +23,20 @@ /* * @test - * @bug 8131029 - * @summary Test that fail-over works for FailOverExecutionControl - * @modules jdk.jshell/jdk.internal.jshell.jdi + * @bug 8131029 8160127 8159935 + * @summary Test that fail-over works for fail-over ExecutionControl generators. + * @modules jdk.jshell/jdk.jshell.execution * jdk.jshell/jdk.jshell.spi * @build KullaTesting ExecutionControlTestBase * @run testng FailOverExecutionControlTest */ - -import java.util.Collection; import org.testng.annotations.Test; import org.testng.annotations.BeforeMethod; -import jdk.internal.jshell.jdi.FailOverExecutionControl; -import jdk.internal.jshell.jdi.JDIExecutionControl; -import jdk.jshell.JShellException; +import jdk.jshell.execution.JDIDefaultExecutionControl; import jdk.jshell.spi.ExecutionControl; import jdk.jshell.spi.ExecutionEnv; +import static jdk.jshell.execution.Util.failOverExecutionControlGenerator; @Test public class FailOverExecutionControlTest extends ExecutionControlTestBase { @@ -47,56 +44,16 @@ @BeforeMethod @Override public void setUp() { - setUp(new FailOverExecutionControl( - new AlwaysFailingExecutionControl(), - new AlwaysFailingExecutionControl(), - new JDIExecutionControl())); + setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator( + new AlwaysFailingGenerator(), + new AlwaysFailingGenerator(), + JDIDefaultExecutionControl.launch()))); } - class AlwaysFailingExecutionControl implements ExecutionControl { - - @Override - public void start(ExecutionEnv env) throws Exception { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public void close() { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public boolean addToClasspath(String path) { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public String invoke(String classname, String methodname) throws JShellException { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public boolean load(Collection classes) { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public boolean redefine(Collection classes) { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public ClassStatus getClassStatus(String classname) { - throw new UnsupportedOperationException("This operation intentionally broken."); - } - - @Override - public void stop() { - throw new UnsupportedOperationException("This operation intentionally broken."); - } + class AlwaysFailingGenerator implements ExecutionControl.Generator { @Override - public String varValue(String classname, String varname) { + public ExecutionControl generate(ExecutionEnv env) throws UnsupportedOperationException { throw new UnsupportedOperationException("This operation intentionally broken."); } --- old/test/jdk/jshell/JDIListeningExecutionControlTest.java 2016-07-12 22:46:53.666907243 -0700 +++ new/test/jdk/jshell/JDIListeningExecutionControlTest.java 2016-07-12 22:46:53.579905006 -0700 @@ -23,9 +23,9 @@ /* * @test - * @bug 8131029 + * @bug 8131029 8159935 8160127 * @summary Tests for alternate JDI connector -- listening - * @modules jdk.jshell/jdk.internal.jshell.jdi + * @modules jdk.jshell/jdk.jshell.execution * @build KullaTesting ExecutionControlTestBase * @run testng JDIListeningExecutionControlTest */ @@ -33,7 +33,7 @@ import org.testng.annotations.Test; import org.testng.annotations.BeforeMethod; -import jdk.internal.jshell.jdi.JDIExecutionControl; +import jdk.jshell.execution.JDIDefaultExecutionControl; @Test public class JDIListeningExecutionControlTest extends ExecutionControlTestBase { @@ -41,6 +41,6 @@ @BeforeMethod @Override public void setUp() { - setUp(new JDIExecutionControl(false)); + setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen())); } } --- old/test/jdk/jshell/KullaTesting.java 2016-07-12 22:46:53.992915626 -0700 +++ new/test/jdk/jshell/KullaTesting.java 2016-07-12 22:46:53.877912669 -0700 @@ -73,7 +73,6 @@ import static jdk.jshell.Snippet.Status.*; import static org.testng.Assert.*; import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND; -import jdk.jshell.spi.ExecutionControl; public class KullaTesting { @@ -158,10 +157,6 @@ setUp(b -> {}); } - public void setUp(ExecutionControl ec) { - setUp(b -> b.executionEngine(ec)); - } - public void setUp(Consumer bc) { inStream = new TestingInputStream(); outStream = new ByteArrayOutputStream(); --- old/test/jdk/jshell/UserExecutionControlTest.java 2016-07-12 22:46:54.320924061 -0700 +++ new/test/jdk/jshell/UserExecutionControlTest.java 2016-07-12 22:46:54.207921156 -0700 @@ -23,13 +23,14 @@ /* * @test - * @bug 8156101 + * @bug 8156101 8159935 * @summary Tests for ExecutionControl SPI - * @build KullaTesting LocalExecutionControl ExecutionControlTestBase + * @build KullaTesting ExecutionControlTestBase * @run testng UserExecutionControlTest */ +import jdk.jshell.execution.LocalExecutionControl; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import org.testng.annotations.BeforeMethod; @@ -40,7 +41,7 @@ @BeforeMethod @Override public void setUp() { - setUp(new LocalExecutionControl()); + setUp(builder -> builder.executionEngine(LocalExecutionControl.create())); } public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java 2016-07-12 22:46:54.629932007 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2016, 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.internal.jshell.jdi; - -import java.util.HashMap; -import java.util.Objects; -import com.sun.jdi.ReferenceType; -import java.util.List; -import com.sun.jdi.VirtualMachine; - -/** - * Tracks the state of a class. - */ -class ClassTracker { - - private final VirtualMachine vm; - private final HashMap map; - - ClassTracker(VirtualMachine vm) { - this.vm = vm; - this.map = new HashMap<>(); - } - - /** - * Associates a class name, class bytes, and ReferenceType. - */ - class ClassInfo { - - // The name of the class -- always set - private final String className; - - // The corresponding compiled class bytes when a load or redefine - // is started. May not be the loaded bytes. May be null. - private byte[] bytes; - - // The class bytes successfully loaded/redefined into the remote VM. - private byte[] loadedBytes; - - // The corresponding JDI ReferenceType. Used by redefineClasses and - // acts as indicator of successful load (null if not loaded). - private ReferenceType rt; - - private ClassInfo(String className) { - this.className = className; - } - - String getClassName() { - return className; - } - - byte[] getLoadedBytes() { - return loadedBytes; - } - - byte[] getBytes() { - return bytes; - } - - private void setBytes(byte[] potentialBytes) { - this.bytes = potentialBytes; - } - - // The class has been successful loaded redefined. The class bytes - // sent are now actually loaded. - void markLoaded() { - loadedBytes = bytes; - } - - // Ask JDI for the ReferenceType, null if not loaded. - ReferenceType getReferenceTypeOrNull() { - if (rt == null) { - rt = nameToRef(className); - } - return rt; - } - - private ReferenceType nameToRef(String name) { - List rtl = vm.classesByName(name); - if (rtl.size() != 1) { - return null; - } - return rtl.get(0); - } - - @Override - public boolean equals(Object o) { - return o instanceof ClassInfo - && ((ClassInfo) o).className.equals(className); - } - - @Override - public int hashCode() { - return Objects.hashCode(this.className); - } - } - - // Map a class name to the current compiled class bytes. - ClassInfo classInfo(String className, byte[] bytes) { - ClassInfo ci = get(className); - ci.setBytes(bytes); - return ci; - } - - // Lookup the ClassInfo by class name, create if it does not exist. - ClassInfo get(String className) { - return map.computeIfAbsent(className, k -> new ClassInfo(k)); - } -} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java 2016-07-12 22:46:54.498928639 -0700 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016, 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; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Objects; +import jdk.jshell.spi.ExecutionControl.ClassBytecodes; + +/** + * Tracks the state of a class. + */ +class ClassTracker { + + private final HashMap map; + + ClassTracker() { + this.map = new HashMap<>(); + } + + /** + * Associates a class name, class bytes (current and loaded). + */ + class ClassInfo { + + // The name of the class + private final String className; + + // The corresponding compiled class bytes when a load or redefine + // is started. May not be the loaded bytes. May be null. + private byte[] currentBytes; + + // The class bytes successfully loaded/redefined into the remote VM. + private byte[] loadedBytes; + + private ClassInfo(String className) { + this.className = className; + } + + String getClassName() { + return className; + } + + byte[] getLoadedBytes() { + return loadedBytes; + } + + byte[] getCurrentBytes() { + return currentBytes; + } + + void setCurrentBytes(byte[] bytes) { + this.currentBytes = bytes; + } + + void setLoadedBytes(byte[] bytes) { + this.loadedBytes = bytes; + } + + boolean isLoaded() { + return loadedBytes != null; + } + + boolean isCurrent() { + return Arrays.equals(currentBytes, loadedBytes); + } + + ClassBytecodes toClassBytecodes() { + return new ClassBytecodes(className, currentBytes); + } + + @Override + public boolean equals(Object o) { + return o instanceof ClassInfo + && ((ClassInfo) o).className.equals(className); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.className); + } + } + + void markLoaded(ClassBytecodes[] cbcs) { + for (ClassBytecodes cbc : cbcs) { + get(cbc.name).setLoadedBytes(cbc.bytecodes); + } + } + + void markLoaded(ClassBytecodes[] cbcs, boolean[] isLoaded) { + for (int i = 0; i < cbcs.length; ++i) { + if (isLoaded[i]) { + ClassBytecodes cbc = cbcs[i]; + get(cbc.name).setLoadedBytes(cbc.bytecodes); + } + } + } + + // Map a class name to the current compiled class bytes. + void setCurrentBytes(String className, byte[] bytes) { + ClassInfo ci = get(className); + ci.setCurrentBytes(bytes); + } + + // Lookup the ClassInfo by class name, create if it does not exist. + ClassInfo get(String className) { + return map.computeIfAbsent(className, k -> new ClassInfo(k)); + } +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java 2016-07-12 22:46:54.861937974 -0700 @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016, 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.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Read from an InputStream which has been packetized and write its contents + * to the named OutputStreams. + * + * @author Jan Lahoda + * @see Util#demultiplexInput(java.io.InputStream, jdk.jshell.execution.ECLogger, java.io.OutputStream, java.io.OutputStream, java.io.OutputStream...) + */ +class DemultiplexInput extends Thread { + + private static final Pattern AUX_FORMAT = Pattern.compile("aux(\\d+)"); + private final DataInputStream delegate; + private final PipeInputStream command; + private final OutputStream out; + private final OutputStream err; + private final OutputStream[] aux; + private final ECLogger log; + + DemultiplexInput(InputStream input, PipeInputStream command, ECLogger log, + OutputStream out, OutputStream err, OutputStream... aux) { + super("output reader"); + this.delegate = new DataInputStream(input); + this.command = command; + this.out = out; + this.err = err; + this.aux = aux; + this.log = log; + } + + @Override + public void run() { + try { + while (true) { + int nameLen = delegate.read(); + if (nameLen == (-1)) { + break; + } + byte[] name = new byte[nameLen]; + DemultiplexInput.this.delegate.readFully(name); + int dataLen = delegate.read(); + byte[] data = new byte[dataLen]; + DemultiplexInput.this.delegate.readFully(data); + String chan = new String(name, "UTF-8"); + switch (chan) { + case "err": + err.write(data); + break; + case "out": + out.write(data); + break; + case "command": + for (byte b : data) { + command.write(Byte.toUnsignedInt(b)); + } + break; + default: { + Matcher mat = AUX_FORMAT.matcher(chan); + if (mat.matches()) { + int i = Integer.parseInt(mat.group(1)); + if (i >= 1 && i < aux.length) { + aux[i-1].write(data); + log.debug("WROTE on %s : %s", chan, data); + break; + } + } + log.debug("Unexpected channel name: %s", chan); + break; + } + } + } + } catch (IOException ex) { + log.debug(ex, "Failed reading output"); + } finally { + command.close(); + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/DirectExecutionControl.java 2016-07-12 22:46:55.133944968 -0700 @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2016, 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.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.TreeMap; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.SPIResolutionException; + +/** + * An {@link ExecutionControl} implementation that runs in the current process. + * May be used directly, or over a channel with + * {@link Util#forwardExecutionControl(ExecutionControl, java.io.ObjectInput, java.io.ObjectOutput) }. + * + * @author Robert Field + * @author Jan Lahoda + */ +public class DirectExecutionControl implements ExecutionControl { + + private final RemoteClassLoader loader; + private final Map> klasses = new TreeMap<>(); + + /** + * Create an instance. + */ + public DirectExecutionControl() { + this.loader = new RemoteClassLoader(); + } + + @Override + public void load(ClassBytecodes[] cbcs) throws ClassInstallException, EngineTerminationException { + boolean[] loaded = new boolean[cbcs.length]; + try { + for (ClassBytecodes cbc : cbcs) { + loader.delare(cbc.name, cbc.bytecodes); + } + for (int i = 0; i < cbcs.length; ++i) { + ClassBytecodes cbc = cbcs[i]; + Class klass = loader.loadClass(cbc.name); + klasses.put(cbc.name, klass); + loaded[i] = true; + // Get class loaded to the point of, at least, preparation + klass.getDeclaredMethods(); + } + } catch (Throwable ex) { + throw new ClassInstallException("load: " + ex.getMessage(), loaded); + } + } + + @Override + public void redefine(ClassBytecodes[] cbcs) throws ClassInstallException, EngineTerminationException { + throw new ClassInstallException("redefine not supported", new boolean[cbcs.length]); + } + + @Override + public String invoke(String className, String methodName) + throws RunException, InternalException, EngineTerminationException { + Method doitMethod; + try { + Class klass = findClass(className); + doitMethod = klass.getDeclaredMethod(methodName, new Class[0]); + doitMethod.setAccessible(true); + } catch (Throwable ex) { + throw new InternalException(ex.toString()); + } + + try { + clientCodeEnter(); + String result = invoke(doitMethod); + System.out.flush(); + return result; + } catch (RunException | InternalException | EngineTerminationException ex) { + throw ex; + } catch (SPIResolutionException ex) { + return throwConvertedInvocationException(ex); + } catch (InvocationTargetException ex) { + return throwConvertedInvocationException(ex.getCause()); + } catch (Throwable ex) { + return throwConvertedOtherException(ex); + } finally { + clientCodeLeave(); + } + } + + @Override + public String varValue(String className, String varName) throws RunException, EngineTerminationException, InternalException { + Object val; + try { + Class klass = findClass(className); + Field var = klass.getDeclaredField(varName); + var.setAccessible(true); + val = var.get(null); + } catch (Throwable ex) { + throw new InternalException(ex.toString()); + } + + try { + clientCodeEnter(); + return valueString(val); + } catch (Throwable ex) { + return throwConvertedInvocationException(ex); + } finally { + clientCodeLeave(); + } + } + + @Override + public void addToClasspath(String cp) throws EngineTerminationException, InternalException { + try { + for (String path : cp.split(File.pathSeparator)) { + loader.addURL(new File(path).toURI().toURL()); + } + } catch (Exception ex) { + throw new InternalException(ex.toString()); + } + } + + @Override + public void setClasspath(String path) throws EngineTerminationException, InternalException { + throw new InternalException("Not supported yet."); + } + + /** + * {@inheritDoc} + *

+ * Not supported. + */ + @Override + public void stop() throws EngineTerminationException, InternalException { + throw new InternalException("Not supported."); + } + + @Override + public void close() { + } + + /** + * Finds the class with the specified binary name. + * @param name the binary name of the class + * @return the Class Object + * @throws ClassNotFoundException if the class could not be found + */ + protected Class findClass(String name) throws ClassNotFoundException { + Class klass = klasses.get(name); + if (klass == null) { + throw new ClassNotFoundException(name + " not found"); + } else { + return klass; + } + } + + /** + * Invoke the specified "doit-method", a static method with no parameters. + * The {@link DirectExecutionControl#invoke(java.lang.String, java.lang.String) } + * in this class will call this to invoke. + * + * @param doitMethod the Method to invoke + * @return the value or null + * @throws Exception any exceptions thrown by + * {@link java.lang.reflect.Method#invoke(Object, Object...) } + * or any {@link ExecutionControl.ExecutionControlException} + * to pass-through. + */ + protected String invoke(Method doitMethod) throws Exception { + Object res = doitMethod.invoke(null, new Object[0]); + return valueString(res); + } + + /** + * Converts the {@code Object} value from + * {@link ExecutionControl#invoke(String, String) } or + * {@link ExecutionControl#varValue(String, String) } to {@code String}. + * + * @param value the value to convert + * @return the {@code String} representation + */ + protected static String valueString(Object value) { + if (value == null) { + return "null"; + } else if (value instanceof String) { + return "\"" + (String) value + "\""; + } else if (value instanceof Character) { + return "'" + value + "'"; + } else { + return value.toString(); + } + } + + /** + * Converts incoming exceptions in user code into instances of subtypes of + * {@link ExecutionControl.ExecutionControlException} and throws the + * converted exception. + * + * @param cause the exception to convert + * @return never returns as it always throws + * @throws ExecutionControl.RunException for normal exception occurrences + * @throws ExecutionControl.InternalException for internal problems + */ + protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException { + if (cause instanceof SPIResolutionException) { + SPIResolutionException spire = (SPIResolutionException) cause; + throw new ResolutionException(spire.id(), spire.getStackTrace()); + } else { + throw new UserException(cause.getMessage(), cause.getClass().getName(), cause.getStackTrace()); + } + } + + /** + * Converts incoming exceptions in agent code into instances of subtypes of + * {@link ExecutionControl.ExecutionControlException} and throws the + * converted exception. + * + * @param ex the exception to convert + * @return never returns as it always throws + * @throws ExecutionControl.RunException for normal exception occurrences + * @throws ExecutionControl.InternalException for internal problems + */ + protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException { + throw new InternalException(ex.toString()); + } + + /** + * Marks entry into user code. + * + * @throws ExecutionControl.InternalException in unexpected failure cases + */ + protected void clientCodeEnter() throws InternalException { + } + + /** + * Marks departure from user code. + * + * @throws ExecutionControl.InternalException in unexpected failure cases + */ + protected void clientCodeLeave() throws InternalException { + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/ECLogger.java 2016-07-12 22:46:55.383951398 -0700 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 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; + +/** + * Log events in the ExecutionControl implementation. + */ +public interface ECLogger { + + /** + * Log debugging information. Arguments as for {@code printf}. + * + * @param format a format string as described in Format string syntax + * @param args arguments referenced by the format specifiers in the format + * string. + */ + void debug(String format, Object... args); + + /** + * Log a serious unexpected internal exception. + * + * @param ex the exception + * @param where a description of the context of the exception + */ + void debug(Throwable ex, String where); + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java 2016-07-12 22:46:55.623957570 -0700 @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2016, 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.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.ExecutionControl.ClassBytecodes; +import jdk.jshell.spi.ExecutionControl.ClassInstallException; +import jdk.jshell.spi.ExecutionControl.EngineTerminationException; +import jdk.jshell.spi.ExecutionControl.InternalException; +import jdk.jshell.spi.ExecutionControl.ResolutionException; +import jdk.jshell.spi.ExecutionControl.StoppedException; +import jdk.jshell.spi.ExecutionControl.UserException; + +/** + * Forwards commands from the input to the specified {@link ExecutionControl} + * instance, then responses back on the output. + */ +class ExecutionControlForwarder { + + private final ExecutionControl ec; + private final ObjectInput in; + private final ObjectOutput out; + + ExecutionControlForwarder(ExecutionControl ec, ObjectInput in, ObjectOutput out) { + this.ec = ec; + this.in = in; + this.out = out; + } + + private boolean writeSuccess() throws IOException { + writeStatus(RemoteStatus.SUCCESS); + flush(); + return true; + } + + private boolean writeSuccessAndResult(String result) throws IOException { + writeStatus(RemoteStatus.SUCCESS); + writeUTF(result); + flush(); + return true; + } + + private void writeStatus(RemoteStatus status) throws IOException { + out.writeObject(status); + } + + private void writeObject(Object o) throws IOException { + out.writeObject(o); + } + + private void writeInt(int i) throws IOException { + out.writeInt(i); + } + + private void writeUTF(String s) throws IOException { + if (s == null) { + s = ""; + } + out.writeUTF(s); + } + + private void flush() throws IOException { + out.flush(); + } + + private boolean processCommand() throws IOException { + RemoteCommand cmd; + try { + cmd = (RemoteCommand) in.readObject(); + if (cmd == null) { + throw new IOException("Missing command: " + cmd); + } + switch (cmd) { + case LOAD: { + // Load a generated class file over the wire + ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject(); + ec.load(cbcs); + return writeSuccess(); + } + case REDEFINE: { + // Load a generated class file over the wire + ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject(); + ec.redefine(cbcs); + return writeSuccess(); + } + case INVOKE: { + // Invoke executable entry point in loaded code + String className = in.readUTF(); + String methodName = in.readUTF(); + String res = ec.invoke(className, methodName); + return writeSuccessAndResult(res); + } + case VAR_VALUE: { + // Retrieve a variable value + String className = in.readUTF(); + String varName = in.readUTF(); + String res = ec.varValue(className, varName); + return writeSuccessAndResult(res); + } + case ADD_CLASSPATH: { + // Append to the claspath + String cp = in.readUTF(); + ec.addToClasspath(cp); + return writeSuccess(); + } + case SET_CLASSPATH: { + // Set the claspath + String cp = in.readUTF(); + ec.setClasspath(cp); + return writeSuccess(); + } + case STOP: { + // Stop the current execution + try { + ec.stop(); + } catch (Throwable ex) { + // JShell-core not waiting for a result, ignore + } + return true; + } + case CLOSE: { + // Terminate this process + try { + ec.close(); + } catch (Throwable ex) { + // JShell-core not waiting for a result, ignore + } + return true; + } + } + return false; // cannot get here, make the compiler happy + } catch (IOException ex) { + // handled by the outer level + throw ex; + } catch (EngineTerminationException ex) { + writeStatus(RemoteStatus.TERMINATED); + writeUTF(ex.getMessage()); + flush(); + return false; + } catch (InternalException ex) { + writeStatus(RemoteStatus.INTERNAL_PROBLEM); + writeUTF(ex.getMessage()); + flush(); + return true; + } catch (ClassInstallException ex) { + writeStatus(RemoteStatus.CLASS_INSTALL_EXCEPTION); + writeUTF(ex.getMessage()); + writeObject(ex.installed()); + flush(); + return true; + } catch (UserException ex) { + writeStatus(RemoteStatus.USER_EXCEPTION); + writeUTF(ex.getMessage()); + writeUTF(ex.causeExceptionClass()); + writeObject(ex.getStackTrace()); + flush(); + return true; + } catch (ResolutionException ex) { + writeStatus(RemoteStatus.CORRALLED); + writeInt(ex.id()); + writeObject(ex.getStackTrace()); + flush(); + return true; + } catch (StoppedException ex) { + writeStatus(RemoteStatus.STOPPED); + flush(); + return true; + } catch (Throwable ex) { + writeStatus(RemoteStatus.TERMINATED); + writeUTF(ex.getMessage()); + flush(); + return false; + } + } + + void commandLoop() { + try { + while (processCommand()) { + // condition is loop action + } + } catch (IOException ex) { + // ignore + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java 2016-07-12 22:46:55.875964050 -0700 @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2016, 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.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import com.sun.jdi.BooleanValue; +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.ExecutionEnv; +import static jdk.jshell.execution.Util.defaultLogger; +import static jdk.jshell.execution.Util.demultiplexInput; + +/** + * The implementation of {@link jdk.jshell.spi.ExecutionControl} that the + * JShell-core uses by default. + * Launches a remote process -- the "remote agent". + * Interfaces to the remote agent over a socket and via JDI. + * Designed to work with {@link RemoteExecutionControl}. + * + * @author Robert Field + * @author Jan Lahoda + */ +public class JDIDefaultExecutionControl extends JDIExecutionControl { + + private static final String REMOTE_AGENT = RemoteExecutionControl.class.getName(); + + private VirtualMachine vm; + private Process process; + + private final Object STOP_LOCK = new Object(); + private boolean userCodeRunning = false; + + /** + * Creates an ExecutionControl instance based on a JDI + * {@code LaunchingConnector}. + * + * @return the generator + */ + public static ExecutionControl.Generator launch() { + return env -> create(env, true); + } + + /** + * Creates an ExecutionControl instance based on a JDI + * {@code ListeningConnector}. + * + * @return the generator + */ + public static ExecutionControl.Generator listen() { + return env -> create(env, false); + } + + /** + * Creates an ExecutionControl instance based on a JDI + * {@code ListeningConnector} or {@code LaunchingConnector}. + * + * Initialize JDI and use it to launch the remote JVM. Set-up a socket for + * commands and results. This socket also transports the user + * input/output/error. + * + * @param env the context passed by + * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } + * @return the channel + * @throws IOException if there are errors in set-up + */ + private static JDIDefaultExecutionControl create(ExecutionEnv env, boolean isLaunch) throws IOException { + ECLogger log = defaultLogger(env); + try (final ServerSocket listener = new ServerSocket(0)) { + // timeout after 60 seconds + listener.setSoTimeout(60000); + int port = listener.getLocalPort(); + + // Set-up the JDI connection + JDIInitiator jdii = new JDIInitiator(log, port, + env.extraRemoteVMOptions(), REMOTE_AGENT, isLaunch); + VirtualMachine vm = jdii.vm(); + Process process = jdii.process(); + + // Forward input to the remote agent + Util.forwardInputToRemote(env.userIn(), process.getOutputStream(), + ex -> log.debug(ex, "input forwarding failure")); + + List> deathListeners = new ArrayList<>(); + deathListeners.add(s -> env.closeDown()); + Util.detectJDIExitEvent(vm, s -> { + for (Consumer h : deathListeners) { + h.accept(s); + } + }); + + // Set-up the commands/reslts on the socket. Piggy-back snippet + // output. + Socket socket = listener.accept(); + // out before in -- match remote creation so we don't hang + ObjectOutput remoteOut = new ObjectOutputStream(socket.getOutputStream()); + InputStream commandIn = demultiplexInput(socket.getInputStream(), log, env.userOut(), env.userErr()); + ObjectInput remoteIn = new ObjectInputStream(commandIn); + return new JDIDefaultExecutionControl(remoteOut, remoteIn, vm, process, deathListeners, log); + } + } + + /** + * Create an instance. + * + * @param out the output for commands + * @param in the input for responses + */ + private JDIDefaultExecutionControl(ObjectOutput out, ObjectInput in, + VirtualMachine vm, Process process, List> deathListeners, ECLogger log) { + super(out, in, log); + this.vm = vm; + this.process = process; + deathListeners.add(s -> disposeVM()); + } + + @Override + public String invoke(String classname, String methodname) + throws RunException, + EngineTerminationException, InternalException { + String res; + synchronized (STOP_LOCK) { + userCodeRunning = true; + } + try { + res = super.invoke(classname, methodname); + } finally { + synchronized (STOP_LOCK) { + userCodeRunning = false; + } + } + return res; + } + + /** + * Interrupts a running remote invoke by manipulating remote variables + * and sending a stop via JDI. + * + * @throws EngineTerminationException the execution engine has terminated + * @throws InternalException an internal problem occurred + */ + @Override + public void stop() throws EngineTerminationException, InternalException { + synchronized (STOP_LOCK) { + if (!userCodeRunning) { + return; + } + + vm().suspend(); + try { + OUTER: + for (ThreadReference thread : vm().allThreads()) { + // could also tag the thread (e.g. using name), to find it easier + for (StackFrame frame : thread.frames()) { + if (REMOTE_AGENT.equals(frame.location().declaringType().name()) && + ( "invoke".equals(frame.location().method().name()) + || "varValue".equals(frame.location().method().name()))) { + ObjectReference thiz = frame.thisObject(); + Field inClientCode = thiz.referenceType().fieldByName("inClientCode"); + Field expectingStop = thiz.referenceType().fieldByName("expectingStop"); + Field stopException = thiz.referenceType().fieldByName("stopException"); + if (((BooleanValue) thiz.getValue(inClientCode)).value()) { + thiz.setValue(expectingStop, vm().mirrorOf(true)); + ObjectReference stopInstance = (ObjectReference) thiz.getValue(stopException); + + vm().resume(); + log.debug("Attempting to stop the client code...\n"); + thread.stop(stopInstance); + thiz.setValue(expectingStop, vm().mirrorOf(false)); + } + + break OUTER; + } + } + } + } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) { + throw new InternalException("Exception on remote stop: " + ex); + } finally { + vm().resume(); + } + } + } + + @Override + public void close() { + super.close(); + disposeVM(); + } + + private synchronized void disposeVM() { + try { + if (vm != null) { + vm.dispose(); // This could NPE, so it is caught below + vm = null; + } + } catch (VMDisconnectedException ex) { + // Ignore if already closed + } catch (Throwable e) { + log.debug("disposeVM threw: " + e); + } finally { + if (process != null) { + process.destroy(); + process = null; + } + } + } + + @Override + protected synchronized VirtualMachine vm() throws EngineTerminationException { + if (vm == null) { + throw new EngineTerminationException("VM closed"); + } else { + return vm; + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIEventHandler.java 2016-07-12 22:46:56.157971301 -0700 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 1998, 2014, 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.util.function.Consumer; +import com.sun.jdi.*; +import com.sun.jdi.event.*; + +/** + * Handler of Java Debug Interface events. + * Adapted from jdb EventHandler. + * Only exit and disconnect events processed. + */ +class JDIEventHandler implements Runnable { + + private final Thread thread; + private volatile boolean connected = true; + private boolean completed = false; + private final VirtualMachine vm; + private final Consumer reportVMExit; + + /** + * Creates an event handler. Start with {@code start()}. + * + * @param vm the virtual machine for which to handle events + * @param reportVMExit callback to report exit/disconnect + * (passed true if the VM has died) + */ + JDIEventHandler(VirtualMachine vm, Consumer reportVMExit) { + this.vm = vm; + this.reportVMExit = reportVMExit; + this.thread = new Thread(this, "event-handler"); + } + + /** + * Starts the event handler. + */ + void start() { + thread.start(); + } + + synchronized void shutdown() { + connected = false; // force run() loop termination + thread.interrupt(); + while (!completed) { + try {wait();} catch (InterruptedException exc) {} + } + } + + @Override + public void run() { + EventQueue queue = vm.eventQueue(); + while (connected) { + try { + EventSet eventSet = queue.remove(); + boolean resumeStoppedApp = false; + EventIterator it = eventSet.eventIterator(); + while (it.hasNext()) { + resumeStoppedApp |= handleEvent(it.nextEvent()); + } + + if (resumeStoppedApp) { + eventSet.resume(); + } + } catch (InterruptedException exc) { + // Do nothing. Any changes will be seen at top of loop. + } catch (VMDisconnectedException discExc) { + handleDisconnectedException(); + break; + } + } + synchronized (this) { + completed = true; + notifyAll(); + } + } + + private boolean handleEvent(Event event) { + handleExitEvent(event); + return true; + } + + private void handleExitEvent(Event event) { + if (event instanceof VMDeathEvent) { + reportVMExit.accept("VM Died"); + } else if (event instanceof VMDisconnectEvent) { + connected = false; + reportVMExit.accept("VM Disconnected"); + } else { + // ignore everything else + } + } + + private synchronized void handleDisconnectedException() { + /* + * A VMDisconnectedException has happened while dealing with + * another event. We need to flush the event queue, dealing only + * with exit events (VMDeath, VMDisconnect) so that we terminate + * correctly. + */ + EventQueue queue = vm.eventQueue(); + while (connected) { + try { + EventSet eventSet = queue.remove(); + EventIterator iter = eventSet.eventIterator(); + while (iter.hasNext()) { + handleExitEvent(iter.next()); + } + } catch (InterruptedException exc) { + // ignore + } catch (InternalError exc) { + // ignore + } + } + } +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIExecutionControl.java 2016-07-12 22:46:56.433978399 -0700 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014, 2016, 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.ObjectInput; +import java.io.ObjectOutput; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.VirtualMachine; +import jdk.jshell.spi.ExecutionControl; +import static java.util.stream.Collectors.toMap; + +/** + * Abstract JDI implementation of {@link jdk.jshell.spi.ExecutionControl} + */ +public abstract class JDIExecutionControl extends StreamingExecutionControl implements ExecutionControl { + + /** + * Mapping from class names to JDI {@link com.sun.jdi.ReferenceType}. + */ + protected Map toReferenceType = new HashMap<>(); + + /** + * Create an instance. + * @param out the output from the remote agent + * @param in the input to the remote agent + * @param log the log + */ + protected JDIExecutionControl(ObjectOutput out, ObjectInput in, ECLogger log) { + super(out, in, log); + } + + /** + * Returns the JDI VirtualMachine instance. + * + * @return the virtual machine + * @throws EngineTerminationException if the VM is dead/disconnected + */ + protected abstract VirtualMachine vm() throws EngineTerminationException; + + /** + * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI, + * an in-place replacement of the classes (preserving class identity) -- + * that is, existing references to the class do not need to be recompiled. + * This implementation uses JDI + * {@link com.sun.jdi.VirtualMachine#redefineClasses(java.util.Map) }. + * It will be unsuccessful if + * the signature of the class has changed (see the JDI spec). The + * JShell-core is designed to adapt to unsuccessful redefine. + */ + @Override + public void redefine(ClassBytecodes[] cbcs) + throws ClassInstallException, EngineTerminationException { + try { + // Convert to the JDI ReferenceType to class bytes map form needed + // by JDI. + VirtualMachine vm = vm(); + Map rmp = Stream.of(cbcs) + .collect(toMap( + cbc -> toReferenceType.computeIfAbsent(cbc.name, name -> nameToRef(vm, name)), + cbc -> cbc.bytecodes)); + // Attempt redefine. Throws exceptions on failure. + vm().redefineClasses(rmp); + } catch (EngineTerminationException ex) { + throw ex; + } catch (Exception ex) { + throw new ClassInstallException("redefine: " + ex.getMessage(), new boolean[cbcs.length]); + } + } + + private static ReferenceType nameToRef(VirtualMachine vm, String name) { + List rtl = vm.classesByName(name); + if (rtl.size() != 1) { + return null; + } + return rtl.get(0); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIInitiator.java 2016-07-12 22:46:56.712985574 -0700 @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2016, 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.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import com.sun.jdi.Bootstrap; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.connect.Connector; +import com.sun.jdi.connect.LaunchingConnector; +import com.sun.jdi.connect.ListeningConnector; + +/** + * Sets up a JDI connection, providing the resulting JDI {@link VirtualMachine} + * and the {@link Process} the remote agent is running in. + */ +public class JDIInitiator { + + private VirtualMachine vm; + private Process process = null; + private final ECLogger log; + private final Connector connector; + private final String remoteAgent; + private final Map connectorArgs; + + /** + * Start the remote agent and establish a JDI connection to it. + * + * @param log the logger + * @param port the socket port for (non-JDI) commands + * @param remoteVMOptions any user requested VM options + * @param remoteAgent full class name of remote agent to launch + * @param isLaunch does JDI do the launch? That is, LaunchingConnector, + * otherwise we start explicitly and use ListeningConnector + */ + public JDIInitiator(ECLogger log, int port, List remoteVMOptions, + String remoteAgent, boolean isLaunch) { + this.log = log; + this.remoteAgent = remoteAgent; + String connectorName + = isLaunch + ? "com.sun.jdi.CommandLineLaunch" + : "com.sun.jdi.SocketListen"; + this.connector = findConnector(connectorName); + if (connector == null) { + throw new IllegalArgumentException("No connector named: " + connectorName); + } + Map argumentName2Value + = isLaunch + ? launchArgs(port, String.join(" ", remoteVMOptions)) + : new HashMap<>(); + this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value); + this.vm = isLaunch + ? launchTarget() + : listenTarget(port, remoteVMOptions); + + } + + /** + * Returns the resulting {@code VirtualMachine} instance. + * + * @return the virtual machine + */ + public VirtualMachine vm() { + return vm; + } + + /** + * Returns the launched process. + * + * @return the remote agent process + */ + public Process process() { + return process; + } + + /* launch child target vm */ + private VirtualMachine launchTarget() { + LaunchingConnector launcher = (LaunchingConnector) connector; + try { + VirtualMachine new_vm = launcher.launch(connectorArgs); + process = new_vm.process(); + return new_vm; + } catch (Exception ex) { + reportLaunchFail(ex, "launch"); + } + return null; + } + + /** + * Directly launch the remote agent and connect JDI to it with a + * ListeningConnector. + */ + private VirtualMachine listenTarget(int port, List remoteVMOptions) { + ListeningConnector listener = (ListeningConnector) connector; + try { + // Start listening, get the JDI connection address + String addr = listener.startListening(connectorArgs); + log.debug("Listening at address: " + addr); + + // Launch the RemoteAgent requesting a connection on that address + String javaHome = System.getProperty("java.home"); + List args = new ArrayList<>(); + args.add(javaHome == null + ? "java" + : javaHome + File.separator + "bin" + File.separator + "java"); + args.add("-agentlib:jdwp=transport=" + connector.transport().name() + + ",address=" + addr); + args.addAll(remoteVMOptions); + args.add(remoteAgent); + args.add("" + port); + ProcessBuilder pb = new ProcessBuilder(args); + process = pb.start(); + + // Forward out, err, and in + // Accept the connection from the remote agent + vm = listener.accept(connectorArgs); + listener.stopListening(connectorArgs); + return vm; + } catch (Exception ex) { + reportLaunchFail(ex, "listen"); + } + return null; + } + + private Connector findConnector(String name) { + for (Connector cntor + : Bootstrap.virtualMachineManager().allConnectors()) { + if (cntor.name().equals(name)) { + return cntor; + } + } + return null; + } + + private Map mergeConnectorArgs(Connector connector, Map argumentName2Value) { + Map arguments = connector.defaultArguments(); + + for (Entry argumentEntry : argumentName2Value.entrySet()) { + String name = argumentEntry.getKey(); + String value = argumentEntry.getValue(); + Connector.Argument argument = arguments.get(name); + + if (argument == null) { + throw new IllegalArgumentException("Argument is not defined for connector:" + + name + " -- " + connector.name()); + } + + argument.setValue(value); + } + + return arguments; + } + + /** + * The JShell specific Connector args for the LaunchingConnector. + * + * @param portthe socket port for (non-JDI) commands + * @param remoteVMOptions any user requested VM options + * @return the argument map + */ + private Map launchArgs(int port, String remoteVMOptions) { + Map argumentName2Value = new HashMap<>(); + argumentName2Value.put("main", remoteAgent + " " + port); + argumentName2Value.put("options", remoteVMOptions); + return argumentName2Value; + } + + private void reportLaunchFail(Exception ex, String context) { + throw new InternalError("Failed remote " + context + ": " + connector + + " -- " + connectorArgs, ex); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/JShellECLogger.java 2016-07-12 22:46:56.991992748 -0700 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, 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 jdk.internal.jshell.debug.InternalDebugControl; +import jdk.jshell.spi.ExecutionEnv; + +/** + * An implementation of {@code ECLogger} which logs to JShell's internal logging + * mechanism. + */ +class JShellECLogger implements ECLogger { + + private final ExecutionEnv execEnv; + + /** + * Create a instance. + * + * @param execEnv the context + */ + JShellECLogger(ExecutionEnv execEnv) { + this.execEnv = execEnv; + } + + @Override + public void debug(String format, Object... args) { + InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), InternalDebugControl.DBG_EC, format, args); + } + + @Override + public void debug(Throwable ex, String where) { + InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java 2016-07-12 22:46:57.282000206 -0700 @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2016, 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicReference; +import jdk.jshell.spi.ExecutionControl; + +/** + * An implementation of {@link jdk.jshell.spi.ExecutionControl} which executes + * in the same JVM as the JShell-core. + * + * @author Grigory Ptashko + */ +public class LocalExecutionControl extends DirectExecutionControl { + + private final Object STOP_LOCK = new Object(); + private boolean userCodeRunning = false; + private ThreadGroup execThreadGroup; + + /** + * Creates a local ExecutionControl instance. + * + * @return the generator + */ + public static ExecutionControl.Generator create() { + return env -> new LocalExecutionControl(); + } + + @Override + protected String invoke(Method doitMethod) throws Exception { + execThreadGroup = new ThreadGroup("JShell process local execution"); + + AtomicReference iteEx = new AtomicReference<>(); + AtomicReference iaeEx = new AtomicReference<>(); + AtomicReference nmeEx = new AtomicReference<>(); + AtomicReference stopped = new AtomicReference<>(false); + + Thread.setDefaultUncaughtExceptionHandler((t, 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 + @SuppressWarnings("deprecation") + 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."); + } + + execThreadGroup.stop(); + } + } + + @Override + protected void clientCodeEnter() { + synchronized (STOP_LOCK) { + userCodeRunning = true; + } + } + + @Override + protected void clientCodeLeave() { + synchronized (STOP_LOCK) { + userCodeRunning = false; + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java 2016-07-12 22:46:57.522006378 -0700 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016, 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.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Packetize an OutputStream, dividing it into named channels. + * + * @author Jan Lahoda + */ +class MultiplexingOutputStream extends OutputStream { + + private static final int PACKET_SIZE = 127; + private final byte[] name; + private final OutputStream delegate; + + MultiplexingOutputStream(String name, OutputStream delegate) { + try { + this.name = name.getBytes("UTF-8"); + this.delegate = delegate; + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); //should not happen + } + } + + @Override + public void write(int b) throws IOException { + synchronized (delegate) { + delegate.write(name.length); //assuming the len is small enough to fit into byte + delegate.write(name); + delegate.write(1); + delegate.write(b); + delegate.flush(); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + synchronized (delegate) { + int i = 0; + while (len > 0) { + int size = Math.min(PACKET_SIZE, len); + delegate.write(name.length); //assuming the len is small enough to fit into byte + delegate.write(name); + delegate.write(size); + delegate.write(b, off + i, size); + i += size; + len -= size; + } + delegate.flush(); + } + } + + @Override + public void flush() throws IOException { + super.flush(); + delegate.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + delegate.close(); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java 2016-07-12 22:46:57.762012549 -0700 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 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.InputStream; + +/** + * + * @author Jan Lahoda + */ +class PipeInputStream extends InputStream { + + private static final int INITIAL_SIZE = 128; + private int[] buffer = new int[INITIAL_SIZE]; + private int start; + private int end; + private boolean closed; + + @Override + public synchronized int read() { + while (start == end) { + if (closed) { + return -1; + } + try { + wait(); + } catch (InterruptedException ex) { + //ignore + } + } + try { + return buffer[start]; + } finally { + start = (start + 1) % buffer.length; + } + } + + public synchronized void write(int b) { + if (closed) { + throw new IllegalStateException("Already closed."); + } + int newEnd = (end + 1) % buffer.length; + if (newEnd == start) { + //overflow: + int[] newBuffer = new int[buffer.length * 2]; + int rightPart = (end > start ? end : buffer.length) - start; + int leftPart = end > start ? 0 : start - 1; + System.arraycopy(buffer, start, newBuffer, 0, rightPart); + System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart); + buffer = newBuffer; + start = 0; + end = rightPart + leftPart; + newEnd = end + 1; + } + buffer[end] = b; + end = newEnd; + notifyAll(); + } + + @Override + public synchronized void close() { + closed = true; + notifyAll(); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteClassLoader.java 2016-07-12 22:46:58.000018670 -0700 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, 2015, 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.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSource; +import java.util.Map; +import java.util.TreeMap; + +/** + * Class loader wrapper which caches class files by name until requested. + * + * @author Robert Field + */ +class RemoteClassLoader extends URLClassLoader { + + private final Map classObjects = new TreeMap<>(); + + RemoteClassLoader() { + super(new URL[0]); + } + + void delare(String name, byte[] bytes) { + classObjects.put(name, bytes); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] b = classObjects.get(name); + if (b == null) { + return super.findClass(name); + } + return super.defineClass(name, b, 0, b.length, (CodeSource) null); + } + + @Override + public void addURL(URL url) { + super.addURL(url); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteCommand.java 2016-07-12 22:46:58.289026101 -0700 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 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; + +/** + * Defines the command codes, used in streaming execution engine + * implementations, to send commands from the main process to the remote agent. + */ +enum RemoteCommand { + /** + * Load classes. + */ + LOAD, + /** + * Redefine classes. + */ + REDEFINE, + /** + * Invoke a method. + */ + INVOKE, + /** + * Retrieve the value of a variable. + */ + VAR_VALUE, + /** + * Add to the class-path. + */ + ADD_CLASSPATH, + /** + * Set the class-path. + */ + SET_CLASSPATH, + /** + * Stop an invoke. + */ + STOP, + /** + * Exit the the agent. + */ + CLOSE + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java 2016-07-12 22:46:58.570033327 -0700 @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014, 2016, 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.lang.reflect.Method; +import java.net.Socket; + +import jdk.jshell.spi.ExecutionControl; +import static jdk.jshell.execution.Util.forwardExecutionControlAndIO; + +/** + * The remote agent runs in the execution process (separate from the main JShell + * process). This agent loads code over a socket from the main JShell process, + * executes the code, and other misc, Specialization of + * {@link DirectExecutionControl} which adds stop support controlled by + * an external process. Designed to work with {@link JDIDefaultExecutionControl}. + * + * @author Jan Lahoda + * @author Robert Field + */ +public class RemoteExecutionControl extends DirectExecutionControl implements ExecutionControl { + + /** + * Launch the agent, connecting to the JShell-core over the socket specified + * in the command-line argument. + * + * @param args standard command-line arguments, expectation is the socket + * number is the only argument + * @throws Exception any unexpected exception + */ + public static void main(String[] args) throws Exception { + String loopBack = null; + Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); + forwardExecutionControlAndIO(new RemoteExecutionControl(), socket); + } + + // These three variables are used by the main JShell process in interrupting + // the running process. Access is via JDI, so the reference is not visible + // to code inspection. + private boolean inClientCode; // Queried by the main process (in superclass) + private boolean expectingStop; // Set by the main process +// Set by the main process + + // thrown by the main process via JDI: + private final StopExecutionException stopException = new StopExecutionException(); + + @Override + public void stop() throws EngineTerminationException, InternalException { + // handled by JDI + } + + // Overridden only so this stack frame is seen + @Override + protected String invoke(Method doitMethod) throws Exception { + return super.invoke(doitMethod); + } + + // Overridden only so this stack frame is seen + @Override + public String varValue(String className, String varName) throws RunException, EngineTerminationException, InternalException { + return super.varValue(className, varName); + } + + @Override + protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException { + if (cause instanceof StopExecutionException) { + expectingStop = false; + throw new StoppedException(); + } else { + return super.throwConvertedInvocationException(cause); + } + } + + @Override + protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException { + if (ex instanceof StopExecutionException || + ex.getCause() instanceof StopExecutionException) { + expectingStop = false; + throw new StoppedException(); + } + return super.throwConvertedOtherException(ex); + } + + @Override + protected void clientCodeEnter() { + expectingStop = false; + inClientCode = true; + } + + @Override + protected void clientCodeLeave() throws InternalException { + inClientCode = false; + while (expectingStop) { + try { + Thread.sleep(0); + } catch (InterruptedException ex) { + throw new InternalException("*** Sleep interrupted while waiting for stop exception: " + ex); + } + } + } + + @SuppressWarnings("serial") // serialVersionUID intentionally omitted + private class StopExecutionException extends ThreadDeath { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteStatus.java 2016-07-12 22:46:58.842040322 -0700 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 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; + +/** + * Defines the command status, used in streaming execution engine + * implementations, to send the result status of commands back from the remote + * agent to the main process. + * + * @see RemoteCommand + */ +enum RemoteStatus { + + /** + * The command succeeded. + */ + SUCCESS, + /** + * Exception encountered during class load/redefine. + */ + CLASS_INSTALL_EXCEPTION, + /** + * User exception encountered. + */ + USER_EXCEPTION, + /** + * Corralled code exception encountered. + */ + CORRALLED, + /** + * The invoke has been stopped. + */ + STOPPED, + /** + * The command failed. + */ + INTERNAL_PROBLEM, + /** + * Unbidden execution engine termination. + */ + TERMINATED + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java 2016-07-12 22:46:59.126047625 -0700 @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2016, 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.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import jdk.jshell.JShellException; +import jdk.jshell.spi.ExecutionControl; + +/** + * An implementation of the {@link jdk.jshell.spi.ExecutionControl} + * execution engine SPI which streams requests to a remote agent where + * execution takes place. + * + * @author Robert Field + */ +public class StreamingExecutionControl implements ExecutionControl { + + protected final ECLogger log; + private final ObjectOutput out; + private final ObjectInput in; + + /** + * Creates an instance. + * + * @param out the output for commands + * @param in the input for command responses + * @param log the log + */ + public StreamingExecutionControl(ObjectOutput out, ObjectInput in, ECLogger log) { + this.out = out; + this.in = in; + this.log = log; + } + + @Override + public void load(ClassBytecodes[] cbcs) + throws ClassInstallException, EngineTerminationException { + try { + // Send a load command to the remote agent. + out.writeObject(RemoteCommand.LOAD); + out.writeObject(cbcs); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportClassInstallResult(); + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote load: " + ex); + } + } + + @Override + public void redefine(ClassBytecodes[] cbcs) + throws ClassInstallException, EngineTerminationException { + try { + // Send a load command to the remote agent. + out.writeObject(RemoteCommand.REDEFINE); + out.writeObject(cbcs); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportClassInstallResult(); + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote redefine: " + ex); + } + } + + @Override + public String invoke(String classname, String methodname) throws RunException, + EngineTerminationException, InternalException { + try { + // Send the invoke command to the remote agent. + out.writeObject(RemoteCommand.INVOKE); + out.writeUTF(classname); + out.writeUTF(methodname); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportExecutionResult(); + String result = in.readUTF(); + return result; + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote invoke: " + ex); + } + } + + @Override + public String varValue(String classname, String varname) throws RunException, + EngineTerminationException, InternalException { + try { + // Send the variable-value command to the remote agent. + out.writeObject(RemoteCommand.VAR_VALUE); + out.writeUTF(classname); + out.writeUTF(varname); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportExecutionResult(); + String result = in.readUTF(); + return result; + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote varValue: " + ex); + } + } + + + @Override + public void addToClasspath(String path) + throws EngineTerminationException, InternalException { + try { + // Send the classpath addition command to the remote agent. + out.writeObject(RemoteCommand.ADD_CLASSPATH); + out.writeUTF(path); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportClassSimpleResult(); + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote add to classpath: " + ex); + } + } + + @Override + public void setClasspath(String path) + throws EngineTerminationException, InternalException { + try { + // Send the classpath addition command to the remote agent. + out.writeObject(RemoteCommand.SET_CLASSPATH); + out.writeUTF(path); + out.flush(); + // Retrieve and report results from the remote agent. + readAndReportClassSimpleResult(); + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote set classpath: " + ex); + } + } + + @Override + public void stop() + throws EngineTerminationException, InternalException { + try { + // Send the variable-value command to the remote agent. + out.writeObject(RemoteCommand.STOP); + out.flush(); + } catch (IOException ex) { + throw new EngineTerminationException("Exception writing remote stop: " + ex); + } + } + + /** + * Closes the execution engine. Send an exit command to the remote agent. + */ + @Override + public void close() { + try { + out.writeObject(RemoteCommand.CLOSE); + out.flush(); + } catch (IOException ex) { + // ignore; + } + } + + /** + * Reports results from a remote agent command that does not expect + * exceptions. + */ + private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException { + try { + RemoteStatus status = (RemoteStatus) in.readObject(); + switch (status) { + case SUCCESS: + return; + case INTERNAL_PROBLEM: { + String message = in.readUTF(); + throw new InternalException(message); + } + case TERMINATED: { + String message = in.readUTF(); + throw new EngineTerminationException(message); + } + default: { + throw new EngineTerminationException("Bad remote result code: " + status); + } + } + } catch (IOException | ClassNotFoundException ex) { + throw new EngineTerminationException(ex.toString()); + } + } + + /** + * Reports results from a remote agent command that does not expect + * exceptions. + */ + private void readAndReportClassInstallResult() throws ClassInstallException, EngineTerminationException { + try { + RemoteStatus status = (RemoteStatus) in.readObject(); + switch (status) { + case SUCCESS: + return; + case CLASS_INSTALL_EXCEPTION: { + String message = in.readUTF(); + boolean[] loaded = (boolean[]) in.readObject(); + throw new ClassInstallException(message, loaded); + } + case TERMINATED: { + String message = in.readUTF(); + throw new EngineTerminationException(message); + } + default: { + throw new EngineTerminationException("Bad remote result code: " + status); + } + } + } catch (IOException | ClassNotFoundException ex) { + throw new EngineTerminationException(ex.toString()); + } + } + + /** + * Reports results from a remote agent command that expects runtime + * exceptions. + * + * @return true if successful + * @throws IOException if the connection has dropped + * @throws JShellException {@link jdk.jshell.EvalException}, if a user + * exception was encountered on invoke; + * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved + * reference was encountered + * @throws java.lang.ClassNotFoundException + */ + private void readAndReportExecutionResult() throws RunException, + EngineTerminationException, InternalException { + try { + RemoteStatus status = (RemoteStatus) in.readObject(); + switch (status) { + case SUCCESS: + return; + case USER_EXCEPTION: { + // A user exception was encountered. + String message = in.readUTF(); + String exceptionClassName = in.readUTF(); + StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); + throw new UserException(message, exceptionClassName, elems); + } + case CORRALLED: { + // An unresolved reference was encountered. + int id = in.readInt(); + StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); + ResolutionException re = new ResolutionException(id, elems); + throw re; + } + case STOPPED: { + // Execution was aborted by the stop() + throw new StoppedException(); + } + case INTERNAL_PROBLEM: { + // An internal error has occurred. + String message = in.readUTF(); + throw new InternalException(message); + } + case TERMINATED: { + String message = in.readUTF(); + throw new EngineTerminationException(message); + } + default: { + throw new EngineTerminationException("Bad remote result code: " + status); + } + } + } catch (IOException | ClassNotFoundException ex) { + throw new EngineTerminationException(ex.toString()); + } + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java 2016-07-12 22:46:59.367053822 -0700 @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2016, 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 jdk.jshell.spi.ExecutionEnv; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.util.function.Consumer; +import com.sun.jdi.VirtualMachine; +import jdk.jshell.spi.ExecutionControl; + + +/** + * Miscellaneous utility methods for setting-up implementations of + * {@link ExecutionControl}. Particularly implementations with remote + * execution. + * + * @author Jan Lahoda + * @author Robert Field + */ +public class Util { + + // never instanciated + private Util() {} + + /** + * Create a composite {@link ExecutionControl.Generator} instance that, when + * generating, will try each specified generator until successfully creating + * an {@link ExecutionControl} instance, or, if all fail, will re-throw the + * first exception. + * + * @param gec0 the first instance to try + * @param gecs the second through Nth instance to try + * @return the fail-over generator + */ + public static ExecutionControl.Generator failOverExecutionControlGenerator( + ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) { + return (ExecutionEnv env) -> { + Throwable thrown; + try { + return gec0.generate(env); + } catch (Throwable ex) { + thrown = ex; + } + for (ExecutionControl.Generator gec : gecs) { + try { + return gec.generate(env); + } catch (Throwable ignore) { + // only care about the first, and only if they all fail + } + } + throw thrown; + }; + } + + /** + * Forward commands from the input to the specified {@link ExecutionControl} + * instance, then responses back on the output. + * @param ec the direct instance of {@link ExecutionControl} to process commands + * @param in the command input + * @param out the command response output + */ + public static void forwardExecutionControl(ExecutionControl ec, + ObjectInput in, ObjectOutput out) { + new ExecutionControlForwarder(ec, in, out).commandLoop(); + } + + /** + * Forward commands from the input to the specified {@link ExecutionControl} + * instance, then responses back on the output. + * @param ec the direct instance of {@link ExecutionControl} to process commands + * @param inStream the stream from which to create the command input + * @param outStream the stream that will carry {@code System.out}, + * {@code System.err}, any specified auxiliary channels, and the + * command response output. + * @param out setter for {@code System.out} + * @param err setter for {@code System.err} + * @param aux setters for any auxiliary channels + * @throws IOException if there are errors using the passed streams + */ + @SafeVarargs + private static void forwardExecutionControlAndIO(ExecutionControl ec, + InputStream inStream, OutputStream outStream, + Consumer out, Consumer err, Consumer... aux) throws IOException { + ObjectInputStream cmdIn = new ObjectInputStream(inStream); + out.accept(multiplexingOutputStream("out", outStream)); + err.accept(multiplexingOutputStream("err", outStream)); + for (int i = 0; i < aux.length; ++i) { + aux[i].accept(multiplexingOutputStream("aux" + (i+1), outStream)); + } + ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream)); + forwardExecutionControl(ec, cmdIn, cmdOut); + } + + /** + * Forward commands from the socket input to the specified + * {@link ExecutionControl} instance, then responses back on the + * socket output; Also, forward the {@code System.out} and + * {@code System.err} streams. + * + * @param ec the direct instance of {@link ExecutionControl} to process commands + * @param socket the command/response/terminal-I/O socket + * @throws IOException if there are errors using the socket + */ + public static void forwardExecutionControlAndIO(ExecutionControl ec, + Socket socket) throws IOException { + // in before out -- so we don't hang the controlling process + InputStream inStream = socket.getInputStream(); + OutputStream outStream = socket.getOutputStream(); + forwardExecutionControlAndIO(ec, inStream, outStream, + st -> System.setOut(new PrintStream(st, true)), + st -> System.setErr(new PrintStream(st, true))); + } + + static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) { + return new MultiplexingOutputStream(label, outputStream); + } + + /** + * Reads from an InputStream which has been packetized and write its contents + * to the out and err OutputStreams; Copies the command stream. + * @param input the packetized input stream + * @param log the log for debugging information + * @param out the output stream to forward {@code System.out} to + * @param err the output stream to forward {@code System.err} to + * @return the command stream + */ + public static InputStream demultiplexInput(InputStream input, ECLogger log, + OutputStream out, OutputStream err) { + PipeInputStream commandIn = new PipeInputStream(); + new DemultiplexInput(input, commandIn, log, out, err).start(); + return commandIn; + } + + /** + * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent} + * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes + * {@code unbiddenExitHandler}. + * + * @param vm the virtual machine to check + * @param unbiddenExitHandler the handler, which will accept the exit + * information + */ + public static void detectJDIExitEvent(VirtualMachine vm, Consumer unbiddenExitHandler) { + if (vm.canBeModified()) { + new JDIEventHandler(vm, unbiddenExitHandler).start(); + } + } + + /** + * Retrieve JShell's default logger. + * + * @param execEnv the execution environment + * @return the logger + */ + public static ECLogger defaultLogger(ExecutionEnv execEnv) { + return new JShellECLogger(execEnv); + } + + /** + * Creates a Thread that will ship all input to the remote agent. + * + * @param inputStream the user input + * @param outStream the input to the remote agent + * @param handler a failure handler + */ + public static void forwardInputToRemote(final InputStream inputStream, + final OutputStream outStream, final Consumer handler) { + Thread thr = new Thread("input reader") { + @Override + public void run() { + try { + byte[] buf = new byte[256]; + int cnt; + while ((cnt = inputStream.read(buf)) != -1) { + outStream.write(buf, 0, cnt); + outStream.flush(); + } + } catch (Exception ex) { + handler.accept(ex); + } + } + }; + thr.setPriority(Thread.MAX_PRIORITY - 1); + thr.start(); + } + +} --- /dev/null 2016-07-12 14:35:29.888302783 -0700 +++ new/test/jdk/jshell/UserJDIUserRemoteTest.java 2016-07-12 22:46:59.640060843 -0700 @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2016, 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 8160128 8159935 + * @summary Tests for Aux channel, custom remote agents, custom JDI implementations. + * @build KullaTesting ExecutionControlTestBase + * @run testng UserJDIUserRemoteTest + */ +import org.testng.annotations.Test; +import org.testng.annotations.BeforeMethod; +import jdk.jshell.Snippet; +import static jdk.jshell.Snippet.Status.OVERWRITTEN; +import static jdk.jshell.Snippet.Status.VALID; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import jdk.jshell.VarSnippet; +import jdk.jshell.execution.DirectExecutionControl; +import jdk.jshell.execution.ECLogger; +import jdk.jshell.execution.JDIExecutionControl; +import jdk.jshell.execution.JDIInitiator; +import jdk.jshell.execution.Util; +import static jdk.jshell.execution.Util.demultiplexInput; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.ExecutionEnv; +import static org.testng.Assert.assertEquals; +import static jdk.jshell.execution.Util.forwardExecutionControlAndIO; + +@Test +public class UserJDIUserRemoteTest extends ExecutionControlTestBase { + + @BeforeMethod + @Override + public void setUp() { + setUp(builder -> builder.executionEngine(MyExecutionControl.create(this))); + } + + public void testVarValue() { + VarSnippet dv = varKey(assertEval("double aDouble = 1.5;")); + String vd = getState().varValue(dv); + assertEquals(vd, "1.5"); + } + + public void testRedefine() { + Snippet vx = varKey(assertEval("int x;")); + Snippet mu = methodKey(assertEval("int mu() { return x * 4; }")); + Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }")); + assertEval("C c0 = new C();"); + assertEval("c0.v();", "\"#0\""); + assertEval("int x = 10;", "10", + ste(MAIN_SNIPPET, VALID, VALID, false, null), + ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); + assertEval("c0.v();", "\"#40\""); + assertEval("C c = new C();"); + assertEval("c.v();", "\"#40\""); + assertEval("int mu() { return x * 3; }", + ste(MAIN_SNIPPET, VALID, VALID, false, null), + ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); + assertEval("c.v();", "\"#30\""); + assertEval("class C { String v() { return \"@\" + mu(); } }", + ste(MAIN_SNIPPET, VALID, VALID, false, null), + ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET)); + assertEval("c0.v();", "\"@30\""); + assertEval("c = new C();"); + assertEval("c.v();", "\"@30\""); + assertActiveKeys(); + } +} + +class MyExecutionControl extends JDIExecutionControl { + + private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName(); + + private VirtualMachine vm; + private Process process; + + /** + * Creates an ExecutionControl instance based on a JDI + * {@code LaunchingConnector}. + * + * @return the generator + */ + public static ExecutionControl.Generator create(UserJDIUserRemoteTest test) { + return env -> make(env, test); + } + + /** + * Creates an ExecutionControl instance based on a JDI + * {@code ListeningConnector} or {@code LaunchingConnector}. + * + * Initialize JDI and use it to launch the remote JVM. Set-up a socket for + * commands and results. This socket also transports the user + * input/output/error. + * + * @param env the context passed by + * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) } + * @return the channel + * @throws IOException if there are errors in set-up + */ + static MyExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException { + ECLogger log = new FileLogger("MyExecutionControl.log"); + log.debug("MyExecutionControl make: %s\n", REMOTE_AGENT); + + try (final ServerSocket listener = new ServerSocket(0)) { + // timeout after 60 seconds + listener.setSoTimeout(60000); + int port = listener.getLocalPort(); + + // Set-up the JDI connection + List opts = new ArrayList<>(env.extraRemoteVMOptions()); + opts.add("-classpath"); + opts.add(System.getProperty("java.class.path") + + System.getProperty("path.separator") + + System.getProperty("user.dir")); + log.debug("MyExecutionControl opts: %s\n", opts); + JDIInitiator jdii = new JDIInitiator(log, port, + opts, REMOTE_AGENT, true); + VirtualMachine vm = jdii.vm(); + Process process = jdii.process(); + + log.debug("MyExecutionControl vm: %s, process: %s\n", vm, process); + + List> deathListeners = new ArrayList<>(); + deathListeners.add(s -> env.closeDown()); + Util.detectJDIExitEvent(vm, s -> { + for (Consumer h : deathListeners) { + h.accept(s); + } + }); + + // Set-up the commands/reslts on the socket. Piggy-back snippet + // output. + Socket socket = listener.accept(); + // out before in -- match remote creation so we don't hang + ObjectOutput remoteOut = new ObjectOutputStream(socket.getOutputStream()); + InputStream commandIn = demultiplexInput(socket.getInputStream(), log, env.userOut(), env.userErr()); + ObjectInput remoteIn = new ObjectInputStream(commandIn); + return new MyExecutionControl(remoteOut, remoteIn, vm, process, deathListeners, log); + } + } + + /** + * Create an instance. + * + * @param out the output for commands + * @param in the input for responses + */ + private MyExecutionControl(ObjectOutput out, ObjectInput in, + VirtualMachine vm, Process process, + List> deathListeners, ECLogger log) { + super(out, in, log); + this.vm = vm; + this.process = process; + deathListeners.add(s -> disposeVM()); + } + + @Override + public void close() { + super.close(); + disposeVM(); + } + + private synchronized void disposeVM() { + try { + if (vm != null) { + vm.dispose(); // This could NPE, so it is caught below + vm = null; + } + } catch (VMDisconnectedException ex) { + // Ignore if already closed + } catch (Throwable e) { + log.debug("disposeVM threw: " + e); + } finally { + if (process != null) { + process.destroy(); + process = null; + } + } + } + + @Override + protected synchronized VirtualMachine vm() throws EngineTerminationException { + if (vm == null) { + log.debug("Access to null VM\n"); + throw new EngineTerminationException("VM closed"); + } else { + return vm; + } + } + +} + +class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl { + + /** + * Launch the agent, connecting to the JShell-core over the socket specified + * in the command-line argument. + * + * @param args standard command-line arguments, expectation is the socket + * number is the only argument + * @throws Exception any unexpected exception + */ + public static void main(String[] args) throws Exception { + ECLogger log = new FileLogger("MyRemoteExecutionControl.log"); + try { + String loopBack = null; + log.debug("MyRemoteExecutionControl.main: %s\n", args[0]); + Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); + forwardExecutionControlAndIO(new MyRemoteExecutionControl(), socket); + } catch (Throwable ex) { + log.debug(ex, "main"); + throw ex; + } + } + + @Override + public String varValue(String className, String varName) + throws RunException, EngineTerminationException, InternalException { + return super.varValue(className, varName); + } + +} + +class FileLogger implements ECLogger { + + PrintWriter pw; + + FileLogger(String fn) { + try { + pw = new PrintWriter(new FileWriter(fn), true); + } catch (IOException ex) { + // ignore + } + } + + @Override + public void debug(String format, Object... args) { + pw.printf("Log: " + format, args); + } + + @Override + public void debug(Throwable ex, String where) { + pw.printf("Log: EXCEPTION %s -- %s\n", ex, where); + ex.printStackTrace(pw); + } + +} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/FailOverExecutionControl.java 2016-07-12 22:47:00.007070280 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2016, 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.internal.jshell.jdi; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import jdk.internal.jshell.debug.InternalDebugControl; -import jdk.jshell.JShellException; -import jdk.jshell.spi.ExecutionControl; -import jdk.jshell.spi.ExecutionEnv; - -/** - * A meta implementation of ExecutionControl which cycles through the specified - * ExecutionControl instances until it finds one that starts. - */ -public class FailOverExecutionControl implements ExecutionControl { - - private final List ecl = new ArrayList<>(); - private ExecutionControl active = null; - private final List thrown = new ArrayList<>(); - - /** - * Create the ExecutionControl instance with at least one actual - * ExecutionControl instance. - * - * @param ec0 the first instance to try - * @param ecs the second and on instance to try - */ - public FailOverExecutionControl(ExecutionControl ec0, ExecutionControl... ecs) { - ecl.add(ec0); - for (ExecutionControl ec : ecs) { - ecl.add(ec); - } - } - - @Override - public void start(ExecutionEnv env) throws Exception { - for (ExecutionControl ec : ecl) { - try { - ec.start(env); - // Success! This is our active ExecutionControl - active = ec; - return; - } catch (Exception ex) { - thrown.add(ex); - } catch (Throwable ex) { - thrown.add(new RuntimeException(ex)); - } - InternalDebugControl.debug(env.state(), env.userErr(), - thrown.get(thrown.size() - 1), "failed one in FailOverExecutionControl"); - } - // They have all failed -- rethrow the first exception we encountered - throw thrown.get(0); - } - - @Override - public void close() { - active.close(); - } - - @Override - public boolean addToClasspath(String path) { - return active.addToClasspath(path); - } - - @Override - public String invoke(String classname, String methodname) throws JShellException { - return active.invoke(classname, methodname); - } - - @Override - public boolean load(Collection classes) { - return active.load(classes); - } - - @Override - public boolean redefine(Collection classes) { - return active.redefine(classes); - } - - @Override - public ClassStatus getClassStatus(String classname) { - return active.getClassStatus(classname); - } - - @Override - public void stop() { - active.stop(); - } - - @Override - public String varValue(String classname, String varname) { - return active.varValue(classname, varname); - } - -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java 2016-07-12 22:47:00.243076349 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,346 +0,0 @@ -/* - * Copyright (c) 1998, 2016, 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. - */ - -/* - * This source code is provided to illustrate the usage of a given feature - * or technique and has been deliberately simplified. Additional steps - * required for a production-quality application, such as security checks, - * input validation and proper error handling, might not be present in - * this sample code. - */ - - -package jdk.internal.jshell.jdi; - -import com.sun.jdi.*; -import com.sun.jdi.connect.*; - -import java.util.*; -import java.util.Map.Entry; -import java.io.*; -import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; - -/** - * Connection to a Java Debug Interface VirtualMachine instance. - * Adapted from jdb VMConnection. Message handling, exception handling, and I/O - * redirection changed. Interface to JShell added. - */ -class JDIConnection { - - private static final String REMOTE_AGENT = "jdk.internal.jshell.remote.RemoteAgent"; - - private VirtualMachine vm; - private boolean active = true; - private Process process = null; - private int outputCompleteCount = 0; - - private final JDIExecutionControl ec; - private final Connector connector; - private final Map connectorArgs; - private final int traceFlags; - - private synchronized void notifyOutputComplete() { - outputCompleteCount++; - notifyAll(); - } - - private synchronized void waitOutputComplete() { - // Wait for stderr and stdout - if (process != null) { - while (outputCompleteCount < 2) { - try {wait();} catch (InterruptedException e) {} - } - } - } - - private Connector findConnector(String name) { - for (Connector cntor : - Bootstrap.virtualMachineManager().allConnectors()) { - if (cntor.name().equals(name)) { - return cntor; - } - } - return null; - } - - private Map mergeConnectorArgs(Connector connector, Map argumentName2Value) { - Map arguments = connector.defaultArguments(); - - for (Entry argumentEntry : argumentName2Value.entrySet()) { - String name = argumentEntry.getKey(); - String value = argumentEntry.getValue(); - Connector.Argument argument = arguments.get(name); - - if (argument == null) { - throw new IllegalArgumentException("Argument is not defined for connector:" + - name + " -- " + connector.name()); - } - - argument.setValue(value); - } - - return arguments; - } - - /** - * The JShell specific Connector args for the LaunchingConnector. - * - * @param portthe socket port for (non-JDI) commands - * @param remoteVMOptions any user requested VM options - * @return the argument map - */ - private static Map launchArgs(int port, String remoteVMOptions) { - Map argumentName2Value = new HashMap<>(); - argumentName2Value.put("main", REMOTE_AGENT + " " + port); - argumentName2Value.put("options", remoteVMOptions); - return argumentName2Value; - } - - /** - * Start the remote agent and establish a JDI connection to it. - * - * @param ec the execution control instance - * @param port the socket port for (non-JDI) commands - * @param remoteVMOptions any user requested VM options - * @param isLaunch does JDI do the launch? That is, LaunchingConnector, - * otherwise we start explicitly and use ListeningConnector - */ - JDIConnection(JDIExecutionControl ec, int port, List remoteVMOptions, boolean isLaunch) { - this(ec, - isLaunch - ? "com.sun.jdi.CommandLineLaunch" - : "com.sun.jdi.SocketListen", - isLaunch - ? launchArgs(port, String.join(" ", remoteVMOptions)) - : new HashMap<>(), - 0); - if (isLaunch) { - vm = launchTarget(); - } else { - vm = listenTarget(port, remoteVMOptions); - } - - if (isOpen() && vm().canBeModified()) { - /* - * Connection opened on startup. - */ - new JDIEventHandler(vm(), (b) -> ec.handleVMExit()) - .start(); - } - } - - /** - * Base constructor -- set-up a JDI connection. - * - * @param ec the execution control instance - * @param connectorName the standardized name of the connector - * @param argumentName2Value the argument map - * @param traceFlags should we trace JDI behavior - */ - JDIConnection(JDIExecutionControl ec, String connectorName, Map argumentName2Value, int traceFlags) { - this.ec = ec; - this.connector = findConnector(connectorName); - if (connector == null) { - throw new IllegalArgumentException("No connector named: " + connectorName); - } - connectorArgs = mergeConnectorArgs(connector, argumentName2Value); - this.traceFlags = traceFlags; - } - - final synchronized VirtualMachine vm() { - if (vm == null) { - throw new JDINotConnectedException(); - } else { - return vm; - } - } - - private synchronized boolean isOpen() { - return (vm != null); - } - - synchronized boolean isRunning() { - return process != null && process.isAlive(); - } - - // Beginning shutdown, ignore any random dying squeals - void beginShutdown() { - active = false; - } - - synchronized void disposeVM() { - try { - if (vm != null) { - vm.dispose(); // This could NPE, so it is caught below - vm = null; - } - } catch (VMDisconnectedException ex) { - // Ignore if already closed - } catch (Throwable e) { - ec.debug(DBG_GEN, null, "disposeVM threw: " + e); - } finally { - if (process != null) { - process.destroy(); - process = null; - } - waitOutputComplete(); - } - } - - private void dumpStream(InputStream inStream, final PrintStream pStream) throws IOException { - BufferedReader in = - new BufferedReader(new InputStreamReader(inStream)); - int i; - try { - while ((i = in.read()) != -1) { - // directly copy input to output, but skip if asked to close - if (active) { - pStream.print((char) i); - } - } - } catch (IOException ex) { - String s = ex.getMessage(); - if (active && !s.startsWith("Bad file number")) { - throw ex; - } - // else we are being shutdown (and don't want any spurious death - // throws to ripple) or - // we got a Bad file number IOException which just means - // that the debuggee has gone away. We'll just treat it the - // same as if we got an EOF. - } - } - - /** - * Create a Thread that will retrieve and display any output. - * Needs to be high priority, else debugger may exit before - * it can be displayed. - */ - private void displayRemoteOutput(final InputStream inStream, final PrintStream pStream) { - Thread thr = new Thread("output reader") { - @Override - public void run() { - try { - dumpStream(inStream, pStream); - } catch (IOException ex) { - ec.debug(ex, "Failed reading output"); - ec.handleVMExit(); - } finally { - notifyOutputComplete(); - } - } - }; - thr.setPriority(Thread.MAX_PRIORITY-1); - thr.start(); - } - - /** - * Create a Thread that will ship all input to remote. - * Does it need be high priority? - */ - private void readRemoteInput(final OutputStream outStream, final InputStream inputStream) { - Thread thr = new Thread("input reader") { - @Override - public void run() { - try { - byte[] buf = new byte[256]; - int cnt; - while ((cnt = inputStream.read(buf)) != -1) { - outStream.write(buf, 0, cnt); - outStream.flush(); - } - } catch (IOException ex) { - ec.debug(ex, "Failed reading output"); - ec.handleVMExit(); - } - } - }; - thr.setPriority(Thread.MAX_PRIORITY-1); - thr.start(); - } - - private void forwardIO() { - displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr()); - displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut()); - readRemoteInput(process.getOutputStream(), ec.execEnv.userIn()); - } - - /* launch child target vm */ - private VirtualMachine launchTarget() { - LaunchingConnector launcher = (LaunchingConnector)connector; - try { - VirtualMachine new_vm = launcher.launch(connectorArgs); - process = new_vm.process(); - forwardIO(); - return new_vm; - } catch (Exception ex) { - reportLaunchFail(ex, "launch"); - } - return null; - } - - /** - * Directly launch the remote agent and connect JDI to it with a - * ListeningConnector. - */ - private VirtualMachine listenTarget(int port, List remoteVMOptions) { - ListeningConnector listener = (ListeningConnector) connector; - try { - // Start listening, get the JDI connection address - String addr = listener.startListening(connectorArgs); - ec.debug(DBG_GEN, "Listening at address: " + addr); - - // Launch the RemoteAgent requesting a connection on that address - String javaHome = System.getProperty("java.home"); - List args = new ArrayList<>(); - args.add(javaHome == null - ? "java" - : javaHome + File.separator + "bin" + File.separator + "java"); - args.add("-agentlib:jdwp=transport=" + connector.transport().name() + - ",address=" + addr); - args.addAll(remoteVMOptions); - args.add(REMOTE_AGENT); - args.add("" + port); - ProcessBuilder pb = new ProcessBuilder(args); - process = pb.start(); - - // Forward out, err, and in - forwardIO(); - - // Accept the connection from the remote agent - vm = listener.accept(connectorArgs); - listener.stopListening(connectorArgs); - return vm; - } catch (Exception ex) { - reportLaunchFail(ex, "listen"); - } - return null; - } - - private void reportLaunchFail(Exception ex, String context) { - throw new InternalError("Failed remote " + context + ": " + connector + - " -- " + connectorArgs, ex); - } -} \ No newline at end of file --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java 2016-07-12 22:47:00.430081158 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,182 +0,0 @@ -/* - * Copyright (c) 1998, 2014, 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.internal.jshell.jdi; - -import java.util.function.Consumer; -import com.sun.jdi.*; -import com.sun.jdi.event.*; - -/** - * Handler of Java Debug Interface events. - * Adapted from jdb EventHandler; Handling of events not used by JShell stubbed out. - */ -class JDIEventHandler implements Runnable { - - private final Thread thread; - private volatile boolean connected = true; - private boolean completed = false; - private final VirtualMachine vm; - private final Consumer reportVMExit; - - JDIEventHandler(VirtualMachine vm, Consumer reportVMExit) { - this.vm = vm; - this.reportVMExit = reportVMExit; - this.thread = new Thread(this, "event-handler"); - } - - void start() { - thread.start(); - } - - synchronized void shutdown() { - connected = false; // force run() loop termination - thread.interrupt(); - while (!completed) { - try {wait();} catch (InterruptedException exc) {} - } - } - - @Override - public void run() { - EventQueue queue = vm.eventQueue(); - while (connected) { - try { - EventSet eventSet = queue.remove(); - boolean resumeStoppedApp = false; - EventIterator it = eventSet.eventIterator(); - while (it.hasNext()) { - resumeStoppedApp |= handleEvent(it.nextEvent()); - } - - if (resumeStoppedApp) { - eventSet.resume(); - } - } catch (InterruptedException exc) { - // Do nothing. Any changes will be seen at top of loop. - } catch (VMDisconnectedException discExc) { - handleDisconnectedException(); - break; - } - } - synchronized (this) { - completed = true; - notifyAll(); - } - } - - private boolean handleEvent(Event event) { - if (event instanceof ExceptionEvent) { - exceptionEvent(event); - } else if (event instanceof WatchpointEvent) { - fieldWatchEvent(event); - } else if (event instanceof MethodEntryEvent) { - methodEntryEvent(event); - } else if (event instanceof MethodExitEvent) { - methodExitEvent(event); - } else if (event instanceof ClassPrepareEvent) { - classPrepareEvent(event); - } else if (event instanceof ThreadStartEvent) { - threadStartEvent(event); - } else if (event instanceof ThreadDeathEvent) { - threadDeathEvent(event); - } else if (event instanceof VMStartEvent) { - vmStartEvent(event); - return true; - } else { - handleExitEvent(event); - } - return true; - } - - private boolean vmDied = false; - - private void handleExitEvent(Event event) { - if (event instanceof VMDeathEvent) { - vmDied = true; - } else if (event instanceof VMDisconnectEvent) { - connected = false; - } else { - throw new InternalError("Unexpected event type: " + - event.getClass()); - } - reportVMExit.accept(vmDied); - } - - private synchronized void handleDisconnectedException() { - /* - * A VMDisconnectedException has happened while dealing with - * another event. We need to flush the event queue, dealing only - * with exit events (VMDeath, VMDisconnect) so that we terminate - * correctly. - */ - EventQueue queue = vm.eventQueue(); - while (connected) { - try { - EventSet eventSet = queue.remove(); - EventIterator iter = eventSet.eventIterator(); - while (iter.hasNext()) { - handleExitEvent(iter.next()); - } - } catch (InterruptedException exc) { - // ignore - } catch (InternalError exc) { - // ignore - } - } - } - - private void vmStartEvent(Event event) { - VMStartEvent se = (VMStartEvent)event; - } - - private void methodEntryEvent(Event event) { - MethodEntryEvent me = (MethodEntryEvent)event; - } - - private void methodExitEvent(Event event) { - MethodExitEvent me = (MethodExitEvent)event; - } - - private void fieldWatchEvent(Event event) { - WatchpointEvent fwe = (WatchpointEvent)event; - } - - private void classPrepareEvent(Event event) { - ClassPrepareEvent cle = (ClassPrepareEvent)event; - } - - private void exceptionEvent(Event event) { - ExceptionEvent ee = (ExceptionEvent)event; - } - - private void threadDeathEvent(Event event) { - ThreadDeathEvent tee = (ThreadDeathEvent)event; - } - - private void threadStartEvent(Event event) { - ThreadStartEvent tse = (ThreadStartEvent)event; - } -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java 2016-07-12 22:47:00.647086738 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,598 +0,0 @@ -/* - * Copyright (c) 2014, 2015, 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.internal.jshell.jdi; - -import static jdk.internal.jshell.remote.RemoteCodes.*; -import java.io.DataInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.PrintStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.io.EOFException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import com.sun.jdi.BooleanValue; -import com.sun.jdi.ClassNotLoadedException; -import com.sun.jdi.IncompatibleThreadStateException; -import com.sun.jdi.InvalidTypeException; -import com.sun.jdi.ObjectReference; -import com.sun.jdi.ReferenceType; -import com.sun.jdi.StackFrame; -import com.sun.jdi.ThreadReference; -import com.sun.jdi.VirtualMachine; -import static java.util.stream.Collectors.toList; -import jdk.jshell.JShellException; -import jdk.jshell.spi.ExecutionControl; -import jdk.jshell.spi.ExecutionEnv; -import jdk.internal.jshell.jdi.ClassTracker.ClassInfo; -import static java.util.stream.Collectors.toMap; -import jdk.internal.jshell.debug.InternalDebugControl; -import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; - -/** - * Controls the remote execution environment. - * Interfaces to the JShell-core by implementing ExecutionControl SPI. - * Interfaces to RemoteAgent over a socket and via JDI. - * Launches a remote process. - */ -public class JDIExecutionControl implements ExecutionControl { - - ExecutionEnv execEnv; - private final boolean isLaunch; - private JDIConnection connection; - private ClassTracker classTracker; - private Socket socket; - private ObjectInputStream remoteIn; - private ObjectOutputStream remoteOut; - - /** - * Creates an ExecutionControl instance based on JDI. - * - * @param isLaunch true for LaunchingConnector; false for ListeningConnector - */ - public JDIExecutionControl(boolean isLaunch) { - this.isLaunch = isLaunch; - } - - /** - * Creates an ExecutionControl instance based on a JDI LaunchingConnector. - */ - public JDIExecutionControl() { - this.isLaunch = true; - } - - /** - * Initializes the launching JDI execution engine. Initialize JDI and use it - * to launch the remote JVM. Set-up control and result communications socket - * to the remote execution environment. This socket also transports the - * input/output channels. - * - * @param execEnv the execution environment provided by the JShell-core - * @throws IOException - */ - @Override - public void start(ExecutionEnv execEnv) throws IOException { - this.execEnv = execEnv; - StringBuilder sb = new StringBuilder(); - try (ServerSocket listener = new ServerSocket(0)) { - // timeout after 60 seconds - listener.setSoTimeout(60000); - int port = listener.getLocalPort(); - connection = new JDIConnection(this, port, execEnv.extraRemoteVMOptions(), isLaunch); - this.socket = listener.accept(); - // out before in -- match remote creation so we don't hang - this.remoteOut = new ObjectOutputStream(socket.getOutputStream()); - PipeInputStream commandIn = new PipeInputStream(); - new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start(); - this.remoteIn = new ObjectInputStream(commandIn); - } - } - - /** - * Closes the execution engine. Send an exit command to the remote agent. - * Shuts down the JDI connection. Should this close the socket? - */ - @Override - public void close() { - try { - if (connection != null) { - connection.beginShutdown(); - } - if (remoteOut != null) { - remoteOut.writeInt(CMD_EXIT); - remoteOut.flush(); - } - if (connection != null) { - connection.disposeVM(); - } - } catch (IOException ex) { - debug(DBG_GEN, "Exception on JDI exit: %s\n", ex); - } - } - - /** - * Loads the list of classes specified. Sends a load command to the remote - * agent with pairs of classname/bytes. - * - * @param classes the names of the wrapper classes to loaded - * @return true if all classes loaded successfully - */ - @Override - public boolean load(Collection classes) { - try { - // Create corresponding ClassInfo instances to track the classes. - // Each ClassInfo has the current class bytes associated with it. - List infos = withBytes(classes); - // Send a load command to the remote agent. - remoteOut.writeInt(CMD_LOAD); - remoteOut.writeInt(classes.size()); - for (ClassInfo ci : infos) { - remoteOut.writeUTF(ci.getClassName()); - remoteOut.writeObject(ci.getBytes()); - } - remoteOut.flush(); - // Retrieve and report results from the remote agent. - boolean result = readAndReportResult(); - // For each class that now has a JDI ReferenceType, mark the bytes - // as loaded. - infos.stream() - .filter(ci -> ci.getReferenceTypeOrNull() != null) - .forEach(ci -> ci.markLoaded()); - return result; - } catch (IOException ex) { - debug(DBG_GEN, "IOException on remote load operation: %s\n", ex); - return false; - } - } - - /** - * Invoke the doit method on the specified class. - * - * @param classname name of the wrapper class whose doit should be invoked - * @return return the result value of the doit - * @throws JShellException if a user exception was thrown (EvalException) or - * an unresolved reference was encountered (UnresolvedReferenceException) - */ - @Override - public String invoke(String classname, String methodname) throws JShellException { - try { - synchronized (STOP_LOCK) { - userCodeRunning = true; - } - // Send the invoke command to the remote agent. - remoteOut.writeInt(CMD_INVOKE); - remoteOut.writeUTF(classname); - remoteOut.writeUTF(methodname); - remoteOut.flush(); - // Retrieve and report results from the remote agent. - if (readAndReportExecutionResult()) { - String result = remoteIn.readUTF(); - return result; - } - } catch (IOException | RuntimeException ex) { - if (!connection.isRunning()) { - // The JDI connection is no longer live, shutdown. - handleVMExit(); - } else { - debug(DBG_GEN, "Exception on remote invoke: %s\n", ex); - return "Execution failure: " + ex.getMessage(); - } - } finally { - synchronized (STOP_LOCK) { - userCodeRunning = false; - } - } - return ""; - } - - /** - * Retrieves the value of a JShell variable. - * - * @param classname name of the wrapper class holding the variable - * @param varname name of the variable - * @return the value as a String - */ - @Override - public String varValue(String classname, String varname) { - try { - // Send the variable-value command to the remote agent. - remoteOut.writeInt(CMD_VARVALUE); - remoteOut.writeUTF(classname); - remoteOut.writeUTF(varname); - remoteOut.flush(); - // Retrieve and report results from the remote agent. - if (readAndReportResult()) { - String result = remoteIn.readUTF(); - return result; - } - } catch (EOFException ex) { - handleVMExit(); - } catch (IOException ex) { - debug(DBG_GEN, "Exception on remote var value: %s\n", ex); - return "Execution failure: " + ex.getMessage(); - } - return ""; - } - - /** - * Adds a path to the remote classpath. - * - * @param cp the additional path element - * @return true if succesful - */ - @Override - public boolean addToClasspath(String cp) { - try { - // Send the classpath addition command to the remote agent. - remoteOut.writeInt(CMD_CLASSPATH); - remoteOut.writeUTF(cp); - remoteOut.flush(); - // Retrieve and report results from the remote agent. - return readAndReportResult(); - } catch (IOException ex) { - throw new InternalError("Classpath addition failed: " + cp, ex); - } - } - - /** - * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI, - * an in-place replacement of the classes (preserving class identity) -- - * that is, existing references to the class do not need to be recompiled. - * This implementation uses JDI redefineClasses. It will be unsuccessful if - * the signature of the class has changed (see the JDI spec). The - * JShell-core is designed to adapt to unsuccessful redefine. - * - * @param classes the names of the classes to redefine - * @return true if all the classes were redefined - */ - @Override - public boolean redefine(Collection classes) { - try { - // Create corresponding ClassInfo instances to track the classes. - // Each ClassInfo has the current class bytes associated with it. - List infos = withBytes(classes); - // Convert to the JDI ReferenceType to class bytes map form needed - // by JDI. - Map rmp = infos.stream() - .collect(toMap( - ci -> ci.getReferenceTypeOrNull(), - ci -> ci.getBytes())); - // Attempt redefine. Throws exceptions on failure. - connection.vm().redefineClasses(rmp); - // Successful: mark the bytes as loaded. - infos.stream() - .forEach(ci -> ci.markLoaded()); - return true; - } catch (UnsupportedOperationException ex) { - // A form of class transformation not supported by JDI - return false; - } catch (Exception ex) { - debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex); - return false; - } - } - - // the VM has gone down in flames or because user evaled System.exit() or the like - void handleVMExit() { - if (connection != null) { - // If there is anything left dispose of it - connection.disposeVM(); - } - // Tell JShell-core that the VM has died - execEnv.closeDown(); - } - - // Lazy init class tracker - private ClassTracker classTracker() { - if (classTracker == null) { - classTracker = new ClassTracker(connection.vm()); - } - return classTracker; - } - - /** - * Converts a collection of class names into ClassInfo instances associated - * with the most recently compiled class bytes. - * - * @param classes names of the classes - * @return a list of corresponding ClassInfo instances - */ - private List withBytes(Collection classes) { - return classes.stream() - .map(cn -> classTracker().classInfo(cn, execEnv.getClassBytes(cn))) - .collect(toList()); - } - - /** - * Reports the status of the named class. UNKNOWN if not loaded. CURRENT if - * the most recent successfully loaded/redefined bytes match the current - * compiled bytes. - * - * @param classname the name of the class to test - * @return the status - */ - @Override - public ClassStatus getClassStatus(String classname) { - ClassInfo ci = classTracker().get(classname); - if (ci.getReferenceTypeOrNull() == null) { - // If the class does not have a JDI ReferenceType it has not been loaded - return ClassStatus.UNKNOWN; - } - // Compare successfully loaded with last compiled bytes. - return (Arrays.equals(execEnv.getClassBytes(classname), ci.getLoadedBytes())) - ? ClassStatus.CURRENT - : ClassStatus.NOT_CURRENT; - } - - /** - * Reports results from a remote agent command that does not expect - * exceptions. - * - * @return true if successful - * @throws IOException if the connection has dropped - */ - private boolean readAndReportResult() throws IOException { - int ok = remoteIn.readInt(); - switch (ok) { - case RESULT_SUCCESS: - return true; - case RESULT_FAIL: { - String ex = remoteIn.readUTF(); - debug(DBG_GEN, "Exception on remote operation: %s\n", ex); - return false; - } - default: { - debug(DBG_GEN, "Bad remote result code: %s\n", ok); - return false; - } - } - } - - /** - * Reports results from a remote agent command that expects runtime - * exceptions. - * - * @return true if successful - * @throws IOException if the connection has dropped - * @throws EvalException if a user exception was encountered on invoke - * @throws UnresolvedReferenceException if an unresolved reference was - * encountered - */ - private boolean readAndReportExecutionResult() throws IOException, JShellException { - int ok = remoteIn.readInt(); - switch (ok) { - case RESULT_SUCCESS: - return true; - case RESULT_FAIL: { - // An internal error has occurred. - String ex = remoteIn.readUTF(); - return false; - } - case RESULT_EXCEPTION: { - // A user exception was encountered. - String exceptionClassName = remoteIn.readUTF(); - String message = remoteIn.readUTF(); - StackTraceElement[] elems = readStackTrace(); - throw execEnv.createEvalException(message, exceptionClassName, elems); - } - case RESULT_CORRALLED: { - // An unresolved reference was encountered. - int id = remoteIn.readInt(); - StackTraceElement[] elems = readStackTrace(); - throw execEnv.createUnresolvedReferenceException(id, elems); - } - case RESULT_KILLED: { - // Execution was aborted by the stop() - debug(DBG_GEN, "Killed."); - return false; - } - default: { - debug(DBG_GEN, "Bad remote result code: %s\n", ok); - return false; - } - } - } - - private StackTraceElement[] readStackTrace() throws IOException { - int elemCount = remoteIn.readInt(); - StackTraceElement[] elems = new StackTraceElement[elemCount]; - for (int i = 0; i < elemCount; ++i) { - String className = remoteIn.readUTF(); - String methodName = remoteIn.readUTF(); - String fileName = remoteIn.readUTF(); - int line = remoteIn.readInt(); - elems[i] = new StackTraceElement(className, methodName, fileName, line); - } - return elems; - } - - private final Object STOP_LOCK = new Object(); - private boolean userCodeRunning = false; - - /** - * Interrupt a running invoke. - */ - @Override - public void stop() { - synchronized (STOP_LOCK) { - if (!userCodeRunning) { - return; - } - - VirtualMachine vm = connection.vm(); - vm.suspend(); - try { - OUTER: - for (ThreadReference thread : vm.allThreads()) { - // could also tag the thread (e.g. using name), to find it easier - for (StackFrame frame : thread.frames()) { - String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent"; - if (remoteAgentName.equals(frame.location().declaringType().name()) - && "commandLoop".equals(frame.location().method().name())) { - ObjectReference thiz = frame.thisObject(); - if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) { - thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true)); - ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException")); - - vm.resume(); - debug(DBG_GEN, "Attempting to stop the client code...\n"); - thread.stop(stopInstance); - thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false)); - } - - break OUTER; - } - } - } - } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) { - debug(DBG_GEN, "Exception on remote stop: %s\n", ex); - } finally { - vm.resume(); - } - } - } - - void debug(int flags, String format, Object... args) { - InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), flags, format, args); - } - - void debug(Exception ex, String where) { - InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where); - } - - private final class DemultiplexInput extends Thread { - - private final DataInputStream delegate; - private final PipeInputStream command; - private final PrintStream out; - private final PrintStream err; - - public DemultiplexInput(InputStream input, - PipeInputStream command, - PrintStream out, - PrintStream err) { - super("output reader"); - this.delegate = new DataInputStream(input); - this.command = command; - this.out = out; - this.err = err; - } - - public void run() { - try { - while (true) { - int nameLen = delegate.read(); - if (nameLen == (-1)) - break; - byte[] name = new byte[nameLen]; - DemultiplexInput.this.delegate.readFully(name); - int dataLen = delegate.read(); - byte[] data = new byte[dataLen]; - DemultiplexInput.this.delegate.readFully(data); - switch (new String(name, "UTF-8")) { - case "err": - err.write(data); - break; - case "out": - out.write(data); - break; - case "command": - for (byte b : data) { - command.write(Byte.toUnsignedInt(b)); - } - break; - } - } - } catch (IOException ex) { - debug(ex, "Failed reading output"); - } finally { - command.close(); - } - } - - } - - public static final class PipeInputStream extends InputStream { - public static final int INITIAL_SIZE = 128; - - private int[] buffer = new int[INITIAL_SIZE]; - private int start; - private int end; - private boolean closed; - - @Override - public synchronized int read() { - while (start == end) { - if (closed) { - return -1; - } - try { - wait(); - } catch (InterruptedException ex) { - //ignore - } - } - try { - return buffer[start]; - } finally { - start = (start + 1) % buffer.length; - } - } - - public synchronized void write(int b) { - if (closed) - throw new IllegalStateException("Already closed."); - int newEnd = (end + 1) % buffer.length; - if (newEnd == start) { - //overflow: - int[] newBuffer = new int[buffer.length * 2]; - int rightPart = (end > start ? end : buffer.length) - start; - int leftPart = end > start ? 0 : start - 1; - System.arraycopy(buffer, start, newBuffer, 0, rightPart); - System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart); - buffer = newBuffer; - start = 0; - end = rightPart + leftPart; - newEnd = end + 1; - } - buffer[end] = b; - end = newEnd; - notifyAll(); - } - - @Override - public synchronized void close() { - closed = true; - notifyAll(); - } - - } -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java 2016-07-12 22:47:00.839091675 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,43 +0,0 @@ -/* - * Copyright (c) 1999, 2015, 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.internal.jshell.jdi; - -/** - * Internal exception when Java Debug Interface VirtualMacine is not connected. - * Copy of jdb VMNotConnectedException. - */ -class JDINotConnectedException extends RuntimeException { - - private static final long serialVersionUID = -7433430494903950165L; - - public JDINotConnectedException() { - super(); - } - - public JDINotConnectedException(String s) { - super(s); - } -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java 2016-07-12 22:47:01.022096381 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,326 +0,0 @@ -/* - * Copyright (c) 2014, 2015, 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.internal.jshell.remote; -import jdk.jshell.spi.SPIResolutionException; -import java.io.File; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; - -import java.util.ArrayList; -import java.util.List; - -import static jdk.internal.jshell.remote.RemoteCodes.*; - -import java.util.Map; -import java.util.TreeMap; - -/** - * The remote agent runs in the execution process (separate from the main JShell - * process. This agent loads code over a socket from the main JShell process, - * executes the code, and other misc, - * @author Robert Field - */ -class RemoteAgent { - - private final RemoteClassLoader loader = new RemoteClassLoader(); - private final Map> klasses = new TreeMap<>(); - - public static void main(String[] args) throws Exception { - String loopBack = null; - Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); - (new RemoteAgent()).commandLoop(socket); - } - - void commandLoop(Socket socket) throws IOException { - // in before out -- so we don't hang the controlling process - ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); - OutputStream socketOut = socket.getOutputStream(); - System.setOut(new PrintStream(new MultiplexingOutputStream("out", socketOut), true)); - System.setErr(new PrintStream(new MultiplexingOutputStream("err", socketOut), true)); - ObjectOutputStream out = new ObjectOutputStream(new MultiplexingOutputStream("command", socketOut)); - while (true) { - int cmd = in.readInt(); - switch (cmd) { - case CMD_EXIT: - // Terminate this process - return; - case CMD_LOAD: - // Load a generated class file over the wire - try { - int count = in.readInt(); - List names = new ArrayList<>(count); - for (int i = 0; i < count; ++i) { - String name = in.readUTF(); - byte[] kb = (byte[]) in.readObject(); - loader.delare(name, kb); - names.add(name); - } - for (String name : names) { - Class klass = loader.loadClass(name); - klasses.put(name, klass); - // Get class loaded to the point of, at least, preparation - klass.getDeclaredMethods(); - } - out.writeInt(RESULT_SUCCESS); - out.flush(); - } catch (IOException | ClassNotFoundException | ClassCastException ex) { - debug("*** Load failure: %s\n", ex); - out.writeInt(RESULT_FAIL); - out.writeUTF(ex.toString()); - out.flush(); - } - break; - case CMD_INVOKE: { - // Invoke executable entry point in loaded code - String name = in.readUTF(); - Class klass = klasses.get(name); - if (klass == null) { - debug("*** Invoke failure: no such class loaded %s\n", name); - out.writeInt(RESULT_FAIL); - out.writeUTF("no such class loaded: " + name); - out.flush(); - break; - } - String methodName = in.readUTF(); - Method doitMethod; - try { - this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule()); - doitMethod = klass.getDeclaredMethod(methodName, new Class[0]); - doitMethod.setAccessible(true); - Object res; - try { - clientCodeEnter(); - res = doitMethod.invoke(null, new Object[0]); - } catch (InvocationTargetException ex) { - if (ex.getCause() instanceof StopExecutionException) { - expectingStop = false; - throw (StopExecutionException) ex.getCause(); - } - throw ex; - } catch (StopExecutionException ex) { - expectingStop = false; - throw ex; - } finally { - clientCodeLeave(); - } - out.writeInt(RESULT_SUCCESS); - out.writeUTF(valueString(res)); - out.flush(); - } catch (InvocationTargetException ex) { - Throwable cause = ex.getCause(); - StackTraceElement[] elems = cause.getStackTrace(); - if (cause instanceof SPIResolutionException) { - out.writeInt(RESULT_CORRALLED); - out.writeInt(((SPIResolutionException) cause).id()); - } else { - out.writeInt(RESULT_EXCEPTION); - out.writeUTF(cause.getClass().getName()); - out.writeUTF(cause.getMessage() == null ? "" : cause.getMessage()); - } - out.writeInt(elems.length); - for (StackTraceElement ste : elems) { - out.writeUTF(ste.getClassName()); - out.writeUTF(ste.getMethodName()); - out.writeUTF(ste.getFileName() == null ? "" : ste.getFileName()); - out.writeInt(ste.getLineNumber()); - } - out.flush(); - } catch (NoSuchMethodException | IllegalAccessException ex) { - debug("*** Invoke failure: %s -- %s\n", ex, ex.getCause()); - out.writeInt(RESULT_FAIL); - out.writeUTF(ex.toString()); - out.flush(); - } catch (StopExecutionException ex) { - try { - out.writeInt(RESULT_KILLED); - out.flush(); - } catch (IOException err) { - debug("*** Error writing killed result: %s -- %s\n", ex, ex.getCause()); - } - } - System.out.flush(); - break; - } - case CMD_VARVALUE: { - // Retrieve a variable value - String classname = in.readUTF(); - String varname = in.readUTF(); - Class klass = klasses.get(classname); - if (klass == null) { - debug("*** Var value failure: no such class loaded %s\n", classname); - out.writeInt(RESULT_FAIL); - out.writeUTF("no such class loaded: " + classname); - out.flush(); - break; - } - try { - Field var = klass.getDeclaredField(varname); - var.setAccessible(true); - Object res = var.get(null); - out.writeInt(RESULT_SUCCESS); - out.writeUTF(valueString(res)); - out.flush(); - } catch (Exception ex) { - debug("*** Var value failure: no such field %s.%s\n", classname, varname); - out.writeInt(RESULT_FAIL); - out.writeUTF("no such field loaded: " + varname + " in class: " + classname); - out.flush(); - } - break; - } - case CMD_CLASSPATH: { - // Append to the claspath - String cp = in.readUTF(); - for (String path : cp.split(File.pathSeparator)) { - loader.addURL(new File(path).toURI().toURL()); - } - out.writeInt(RESULT_SUCCESS); - out.flush(); - break; - } - default: - debug("*** Bad command code: %d\n", cmd); - break; - } - } - } - - // These three variables are used by the main JShell process in interrupting - // the running process. Access is via JDI, so the reference is not visible - // to code inspection. - private boolean inClientCode; // Queried by the main process - private boolean expectingStop; // Set by the main process - - // thrown by the main process via JDI: - private final StopExecutionException stopException = new StopExecutionException(); - - @SuppressWarnings("serial") // serialVersionUID intentionally omitted - private class StopExecutionException extends ThreadDeath { - @Override public synchronized Throwable fillInStackTrace() { - return this; - } - } - - void clientCodeEnter() { - expectingStop = false; - inClientCode = true; - } - - void clientCodeLeave() { - inClientCode = false; - while (expectingStop) { - try { - Thread.sleep(0); - } catch (InterruptedException ex) { - debug("*** Sleep interrupted while waiting for stop exception: %s\n", ex); - } - } - } - - private void debug(String format, Object... args) { - System.err.printf("REMOTE: "+format, args); - } - - static String valueString(Object value) { - if (value == null) { - return "null"; - } else if (value instanceof String) { - return "\"" + (String)value + "\""; - } else if (value instanceof Character) { - return "'" + value + "'"; - } else { - return value.toString(); - } - } - - private static final class MultiplexingOutputStream extends OutputStream { - - private static final int PACKET_SIZE = 127; - - private final byte[] name; - private final OutputStream delegate; - - public MultiplexingOutputStream(String name, OutputStream delegate) { - try { - this.name = name.getBytes("UTF-8"); - this.delegate = delegate; - } catch (UnsupportedEncodingException ex) { - throw new IllegalStateException(ex); //should not happen - } - } - - @Override - public void write(int b) throws IOException { - synchronized (delegate) { - delegate.write(name.length); //assuming the len is small enough to fit into byte - delegate.write(name); - delegate.write(1); - delegate.write(b); - delegate.flush(); - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - synchronized (delegate) { - int i = 0; - while (len > 0) { - int size = Math.min(PACKET_SIZE, len); - - delegate.write(name.length); //assuming the len is small enough to fit into byte - delegate.write(name); - delegate.write(size); - delegate.write(b, off + i, size); - i += size; - len -= size; - } - - delegate.flush(); - } - } - - @Override - public void flush() throws IOException { - super.flush(); - delegate.flush(); - } - - @Override - public void close() throws IOException { - super.close(); - delegate.close(); - } - - } -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java 2016-07-12 22:47:01.199100932 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2014, 2015, 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.internal.jshell.remote; - -import java.net.URL; -import java.net.URLClassLoader; -import java.security.CodeSource; -import java.util.Map; -import java.util.TreeMap; - -/** - * Class loader wrapper which caches class files by name until requested. - * @author Robert Field - */ -class RemoteClassLoader extends URLClassLoader { - - private final Map classObjects = new TreeMap<>(); - - RemoteClassLoader() { - super(new URL[0]); - } - - void delare(String name, byte[] bytes) { - classObjects.put(name, bytes); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - byte[] b = classObjects.get(name); - if (b == null) { - return super.findClass(name); - } - return super.defineClass(name, b, 0, b.length, (CodeSource) null); - } - - @Override - public void addURL(URL url) { - super.addURL(url); - } - -} --- old/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java 2016-07-12 22:47:01.381105613 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014, 2015, 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.internal.jshell.remote; - -/** - * Communication constants shared between the main process and the remote - * execution process - * @author Robert Field - */ -public class RemoteCodes { - // Command codes - public static final int CMD_EXIT = 0; - public static final int CMD_LOAD = 1; - public static final int CMD_INVOKE = 3; - public static final int CMD_CLASSPATH = 4; - public static final int CMD_VARVALUE = 5; - - // Return result codes - public static final int RESULT_SUCCESS = 100; - public static final int RESULT_FAIL = 101; - public static final int RESULT_EXCEPTION = 102; - public static final int RESULT_CORRALLED = 103; - public static final int RESULT_KILLED = 104; -} --- old/src/jdk.jshell/share/classes/jdk/jshell/execution/Internal.java 2016-07-12 22:47:01.571110499 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2016, 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; - -/** - * Temp class needed so the package exists. - */ -class Internal {} --- old/test/jdk/jshell/LocalExecutionControl.java 2016-07-12 22:47:01.748115050 -0700 +++ /dev/null 2016-07-12 14:35:29.888302783 -0700 @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2016, 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. - */ - -import jdk.jshell.spi.ExecutionControl; -import jdk.jshell.spi.ExecutionEnv; -import jdk.jshell.spi.SPIResolutionException; -import jdk.jshell.EvalException; -import jdk.jshell.UnresolvedReferenceException; - -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.CodeSource; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicReference; - -/** - * An implementation of ExecutionControl which executes in the same JVM as the - * JShell core. - * - * @author Grigory Ptashko - */ -class LocalExecutionControl implements ExecutionControl { - private class REPLClassLoader extends URLClassLoader { - REPLClassLoader() { - super(new URL[0]); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - debug("findClass %s\n", name); - byte[] b = execEnv.getClassBytes(name); - if (b == null) { - return super.findClass(name); - } - return super.defineClass(name, b, 0, b.length, (CodeSource)null); - } - - @Override - public void addURL(URL url) { - super.addURL(url); - } - } - - private ExecutionEnv execEnv; - private final Object STOP_LOCK = new Object(); - private boolean userCodeRunning = false; - private REPLClassLoader loader = new REPLClassLoader(); - private final Map> klasses = new TreeMap<>(); - private final Map classBytes = new HashMap<>(); - private ThreadGroup execThreadGroup; - - @Override - public void start(ExecutionEnv execEnv) throws Exception { - this.execEnv = execEnv; - - debug("Process-local code snippets execution control started"); - } - - @Override - public void close() { - } - - @Override - public boolean load(Collection classes) { - try { - loadLocal(classes); - - return true; - } catch (ClassNotFoundException | ClassCastException ex) { - debug(ex, "Exception on load operation"); - } - - return false; - } - - @Override - public String invoke(String classname, String methodname) throws EvalException, UnresolvedReferenceException { - try { - synchronized (STOP_LOCK) { - userCodeRunning = true; - } - - // Invoke executable entry point in loaded code - Class klass = klasses.get(classname); - if (klass == null) { - debug("Invoke failure: no such class loaded %s\n", classname); - - return ""; - } - - Method doitMethod; - try { - this.getClass().getModule().addReads(klass.getModule()); - this.getClass().getModule().addExports(SPIResolutionException.class.getPackage() - .getName(), klass.getModule()); - doitMethod = klass.getDeclaredMethod(methodname, new Class[0]); - doitMethod.setAccessible(true); - - execThreadGroup = new ThreadGroup("JShell process local execution"); - - AtomicReference iteEx = new AtomicReference<>(); - AtomicReference iaeEx = new AtomicReference<>(); - AtomicReference nmeEx = new AtomicReference<>(); - AtomicReference stopped = new AtomicReference<>(false); - - Thread.setDefaultUncaughtExceptionHandler((t, 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()) { - debug("Killed."); - - return ""; - } - - 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]); - } catch (InvocationTargetException ex) { - Throwable cause = ex.getCause(); - StackTraceElement[] elems = cause.getStackTrace(); - if (cause instanceof SPIResolutionException) { - int id = ((SPIResolutionException)cause).id(); - - throw execEnv.createUnresolvedReferenceException(id, elems); - } else { - throw execEnv.createEvalException(cause.getMessage() == null ? - "" : cause.getMessage(), cause.getClass().getName(), elems); - } - } catch (NoSuchMethodException | IllegalAccessException | InterruptedException ex) { - debug(ex, "Invoke failure"); - } - } finally { - synchronized (STOP_LOCK) { - userCodeRunning = false; - } - } - - return ""; - } - - @Override - @SuppressWarnings("deprecation") - public void stop() { - synchronized (STOP_LOCK) { - if (!userCodeRunning) - return; - - if (execThreadGroup == null) { - debug("Process-local code snippets thread group is null. Aborting stop."); - - return; - } - - execThreadGroup.stop(); - } - } - - @Override - public String varValue(String classname, String varname) { - Class klass = klasses.get(classname); - if (klass == null) { - debug("Var value failure: no such class loaded %s\n", classname); - - return ""; - } - try { - this.getClass().getModule().addReads(klass.getModule()); - Field var = klass.getDeclaredField(varname); - var.setAccessible(true); - Object res = var.get(null); - - return valueString(res); - } catch (Exception ex) { - debug("Var value failure: no such field %s.%s\n", classname, varname); - } - - return ""; - } - - @Override - public boolean addToClasspath(String cp) { - // Append to the claspath - for (String path : cp.split(File.pathSeparator)) { - try { - loader.addURL(new File(path).toURI().toURL()); - } catch (MalformedURLException e) { - throw new InternalError("Classpath addition failed: " + cp, e); - } - } - - return true; - } - - @Override - public boolean redefine(Collection classes) { - return false; - } - - @Override - public ClassStatus getClassStatus(String classname) { - if (!classBytes.containsKey(classname)) { - return ClassStatus.UNKNOWN; - } else if (!Arrays.equals(classBytes.get(classname), execEnv.getClassBytes(classname))) { - return ClassStatus.NOT_CURRENT; - } else { - return ClassStatus.CURRENT; - } - } - - private void loadLocal(Collection classes) throws ClassNotFoundException { - for (String className : classes) { - Class klass = loader.loadClass(className); - klasses.put(className, klass); - classBytes.put(className, execEnv.getClassBytes(className)); - klass.getDeclaredMethods(); - } - } - - private void debug(String format, Object... args) { - //debug(execEnv.state(), execEnv.userErr(), flags, format, args); - } - - private void debug(Exception ex, String where) { - //debug(execEnv.state(), execEnv.userErr(), ex, where); - } - - private static String valueString(Object value) { - if (value == null) { - return "null"; - } else if (value instanceof String) { - return "\"" + (String)value + "\""; - } else if (value instanceof Character) { - return "'" + value + "'"; - } else { - return value.toString(); - } - } -}