8156101: JShell SPI: Provide a pluggable execution control SPI
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java Sat May 21 22:32:08 2016 -0700
@@ -23,24 +23,60 @@
package jdk.internal.jshell.debug;
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import jdk.jshell.JShell;
/**
- * Used to externally control output messages for debugging the implementation
- * of the JShell API. This is NOT a supported interface,
- * @author Robert Field
+/**
+ * This class is used to externally control output messages for debugging the
+ * implementation of the JShell API.
+ * <p>
+ * This is not part of the SPI, not API.
*/
public class InternalDebugControl {
- public static final int DBG_GEN = 0b0000001;
- public static final int DBG_FMGR = 0b0000010;
+
+ /**
+ * This is a static only class; The constructor should never be called.
+ */
+ private InternalDebugControl() {
+ }
+
+ /**
+ * General debugging.
+ */
+ public static final int DBG_GEN = 0b0000001;
+
+ /**
+ * File manager debuging.
+ */
+ public static final int DBG_FMGR = 0b0000010;
+
+ /**
+ * Completion analysis debugging.
+ */
public static final int DBG_COMPA = 0b0000100;
- public static final int DBG_DEP = 0b0001000;
- public static final int DBG_EVNT = 0b0010000;
+
+ /**
+ * Dependency debugging.
+ */
+ public static final int DBG_DEP = 0b0001000;
+
+ /**
+ * Event debugging.
+ */
+ public static final int DBG_EVNT = 0b0010000;
private static Map<JShell, Integer> debugMap = null;
+ /**
+ * Sets which debug flags are enabled for a given JShell instance. The flags
+ * are or'ed bits as defined in {@code DBG_*}.
+ *
+ * @param state the JShell instance
+ * @param flags the or'ed debug bits
+ */
public static void setDebugFlags(JShell state, int flags) {
if (debugMap == null) {
debugMap = new HashMap<>();
@@ -48,7 +84,14 @@
debugMap.put(state, flags);
}
- public static boolean debugEnabled(JShell state, int flag) {
+ /**
+ * Tests if any of the specified debug flags are enabled.
+ *
+ * @param state the JShell instance
+ * @param flag the {@code DBG_*} bits to check
+ * @return true if any of the flags are enabled
+ */
+ public static boolean isDebugEnabled(JShell state, int flag) {
if (debugMap == null) {
return false;
}
@@ -58,4 +101,34 @@
}
return (flags & flag) != 0;
}
+
+ /**
+ * Displays debug info if the specified debug flags are enabled.
+ *
+ * @param state the current JShell instance
+ * @param err the {@code PrintStream} to report on
+ * @param flags {@code DBG_*} flag bits to check
+ * @param format format string for the output
+ * @param args args for the format string
+ */
+ public static void debug(JShell state, PrintStream err, int flags, String format, Object... args) {
+ if (isDebugEnabled(state, flags)) {
+ err.printf(format, args);
+ }
+ }
+
+ /**
+ * Displays a fatal exception as debug info.
+ *
+ * @param state the current JShell instance
+ * @param err the {@code PrintStream} to report on
+ * @param ex the fatal Exception
+ * @param where additional context
+ */
+ public static void debug(JShell state, PrintStream err, Exception ex, String where) {
+ if (isDebugEnabled(state, 0xFFFFFFFF)) {
+ err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
+ ex.printStackTrace(err);
+ }
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+/**
+ * Tracks the state of a class.
+ */
+class ClassTracker {
+
+ private final JDIEnv jdiEnv;
+ private final HashMap<String, ClassInfo> map;
+
+ ClassTracker(JDIEnv jdiEnv) {
+ this.jdiEnv = jdiEnv;
+ 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<ReferenceType> rtl = jdiEnv.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 Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,345 @@
+/*
+ * 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 VirtualMachine vm;
+ private Process process = null;
+ private int outputCompleteCount = 0;
+
+ private final JDIExecutionControl ec;
+ private final Connector connector;
+ private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
+ private final int traceFlags;
+
+ synchronized void notifyOutputComplete() {
+ outputCompleteCount++;
+ notifyAll();
+ }
+
+ 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 <String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) {
+ Map<String, Connector.Argument> arguments = connector.defaultArguments();
+
+ for (Entry<String, String> 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;
+ }
+
+
+ JDIConnection(JDIExecutionControl ec, String connectorName, Map<String, String> 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;
+ }
+
+ synchronized VirtualMachine open() {
+ if (connector instanceof LaunchingConnector) {
+ vm = launchTarget();
+ } else if (connector instanceof AttachingConnector) {
+ vm = attachTarget();
+ } else if (connector instanceof ListeningConnector) {
+ vm = listenTarget();
+ } else {
+ throw new InternalError("Invalid connect type");
+ }
+ vm.setDebugTraceMode(traceFlags);
+ // Uncomment here and below to enable event requests
+ // installEventRequests(vm);
+
+ return vm;
+ }
+
+ synchronized boolean setConnectorArg(String name, String value) {
+ /*
+ * Too late if the connection already made
+ */
+ if (vm != null) {
+ return false;
+ }
+
+ Connector.Argument argument = connectorArgs.get(name);
+ if (argument == null) {
+ return false;
+ }
+ argument.setValue(value);
+ return true;
+ }
+
+ String connectorArg(String name) {
+ Connector.Argument argument = connectorArgs.get(name);
+ if (argument == null) {
+ return "";
+ }
+ return argument.value();
+ }
+
+ public synchronized VirtualMachine vm() {
+ if (vm == null) {
+ throw new JDINotConnectedException();
+ } else {
+ return vm;
+ }
+ }
+
+ synchronized boolean isOpen() {
+ return (vm != null);
+ }
+
+ boolean isLaunch() {
+ return (connector instanceof LaunchingConnector);
+ }
+
+ synchronized boolean isRunning() {
+ return process != null && process.isAlive();
+ }
+
+ public 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
+ } finally {
+ if (process != null) {
+ process.destroy();
+ process = null;
+ }
+ waitOutputComplete();
+ }
+ }
+
+/*** Preserved for possible future support of event requests
+
+ private void installEventRequests(VirtualMachine vm) {
+ if (vm.canBeModified()){
+ setEventRequests(vm);
+ resolveEventRequests();
+ }
+ }
+
+ private void setEventRequests(VirtualMachine vm) {
+ EventRequestManager erm = vm.eventRequestManager();
+
+ // Normally, we want all uncaught exceptions. We request them
+ // via the same mechanism as Commands.commandCatchException()
+ // so the user can ignore them later if they are not
+ // interested.
+ // FIXME: this works but generates spurious messages on stdout
+ // during startup:
+ // Set uncaught java.lang.Throwable
+ // Set deferred uncaught java.lang.Throwable
+ Commands evaluator = new Commands();
+ evaluator.commandCatchException
+ (new StringTokenizer("uncaught java.lang.Throwable"));
+
+ ThreadStartRequest tsr = erm.createThreadStartRequest();
+ tsr.enable();
+ ThreadDeathRequest tdr = erm.createThreadDeathRequest();
+ tdr.enable();
+ }
+
+ private void resolveEventRequests() {
+ Env.specList.resolveAll();
+ }
+***/
+
+ 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) {
+ pStream.print((char) i);
+ }
+ } catch (IOException ex) {
+ String s = ex.getMessage();
+ if (!s.startsWith("Bad file number")) {
+ throw ex;
+ }
+ // else 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.jdiEnv.shutdown();
+ } 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.jdiEnv.shutdown();
+ }
+ }
+ };
+ thr.setPriority(Thread.MAX_PRIORITY-1);
+ thr.start();
+ }
+
+ /* launch child target vm */
+ private VirtualMachine launchTarget() {
+ LaunchingConnector launcher = (LaunchingConnector)connector;
+ try {
+ VirtualMachine new_vm = launcher.launch(connectorArgs);
+ process = new_vm.process();
+ displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
+ displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
+ readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
+ return new_vm;
+ } catch (Exception ex) {
+ reportLaunchFail(ex, "launch");
+ }
+ return null;
+ }
+
+ /* JShell currently uses only launch, preserved for futures: */
+ /* attach to running target vm */
+ private VirtualMachine attachTarget() {
+ AttachingConnector attacher = (AttachingConnector)connector;
+ try {
+ return attacher.attach(connectorArgs);
+ } catch (Exception ex) {
+ reportLaunchFail(ex, "attach");
+ }
+ return null;
+ }
+
+ /* JShell currently uses only launch, preserved for futures: */
+ /* listen for connection from target vm */
+ private VirtualMachine listenTarget() {
+ ListeningConnector listener = (ListeningConnector)connector;
+ try {
+ String retAddress = listener.startListening(connectorArgs);
+ ec.debug(DBG_GEN, "Listening at address: " + retAddress);
+ 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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEnv.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 1998, 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 java.util.Map;
+
+import com.sun.jdi.*;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+
+/**
+ * Representation of a Java Debug Interface environment
+ * Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
+ */
+class JDIEnv {
+
+ private JDIConnection connection;
+ private final JDIExecutionControl ec;
+
+ JDIEnv(JDIExecutionControl ec) {
+ this.ec = ec;
+ }
+
+ void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
+ connection = new JDIConnection(ec, connectorName, argumentName2Value, flags);
+ if (!connection.isLaunch() || openNow) {
+ connection.open();
+ }
+ }
+
+ JDIConnection connection() {
+ return connection;
+ }
+
+ VirtualMachine vm() {
+ return connection.vm();
+ }
+
+ void shutdown() {
+ if (connection != null) {
+ try {
+ connection.disposeVM();
+ } catch (VMDisconnectedException e) {
+ // Shutting down after the VM has gone away. This is
+ // not an error, and we just ignore it.
+ } catch (Throwable e) {
+ ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
+ }
+ }
+ if (ec.execEnv.state() != null) { // If state has been set-up
+ try {
+ ec.execEnv.closeDown();
+ } catch (Throwable e) {
+ ec.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,181 @@
+/*
+ * 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 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 {
+
+ Thread thread;
+ volatile boolean connected = true;
+ boolean completed = false;
+ String shutdownMessageKey;
+ final JDIEnv env;
+
+ JDIEventHandler(JDIEnv env) {
+ this.env = env;
+ this.thread = new Thread(this, "event-handler");
+ this.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 = env.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;
+ shutdownMessageKey = "The application exited";
+ } else if (event instanceof VMDisconnectEvent) {
+ connected = false;
+ if (!vmDied) {
+ shutdownMessageKey = "The application has been disconnected";
+ }
+ } else {
+ throw new InternalError("Unexpected event type: " +
+ event.getClass());
+ }
+ env.shutdown();
+ }
+
+ 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 = env.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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,595 @@
+/*
+ * 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 com.sun.jdi.*;
+import java.io.EOFException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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;
+ JDIEnv jdiEnv;
+ private ClassTracker tracker;
+ private JDIEventHandler handler;
+ private Socket socket;
+ private ObjectInputStream remoteIn;
+ private ObjectOutputStream remoteOut;
+ private String remoteVMOptions;
+
+ /**
+ * 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;
+ this.jdiEnv = new JDIEnv(this);
+ this.tracker = new ClassTracker(jdiEnv);
+ StringBuilder sb = new StringBuilder();
+ execEnv.extraRemoteVMOptions().stream()
+ .forEach(s -> {
+ sb.append(" ");
+ sb.append(s);
+ });
+ this.remoteVMOptions = sb.toString();
+ try (ServerSocket listener = new ServerSocket(0)) {
+ // timeout after 60 seconds
+ listener.setSoTimeout(60000);
+ int port = listener.getLocalPort();
+ jdiGo(port);
+ 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 (remoteOut != null) {
+ remoteOut.writeInt(CMD_EXIT);
+ remoteOut.flush();
+ }
+ JDIConnection c = jdiEnv.connection();
+ if (c != null) {
+ c.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<String> classes) {
+ try {
+ // Create corresponding ClassInfo instances to track the classes.
+ // Each ClassInfo has the current class bytes associated with it.
+ List<ClassInfo> 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 (!jdiEnv.connection().isRunning()) {
+ // The JDI connection is no longer live, shutdown.
+ jdiEnv.shutdown();
+ } 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) {
+ jdiEnv.shutdown();
+ } 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<String> classes) {
+ try {
+ // Create corresponding ClassInfo instances to track the classes.
+ // Each ClassInfo has the current class bytes associated with it.
+ List<ClassInfo> infos = withBytes(classes);
+ // Convert to the JDI ReferenceType to class bytes map form needed
+ // by JDI.
+ Map<ReferenceType, byte[]> rmp = infos.stream()
+ .collect(toMap(
+ ci -> ci.getReferenceTypeOrNull(),
+ ci -> ci.getBytes()));
+ // Attempt redefine. Throws exceptions on failure.
+ jdiEnv.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;
+ }
+ }
+
+ /**
+ * 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<ClassInfo> withBytes(Collection<String> classes) {
+ return classes.stream()
+ .map(cn -> tracker.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 = tracker.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;
+ }
+
+ /**
+ * Launch the remote agent as a JDI connection.
+ *
+ * @param port the socket port for (non-JDI) commands
+ */
+ private void jdiGo(int port) {
+ //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
+ // Locale.getDefault());
+
+ // Set-up for a fresh launch of a remote agent with any user-specified VM options.
+ String connectorName = "com.sun.jdi.CommandLineLaunch";
+ Map<String, String> argumentName2Value = new HashMap<>();
+ argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
+ argumentName2Value.put("options", remoteVMOptions);
+
+ boolean launchImmediately = true;
+ int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
+
+ // Launch.
+ jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
+
+ if (jdiEnv.connection().isOpen() && jdiEnv.vm().canBeModified()) {
+ /*
+ * Connection opened on startup. Start event handler
+ * immediately, telling it (through arg 2) to stop on the
+ * VM start event.
+ */
+ handler = new JDIEventHandler(jdiEnv);
+ }
+ }
+
+ 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 = handler.env.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();
+ }
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,43 @@
+/*
+ * 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);
+ }
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java Sat May 21 22:32:08 2016 -0700
@@ -24,6 +24,7 @@
*/
package jdk.internal.jshell.remote;
+import jdk.jshell.spi.SPIResolutionException;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -111,10 +112,11 @@
out.flush();
break;
}
+ String methodName = in.readUTF();
Method doitMethod;
try {
- this.getClass().getModule().addExports(RemoteResolutionException.class.getPackage().getName(), klass.getModule());
- doitMethod = klass.getDeclaredMethod(DOIT_METHOD_NAME, new Class<?>[0]);
+ this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
+ doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
doitMethod.setAccessible(true);
Object res;
try {
@@ -138,9 +140,9 @@
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
StackTraceElement[] elems = cause.getStackTrace();
- if (cause instanceof RemoteResolutionException) {
+ if (cause instanceof SPIResolutionException) {
out.writeInt(RESULT_CORRALLED);
- out.writeInt(((RemoteResolutionException) cause).id);
+ out.writeInt(((SPIResolutionException) cause).id());
} else {
out.writeInt(RESULT_EXCEPTION);
out.writeUTF(cause.getClass().getName());
@@ -254,27 +256,14 @@
if (value == null) {
return "null";
} else if (value instanceof String) {
- return "\"" + expunge((String)value) + "\"";
+ return "\"" + (String)value + "\"";
} else if (value instanceof Character) {
return "'" + value + "'";
} else {
- return expunge(value.toString());
+ return value.toString();
}
}
- /**
- * Expunge internal info from string
- * @param s string to process
- * @return string the display, JShell package and wrapper class names removed
- */
- static String expunge(String s) {
- StringBuilder sb = new StringBuilder();
- for (String comp : PREFIX_PATTERN.split(s)) {
- sb.append(comp);
- }
- return sb.toString();
- }
-
private static final class MultiplexingOutputStream extends OutputStream {
private static final int PACKET_SIZE = 127;
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java Sat May 21 22:32:08 2016 -0700
@@ -25,8 +25,6 @@
package jdk.internal.jshell.remote;
-import java.util.regex.Pattern;
-
/**
* Communication constants shared between the main process and the remote
* execution process
@@ -46,13 +44,4 @@
public static final int RESULT_EXCEPTION = 102;
public static final int RESULT_CORRALLED = 103;
public static final int RESULT_KILLED = 104;
-
- // String constants
- public static final String REPL_PACKAGE = "REPL";
- public static final String REPL_CLASS_PREFIX = "$JShell$";
- public static final String DOIT_METHOD_NAME = "do_it$";
- public static final Pattern PREFIX_PATTERN = Pattern.compile(
- "(" + REPL_PACKAGE + "\\.)?" +
- "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX) +
- "\\w+" + ")" + "[\\$\\.]?");
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteResolutionException.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 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;
-
-/**
- * The exception thrown on the remote side upon executing a
- * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
- * user method. This exception is not seen by the end user nor through the API.
- * @author Robert Field
- */
-@SuppressWarnings("serial") // serialVersionUID intentionally omitted
-public class RemoteResolutionException extends RuntimeException {
-
- final int id;
-
- /**
- * 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
- */
- public RemoteResolutionException(int id) {
- super("RemoteResolutionException");
- this.id = id;
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Sat May 21 22:32:08 2016 -0700
@@ -101,8 +101,13 @@
import jdk.internal.jshell.tool.Feedback.FormatUnresolved;
import jdk.internal.jshell.tool.Feedback.FormatWhen;
import static java.util.stream.Collectors.toList;
+import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
import static java.util.stream.Collectors.toMap;
-import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
+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_EVNT;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
/**
* Command line REPL tool for Java using the JShell API.
@@ -1344,7 +1349,7 @@
boolean cmdDebug(String arg) {
if (arg.isEmpty()) {
debug = !debug;
- InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
+ InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0);
fluff("Debugging %s", debug ? "on" : "off");
} else {
int flags = 0;
@@ -1360,23 +1365,23 @@
fluff("REPL tool debugging on");
break;
case 'g':
- flags |= InternalDebugControl.DBG_GEN;
+ flags |= DBG_GEN;
fluff("General debugging on");
break;
case 'f':
- flags |= InternalDebugControl.DBG_FMGR;
+ flags |= DBG_FMGR;
fluff("File manager debugging on");
break;
case 'c':
- flags |= InternalDebugControl.DBG_COMPA;
+ flags |= DBG_COMPA;
fluff("Completion analysis debugging on");
break;
case 'd':
- flags |= InternalDebugControl.DBG_DEP;
+ flags |= DBG_DEP;
fluff("Dependency debugging on");
break;
case 'e':
- flags |= InternalDebugControl.DBG_EVNT;
+ flags |= DBG_EVNT;
fluff("Event debugging on");
break;
default:
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 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;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Objects;
-import com.sun.jdi.ReferenceType;
-
-/**
- * Tracks the state of a class through compilation and loading in the remote
- * environment.
- *
- * @author Robert Field
- */
-class ClassTracker {
-
- private final JShell state;
- private final HashMap<String, ClassInfo> map;
-
- ClassTracker(JShell state) {
- this.state = state;
- this.map = new HashMap<>();
- }
-
- class ClassInfo {
-
- private final String className;
- private byte[] bytes;
- private byte[] loadedBytes;
- private ReferenceType rt;
-
- private ClassInfo(String className) {
- this.className = className;
- }
-
- String getClassName() {
- return className;
- }
-
- byte[] getBytes() {
- return bytes;
- }
-
- void setBytes(byte[] bytes) {
- this.bytes = bytes;
- }
-
- void setLoaded() {
- loadedBytes = bytes;
- }
-
- boolean isLoaded() {
- return Arrays.equals(loadedBytes, bytes);
- }
-
- ReferenceType getReferenceTypeOrNull() {
- if (rt == null) {
- rt = state.executionControl().nameToRef(className);
- }
- return rt;
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof ClassInfo &&
- ((ClassInfo) o).className.equals(className);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(this.className);
- }
- }
-
- ClassInfo classInfo(String className, byte[] bytes) {
- ClassInfo ci = map.computeIfAbsent(className, k -> new ClassInfo(k));
- ci.setBytes(bytes);
- return ci;
- }
-
- ClassInfo get(String className) {
- return map.get(className);
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Corraller.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Corraller.java Sat May 21 22:32:08 2016 -0700
@@ -49,6 +49,8 @@
import static com.sun.tools.javac.code.Flags.INTERFACE;
import static com.sun.tools.javac.code.Flags.ENUM;
import static com.sun.tools.javac.code.Flags.PUBLIC;
+import com.sun.tools.javac.util.Name;
+import jdk.jshell.spi.SPIResolutionException;
/**
* Produce a corralled version of the Wrap for a snippet.
@@ -129,16 +131,22 @@
super.visitClassDef(tree);
}
+ // Build a compiler tree for an exception throwing block, e.g.:
+ // {
+ // throw new jdk.jshell.spi.SPIResolutionException(9);
+ // }
private JCBlock resolutionExceptionBlock() {
if (resolutionExceptionBlock == null) {
- JCExpression expClass
- = make.Select(make.Select(make.Select(make.Select(
- make.Ident(names.fromString("jdk")),
- names.fromString("internal")),
- names.fromString("jshell")),
- names.fromString("remote")),
- names.fromString("RemoteResolutionException")
- );
+ JCExpression expClass = null;
+ // Split the exception class name at dots
+ for (String id : SPIResolutionException.class.getName().split("\\.")) {
+ Name nm = names.fromString(id);
+ if (expClass == null) {
+ expClass = make.Ident(nm);
+ } else {
+ expClass = make.Select(expClass, nm);
+ }
+ }
JCNewClass exp = make.NewClass(null,
null, expClass, List.of(make.Literal(keyIndex)), null);
resolutionExceptionBlock = make.Block(0L, List.<JCStatement>of(
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Sat May 21 22:32:08 2016 -0700
@@ -49,7 +49,6 @@
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;
-import jdk.jshell.ClassTracker.ClassInfo;
import jdk.jshell.Key.ErroneousKey;
import jdk.jshell.Key.MethodKey;
import jdk.jshell.Key.TypeDeclKey;
@@ -64,9 +63,9 @@
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-import static jdk.jshell.Util.*;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
+import static jdk.jshell.Util.DOIT_METHOD_NAME;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.expunge;
import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
@@ -456,7 +455,7 @@
if (si.status().isDefined) {
if (si.isExecutable()) {
try {
- value = state.executionControl().commandInvoke(si.classFullName());
+ value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
value = si.subKind().hasValue()
? expunge(value)
: "";
@@ -578,7 +577,7 @@
// load all new classes
load(legit.stream()
- .flatMap(u -> u.classesToLoad(ct.classInfoList(u.snippet().outerWrap())))
+ .flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
.collect(toSet()));
// attempt to redefine the remaining classes
List<Unit> toReplace = legit.stream()
@@ -613,9 +612,13 @@
}
}
- private void load(Set<ClassInfo> cil) {
- if (!cil.isEmpty()) {
- state.executionControl().commandLoad(cil);
+ /**
+ * If there are classes to load, loads by calling the execution engine.
+ * @param classnames names of the classes to load.
+ */
+ private void load(Collection<String> classnames) {
+ if (!classnames.isEmpty()) {
+ state.executionControl().load(classnames);
}
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/ExecutionControl.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,434 +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.jshell;
-
-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 com.sun.jdi.*;
-import java.io.EOFException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import jdk.jshell.ClassTracker.ClassInfo;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Controls the remote execution environment.
- *
- * @author Robert Field
- */
-class ExecutionControl {
-
- private final JDIEnv env;
- private final SnippetMaps maps;
- private JDIEventHandler handler;
- private Socket socket;
- private ObjectInputStream in;
- private ObjectOutputStream out;
- private final JShell proc;
- private final String remoteVMOptions;
-
- ExecutionControl(JDIEnv env, SnippetMaps maps, JShell proc, List<String> extraRemoteVMOptions) {
- this.env = env;
- this.maps = maps;
- this.proc = proc;
- StringBuilder sb = new StringBuilder();
- extraRemoteVMOptions.stream()
- .forEach(s -> {
- sb.append(" ");
- sb.append(s);
- });
- this.remoteVMOptions = sb.toString();
- }
-
- void launch() throws IOException {
- try (ServerSocket listener = new ServerSocket(0)) {
- // timeout after 60 seconds
- listener.setSoTimeout(60000);
- int port = listener.getLocalPort();
- jdiGo(port);
- socket = listener.accept();
- // out before in -- match remote creation so we don't hang
- out = new ObjectOutputStream(socket.getOutputStream());
- PipeInputStream commandIn = new PipeInputStream();
- new DemultiplexInput(socket.getInputStream(), commandIn, proc.out, proc.err).start();
- in = new ObjectInputStream(commandIn);
- }
- }
-
- void commandExit() {
- try {
- if (out != null) {
- out.writeInt(CMD_EXIT);
- out.flush();
- }
- JDIConnection c = env.connection();
- if (c != null) {
- c.disposeVM();
- }
- } catch (IOException ex) {
- proc.debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
- }
- }
-
-
- boolean commandLoad(Collection<ClassInfo> cil) {
- try {
- out.writeInt(CMD_LOAD);
- out.writeInt(cil.size());
- for (ClassInfo ci : cil) {
- out.writeUTF(ci.getClassName());
- out.writeObject(ci.getBytes());
- }
- out.flush();
- return readAndReportResult();
- } catch (IOException ex) {
- proc.debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
- return false;
- }
- }
-
- String commandInvoke(String classname) throws JShellException {
- try {
- synchronized (STOP_LOCK) {
- userCodeRunning = true;
- }
- out.writeInt(CMD_INVOKE);
- out.writeUTF(classname);
- out.flush();
- if (readAndReportExecutionResult()) {
- String result = in.readUTF();
- return result;
- }
- } catch (IOException | RuntimeException ex) {
- if (!env.connection().isRunning()) {
- env.shutdown();
- } else {
- proc.debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
- return "Execution failure: " + ex.getMessage();
- }
- } finally {
- synchronized (STOP_LOCK) {
- userCodeRunning = false;
- }
- }
- return "";
- }
-
- String commandVarValue(String classname, String varname) {
- try {
- out.writeInt(CMD_VARVALUE);
- out.writeUTF(classname);
- out.writeUTF(varname);
- out.flush();
- if (readAndReportResult()) {
- String result = in.readUTF();
- return result;
- }
- } catch (EOFException ex) {
- env.shutdown();
- } catch (IOException ex) {
- proc.debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
- return "Execution failure: " + ex.getMessage();
- }
- return "";
- }
-
- boolean commandAddToClasspath(String cp) {
- try {
- out.writeInt(CMD_CLASSPATH);
- out.writeUTF(cp);
- out.flush();
- return readAndReportResult();
- } catch (IOException ex) {
- throw new InternalError("Classpath addition failed: " + cp, ex);
- }
- }
-
- boolean commandRedefine(Map<ReferenceType, byte[]> mp) {
- try {
- env.vm().redefineClasses(mp);
- return true;
- } catch (UnsupportedOperationException ex) {
- return false;
- } catch (Exception ex) {
- proc.debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
- return false;
- }
- }
-
- ReferenceType nameToRef(String name) {
- List<ReferenceType> rtl = env.vm().classesByName(name);
- if (rtl.size() != 1) {
- return null;
- }
- return rtl.get(0);
- }
-
- private boolean readAndReportResult() throws IOException {
- int ok = in.readInt();
- switch (ok) {
- case RESULT_SUCCESS:
- return true;
- case RESULT_FAIL: {
- String ex = in.readUTF();
- proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
- return false;
- }
- default: {
- proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
- return false;
- }
- }
- }
-
- private boolean readAndReportExecutionResult() throws IOException, JShellException {
- int ok = in.readInt();
- switch (ok) {
- case RESULT_SUCCESS:
- return true;
- case RESULT_FAIL: {
- String ex = in.readUTF();
- proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
- return false;
- }
- case RESULT_EXCEPTION: {
- String exceptionClassName = in.readUTF();
- String message = in.readUTF();
- StackTraceElement[] elems = readStackTrace();
- EvalException ee = new EvalException(message, exceptionClassName, elems);
- throw ee;
- }
- case RESULT_CORRALLED: {
- int id = in.readInt();
- StackTraceElement[] elems = readStackTrace();
- Snippet si = maps.getSnippetDeadOrAlive(id);
- throw new UnresolvedReferenceException((DeclarationSnippet) si, elems);
- }
- case RESULT_KILLED: {
- proc.out.println("Killed.");
- return false;
- }
- default: {
- proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
- return false;
- }
- }
- }
-
- private StackTraceElement[] readStackTrace() throws IOException {
- int elemCount = in.readInt();
- StackTraceElement[] elems = new StackTraceElement[elemCount];
- for (int i = 0; i < elemCount; ++i) {
- String className = in.readUTF();
- String methodName = in.readUTF();
- String fileName = in.readUTF();
- int line = in.readInt();
- elems[i] = new StackTraceElement(className, methodName, fileName, line);
- }
- return elems;
- }
-
- private void jdiGo(int port) {
- //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
- // Locale.getDefault());
-
- String connectorName = "com.sun.jdi.CommandLineLaunch";
- Map<String, String> argumentName2Value = new HashMap<>();
- argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
- argumentName2Value.put("options", remoteVMOptions);
-
- boolean launchImmediately = true;
- int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
-
- env.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
-
- if (env.connection().isOpen() && env.vm().canBeModified()) {
- /*
- * Connection opened on startup. Start event handler
- * immediately, telling it (through arg 2) to stop on the
- * VM start event.
- */
- handler = new JDIEventHandler(env);
- }
- }
-
- private final Object STOP_LOCK = new Object();
- private boolean userCodeRunning = false;
-
- void commandStop() {
- synchronized (STOP_LOCK) {
- if (!userCodeRunning)
- return ;
-
- VirtualMachine vm = handler.env.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();
- proc.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) {
- proc.debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
- } finally {
- vm.resume();
- }
- }
- }
-
- 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) {
- proc.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();
- }
-
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIConnection.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,347 +0,0 @@
-/*
- * Copyright (c) 1998, 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.
- */
-
-/*
- * 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.jshell;
-
-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 VirtualMachine vm;
- private Process process = null;
- private int outputCompleteCount = 0;
-
- private final JShell proc;
- private final JDIEnv env;
- private final Connector connector;
- private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
- private final int traceFlags;
-
- synchronized void notifyOutputComplete() {
- outputCompleteCount++;
- notifyAll();
- }
-
- 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 <String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) {
- Map<String, Connector.Argument> arguments = connector.defaultArguments();
-
- for (Entry<String, String> 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;
- }
-
- JDIConnection(JDIEnv env, String connectorName, Map<String, String> argumentName2Value, int traceFlags, JShell proc) {
- this.env = env;
- this.proc = proc;
- this.connector = findConnector(connectorName);
-
- if (connector == null) {
- throw new IllegalArgumentException("No connector named: " + connectorName);
- }
-
- connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
- this.traceFlags = traceFlags;
- }
-
- synchronized VirtualMachine open() {
- if (connector instanceof LaunchingConnector) {
- vm = launchTarget();
- } else if (connector instanceof AttachingConnector) {
- vm = attachTarget();
- } else if (connector instanceof ListeningConnector) {
- vm = listenTarget();
- } else {
- throw new InternalError("Invalid connect type");
- }
- vm.setDebugTraceMode(traceFlags);
- // Uncomment here and below to enable event requests
- // installEventRequests(vm);
-
- return vm;
- }
-
- synchronized boolean setConnectorArg(String name, String value) {
- /*
- * Too late if the connection already made
- */
- if (vm != null) {
- return false;
- }
-
- Connector.Argument argument = connectorArgs.get(name);
- if (argument == null) {
- return false;
- }
- argument.setValue(value);
- return true;
- }
-
- String connectorArg(String name) {
- Connector.Argument argument = connectorArgs.get(name);
- if (argument == null) {
- return "";
- }
- return argument.value();
- }
-
- public synchronized VirtualMachine vm() {
- if (vm == null) {
- throw new JDINotConnectedException();
- } else {
- return vm;
- }
- }
-
- synchronized boolean isOpen() {
- return (vm != null);
- }
-
- boolean isLaunch() {
- return (connector instanceof LaunchingConnector);
- }
-
- synchronized boolean isRunning() {
- return process != null && process.isAlive();
- }
-
- public 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
- } finally {
- if (process != null) {
- process.destroy();
- process = null;
- }
- waitOutputComplete();
- }
- }
-
-/*** Preserved for possible future support of event requests
-
- private void installEventRequests(VirtualMachine vm) {
- if (vm.canBeModified()){
- setEventRequests(vm);
- resolveEventRequests();
- }
- }
-
- private void setEventRequests(VirtualMachine vm) {
- EventRequestManager erm = vm.eventRequestManager();
-
- // Normally, we want all uncaught exceptions. We request them
- // via the same mechanism as Commands.commandCatchException()
- // so the user can ignore them later if they are not
- // interested.
- // FIXME: this works but generates spurious messages on stdout
- // during startup:
- // Set uncaught java.lang.Throwable
- // Set deferred uncaught java.lang.Throwable
- Commands evaluator = new Commands();
- evaluator.commandCatchException
- (new StringTokenizer("uncaught java.lang.Throwable"));
-
- ThreadStartRequest tsr = erm.createThreadStartRequest();
- tsr.enable();
- ThreadDeathRequest tdr = erm.createThreadDeathRequest();
- tdr.enable();
- }
-
- private void resolveEventRequests() {
- Env.specList.resolveAll();
- }
-***/
-
- 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) {
- pStream.print((char) i);
- }
- } catch (IOException ex) {
- String s = ex.getMessage();
- if (!s.startsWith("Bad file number")) {
- throw ex;
- }
- // else 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) {
- proc.debug(ex, "Failed reading output");
- env.shutdown();
- } 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) {
- proc.debug(ex, "Failed reading output");
- env.shutdown();
- }
- }
- };
- thr.setPriority(Thread.MAX_PRIORITY-1);
- thr.start();
- }
-
- /* launch child target vm */
- private VirtualMachine launchTarget() {
- LaunchingConnector launcher = (LaunchingConnector)connector;
- try {
- VirtualMachine new_vm = launcher.launch(connectorArgs);
- process = new_vm.process();
- displayRemoteOutput(process.getErrorStream(), proc.err);
- displayRemoteOutput(process.getInputStream(), proc.out);
- readRemoteInput(process.getOutputStream(), proc.in);
- return new_vm;
- } catch (Exception ex) {
- reportLaunchFail(ex, "launch");
- }
- return null;
- }
-
- /* JShell currently uses only launch, preserved for futures: */
- /* attach to running target vm */
- private VirtualMachine attachTarget() {
- AttachingConnector attacher = (AttachingConnector)connector;
- try {
- return attacher.attach(connectorArgs);
- } catch (Exception ex) {
- reportLaunchFail(ex, "attach");
- }
- return null;
- }
-
- /* JShell currently uses only launch, preserved for futures: */
- /* listen for connection from target vm */
- private VirtualMachine listenTarget() {
- ListeningConnector listener = (ListeningConnector)connector;
- try {
- String retAddress = listener.startListening(connectorArgs);
- proc.debug(DBG_GEN, "Listening at address: " + retAddress);
- 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);
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEnv.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 1998, 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;
-
-import java.util.Map;
-
-import com.sun.jdi.*;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Representation of a Java Debug Interface environment
- * Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
- */
-class JDIEnv {
-
- private JDIConnection connection;
- private final JShell state;
-
- JDIEnv(JShell state) {
- this.state = state;
- }
-
- void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
- connection = new JDIConnection(this, connectorName, argumentName2Value, flags, state);
- if (!connection.isLaunch() || openNow) {
- connection.open();
- }
- }
-
- JDIConnection connection() {
- return connection;
- }
-
- VirtualMachine vm() {
- return connection.vm();
- }
-
- void shutdown() {
- if (connection != null) {
- try {
- connection.disposeVM();
- } catch (VMDisconnectedException e) {
- // Shutting down after the VM has gone away. This is
- // not an error, and we just ignore it.
- } catch (Throwable e) {
- state.debug(DBG_GEN, null, "disposeVM threw: " + e);
- }
- }
- if (state != null) { // If state has been set-up
- try {
- state.closeDown();
- } catch (Throwable e) {
- state.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
- }
- }
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEventHandler.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +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.jshell;
-
-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 {
-
- Thread thread;
- volatile boolean connected = true;
- boolean completed = false;
- String shutdownMessageKey;
- final JDIEnv env;
-
- JDIEventHandler(JDIEnv env) {
- this.env = env;
- this.thread = new Thread(this, "event-handler");
- this.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 = env.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;
- shutdownMessageKey = "The application exited";
- } else if (event instanceof VMDisconnectEvent) {
- connected = false;
- if (!vmDied) {
- shutdownMessageKey = "The application has been disconnected";
- }
- } else {
- throw new InternalError("Unexpected event type: " +
- event.getClass());
- }
- env.shutdown();
- }
-
- 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 = env.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;
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDINotConnectedException.java Fri May 20 17:00:03 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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.jshell;
-
-/**
- * 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);
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Sat May 21 22:32:08 2016 -0700
@@ -25,6 +25,7 @@
package jdk.jshell;
+import jdk.jshell.spi.ExecutionControl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.PrintStream;
@@ -47,6 +48,8 @@
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
@@ -71,7 +74,6 @@
* <p>
* This class is not thread safe, except as noted, all access should be through
* a single thread.
- * @see jdk.jshell
* @author Robert Field
*/
public class JShell implements AutoCloseable {
@@ -86,16 +88,17 @@
final Supplier<String> tempVariableNameGenerator;
final BiFunction<Snippet, Integer, String> idGenerator;
final List<String> extraRemoteVMOptions;
+ final ExecutionControl executionControl;
private int nextKeyIndex = 1;
final Eval eval;
- final ClassTracker classTracker;
+ private final Map<String, byte[]> classnameToBytes = new HashMap<>();
private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
private boolean closed = false;
- private ExecutionControl executionControl = null;
+ private boolean executionControlLaunched = false;
private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n";
@@ -108,13 +111,15 @@
this.tempVariableNameGenerator = b.tempVariableNameGenerator;
this.idGenerator = b.idGenerator;
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
+ this.executionControl = b.executionControl==null
+ ? new JDIExecutionControl()
+ : b.executionControl;
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(this);
}
/**
@@ -143,22 +148,23 @@
Supplier<String> tempVariableNameGenerator = null;
BiFunction<Snippet, Integer, String> idGenerator = null;
List<String> extraRemoteVMOptions = new ArrayList<>();
+ ExecutionControl executionControl;
Builder() { }
/**
- * Input for the running evaluation (it's <code>System.in</code>). Note:
- * applications that use <code>System.in</code> for snippet or other
- * user input cannot use <code>System.in</code> as the input stream for
+ * Sets the input for the running evaluation (it's {@code System.in}). Note:
+ * applications that use {@code System.in} for snippet or other
+ * user input cannot use {@code System.in} as the input stream for
* the remote process.
* <p>
* The default, if this is not set, is to provide an empty input stream
- * -- <code>new ByteArrayInputStream(new byte[0])</code>.
+ * -- {@code new ByteArrayInputStream(new byte[0])}.
*
- * @param in the <code>InputStream</code> to be channelled to
- * <code>System.in</code> in the remote execution process.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * @param in the {@code InputStream} to be channelled to
+ * {@code System.in} in the remote execution process
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder in(InputStream in) {
this.in = in;
@@ -166,16 +172,16 @@
}
/**
- * Output for the running evaluation (it's <code>System.out</code>).
+ * Sets the output for the running evaluation (it's {@code System.out}).
* The controlling process and
- * the remote process can share <code>System.out</code>.
+ * the remote process can share {@code System.out}.
* <p>
- * The default, if this is not set, is <code>System.out</code>.
+ * The default, if this is not set, is {@code System.out}.
*
- * @param out the <code>PrintStream</code> to be channelled to
- * <code>System.out</code> in the remote execution process.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * @param out the {@code PrintStream} to be channelled to
+ * {@code System.out} in the remote execution process
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder out(PrintStream out) {
this.out = out;
@@ -183,16 +189,16 @@
}
/**
- * Error output for the running evaluation (it's
- * <code>System.err</code>). The controlling process and the remote
- * process can share <code>System.err</code>.
+ * Sets the error output for the running evaluation (it's
+ * {@code System.err}). The controlling process and the remote
+ * process can share {@code System.err}.
* <p>
- * The default, if this is not set, is <code>System.err</code>.
+ * The default, if this is not set, is {@code System.err}.
*
- * @param err the <code>PrintStream</code> to be channelled to
- * <code>System.err</code> in the remote execution process.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * @param err the {@code PrintStream} to be channelled to
+ * {@code System.err} in the remote execution process
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder err(PrintStream err) {
this.err = err;
@@ -200,7 +206,7 @@
}
/**
- * Set a generator of temp variable names for
+ * Sets a generator of temp variable names for
* {@link jdk.jshell.VarSnippet} of
* {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
* <p>
@@ -221,9 +227,9 @@
* prefixing dollar sign ("$").
*
* @param generator the <code>Supplier</code> to generate the temporary
- * variable name string or <code>null</code>.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * variable name string or <code>null</code>
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder tempVariableNameGenerator(Supplier<String> generator) {
this.tempVariableNameGenerator = generator;
@@ -231,7 +237,7 @@
}
/**
- * Set the generator of identifying names for Snippets.
+ * Sets the generator of identifying names for Snippets.
* <p>
* Do not use this method unless you have explicit need for it.
* <p>
@@ -258,9 +264,9 @@
* is null) is to generate the id as the integer converted to a string.
*
* @param generator the <code>BiFunction</code> to generate the id
- * string or <code>null</code>.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * string or <code>null</code>
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
this.idGenerator = generator;
@@ -268,11 +274,11 @@
}
/**
- * Set additional VM options for launching the VM.
+ * Sets additional VM options for launching the VM.
*
- * @param options The options for the remote VM.
- * @return the <code>Builder</code> instance (for use in chained
- * initialization).
+ * @param options The options for the remote VM
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
*/
public Builder remoteVMOptions(String... options) {
this.extraRemoteVMOptions.addAll(Arrays.asList(options));
@@ -280,11 +286,24 @@
}
/**
- * Build a JShell state engine. This is the entry-point to all JShell
+ * Sets the custom engine for execution. Snippet execution will be
+ * provided by the specified {@link ExecutionControl} instance.
+ *
+ * @param execEngine the execution engine
+ * @return the {@code Builder} instance (for use in chained
+ * initialization)
+ */
+ public Builder executionEngine(ExecutionControl execEngine) {
+ this.executionControl = execEngine;
+ return this;
+ }
+
+ /**
+ * Builds a JShell state engine. This is the entry-point to all JShell
* functionality. This creates a remote process for execution. It is
* thus important to close the returned instance.
*
- * @return the state engine.
+ * @return the state engine
*/
public JShell build() {
return new JShell(this);
@@ -406,7 +425,7 @@
*/
public void addToClasspath(String path) {
taskFactory.addToClasspath(path); // Compiler
- executionControl().commandAddToClasspath(path); // Runtime
+ executionControl().addToClasspath(path); // Runtime
if (sourceCodeAnalysis != null) {
sourceCodeAnalysis.classpathChanged();
}
@@ -427,7 +446,7 @@
*/
public void stop() {
if (executionControl != null)
- executionControl.commandStop();
+ executionControl.stop();
}
/**
@@ -438,7 +457,7 @@
public void close() {
if (!closed) {
closeDown();
- executionControl().commandExit();
+ executionControl().close();
}
}
@@ -580,7 +599,7 @@
throw new IllegalArgumentException(
messageFormat("jshell.exc.var.not.valid", snippet, snippet.status()));
}
- String value = executionControl().commandVarValue(snippet.classFullName(), snippet.name());
+ String value = executionControl().varValue(snippet.classFullName(), snippet.name());
return expunge(value);
}
@@ -633,35 +652,86 @@
}
}
+ /**
+ * Provide the environment for a execution engine.
+ */
+ class ExecutionEnvImpl implements ExecutionEnv {
+
+ @Override
+ public InputStream userIn() {
+ return in;
+ }
+
+ @Override
+ public PrintStream userOut() {
+ return out;
+ }
+
+ @Override
+ public PrintStream userErr() {
+ return err;
+ }
+
+ @Override
+ public JShell state() {
+ return JShell.this;
+ }
+
+ @Override
+ public List<String> extraRemoteVMOptions() {
+ return extraRemoteVMOptions;
+ }
+
+ @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 (executionControl == null) {
- this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this, extraRemoteVMOptions);
+ if (!executionControlLaunched) {
try {
- executionControl.launch();
+ executionControlLaunched = true;
+ executionControl.start(new ExecutionEnvImpl());
} catch (Throwable ex) {
- throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
+ throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
}
}
return executionControl;
}
+ void setClassnameToBytes(String classname, byte[] bytes) {
+ classnameToBytes.put(classname, bytes);
+ }
+
void debug(int flags, String format, Object... args) {
- if (InternalDebugControl.debugEnabled(this, flags)) {
- err.printf(format, args);
- }
+ InternalDebugControl.debug(this, err, flags, format, args);
}
void debug(Exception ex, String where) {
- if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
- err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
- ex.printStackTrace(err);
- }
+ InternalDebugControl.debug(this, err, ex, where);
}
/**
* Generate the next key index, indicating a unique snippet signature.
+ *
* @return the next key index
*/
int nextKeyIndex() {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java Sat May 21 22:32:08 2016 -0700
@@ -28,9 +28,11 @@
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
-import jdk.internal.jshell.remote.RemoteCodes;
-import static jdk.jshell.Util.*;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
+import static jdk.jshell.Util.PARSED_LOCALE;
+import static jdk.jshell.Util.REPL_CLASS_PREFIX;
+import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
+import static jdk.jshell.Util.REPL_PACKAGE;
+import static jdk.jshell.Util.expunge;
/**
*
@@ -163,7 +165,7 @@
}
for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
if (line.trim().startsWith("location:")) {
- if (!line.contains(RemoteCodes.REPL_CLASS_PREFIX)) {
+ if (!line.contains(REPL_CLASS_PREFIX)) {
// Resolution error must occur within a REPL class or it is not resolvable
return false;
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java Sat May 21 22:32:08 2016 -0700
@@ -35,8 +35,8 @@
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import jdk.jshell.Wrap.CompoundWrap;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.REPL_CLASS_PREFIX;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static jdk.jshell.Util.asLetters;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java Sat May 21 22:32:08 2016 -0700
@@ -38,9 +38,9 @@
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.REPL_PACKAGE;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
/**
* Maintain relationships between the significant entities: Snippets,
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java Sat May 21 22:32:08 2016 -0700
@@ -104,7 +104,6 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.collectingAndThen;
-import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@@ -130,6 +129,7 @@
import javax.tools.ToolProvider;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
+import static java.util.stream.Collectors.joining;
/**
* The concrete implementation of SourceCodeAnalysis.
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Sat May 21 22:32:08 2016 -0700
@@ -60,7 +60,6 @@
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
-import jdk.jshell.ClassTracker.ClassInfo;
import java.lang.Runtime.Version;
/**
@@ -278,13 +277,19 @@
return result;
}
-
- List<ClassInfo> classInfoList(OuterWrap w) {
+ // Returns the list of classes generated during this compile.
+ // Stores the mapping between class name and current compiled bytes.
+ List<String> classList(OuterWrap w) {
List<OutputMemoryJavaFileObject> l = classObjs.get(w);
- if (l == null) return Collections.emptyList();
- return l.stream()
- .map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
- .collect(Collectors.toList());
+ if (l == null) {
+ return Collections.emptyList();
+ }
+ List<String> list = new ArrayList<>();
+ for (OutputMemoryJavaFileObject fo : l) {
+ state.setClassnameToBytes(fo.getName(), fo.getBytes());
+ list.add(fo.getName());
+ }
+ return list;
}
private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Sat May 21 22:32:08 2016 -0700
@@ -30,18 +30,14 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
-import com.sun.jdi.ReferenceType;
import jdk.jshell.Snippet.Kind;
import jdk.jshell.Snippet.Status;
import jdk.jshell.Snippet.SubKind;
import jdk.jshell.TaskFactory.AnalyzeTask;
-import jdk.jshell.ClassTracker.ClassInfo;
import jdk.jshell.TaskFactory.CompileTask;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
@@ -79,7 +75,7 @@
private SnippetEvent replaceOldEvent;
private List<SnippetEvent> secondaryEvents;
private boolean isAttemptingCorral;
- private List<ClassInfo> toRedefine;
+ private List<String> toRedefine;
private boolean dependenciesNeeded;
Unit(JShell state, Snippet si, Snippet causalSnippet,
@@ -260,10 +256,6 @@
si, status);
}
- /**
- * Must be called for each unit
- * @return
- */
boolean isDefined() {
return status.isDefined;
}
@@ -273,21 +265,28 @@
* Requires loading of returned list.
* @return the list of classes to load
*/
- Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
+ Stream<String> classesToLoad(List<String> classnames) {
toRedefine = new ArrayList<>();
- List<ClassInfo> toLoad = new ArrayList<>();
+ List<String> toLoad = new ArrayList<>();
if (status.isDefined && !isImport()) {
- cil.stream().forEach(ci -> {
- if (!ci.isLoaded()) {
- if (ci.getReferenceTypeOrNull() == null) {
- toLoad.add(ci);
- ci.setLoaded();
+ // 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;
- } else {
- toRedefine.add(ci);
- }
+ 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;
}
- });
+ }
}
return toLoad.stream();
}
@@ -298,19 +297,9 @@
* @return true if all redefines succeeded (can be vacuously true)
*/
boolean doRedefines() {
- if (toRedefine.isEmpty()) {
- return true;
- }
- Map<ReferenceType, byte[]> mp = toRedefine.stream()
- .collect(toMap(ci -> ci.getReferenceTypeOrNull(), ci -> ci.getBytes()));
- if (state.executionControl().commandRedefine(mp)) {
- // success, mark as loaded
- toRedefine.stream().forEach(ci -> ci.setLoaded());
- return true;
- } else {
- // failed to redefine
- return false;
- }
+ return toRedefine.isEmpty()
+ ? true
+ : state.executionControl().redefine(toRedefine);
}
void markForReplacement() {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java Sat May 21 22:32:08 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -29,20 +29,40 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.lang.model.element.Name;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
/**
- * Assorted shared utilities.
- * @author Robert Field
+ * Assorted shared utilities and constants.
*/
class Util {
- static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"DOESNOTMATTER";
+ /**
+ * The package name of all wrapper classes.
+ */
+ static final String REPL_PACKAGE = "REPL";
+
+ /**
+ * The prefix for all wrapper class names.
+ */
+ static final String REPL_CLASS_PREFIX = "$JShell$";
+
+ /**
+ * The name of the invoke method.
+ */
+ static final String DOIT_METHOD_NAME = "do_it$";
+
+ /**
+ * A pattern matching the full or simple class name of a wrapper class.
+ */
+ static final Pattern PREFIX_PATTERN = Pattern.compile(
+ "(" + REPL_PACKAGE + "\\.)?"
+ + "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX)
+ + "\\w+" + ")" + "[\\$\\.]?");
+
+ static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX + "DOESNOTMATTER";
static final Locale PARSED_LOCALE = Locale.ROOT;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java Sat May 21 22:32:08 2016 -0700
@@ -27,7 +27,7 @@
import java.util.Arrays;
import static java.util.stream.Collectors.joining;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
+import static jdk.jshell.Util.DOIT_METHOD_NAME;
/**
* Wrapping of source into Java methods, fields, etc. All but outer layer
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,154 @@
+/*
+ * 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.spi;
+
+import java.util.Collection;
+import jdk.jshell.JShellException;
+
+/**
+ * This interface specifies the functionality that must provided to implement
+ * a pluggable JShell execution engine.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * Methods defined in this interface should only be called by the core JShell
+ * implementation.
+ * <p>
+ * To install an instance of ExecutionControl, it is passed to
+ * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }.
+ */
+public interface ExecutionControl {
+
+ /**
+ * 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.
+ *
+ * @param env the execution environment information provided by JShell
+ * @throws Exception if the instance is unable to initialize
+ */
+ void start(ExecutionEnv env) throws Exception;
+
+ /**
+ * Shuts down this execution engine. Implementation should free all
+ * resources held by this execution engine.
+ * <p>
+ * No calls to methods on this interface should be made after close.
+ */
+ void close();
+
+ /**
+ * Adds the path to the execution class path.
+ *
+ * @param path the path to add
+ * @return true if successful
+ */
+ boolean addToClasspath(String path);
+
+ /**
+ * 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
+ * @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;
+
+ /**
+ * Attempts to load new classes. Class bytes are retrieved from
+ * {@link ExecutionEnv#getClassBytes(java.lang.String) }
+ *
+ * @param classes list of class names to load
+ * @return true if load succeeded
+ */
+ boolean load(Collection<String> classes);
+
+ /**
+ * Attempts to redefine previously loaded classes. Class bytes are retrieved
+ * from {@link ExecutionEnv#getClassBytes(java.lang.String) }
+ *
+ * @param classes list of class names to redefine
+ * @return true if redefine succeeded
+ */
+ boolean redefine(Collection<String> classes);
+
+ /**
+ * Queries if the class is loaded and the class bytes are current.
+ *
+ * @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}
+ */
+ ClassStatus getClassStatus(String classname);
+
+ /**
+ * Interrupt a running invoke.
+ */
+ void stop();
+
+ /**
+ * 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
+ */
+ String varValue(String classname, String varname);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,127 @@
+/*
+ * 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.spi;
+
+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) }.
+ * <p>
+ * This interface is designed to provide the access to core JShell functionality
+ * needed to implement ExecutionControl.
+ *
+ * @see ExecutionControl
+ */
+public interface ExecutionEnv {
+
+ /**
+ * Returns the user's input stream.
+ *
+ * @return the user's input stream
+ */
+ InputStream userIn();
+
+ /**
+ * Returns the user's output stream.
+ *
+ * @return the user's output stream
+ */
+ PrintStream userOut();
+
+ /**
+ * Returns the user's error stream.
+ *
+ * @return the user's error stream
+ */
+ PrintStream userErr();
+
+ /**
+ * @return the JShell instance
+ */
+ JShell state();
+
+ /**
+ * Returns the additional VM options to be used when launching the remote
+ * JVM. This is advice to the execution engine.
+ * <p>
+ * Note: an execution engine need not launch a remote JVM.
+ *
+ * @return the additional options with which to launch the remote JVM
+ */
+ List<String> 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.
+ * <p>
+ * 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();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,69 @@
+/*
+ * 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.spi;
+
+/**
+ * The construction and throw of this exception is embedded in code generated by
+ * the JShell core implementation in such a way that, upon executing a
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+ * user method, this exception is thrown.
+ * <p>
+ * 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 {
+
+ private final int id;
+
+ /**
+ * Constructs an SPI layer 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
+ */
+ public SPIResolutionException(int id) {
+ super("resolution exception");
+ this.id = id;
+ }
+
+ /**
+ * Retrieves the internal identifer of the unresolved identifer.
+ *
+ * @return the internal identifer
+ * @see ExecutionEnv#createUnresolvedReferenceException(int,
+ * java.lang.StackTraceElement[])
+ */
+ public int id() {
+ return id;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides support for alternate implementations of the JShell execution
+ * engine. The JShell core tracks and compiles Snippets then sends them
+ * (represented in a wrapper class) to the execution engine for loading,
+ * and in the case of executable Snippets, execution. The JShell
+ * implementation includes a default execution engine (currently a remote
+ * process which is JDI controlled). By implementing the
+ * {@link ExecutionControl} interface and installing it with
+ * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
+ * other execution engines can be used.
+ * <p>
+ * This is not a part of the JShell API.
+ */
+package jdk.jshell.spi;
--- a/langtools/src/jdk.jshell/share/classes/module-info.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/module-info.java Sat May 21 22:32:08 2016 -0700
@@ -32,4 +32,5 @@
requires jdk.jdi;
exports jdk.jshell;
+ exports jdk.jshell.spi;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExecutionControlTest.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,150 @@
+/*
+ * 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 8156101
+ * @summary Tests for ExecutionControl SPI
+ * @build KullaTesting LocalExecutionControl
+ * @run testng ExecutionControlTest
+ */
+
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.SubKind.*;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.BeforeMethod;
+
+@Test
+public class ExecutionControlTest extends KullaTesting {
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ setUp(new LocalExecutionControl());
+ }
+
+ public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
+ System.setProperty("LOCAL_CHECK", "TBD");
+ assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
+ assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
+ assertEquals(System.getProperty("LOCAL_CHECK"), "local");
+ }
+
+ public void classesDeclaration() {
+ assertEval("interface A { }");
+ assertEval("class B implements A { }");
+ assertEval("interface C extends A { }");
+ assertEval("enum D implements C { }");
+ assertEval("@interface E { }");
+ assertClasses(
+ clazz(KullaTesting.ClassType.INTERFACE, "A"),
+ clazz(KullaTesting.ClassType.CLASS, "B"),
+ clazz(KullaTesting.ClassType.INTERFACE, "C"),
+ clazz(KullaTesting.ClassType.ENUM, "D"),
+ clazz(KullaTesting.ClassType.ANNOTATION, "E"));
+ assertActiveKeys();
+ }
+
+ @Test
+ public void interfaceTest() {
+ String interfaceSource
+ = "interface A {\n"
+ + " default int defaultMethod() { return 1; }\n"
+ + " static int staticMethod() { return 2; }\n"
+ + " int method();\n"
+ + " class Inner1 {}\n"
+ + " static class Inner2 {}\n"
+ + "}";
+ assertEval(interfaceSource);
+ assertEval("A.staticMethod();", "2");
+ String classSource
+ = "class B implements A {\n"
+ + " public int method() { return 3; }\n"
+ + "}";
+ assertEval(classSource);
+ assertEval("B b = new B();");
+ assertEval("b.defaultMethod();", "1");
+ assertDeclareFail("B.staticMethod();",
+ new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("b.method();", "3");
+ assertEval("new A.Inner1();");
+ assertEval("new A.Inner2();");
+ assertEval("new B.Inner1();");
+ assertEval("new B.Inner2();");
+ }
+
+ public void variables() {
+ VarSnippet snx = varKey(assertEval("int x = 10;"));
+ VarSnippet sny = varKey(assertEval("String y = \"hi\";"));
+ VarSnippet snz = varKey(assertEval("long z;"));
+ assertVariables(variable("int", "x"), variable("String", "y"), variable("long", "z"));
+ assertVarValue(snx, "10");
+ assertVarValue(sny, "\"hi\"");
+ assertVarValue(snz, "0");
+ assertActiveKeys();
+ }
+
+ public void methodOverload() {
+ assertEval("int m() { return 1; }");
+ assertEval("int m(int x) { return 2; }");
+ assertEval("int m(String s) { return 3; }");
+ assertEval("int m(int x, int y) { return 4; }");
+ assertEval("int m(int x, String z) { return 5; }");
+ assertEval("int m(int x, String z, long g) { return 6; }");
+ assertMethods(
+ method("()int", "m"),
+ method("(int)int", "m"),
+ method("(String)int", "m"),
+ method("(int,int)int", "m"),
+ method("(int,String)int", "m"),
+ method("(int,String,long)int", "m")
+ );
+ assertEval("m();", "1");
+ assertEval("m(3);", "2");
+ assertEval("m(\"hi\");", "3");
+ assertEval("m(7, 8);", "4");
+ assertEval("m(7, \"eight\");", "5");
+ assertEval("m(7, \"eight\", 9L);", "6");
+ assertActiveKeys();
+ }
+
+ public void testExprSanity() {
+ assertEval("int x = 3;", "3");
+ assertEval("int y = 4;", "4");
+ assertEval("x + y;", "7");
+ assertActiveKeys();
+ }
+
+ public void testImportOnDemand() {
+ assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertEval("List<Integer> list = new ArrayList<>();");
+ assertEval("list.add(45);");
+ assertEval("list.size();", "1");
+ }
+}
--- a/langtools/test/jdk/jshell/KullaTesting.java Fri May 20 17:00:03 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java Sat May 21 22:32:08 2016 -0700
@@ -72,6 +72,7 @@
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 {
@@ -166,6 +167,21 @@
classpath = new ArrayList<>();
}
+ public void setUp(ExecutionControl ec) {
+ inStream = new TestingInputStream();
+ outStream = new ByteArrayOutputStream();
+ errStream = new ByteArrayOutputStream();
+ state = JShell.builder()
+ .executionEngine(ec)
+ .in(inStream)
+ .out(new PrintStream(outStream))
+ .err(new PrintStream(errStream))
+ .build();
+ allSnippets = new LinkedHashSet<>();
+ idToSnippet = new LinkedHashMap<>();
+ classpath = new ArrayList<>();
+ }
+
@AfterMethod
public void tearDown() {
if (state != null) state.close();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/LocalExecutionControl.java Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,312 @@
+/*
+ * 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<String, Class<?>> klasses = new TreeMap<>();
+ private final Map<String, byte[]> 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<String> 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<InvocationTargetException> iteEx = new AtomicReference<>();
+ AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
+ AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
+ AtomicReference<Boolean> 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) {
+ 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 ?
+ "<none>" : 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<String> 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<String> 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();
+ }
+ }
+}