8160127: JShell API: extract abstract JDI and abstract streaming implementations of ExecutionControl
8159935: JShell API: Reorganize execution support code into jdk.jshell.execution (previously sent for review, and combined here)
8160128: JShell API: extract abstract streaming remote agent
8159122: JShell API: Configurable invocation mechanism
Summary: ExecutionControl implementation support with simplified ExecutionControl interface
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jshell.jdi;
-
-import java.util.HashMap;
-import java.util.Objects;
-import com.sun.jdi.ReferenceType;
-import java.util.List;
-import com.sun.jdi.VirtualMachine;
-
-/**
- * Tracks the state of a class.
- */
-class ClassTracker {
-
- private final VirtualMachine vm;
- private final HashMap<String, ClassInfo> map;
-
- ClassTracker(VirtualMachine vm) {
- this.vm = vm;
- this.map = new HashMap<>();
- }
-
- /**
- * Associates a class name, class bytes, and ReferenceType.
- */
- class ClassInfo {
-
- // The name of the class -- always set
- private final String className;
-
- // The corresponding compiled class bytes when a load or redefine
- // is started. May not be the loaded bytes. May be null.
- private byte[] bytes;
-
- // The class bytes successfully loaded/redefined into the remote VM.
- private byte[] loadedBytes;
-
- // The corresponding JDI ReferenceType. Used by redefineClasses and
- // acts as indicator of successful load (null if not loaded).
- private ReferenceType rt;
-
- private ClassInfo(String className) {
- this.className = className;
- }
-
- String getClassName() {
- return className;
- }
-
- byte[] getLoadedBytes() {
- return loadedBytes;
- }
-
- byte[] getBytes() {
- return bytes;
- }
-
- private void setBytes(byte[] potentialBytes) {
- this.bytes = potentialBytes;
- }
-
- // The class has been successful loaded redefined. The class bytes
- // sent are now actually loaded.
- void markLoaded() {
- loadedBytes = bytes;
- }
-
- // Ask JDI for the ReferenceType, null if not loaded.
- ReferenceType getReferenceTypeOrNull() {
- if (rt == null) {
- rt = nameToRef(className);
- }
- return rt;
- }
-
- private ReferenceType nameToRef(String name) {
- List<ReferenceType> rtl = vm.classesByName(name);
- if (rtl.size() != 1) {
- return null;
- }
- return rtl.get(0);
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof ClassInfo
- && ((ClassInfo) o).className.equals(className);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(this.className);
- }
- }
-
- // Map a class name to the current compiled class bytes.
- ClassInfo classInfo(String className, byte[] bytes) {
- ClassInfo ci = get(className);
- ci.setBytes(bytes);
- return ci;
- }
-
- // Lookup the ClassInfo by class name, create if it does not exist.
- ClassInfo get(String className) {
- return map.computeIfAbsent(className, k -> new ClassInfo(k));
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/FailOverExecutionControl.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.jshell.jdi;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import jdk.internal.jshell.debug.InternalDebugControl;
-import jdk.jshell.JShellException;
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-
-/**
- * A meta implementation of ExecutionControl which cycles through the specified
- * ExecutionControl instances until it finds one that starts.
- */
-public class FailOverExecutionControl implements ExecutionControl {
-
- private final List<ExecutionControl> ecl = new ArrayList<>();
- private ExecutionControl active = null;
- private final List<Exception> thrown = new ArrayList<>();
-
- /**
- * Create the ExecutionControl instance with at least one actual
- * ExecutionControl instance.
- *
- * @param ec0 the first instance to try
- * @param ecs the second and on instance to try
- */
- public FailOverExecutionControl(ExecutionControl ec0, ExecutionControl... ecs) {
- ecl.add(ec0);
- for (ExecutionControl ec : ecs) {
- ecl.add(ec);
- }
- }
-
- @Override
- public void start(ExecutionEnv env) throws Exception {
- for (ExecutionControl ec : ecl) {
- try {
- ec.start(env);
- // Success! This is our active ExecutionControl
- active = ec;
- return;
- } catch (Exception ex) {
- thrown.add(ex);
- } catch (Throwable ex) {
- thrown.add(new RuntimeException(ex));
- }
- InternalDebugControl.debug(env.state(), env.userErr(),
- thrown.get(thrown.size() - 1), "failed one in FailOverExecutionControl");
- }
- // They have all failed -- rethrow the first exception we encountered
- throw thrown.get(0);
- }
-
- @Override
- public void close() {
- active.close();
- }
-
- @Override
- public boolean addToClasspath(String path) {
- return active.addToClasspath(path);
- }
-
- @Override
- public String invoke(String classname, String methodname) throws JShellException {
- return active.invoke(classname, methodname);
- }
-
- @Override
- public boolean load(Collection<String> classes) {
- return active.load(classes);
- }
-
- @Override
- public boolean redefine(Collection<String> classes) {
- return active.redefine(classes);
- }
-
- @Override
- public ClassStatus getClassStatus(String classname) {
- return active.getClassStatus(classname);
- }
-
- @Override
- public void stop() {
- active.stop();
- }
-
- @Override
- public String varValue(String classname, String varname) {
- return active.varValue(classname, varname);
- }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,346 +0,0 @@
-/*
- * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * This source code is provided to illustrate the usage of a given feature
- * or technique and has been deliberately simplified. Additional steps
- * required for a production-quality application, such as security checks,
- * input validation and proper error handling, might not be present in
- * this sample code.
- */
-
-
-package jdk.internal.jshell.jdi;
-
-import com.sun.jdi.*;
-import com.sun.jdi.connect.*;
-
-import java.util.*;
-import java.util.Map.Entry;
-import java.io.*;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Connection to a Java Debug Interface VirtualMachine instance.
- * Adapted from jdb VMConnection. Message handling, exception handling, and I/O
- * redirection changed. Interface to JShell added.
- */
-class JDIConnection {
-
- private static final String REMOTE_AGENT = "jdk.internal.jshell.remote.RemoteAgent";
-
- private VirtualMachine vm;
- private boolean active = true;
- private Process process = null;
- private int outputCompleteCount = 0;
-
- private final JDIExecutionControl ec;
- private final Connector connector;
- private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
- private final int traceFlags;
-
- private synchronized void notifyOutputComplete() {
- outputCompleteCount++;
- notifyAll();
- }
-
- private synchronized void waitOutputComplete() {
- // Wait for stderr and stdout
- if (process != null) {
- while (outputCompleteCount < 2) {
- try {wait();} catch (InterruptedException e) {}
- }
- }
- }
-
- private Connector findConnector(String name) {
- for (Connector cntor :
- Bootstrap.virtualMachineManager().allConnectors()) {
- if (cntor.name().equals(name)) {
- return cntor;
- }
- }
- return null;
- }
-
- private Map <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;
- }
-
- /**
- * The JShell specific Connector args for the LaunchingConnector.
- *
- * @param portthe socket port for (non-JDI) commands
- * @param remoteVMOptions any user requested VM options
- * @return the argument map
- */
- private static Map<String, String> launchArgs(int port, String remoteVMOptions) {
- Map<String, String> argumentName2Value = new HashMap<>();
- argumentName2Value.put("main", REMOTE_AGENT + " " + port);
- argumentName2Value.put("options", remoteVMOptions);
- return argumentName2Value;
- }
-
- /**
- * Start the remote agent and establish a JDI connection to it.
- *
- * @param ec the execution control instance
- * @param port the socket port for (non-JDI) commands
- * @param remoteVMOptions any user requested VM options
- * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
- * otherwise we start explicitly and use ListeningConnector
- */
- JDIConnection(JDIExecutionControl ec, int port, List<String> remoteVMOptions, boolean isLaunch) {
- this(ec,
- isLaunch
- ? "com.sun.jdi.CommandLineLaunch"
- : "com.sun.jdi.SocketListen",
- isLaunch
- ? launchArgs(port, String.join(" ", remoteVMOptions))
- : new HashMap<>(),
- 0);
- if (isLaunch) {
- vm = launchTarget();
- } else {
- vm = listenTarget(port, remoteVMOptions);
- }
-
- if (isOpen() && vm().canBeModified()) {
- /*
- * Connection opened on startup.
- */
- new JDIEventHandler(vm(), (b) -> ec.handleVMExit())
- .start();
- }
- }
-
- /**
- * Base constructor -- set-up a JDI connection.
- *
- * @param ec the execution control instance
- * @param connectorName the standardized name of the connector
- * @param argumentName2Value the argument map
- * @param traceFlags should we trace JDI behavior
- */
- JDIConnection(JDIExecutionControl ec, String connectorName, Map<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;
- }
-
- final synchronized VirtualMachine vm() {
- if (vm == null) {
- throw new JDINotConnectedException();
- } else {
- return vm;
- }
- }
-
- private synchronized boolean isOpen() {
- return (vm != null);
- }
-
- synchronized boolean isRunning() {
- return process != null && process.isAlive();
- }
-
- // Beginning shutdown, ignore any random dying squeals
- void beginShutdown() {
- active = false;
- }
-
- synchronized void disposeVM() {
- try {
- if (vm != null) {
- vm.dispose(); // This could NPE, so it is caught below
- vm = null;
- }
- } catch (VMDisconnectedException ex) {
- // Ignore if already closed
- } catch (Throwable e) {
- ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
- } finally {
- if (process != null) {
- process.destroy();
- process = null;
- }
- waitOutputComplete();
- }
- }
-
- private void dumpStream(InputStream inStream, final PrintStream pStream) throws IOException {
- BufferedReader in =
- new BufferedReader(new InputStreamReader(inStream));
- int i;
- try {
- while ((i = in.read()) != -1) {
- // directly copy input to output, but skip if asked to close
- if (active) {
- pStream.print((char) i);
- }
- }
- } catch (IOException ex) {
- String s = ex.getMessage();
- if (active && !s.startsWith("Bad file number")) {
- throw ex;
- }
- // else we are being shutdown (and don't want any spurious death
- // throws to ripple) or
- // we got a Bad file number IOException which just means
- // that the debuggee has gone away. We'll just treat it the
- // same as if we got an EOF.
- }
- }
-
- /**
- * Create a Thread that will retrieve and display any output.
- * Needs to be high priority, else debugger may exit before
- * it can be displayed.
- */
- private void displayRemoteOutput(final InputStream inStream, final PrintStream pStream) {
- Thread thr = new Thread("output reader") {
- @Override
- public void run() {
- try {
- dumpStream(inStream, pStream);
- } catch (IOException ex) {
- ec.debug(ex, "Failed reading output");
- ec.handleVMExit();
- } finally {
- notifyOutputComplete();
- }
- }
- };
- thr.setPriority(Thread.MAX_PRIORITY-1);
- thr.start();
- }
-
- /**
- * Create a Thread that will ship all input to remote.
- * Does it need be high priority?
- */
- private void readRemoteInput(final OutputStream outStream, final InputStream inputStream) {
- Thread thr = new Thread("input reader") {
- @Override
- public void run() {
- try {
- byte[] buf = new byte[256];
- int cnt;
- while ((cnt = inputStream.read(buf)) != -1) {
- outStream.write(buf, 0, cnt);
- outStream.flush();
- }
- } catch (IOException ex) {
- ec.debug(ex, "Failed reading output");
- ec.handleVMExit();
- }
- }
- };
- thr.setPriority(Thread.MAX_PRIORITY-1);
- thr.start();
- }
-
- private void forwardIO() {
- displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
- displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
- readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
- }
-
- /* launch child target vm */
- private VirtualMachine launchTarget() {
- LaunchingConnector launcher = (LaunchingConnector)connector;
- try {
- VirtualMachine new_vm = launcher.launch(connectorArgs);
- process = new_vm.process();
- forwardIO();
- return new_vm;
- } catch (Exception ex) {
- reportLaunchFail(ex, "launch");
- }
- return null;
- }
-
- /**
- * Directly launch the remote agent and connect JDI to it with a
- * ListeningConnector.
- */
- private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) {
- ListeningConnector listener = (ListeningConnector) connector;
- try {
- // Start listening, get the JDI connection address
- String addr = listener.startListening(connectorArgs);
- ec.debug(DBG_GEN, "Listening at address: " + addr);
-
- // Launch the RemoteAgent requesting a connection on that address
- String javaHome = System.getProperty("java.home");
- List<String> args = new ArrayList<>();
- args.add(javaHome == null
- ? "java"
- : javaHome + File.separator + "bin" + File.separator + "java");
- args.add("-agentlib:jdwp=transport=" + connector.transport().name() +
- ",address=" + addr);
- args.addAll(remoteVMOptions);
- args.add(REMOTE_AGENT);
- args.add("" + port);
- ProcessBuilder pb = new ProcessBuilder(args);
- process = pb.start();
-
- // Forward out, err, and in
- forwardIO();
-
- // Accept the connection from the remote agent
- vm = listener.accept(connectorArgs);
- listener.stopListening(connectorArgs);
- return vm;
- } catch (Exception ex) {
- reportLaunchFail(ex, "listen");
- }
- return null;
- }
-
- private void reportLaunchFail(Exception ex, String context) {
- throw new InternalError("Failed remote " + context + ": " + connector +
- " -- " + connectorArgs, ex);
- }
-}
\ No newline at end of file
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.jshell.jdi;
-
-import java.util.function.Consumer;
-import com.sun.jdi.*;
-import com.sun.jdi.event.*;
-
-/**
- * Handler of Java Debug Interface events.
- * Adapted from jdb EventHandler; Handling of events not used by JShell stubbed out.
- */
-class JDIEventHandler implements Runnable {
-
- private final Thread thread;
- private volatile boolean connected = true;
- private boolean completed = false;
- private final VirtualMachine vm;
- private final Consumer<Boolean> reportVMExit;
-
- JDIEventHandler(VirtualMachine vm, Consumer<Boolean> reportVMExit) {
- this.vm = vm;
- this.reportVMExit = reportVMExit;
- this.thread = new Thread(this, "event-handler");
- }
-
- void start() {
- thread.start();
- }
-
- synchronized void shutdown() {
- connected = false; // force run() loop termination
- thread.interrupt();
- while (!completed) {
- try {wait();} catch (InterruptedException exc) {}
- }
- }
-
- @Override
- public void run() {
- EventQueue queue = vm.eventQueue();
- while (connected) {
- try {
- EventSet eventSet = queue.remove();
- boolean resumeStoppedApp = false;
- EventIterator it = eventSet.eventIterator();
- while (it.hasNext()) {
- resumeStoppedApp |= handleEvent(it.nextEvent());
- }
-
- if (resumeStoppedApp) {
- eventSet.resume();
- }
- } catch (InterruptedException exc) {
- // Do nothing. Any changes will be seen at top of loop.
- } catch (VMDisconnectedException discExc) {
- handleDisconnectedException();
- break;
- }
- }
- synchronized (this) {
- completed = true;
- notifyAll();
- }
- }
-
- private boolean handleEvent(Event event) {
- if (event instanceof ExceptionEvent) {
- exceptionEvent(event);
- } else if (event instanceof WatchpointEvent) {
- fieldWatchEvent(event);
- } else if (event instanceof MethodEntryEvent) {
- methodEntryEvent(event);
- } else if (event instanceof MethodExitEvent) {
- methodExitEvent(event);
- } else if (event instanceof ClassPrepareEvent) {
- classPrepareEvent(event);
- } else if (event instanceof ThreadStartEvent) {
- threadStartEvent(event);
- } else if (event instanceof ThreadDeathEvent) {
- threadDeathEvent(event);
- } else if (event instanceof VMStartEvent) {
- vmStartEvent(event);
- return true;
- } else {
- handleExitEvent(event);
- }
- return true;
- }
-
- private boolean vmDied = false;
-
- private void handleExitEvent(Event event) {
- if (event instanceof VMDeathEvent) {
- vmDied = true;
- } else if (event instanceof VMDisconnectEvent) {
- connected = false;
- } else {
- throw new InternalError("Unexpected event type: " +
- event.getClass());
- }
- reportVMExit.accept(vmDied);
- }
-
- private synchronized void handleDisconnectedException() {
- /*
- * A VMDisconnectedException has happened while dealing with
- * another event. We need to flush the event queue, dealing only
- * with exit events (VMDeath, VMDisconnect) so that we terminate
- * correctly.
- */
- EventQueue queue = vm.eventQueue();
- while (connected) {
- try {
- EventSet eventSet = queue.remove();
- EventIterator iter = eventSet.eventIterator();
- while (iter.hasNext()) {
- handleExitEvent(iter.next());
- }
- } catch (InterruptedException exc) {
- // ignore
- } catch (InternalError exc) {
- // ignore
- }
- }
- }
-
- private void vmStartEvent(Event event) {
- VMStartEvent se = (VMStartEvent)event;
- }
-
- private void methodEntryEvent(Event event) {
- MethodEntryEvent me = (MethodEntryEvent)event;
- }
-
- private void methodExitEvent(Event event) {
- MethodExitEvent me = (MethodExitEvent)event;
- }
-
- private void fieldWatchEvent(Event event) {
- WatchpointEvent fwe = (WatchpointEvent)event;
- }
-
- private void classPrepareEvent(Event event) {
- ClassPrepareEvent cle = (ClassPrepareEvent)event;
- }
-
- private void exceptionEvent(Event event) {
- ExceptionEvent ee = (ExceptionEvent)event;
- }
-
- private void threadDeathEvent(Event event) {
- ThreadDeathEvent tee = (ThreadDeathEvent)event;
- }
-
- private void threadStartEvent(Event event) {
- ThreadStartEvent tse = (ThreadStartEvent)event;
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,598 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.jshell.jdi;
-
-import static jdk.internal.jshell.remote.RemoteCodes.*;
-import java.io.DataInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.PrintStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.io.EOFException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import com.sun.jdi.BooleanValue;
-import com.sun.jdi.ClassNotLoadedException;
-import com.sun.jdi.IncompatibleThreadStateException;
-import com.sun.jdi.InvalidTypeException;
-import com.sun.jdi.ObjectReference;
-import com.sun.jdi.ReferenceType;
-import com.sun.jdi.StackFrame;
-import com.sun.jdi.ThreadReference;
-import com.sun.jdi.VirtualMachine;
-import static java.util.stream.Collectors.toList;
-import jdk.jshell.JShellException;
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
-import static java.util.stream.Collectors.toMap;
-import jdk.internal.jshell.debug.InternalDebugControl;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Controls the remote execution environment.
- * Interfaces to the JShell-core by implementing ExecutionControl SPI.
- * Interfaces to RemoteAgent over a socket and via JDI.
- * Launches a remote process.
- */
-public class JDIExecutionControl implements ExecutionControl {
-
- ExecutionEnv execEnv;
- private final boolean isLaunch;
- private JDIConnection connection;
- private ClassTracker classTracker;
- private Socket socket;
- private ObjectInputStream remoteIn;
- private ObjectOutputStream remoteOut;
-
- /**
- * Creates an ExecutionControl instance based on JDI.
- *
- * @param isLaunch true for LaunchingConnector; false for ListeningConnector
- */
- public JDIExecutionControl(boolean isLaunch) {
- this.isLaunch = isLaunch;
- }
-
- /**
- * Creates an ExecutionControl instance based on a JDI LaunchingConnector.
- */
- public JDIExecutionControl() {
- this.isLaunch = true;
- }
-
- /**
- * Initializes the launching JDI execution engine. Initialize JDI and use it
- * to launch the remote JVM. Set-up control and result communications socket
- * to the remote execution environment. This socket also transports the
- * input/output channels.
- *
- * @param execEnv the execution environment provided by the JShell-core
- * @throws IOException
- */
- @Override
- public void start(ExecutionEnv execEnv) throws IOException {
- this.execEnv = execEnv;
- StringBuilder sb = new StringBuilder();
- try (ServerSocket listener = new ServerSocket(0)) {
- // timeout after 60 seconds
- listener.setSoTimeout(60000);
- int port = listener.getLocalPort();
- connection = new JDIConnection(this, port, execEnv.extraRemoteVMOptions(), isLaunch);
- this.socket = listener.accept();
- // out before in -- match remote creation so we don't hang
- this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
- PipeInputStream commandIn = new PipeInputStream();
- new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
- this.remoteIn = new ObjectInputStream(commandIn);
- }
- }
-
- /**
- * Closes the execution engine. Send an exit command to the remote agent.
- * Shuts down the JDI connection. Should this close the socket?
- */
- @Override
- public void close() {
- try {
- if (connection != null) {
- connection.beginShutdown();
- }
- if (remoteOut != null) {
- remoteOut.writeInt(CMD_EXIT);
- remoteOut.flush();
- }
- if (connection != null) {
- connection.disposeVM();
- }
- } catch (IOException ex) {
- debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
- }
- }
-
- /**
- * Loads the list of classes specified. Sends a load command to the remote
- * agent with pairs of classname/bytes.
- *
- * @param classes the names of the wrapper classes to loaded
- * @return true if all classes loaded successfully
- */
- @Override
- public boolean load(Collection<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 (!connection.isRunning()) {
- // The JDI connection is no longer live, shutdown.
- handleVMExit();
- } else {
- debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
- return "Execution failure: " + ex.getMessage();
- }
- } finally {
- synchronized (STOP_LOCK) {
- userCodeRunning = false;
- }
- }
- return "";
- }
-
- /**
- * Retrieves the value of a JShell variable.
- *
- * @param classname name of the wrapper class holding the variable
- * @param varname name of the variable
- * @return the value as a String
- */
- @Override
- public String varValue(String classname, String varname) {
- try {
- // Send the variable-value command to the remote agent.
- remoteOut.writeInt(CMD_VARVALUE);
- remoteOut.writeUTF(classname);
- remoteOut.writeUTF(varname);
- remoteOut.flush();
- // Retrieve and report results from the remote agent.
- if (readAndReportResult()) {
- String result = remoteIn.readUTF();
- return result;
- }
- } catch (EOFException ex) {
- handleVMExit();
- } catch (IOException ex) {
- debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
- return "Execution failure: " + ex.getMessage();
- }
- return "";
- }
-
- /**
- * Adds a path to the remote classpath.
- *
- * @param cp the additional path element
- * @return true if succesful
- */
- @Override
- public boolean addToClasspath(String cp) {
- try {
- // Send the classpath addition command to the remote agent.
- remoteOut.writeInt(CMD_CLASSPATH);
- remoteOut.writeUTF(cp);
- remoteOut.flush();
- // Retrieve and report results from the remote agent.
- return readAndReportResult();
- } catch (IOException ex) {
- throw new InternalError("Classpath addition failed: " + cp, ex);
- }
- }
-
- /**
- * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
- * an in-place replacement of the classes (preserving class identity) --
- * that is, existing references to the class do not need to be recompiled.
- * This implementation uses JDI redefineClasses. It will be unsuccessful if
- * the signature of the class has changed (see the JDI spec). The
- * JShell-core is designed to adapt to unsuccessful redefine.
- *
- * @param classes the names of the classes to redefine
- * @return true if all the classes were redefined
- */
- @Override
- public boolean redefine(Collection<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.
- connection.vm().redefineClasses(rmp);
- // Successful: mark the bytes as loaded.
- infos.stream()
- .forEach(ci -> ci.markLoaded());
- return true;
- } catch (UnsupportedOperationException ex) {
- // A form of class transformation not supported by JDI
- return false;
- } catch (Exception ex) {
- debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
- return false;
- }
- }
-
- // the VM has gone down in flames or because user evaled System.exit() or the like
- void handleVMExit() {
- if (connection != null) {
- // If there is anything left dispose of it
- connection.disposeVM();
- }
- // Tell JShell-core that the VM has died
- execEnv.closeDown();
- }
-
- // Lazy init class tracker
- private ClassTracker classTracker() {
- if (classTracker == null) {
- classTracker = new ClassTracker(connection.vm());
- }
- return classTracker;
- }
-
- /**
- * Converts a collection of class names into ClassInfo instances associated
- * with the most recently compiled class bytes.
- *
- * @param classes names of the classes
- * @return a list of corresponding ClassInfo instances
- */
- private List<ClassInfo> withBytes(Collection<String> classes) {
- return classes.stream()
- .map(cn -> classTracker().classInfo(cn, execEnv.getClassBytes(cn)))
- .collect(toList());
- }
-
- /**
- * Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
- * the most recent successfully loaded/redefined bytes match the current
- * compiled bytes.
- *
- * @param classname the name of the class to test
- * @return the status
- */
- @Override
- public ClassStatus getClassStatus(String classname) {
- ClassInfo ci = classTracker().get(classname);
- if (ci.getReferenceTypeOrNull() == null) {
- // If the class does not have a JDI ReferenceType it has not been loaded
- return ClassStatus.UNKNOWN;
- }
- // Compare successfully loaded with last compiled bytes.
- return (Arrays.equals(execEnv.getClassBytes(classname), ci.getLoadedBytes()))
- ? ClassStatus.CURRENT
- : ClassStatus.NOT_CURRENT;
- }
-
- /**
- * Reports results from a remote agent command that does not expect
- * exceptions.
- *
- * @return true if successful
- * @throws IOException if the connection has dropped
- */
- private boolean readAndReportResult() throws IOException {
- int ok = remoteIn.readInt();
- switch (ok) {
- case RESULT_SUCCESS:
- return true;
- case RESULT_FAIL: {
- String ex = remoteIn.readUTF();
- debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
- return false;
- }
- default: {
- debug(DBG_GEN, "Bad remote result code: %s\n", ok);
- return false;
- }
- }
- }
-
- /**
- * Reports results from a remote agent command that expects runtime
- * exceptions.
- *
- * @return true if successful
- * @throws IOException if the connection has dropped
- * @throws EvalException if a user exception was encountered on invoke
- * @throws UnresolvedReferenceException if an unresolved reference was
- * encountered
- */
- private boolean readAndReportExecutionResult() throws IOException, JShellException {
- int ok = remoteIn.readInt();
- switch (ok) {
- case RESULT_SUCCESS:
- return true;
- case RESULT_FAIL: {
- // An internal error has occurred.
- String ex = remoteIn.readUTF();
- return false;
- }
- case RESULT_EXCEPTION: {
- // A user exception was encountered.
- String exceptionClassName = remoteIn.readUTF();
- String message = remoteIn.readUTF();
- StackTraceElement[] elems = readStackTrace();
- throw execEnv.createEvalException(message, exceptionClassName, elems);
- }
- case RESULT_CORRALLED: {
- // An unresolved reference was encountered.
- int id = remoteIn.readInt();
- StackTraceElement[] elems = readStackTrace();
- throw execEnv.createUnresolvedReferenceException(id, elems);
- }
- case RESULT_KILLED: {
- // Execution was aborted by the stop()
- debug(DBG_GEN, "Killed.");
- return false;
- }
- default: {
- debug(DBG_GEN, "Bad remote result code: %s\n", ok);
- return false;
- }
- }
- }
-
- private StackTraceElement[] readStackTrace() throws IOException {
- int elemCount = remoteIn.readInt();
- StackTraceElement[] elems = new StackTraceElement[elemCount];
- for (int i = 0; i < elemCount; ++i) {
- String className = remoteIn.readUTF();
- String methodName = remoteIn.readUTF();
- String fileName = remoteIn.readUTF();
- int line = remoteIn.readInt();
- elems[i] = new StackTraceElement(className, methodName, fileName, line);
- }
- return elems;
- }
-
- private final Object STOP_LOCK = new Object();
- private boolean userCodeRunning = false;
-
- /**
- * Interrupt a running invoke.
- */
- @Override
- public void stop() {
- synchronized (STOP_LOCK) {
- if (!userCodeRunning) {
- return;
- }
-
- VirtualMachine vm = connection.vm();
- vm.suspend();
- try {
- OUTER:
- for (ThreadReference thread : vm.allThreads()) {
- // could also tag the thread (e.g. using name), to find it easier
- for (StackFrame frame : thread.frames()) {
- String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent";
- if (remoteAgentName.equals(frame.location().declaringType().name())
- && "commandLoop".equals(frame.location().method().name())) {
- ObjectReference thiz = frame.thisObject();
- if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) {
- thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true));
- ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException"));
-
- vm.resume();
- debug(DBG_GEN, "Attempting to stop the client code...\n");
- thread.stop(stopInstance);
- thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false));
- }
-
- break OUTER;
- }
- }
- }
- } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
- debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
- } finally {
- vm.resume();
- }
- }
- }
-
- void debug(int flags, String format, Object... args) {
- InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), flags, format, args);
- }
-
- void debug(Exception ex, String where) {
- InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where);
- }
-
- private final class DemultiplexInput extends Thread {
-
- private final DataInputStream delegate;
- private final PipeInputStream command;
- private final PrintStream out;
- private final PrintStream err;
-
- public DemultiplexInput(InputStream input,
- PipeInputStream command,
- PrintStream out,
- PrintStream err) {
- super("output reader");
- this.delegate = new DataInputStream(input);
- this.command = command;
- this.out = out;
- this.err = err;
- }
-
- public void run() {
- try {
- while (true) {
- int nameLen = delegate.read();
- if (nameLen == (-1))
- break;
- byte[] name = new byte[nameLen];
- DemultiplexInput.this.delegate.readFully(name);
- int dataLen = delegate.read();
- byte[] data = new byte[dataLen];
- DemultiplexInput.this.delegate.readFully(data);
- switch (new String(name, "UTF-8")) {
- case "err":
- err.write(data);
- break;
- case "out":
- out.write(data);
- break;
- case "command":
- for (byte b : data) {
- command.write(Byte.toUnsignedInt(b));
- }
- break;
- }
- }
- } catch (IOException ex) {
- debug(ex, "Failed reading output");
- } finally {
- command.close();
- }
- }
-
- }
-
- public static final class PipeInputStream extends InputStream {
- public static final int INITIAL_SIZE = 128;
-
- private int[] buffer = new int[INITIAL_SIZE];
- private int start;
- private int end;
- private boolean closed;
-
- @Override
- public synchronized int read() {
- while (start == end) {
- if (closed) {
- return -1;
- }
- try {
- wait();
- } catch (InterruptedException ex) {
- //ignore
- }
- }
- try {
- return buffer[start];
- } finally {
- start = (start + 1) % buffer.length;
- }
- }
-
- public synchronized void write(int b) {
- if (closed)
- throw new IllegalStateException("Already closed.");
- int newEnd = (end + 1) % buffer.length;
- if (newEnd == start) {
- //overflow:
- int[] newBuffer = new int[buffer.length * 2];
- int rightPart = (end > start ? end : buffer.length) - start;
- int leftPart = end > start ? 0 : start - 1;
- System.arraycopy(buffer, start, newBuffer, 0, rightPart);
- System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
- buffer = newBuffer;
- start = 0;
- end = rightPart + leftPart;
- newEnd = end + 1;
- }
- buffer[end] = b;
- end = newEnd;
- notifyAll();
- }
-
- @Override
- public synchronized void close() {
- closed = true;
- notifyAll();
- }
-
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java Tue Jul 19 11:27:56 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.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 Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,326 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.jshell.remote;
-import jdk.jshell.spi.SPIResolutionException;
-import java.io.File;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.Socket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static jdk.internal.jshell.remote.RemoteCodes.*;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * The remote agent runs in the execution process (separate from the main JShell
- * process. This agent loads code over a socket from the main JShell process,
- * executes the code, and other misc,
- * @author Robert Field
- */
-class RemoteAgent {
-
- private final RemoteClassLoader loader = new RemoteClassLoader();
- private final Map<String, Class<?>> klasses = new TreeMap<>();
-
- public static void main(String[] args) throws Exception {
- String loopBack = null;
- Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
- (new RemoteAgent()).commandLoop(socket);
- }
-
- void commandLoop(Socket socket) throws IOException {
- // in before out -- so we don't hang the controlling process
- ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
- OutputStream socketOut = socket.getOutputStream();
- System.setOut(new PrintStream(new MultiplexingOutputStream("out", socketOut), true));
- System.setErr(new PrintStream(new MultiplexingOutputStream("err", socketOut), true));
- ObjectOutputStream out = new ObjectOutputStream(new MultiplexingOutputStream("command", socketOut));
- while (true) {
- int cmd = in.readInt();
- switch (cmd) {
- case CMD_EXIT:
- // Terminate this process
- return;
- case CMD_LOAD:
- // Load a generated class file over the wire
- try {
- int count = in.readInt();
- List<String> names = new ArrayList<>(count);
- for (int i = 0; i < count; ++i) {
- String name = in.readUTF();
- byte[] kb = (byte[]) in.readObject();
- loader.delare(name, kb);
- names.add(name);
- }
- for (String name : names) {
- Class<?> klass = loader.loadClass(name);
- klasses.put(name, klass);
- // Get class loaded to the point of, at least, preparation
- klass.getDeclaredMethods();
- }
- out.writeInt(RESULT_SUCCESS);
- out.flush();
- } catch (IOException | ClassNotFoundException | ClassCastException ex) {
- debug("*** Load failure: %s\n", ex);
- out.writeInt(RESULT_FAIL);
- out.writeUTF(ex.toString());
- out.flush();
- }
- break;
- case CMD_INVOKE: {
- // Invoke executable entry point in loaded code
- String name = in.readUTF();
- Class<?> klass = klasses.get(name);
- if (klass == null) {
- debug("*** Invoke failure: no such class loaded %s\n", name);
- out.writeInt(RESULT_FAIL);
- out.writeUTF("no such class loaded: " + name);
- out.flush();
- break;
- }
- String methodName = in.readUTF();
- Method doitMethod;
- try {
- this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
- doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
- doitMethod.setAccessible(true);
- Object res;
- try {
- clientCodeEnter();
- res = doitMethod.invoke(null, new Object[0]);
- } catch (InvocationTargetException ex) {
- if (ex.getCause() instanceof StopExecutionException) {
- expectingStop = false;
- throw (StopExecutionException) ex.getCause();
- }
- throw ex;
- } catch (StopExecutionException ex) {
- expectingStop = false;
- throw ex;
- } finally {
- clientCodeLeave();
- }
- out.writeInt(RESULT_SUCCESS);
- out.writeUTF(valueString(res));
- out.flush();
- } catch (InvocationTargetException ex) {
- Throwable cause = ex.getCause();
- StackTraceElement[] elems = cause.getStackTrace();
- if (cause instanceof SPIResolutionException) {
- out.writeInt(RESULT_CORRALLED);
- out.writeInt(((SPIResolutionException) cause).id());
- } else {
- out.writeInt(RESULT_EXCEPTION);
- out.writeUTF(cause.getClass().getName());
- out.writeUTF(cause.getMessage() == null ? "<none>" : cause.getMessage());
- }
- out.writeInt(elems.length);
- for (StackTraceElement ste : elems) {
- out.writeUTF(ste.getClassName());
- out.writeUTF(ste.getMethodName());
- out.writeUTF(ste.getFileName() == null ? "<none>" : ste.getFileName());
- out.writeInt(ste.getLineNumber());
- }
- out.flush();
- } catch (NoSuchMethodException | IllegalAccessException ex) {
- debug("*** Invoke failure: %s -- %s\n", ex, ex.getCause());
- out.writeInt(RESULT_FAIL);
- out.writeUTF(ex.toString());
- out.flush();
- } catch (StopExecutionException ex) {
- try {
- out.writeInt(RESULT_KILLED);
- out.flush();
- } catch (IOException err) {
- debug("*** Error writing killed result: %s -- %s\n", ex, ex.getCause());
- }
- }
- System.out.flush();
- break;
- }
- case CMD_VARVALUE: {
- // Retrieve a variable value
- String classname = in.readUTF();
- String varname = in.readUTF();
- Class<?> klass = klasses.get(classname);
- if (klass == null) {
- debug("*** Var value failure: no such class loaded %s\n", classname);
- out.writeInt(RESULT_FAIL);
- out.writeUTF("no such class loaded: " + classname);
- out.flush();
- break;
- }
- try {
- Field var = klass.getDeclaredField(varname);
- var.setAccessible(true);
- Object res = var.get(null);
- out.writeInt(RESULT_SUCCESS);
- out.writeUTF(valueString(res));
- out.flush();
- } catch (Exception ex) {
- debug("*** Var value failure: no such field %s.%s\n", classname, varname);
- out.writeInt(RESULT_FAIL);
- out.writeUTF("no such field loaded: " + varname + " in class: " + classname);
- out.flush();
- }
- break;
- }
- case CMD_CLASSPATH: {
- // Append to the claspath
- String cp = in.readUTF();
- for (String path : cp.split(File.pathSeparator)) {
- loader.addURL(new File(path).toURI().toURL());
- }
- out.writeInt(RESULT_SUCCESS);
- out.flush();
- break;
- }
- default:
- debug("*** Bad command code: %d\n", cmd);
- break;
- }
- }
- }
-
- // These three variables are used by the main JShell process in interrupting
- // the running process. Access is via JDI, so the reference is not visible
- // to code inspection.
- private boolean inClientCode; // Queried by the main process
- private boolean expectingStop; // Set by the main process
-
- // thrown by the main process via JDI:
- private final StopExecutionException stopException = new StopExecutionException();
-
- @SuppressWarnings("serial") // serialVersionUID intentionally omitted
- private class StopExecutionException extends ThreadDeath {
- @Override public synchronized Throwable fillInStackTrace() {
- return this;
- }
- }
-
- void clientCodeEnter() {
- expectingStop = false;
- inClientCode = true;
- }
-
- void clientCodeLeave() {
- inClientCode = false;
- while (expectingStop) {
- try {
- Thread.sleep(0);
- } catch (InterruptedException ex) {
- debug("*** Sleep interrupted while waiting for stop exception: %s\n", ex);
- }
- }
- }
-
- private void debug(String format, Object... args) {
- System.err.printf("REMOTE: "+format, args);
- }
-
- static String valueString(Object value) {
- if (value == null) {
- return "null";
- } else if (value instanceof String) {
- return "\"" + (String)value + "\"";
- } else if (value instanceof Character) {
- return "'" + value + "'";
- } else {
- return value.toString();
- }
- }
-
- private static final class MultiplexingOutputStream extends OutputStream {
-
- private static final int PACKET_SIZE = 127;
-
- private final byte[] name;
- private final OutputStream delegate;
-
- public MultiplexingOutputStream(String name, OutputStream delegate) {
- try {
- this.name = name.getBytes("UTF-8");
- this.delegate = delegate;
- } catch (UnsupportedEncodingException ex) {
- throw new IllegalStateException(ex); //should not happen
- }
- }
-
- @Override
- public void write(int b) throws IOException {
- synchronized (delegate) {
- delegate.write(name.length); //assuming the len is small enough to fit into byte
- delegate.write(name);
- delegate.write(1);
- delegate.write(b);
- delegate.flush();
- }
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- synchronized (delegate) {
- int i = 0;
- while (len > 0) {
- int size = Math.min(PACKET_SIZE, len);
-
- delegate.write(name.length); //assuming the len is small enough to fit into byte
- delegate.write(name);
- delegate.write(size);
- delegate.write(b, off + i, size);
- i += size;
- len -= size;
- }
-
- delegate.flush();
- }
- }
-
- @Override
- public void flush() throws IOException {
- super.flush();
- delegate.flush();
- }
-
- @Override
- public void close() throws IOException {
- super.close();
- delegate.close();
- }
-
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.jshell.remote;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Class loader wrapper which caches class files by name until requested.
- * @author Robert Field
- */
-class RemoteClassLoader extends URLClassLoader {
-
- private final Map<String, byte[]> classObjects = new TreeMap<>();
-
- RemoteClassLoader() {
- super(new URL[0]);
- }
-
- void delare(String name, byte[] bytes) {
- classObjects.put(name, bytes);
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- byte[] b = classObjects.get(name);
- if (b == null) {
- return super.findClass(name);
- }
- return super.defineClass(name, b, 0, b.length, (CodeSource) null);
- }
-
- @Override
- public void addURL(URL url) {
- super.addURL(url);
- }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.jshell.remote;
-
-/**
- * Communication constants shared between the main process and the remote
- * execution process
- * @author Robert Field
- */
-public class RemoteCodes {
- // Command codes
- public static final int CMD_EXIT = 0;
- public static final int CMD_LOAD = 1;
- public static final int CMD_INVOKE = 3;
- public static final int CMD_CLASSPATH = 4;
- public static final int CMD_VARVALUE = 5;
-
- // Return result codes
- public static final int RESULT_SUCCESS = 100;
- public static final int RESULT_FAIL = 101;
- public static final int RESULT_EXCEPTION = 102;
- public static final int RESULT_CORRALLED = 103;
- public static final int RESULT_KILLED = 104;
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+
+/**
+ * Tracks the state of a class.
+ */
+class ClassTracker {
+
+ private final HashMap<String, ClassInfo> map;
+
+ ClassTracker() {
+ this.map = new HashMap<>();
+ }
+
+ /**
+ * Associates a class name, class bytes (current and loaded).
+ */
+ class ClassInfo {
+
+ // The name of the class
+ private final String className;
+
+ // The corresponding compiled class bytes when a load or redefine
+ // is started. May not be the loaded bytes. May be null.
+ private byte[] currentBytes;
+
+ // The class bytes successfully loaded/redefined into the remote VM.
+ private byte[] loadedBytes;
+
+ private ClassInfo(String className) {
+ this.className = className;
+ }
+
+ String getClassName() {
+ return className;
+ }
+
+ byte[] getLoadedBytes() {
+ return loadedBytes;
+ }
+
+ byte[] getCurrentBytes() {
+ return currentBytes;
+ }
+
+ void setCurrentBytes(byte[] bytes) {
+ this.currentBytes = bytes;
+ }
+
+ void setLoadedBytes(byte[] bytes) {
+ this.loadedBytes = bytes;
+ }
+
+ boolean isLoaded() {
+ return loadedBytes != null;
+ }
+
+ boolean isCurrent() {
+ return Arrays.equals(currentBytes, loadedBytes);
+ }
+
+ ClassBytecodes toClassBytecodes() {
+ return new ClassBytecodes(className, currentBytes);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ClassInfo
+ && ((ClassInfo) o).className.equals(className);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.className);
+ }
+ }
+
+ void markLoaded(ClassBytecodes[] cbcs) {
+ for (ClassBytecodes cbc : cbcs) {
+ get(cbc.name()).setLoadedBytes(cbc.bytecodes());
+ }
+ }
+
+ void markLoaded(ClassBytecodes[] cbcs, boolean[] isLoaded) {
+ for (int i = 0; i < cbcs.length; ++i) {
+ if (isLoaded[i]) {
+ ClassBytecodes cbc = cbcs[i];
+ get(cbc.name()).setLoadedBytes(cbc.bytecodes());
+ }
+ }
+ }
+
+ // Map a class name to the current compiled class bytes.
+ void setCurrentBytes(String className, byte[] bytes) {
+ ClassInfo ci = get(className);
+ ci.setCurrentBytes(bytes);
+ }
+
+ // Lookup the ClassInfo by class name, create if it does not exist.
+ ClassInfo get(String className) {
+ return map.computeIfAbsent(className, k -> new ClassInfo(k));
+ }
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Wed Jul 20 23:19:09 2016 -0700
@@ -61,6 +61,14 @@
import jdk.jshell.TreeDissector.ExpressionInfo;
import jdk.jshell.Wrap.Range;
import jdk.jshell.Snippet.Status;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+import jdk.jshell.spi.ExecutionControl.ResolutionException;
+import jdk.jshell.spi.ExecutionControl.RunException;
+import jdk.jshell.spi.ExecutionControl.UserException;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static java.util.Collections.singletonList;
@@ -541,15 +549,21 @@
if (si.status().isDefined()) {
if (si.isExecutable()) {
try {
- value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
+ value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
value = si.subKind().hasValue()
? expunge(value)
: "";
- } catch (EvalException ex) {
+ } catch (ResolutionException ex) {
+ DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id());
+ exception = new UnresolvedReferenceException(sn, ex.getStackTrace());
+ } catch (UserException ex) {
exception = translateExecutionException(ex);
- } catch (JShellException ex) {
- // UnresolvedReferenceException
- exception = ex;
+ } catch (RunException ex) {
+ // StopException - no-op
+ } catch (InternalException ex) {
+ state.debug(ex, "invoke");
+ } catch (EngineTerminationException ex) {
+ state.closeDown();
}
} else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) {
switch (((VarSnippet) si).typeName()) {
@@ -700,15 +714,25 @@
/**
* If there are classes to load, loads by calling the execution engine.
- * @param classnames names of the classes to load.
+ * @param classbytecoes names of the classes to load.
*/
- private void load(Collection<String> classnames) {
- if (!classnames.isEmpty()) {
- state.executionControl().load(classnames);
+ private void load(Collection<ClassBytecodes> classbytecoes) {
+ if (!classbytecoes.isEmpty()) {
+ ClassBytecodes[] cbcs = classbytecoes.toArray(new ClassBytecodes[classbytecoes.size()]);
+ try {
+ state.executionControl().load(cbcs);
+ state.classTracker.markLoaded(cbcs);
+ } catch (ClassInstallException ex) {
+ state.classTracker.markLoaded(cbcs, ex.installed());
+ } catch (NotImplementedException ex) {
+ state.debug(ex, "Seriously?!? load not implemented");
+ } catch (EngineTerminationException ex) {
+ state.closeDown();
+ }
}
}
- private EvalException translateExecutionException(EvalException ex) {
+ private EvalException translateExecutionException(UserException ex) {
StackTraceElement[] raw = ex.getStackTrace();
int last = raw.length;
do {
@@ -739,7 +763,7 @@
if (msg.equals("<none>")) {
msg = null;
}
- return new EvalException(msg, ex.getExceptionClassName(), elems);
+ return new EvalException(msg, ex.causeExceptionClass(), elems);
}
private boolean isWrap(StackTraceElement ste) {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Wed Jul 20 23:19:09 2016 -0700
@@ -44,13 +44,15 @@
import java.util.function.Supplier;
import jdk.internal.jshell.debug.InternalDebugControl;
-import jdk.internal.jshell.jdi.FailOverExecutionControl;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static jdk.jshell.Util.expunge;
-import jdk.jshell.Snippet.Status;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
/**
* The JShell evaluation state engine. This is the central class in the JShell
@@ -90,17 +92,17 @@
final BiFunction<Snippet, Integer, String> idGenerator;
final List<String> extraRemoteVMOptions;
final List<String> extraCompilerOptions;
- final ExecutionControl executionControl;
+ final ExecutionControl.Generator executionControlGenerator;
private int nextKeyIndex = 1;
final Eval eval;
- private final Map<String, byte[]> classnameToBytes = new HashMap<>();
+ final ClassTracker classTracker;
private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
private boolean closed = false;
- private boolean executionControlLaunched = false;
+ private ExecutionControl executionControl = null;
private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n";
@@ -114,17 +116,18 @@
this.idGenerator = b.idGenerator;
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
this.extraCompilerOptions = b.extraCompilerOptions;
- this.executionControl = b.executionControl==null
- ? new FailOverExecutionControl(
- new JDIExecutionControl(),
- new JDIExecutionControl(false))
- : b.executionControl;
+ this.executionControlGenerator = b.executionControlGenerator==null
+ ? failOverExecutionControlGenerator(
+ JDIDefaultExecutionControl.launch(),
+ JDIDefaultExecutionControl.listen())
+ : b.executionControlGenerator;
this.maps = new SnippetMaps(this);
this.keyMap = new KeyMap(this);
this.outerMap = new OuterWrapMap(this);
this.taskFactory = new TaskFactory(this);
this.eval = new Eval(this);
+ this.classTracker = new ClassTracker();
}
/**
@@ -154,7 +157,7 @@
BiFunction<Snippet, Integer, String> idGenerator = null;
List<String> extraRemoteVMOptions = new ArrayList<>();
List<String> extraCompilerOptions = new ArrayList<>();
- ExecutionControl executionControl;
+ ExecutionControl.Generator executionControlGenerator;
Builder() { }
@@ -310,12 +313,12 @@
* Sets the custom engine for execution. Snippet execution will be
* provided by the specified {@link ExecutionControl} instance.
*
- * @param execEngine the execution engine
+ * @param executionControlGenerator the execution engine generator
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
- public Builder executionEngine(ExecutionControl execEngine) {
- this.executionControl = execEngine;
+ public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) {
+ this.executionControlGenerator = executionControlGenerator;
return this;
}
@@ -397,7 +400,8 @@
* be an event showing its status changed to OVERWRITTEN, this will not
* occur for dropped, rejected, or already overwritten declarations.
* <p>
- * The execution environment is out of process. If the evaluated code
+ * If execution environment is out of process, as is the default case, then
+ * if the evaluated code
* causes the execution environment to terminate, this {@code JShell}
* instance will be closed but the calling process and VM remain valid.
* @param input The input String to evaluate
@@ -447,8 +451,14 @@
* @param path the path to add to the classpath.
*/
public void addToClasspath(String path) {
- taskFactory.addToClasspath(path); // Compiler
- executionControl().addToClasspath(path); // Runtime
+ // Compiler
+ taskFactory.addToClasspath(path);
+ // Runtime
+ try {
+ executionControl().addToClasspath(path);
+ } catch (ExecutionControlException ex) {
+ debug(ex, "on addToClasspath(" + path + ")");
+ }
if (sourceCodeAnalysis != null) {
sourceCodeAnalysis.classpathChanged();
}
@@ -468,8 +478,13 @@
* catching the {@link ThreadDeath} exception.
*/
public void stop() {
- if (executionControl != null)
- executionControl.stop();
+ if (executionControl != null) {
+ try {
+ executionControl.stop();
+ } catch (ExecutionControlException ex) {
+ debug(ex, "on stop()");
+ }
+ }
}
/**
@@ -622,7 +637,15 @@
throw new IllegalArgumentException(
messageFormat("jshell.exc.var.not.valid", snippet, snippet.status()));
}
- String value = executionControl().varValue(snippet.classFullName(), snippet.name());
+ String value;
+ try {
+ value = executionControl().varValue(snippet.classFullName(), snippet.name());
+ } catch (EngineTerminationException ex) {
+ throw new IllegalStateException(ex.getMessage());
+ } catch (ExecutionControlException ex) {
+ debug(ex, "In varValue()");
+ return "[" + ex.getMessage() + "]";
+ }
return expunge(value);
}
@@ -696,43 +719,22 @@
}
@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 (!executionControlLaunched) {
+ if (executionControl == null) {
try {
- executionControlLaunched = true;
- executionControl.start(new ExecutionEnvImpl());
+ executionControl = executionControlGenerator.generate(new ExecutionEnvImpl());
} catch (Throwable ex) {
throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
}
@@ -740,10 +742,6 @@
return executionControl;
}
- void setClassnameToBytes(String classname, byte[] bytes) {
- classnameToBytes.put(classname, bytes);
- }
-
void debug(int flags, String format, Object... args) {
InternalDebugControl.debug(this, err, flags, format, args);
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Wed Jul 20 23:19:09 2016 -0700
@@ -223,7 +223,6 @@
new WrapSourceHandler(),
Util.join(new String[] {
"-Xshouldstop:at=FLOW", "-Xlint:unchecked",
- "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED",
"-proc:none"
}, extraArgs));
}
@@ -267,7 +266,7 @@
CompileTask(final Collection<OuterWrap> wraps) {
super(wraps.stream(), new WrapSourceHandler(),
- "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none", "-parameters");
+ "-Xlint:unchecked", "-proc:none", "-parameters");
}
boolean compile() {
@@ -286,7 +285,7 @@
}
List<String> list = new ArrayList<>();
for (OutputMemoryJavaFileObject fo : l) {
- state.setClassnameToBytes(fo.getName(), fo.getBytes());
+ state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes());
list.add(fo.getName());
}
return list;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Wed Jul 20 23:19:09 2016 -0700
@@ -32,11 +32,16 @@
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
+import jdk.jshell.ClassTracker.ClassInfo;
import jdk.jshell.Snippet.Kind;
import jdk.jshell.Snippet.Status;
import jdk.jshell.Snippet.SubKind;
import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.TaskFactory.CompileTask;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
@@ -75,7 +80,7 @@
private SnippetEvent replaceOldEvent;
private List<SnippetEvent> secondaryEvents;
private boolean isAttemptingCorral;
- private List<String> toRedefine;
+ private List<ClassInfo> toRedefine;
private boolean dependenciesNeeded;
Unit(JShell state, Snippet si, Snippet causalSnippet,
@@ -261,30 +266,29 @@
}
/**
- * Process the class information from the last compile.
- * Requires loading of returned list.
+ * Process the class information from the last compile. Requires loading of
+ * returned list.
+ *
* @return the list of classes to load
*/
- Stream<String> classesToLoad(List<String> classnames) {
+ Stream<ClassBytecodes> classesToLoad(List<String> classnames) {
toRedefine = new ArrayList<>();
- List<String> toLoad = new ArrayList<>();
+ List<ClassBytecodes> toLoad = new ArrayList<>();
if (status.isDefined() && !isImport()) {
// Classes should only be loaded/redefined if the compile left them
// in a defined state. Imports do not have code and are not loaded.
for (String cn : classnames) {
- switch (state.executionControl().getClassStatus(cn)) {
- case UNKNOWN:
- // If not loaded, add to the list of classes to load.
- toLoad.add(cn);
- dependenciesNeeded = true;
- break;
- case NOT_CURRENT:
- // If loaded but out of date, add to the list of classes to attempt redefine.
- toRedefine.add(cn);
- break;
- case CURRENT:
- // Loaded and current, so nothing to do
- break;
+ ClassInfo ci = state.classTracker.get(cn);
+ if (ci.isLoaded()) {
+ if (ci.isCurrent()) {
+ // nothing to do
+ } else {
+ toRedefine.add(ci);
+ }
+ } else {
+ // If not loaded, add to the list of classes to load.
+ toLoad.add(ci.toClassBytecodes());
+ dependenciesNeeded = true;
}
}
}
@@ -292,14 +296,30 @@
}
/**
- * Redefine classes needing redefine.
- * classesToLoad() must be called first.
+ * Redefine classes needing redefine. classesToLoad() must be called first.
+ *
* @return true if all redefines succeeded (can be vacuously true)
*/
boolean doRedefines() {
- return toRedefine.isEmpty()
- ? true
- : state.executionControl().redefine(toRedefine);
+ if (toRedefine.isEmpty()) {
+ return true;
+ }
+ ClassBytecodes[] cbcs = toRedefine.stream()
+ .map(ci -> ci.toClassBytecodes())
+ .toArray(size -> new ClassBytecodes[size]);
+ try {
+ state.executionControl().redefine(cbcs);
+ state.classTracker.markLoaded(cbcs);
+ return true;
+ } catch (ClassInstallException ex) {
+ state.classTracker.markLoaded(cbcs, ex.installed());
+ return false;
+ } catch (EngineTerminationException ex) {
+ state.closeDown();
+ return false;
+ } catch (NotImplementedException ex) {
+ return false;
+ }
}
void markForReplacement() {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DefaultLoaderDelegate.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.Map;
+import java.util.TreeMap;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+
+/**
+ * The standard implementation of {@link LoaderDelegate} using
+ * a {@link URLClassLoader}.
+ *
+ * @author Robert Field
+ */
+class DefaultLoaderDelegate implements LoaderDelegate {
+
+ private final RemoteClassLoader loader;
+ private final Map<String, Class<?>> klasses = new TreeMap<>();
+
+ class RemoteClassLoader extends URLClassLoader {
+
+ private final Map<String, byte[]> classObjects = new TreeMap<>();
+
+ RemoteClassLoader() {
+ super(new URL[0]);
+ }
+
+ void delare(String name, byte[] bytes) {
+ classObjects.put(name, bytes);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] b = classObjects.get(name);
+ if (b == null) {
+ return super.findClass(name);
+ }
+ return super.defineClass(name, b, 0, b.length, (CodeSource) null);
+ }
+
+ @Override
+ public void addURL(URL url) {
+ super.addURL(url);
+ }
+
+ }
+
+ public DefaultLoaderDelegate() {
+ this.loader = new RemoteClassLoader();
+ }
+
+ @Override
+ public void load(ClassBytecodes[] cbcs)
+ throws ClassInstallException, EngineTerminationException {
+ boolean[] loaded = new boolean[cbcs.length];
+ try {
+ for (ClassBytecodes cbc : cbcs) {
+ loader.delare(cbc.name(), cbc.bytecodes());
+ }
+ for (int i = 0; i < cbcs.length; ++i) {
+ ClassBytecodes cbc = cbcs[i];
+ Class<?> klass = loader.loadClass(cbc.name());
+ klasses.put(cbc.name(), klass);
+ loaded[i] = true;
+ // Get class loaded to the point of, at least, preparation
+ klass.getDeclaredMethods();
+ }
+ } catch (Throwable ex) {
+ throw new ClassInstallException("load: " + ex.getMessage(), loaded);
+ }
+ }
+
+
+ @Override
+ public void addToClasspath(String cp)
+ throws EngineTerminationException, InternalException {
+ try {
+ for (String path : cp.split(File.pathSeparator)) {
+ loader.addURL(new File(path).toURI().toURL());
+ }
+ } catch (Exception ex) {
+ throw new InternalException(ex.toString());
+ }
+ }
+
+ @Override
+ public void setClasspath(String path)
+ throws EngineTerminationException, InternalException {
+ throw new NotImplementedException("setClasspath: Not supported yet.");
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ Class<?> klass = klasses.get(name);
+ if (klass == null) {
+ throw new ClassNotFoundException(name + " not found");
+ } else {
+ return klass;
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+/**
+ * Read from an InputStream which has been packetized and write its contents
+ * to the named OutputStreams.
+ *
+ * @author Jan Lahoda
+ * @see Util#demultiplexInput(java.io.InputStream, java.io.OutputStream, java.io.OutputStream, java.io.OutputStream...)
+ */
+class DemultiplexInput extends Thread {
+
+ private final DataInputStream delegate;
+ private final PipeInputStream command;
+ private final Map<String, OutputStream> io;
+
+ DemultiplexInput(InputStream input, PipeInputStream command,
+ Map<String, OutputStream> io) {
+ super("output reader");
+ this.delegate = new DataInputStream(input);
+ this.command = command;
+ this.io = io;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ int nameLen = delegate.read();
+ if (nameLen == (-1)) {
+ break;
+ }
+ byte[] name = new byte[nameLen];
+ DemultiplexInput.this.delegate.readFully(name);
+ int dataLen = delegate.read();
+ byte[] data = new byte[dataLen];
+ DemultiplexInput.this.delegate.readFully(data);
+ String chan = new String(name, "UTF-8");
+ if (chan.equals("command")) {
+ for (byte b : data) {
+ command.write(Byte.toUnsignedInt(b));
+ }
+ } else {
+ OutputStream out = io.get(chan);
+ if (out == null) {
+ debug("Unexpected channel name: %s", chan);
+ } else {
+ out.write(data);
+ }
+ }
+ }
+ } catch (IOException ex) {
+ debug(ex, "Failed reading output");
+ } finally {
+ command.close();
+ }
+ }
+
+ /**
+ * Log debugging information. Arguments as for {@code printf}.
+ *
+ * @param format a format string as described in Format string syntax
+ * @param args arguments referenced by the format specifiers in the format
+ * string.
+ */
+ private void debug(String format, Object... args) {
+ // Reserved for future logging
+ }
+
+ /**
+ * Log a serious unexpected internal exception.
+ *
+ * @param ex the exception
+ * @param where a description of the context of the exception
+ */
+ private void debug(Throwable ex, String where) {
+ // Reserved for future logging
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DirectExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.SPIResolutionException;
+
+/**
+ * An {@link ExecutionControl} implementation that runs in the current process.
+ * May be used directly, or over a channel with
+ * {@link Util#forwardExecutionControl(ExecutionControl, java.io.ObjectInput, java.io.ObjectOutput) }.
+ *
+ * @author Robert Field
+ * @author Jan Lahoda
+ */
+public class DirectExecutionControl implements ExecutionControl {
+
+ private final LoaderDelegate loaderDelegate;
+
+ /**
+ * Creates an instance, delegating loader operations to the specified
+ * delegate.
+ *
+ * @param loaderDelegate the delegate to handle loading classes
+ */
+ public DirectExecutionControl(LoaderDelegate loaderDelegate) {
+ this.loaderDelegate = loaderDelegate;
+ }
+
+ /**
+ * Create an instance using the default class loading.
+ */
+ public DirectExecutionControl() {
+ this(new DefaultLoaderDelegate());
+ }
+
+ @Override
+ public void load(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException {
+ loaderDelegate.load(cbcs);
+ }
+
+ @Override
+ public void redefine(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException {
+ throw new NotImplementedException("redefine not supported");
+ }
+
+ @Override
+ public String invoke(String className, String methodName)
+ throws RunException, InternalException, EngineTerminationException {
+ Method doitMethod;
+ try {
+ Class<?> klass = findClass(className);
+ doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
+ doitMethod.setAccessible(true);
+ } catch (Throwable ex) {
+ throw new InternalException(ex.toString());
+ }
+
+ try {
+ clientCodeEnter();
+ String result = invoke(doitMethod);
+ System.out.flush();
+ return result;
+ } catch (RunException | InternalException | EngineTerminationException ex) {
+ throw ex;
+ } catch (SPIResolutionException ex) {
+ return throwConvertedInvocationException(ex);
+ } catch (InvocationTargetException ex) {
+ return throwConvertedInvocationException(ex.getCause());
+ } catch (Throwable ex) {
+ return throwConvertedOtherException(ex);
+ } finally {
+ clientCodeLeave();
+ }
+ }
+
+ @Override
+ public String varValue(String className, String varName)
+ throws RunException, EngineTerminationException, InternalException {
+ Object val;
+ try {
+ Class<?> klass = findClass(className);
+ Field var = klass.getDeclaredField(varName);
+ var.setAccessible(true);
+ val = var.get(null);
+ } catch (Throwable ex) {
+ throw new InternalException(ex.toString());
+ }
+
+ try {
+ clientCodeEnter();
+ return valueString(val);
+ } catch (Throwable ex) {
+ return throwConvertedInvocationException(ex);
+ } finally {
+ clientCodeLeave();
+ }
+ }
+
+ @Override
+ public void addToClasspath(String cp)
+ throws EngineTerminationException, InternalException {
+ loaderDelegate.addToClasspath(cp);
+ }
+
+ @Override
+ public void setClasspath(String path)
+ throws EngineTerminationException, InternalException {
+ loaderDelegate.setClasspath(path);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Not supported.
+ */
+ @Override
+ public void stop()
+ throws EngineTerminationException, InternalException {
+ throw new NotImplementedException("stop: Not supported.");
+ }
+
+ @Override
+ public Object extensionCommand(String command, Object arg)
+ throws RunException, EngineTerminationException, InternalException {
+ throw new NotImplementedException("Unknown command: " + command);
+ }
+
+ @Override
+ public void close() {
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * @param name the binary name of the class
+ * @return the Class Object
+ * @throws ClassNotFoundException if the class could not be found
+ */
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ return loaderDelegate.findClass(name);
+ }
+
+ /**
+ * Invoke the specified "doit-method", a static method with no parameters.
+ * The {@link DirectExecutionControl#invoke(java.lang.String, java.lang.String) }
+ * in this class will call this to invoke.
+ *
+ * @param doitMethod the Method to invoke
+ * @return the value or null
+ * @throws Exception any exceptions thrown by
+ * {@link java.lang.reflect.Method#invoke(Object, Object...) }
+ * or any {@link ExecutionControl.ExecutionControlException}
+ * to pass-through.
+ */
+ protected String invoke(Method doitMethod) throws Exception {
+ Object res = doitMethod.invoke(null, new Object[0]);
+ return valueString(res);
+ }
+
+ /**
+ * Converts the {@code Object} value from
+ * {@link ExecutionControl#invoke(String, String) } or
+ * {@link ExecutionControl#varValue(String, String) } to {@code String}.
+ *
+ * @param value the value to convert
+ * @return the {@code String} representation
+ */
+ protected static String valueString(Object value) {
+ if (value == null) {
+ return "null";
+ } else if (value instanceof String) {
+ return "\"" + (String) value + "\"";
+ } else if (value instanceof Character) {
+ return "'" + value + "'";
+ } else {
+ return value.toString();
+ }
+ }
+
+ /**
+ * Converts incoming exceptions in user code into instances of subtypes of
+ * {@link ExecutionControl.ExecutionControlException} and throws the
+ * converted exception.
+ *
+ * @param cause the exception to convert
+ * @return never returns as it always throws
+ * @throws ExecutionControl.RunException for normal exception occurrences
+ * @throws ExecutionControl.InternalException for internal problems
+ */
+ protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException {
+ if (cause instanceof SPIResolutionException) {
+ SPIResolutionException spire = (SPIResolutionException) cause;
+ throw new ResolutionException(spire.id(), spire.getStackTrace());
+ } else {
+ throw new UserException(cause.getMessage(), cause.getClass().getName(), cause.getStackTrace());
+ }
+ }
+
+ /**
+ * Converts incoming exceptions in agent code into instances of subtypes of
+ * {@link ExecutionControl.ExecutionControlException} and throws the
+ * converted exception.
+ *
+ * @param ex the exception to convert
+ * @return never returns as it always throws
+ * @throws ExecutionControl.RunException for normal exception occurrences
+ * @throws ExecutionControl.InternalException for internal problems
+ */
+ protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException {
+ throw new InternalException(ex.toString());
+ }
+
+ /**
+ * Marks entry into user code.
+ *
+ * @throws ExecutionControl.InternalException in unexpected failure cases
+ */
+ protected void clientCodeEnter() throws InternalException {
+ }
+
+ /**
+ * Marks departure from user code.
+ *
+ * @throws ExecutionControl.InternalException in unexpected failure cases
+ */
+ protected void clientCodeLeave() throws InternalException {
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+import jdk.jshell.spi.ExecutionControl.ResolutionException;
+import jdk.jshell.spi.ExecutionControl.StoppedException;
+import jdk.jshell.spi.ExecutionControl.UserException;
+import static jdk.jshell.execution.RemoteCodes.*;
+
+/**
+ * Forwards commands from the input to the specified {@link ExecutionControl}
+ * instance, then responses back on the output.
+ */
+class ExecutionControlForwarder {
+
+ private final ExecutionControl ec;
+ private final ObjectInput in;
+ private final ObjectOutput out;
+
+ ExecutionControlForwarder(ExecutionControl ec, ObjectInput in, ObjectOutput out) {
+ this.ec = ec;
+ this.in = in;
+ this.out = out;
+ }
+
+ private boolean writeSuccess() throws IOException {
+ writeStatus(RESULT_SUCCESS);
+ flush();
+ return true;
+ }
+
+ private boolean writeSuccessAndResult(String result) throws IOException {
+ writeStatus(RESULT_SUCCESS);
+ writeUTF(result);
+ flush();
+ return true;
+ }
+
+ private boolean writeSuccessAndResult(Object result) throws IOException {
+ writeStatus(RESULT_SUCCESS);
+ writeObject(result);
+ flush();
+ return true;
+ }
+
+ private void writeStatus(int status) throws IOException {
+ out.writeInt(status);
+ }
+
+ private void writeObject(Object o) throws IOException {
+ out.writeObject(o);
+ }
+
+ private void writeInt(int i) throws IOException {
+ out.writeInt(i);
+ }
+
+ private void writeUTF(String s) throws IOException {
+ if (s == null) {
+ s = "";
+ }
+ out.writeUTF(s);
+ }
+
+ private void flush() throws IOException {
+ out.flush();
+ }
+
+ private boolean processCommand() throws IOException {
+ try {
+ int prefix = in.readInt();
+ if (prefix != COMMAND_PREFIX) {
+ throw new EngineTerminationException("Invalid command prefix: " + prefix);
+ }
+ String cmd = in.readUTF();
+ switch (cmd) {
+ case CMD_LOAD: {
+ // Load a generated class file over the wire
+ ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
+ ec.load(cbcs);
+ return writeSuccess();
+ }
+ case CMD_REDEFINE: {
+ // Load a generated class file over the wire
+ ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
+ ec.redefine(cbcs);
+ return writeSuccess();
+ }
+ case CMD_INVOKE: {
+ // Invoke executable entry point in loaded code
+ String className = in.readUTF();
+ String methodName = in.readUTF();
+ String res = ec.invoke(className, methodName);
+ return writeSuccessAndResult(res);
+ }
+ case CMD_VAR_VALUE: {
+ // Retrieve a variable value
+ String className = in.readUTF();
+ String varName = in.readUTF();
+ String res = ec.varValue(className, varName);
+ return writeSuccessAndResult(res);
+ }
+ case CMD_ADD_CLASSPATH: {
+ // Append to the claspath
+ String cp = in.readUTF();
+ ec.addToClasspath(cp);
+ return writeSuccess();
+ }
+ case CMD_SET_CLASSPATH: {
+ // Set the claspath
+ String cp = in.readUTF();
+ ec.setClasspath(cp);
+ return writeSuccess();
+ }
+ case CMD_STOP: {
+ // Stop the current execution
+ try {
+ ec.stop();
+ } catch (Throwable ex) {
+ // JShell-core not waiting for a result, ignore
+ }
+ return true;
+ }
+ case CMD_CLOSE: {
+ // Terminate this process
+ try {
+ ec.close();
+ } catch (Throwable ex) {
+ // JShell-core not waiting for a result, ignore
+ }
+ return true;
+ }
+ default: {
+ Object arg = in.readObject();
+ Object res = ec.extensionCommand(cmd, arg);
+ return writeSuccessAndResult(res);
+ }
+ }
+ } catch (IOException ex) {
+ // handled by the outer level
+ throw ex;
+ } catch (EngineTerminationException ex) {
+ writeStatus(RESULT_TERMINATED);
+ writeUTF(ex.getMessage());
+ flush();
+ return false;
+ } catch (NotImplementedException ex) {
+ writeStatus(RESULT_NOT_IMPLEMENTED);
+ writeUTF(ex.getMessage());
+ flush();
+ return true;
+ } catch (InternalException ex) {
+ writeStatus(RESULT_INTERNAL_PROBLEM);
+ writeUTF(ex.getMessage());
+ flush();
+ return true;
+ } catch (ClassInstallException ex) {
+ writeStatus(RESULT_CLASS_INSTALL_EXCEPTION);
+ writeUTF(ex.getMessage());
+ writeObject(ex.installed());
+ flush();
+ return true;
+ } catch (UserException ex) {
+ writeStatus(RESULT_USER_EXCEPTION);
+ writeUTF(ex.getMessage());
+ writeUTF(ex.causeExceptionClass());
+ writeObject(ex.getStackTrace());
+ flush();
+ return true;
+ } catch (ResolutionException ex) {
+ writeStatus(RESULT_CORRALLED);
+ writeInt(ex.id());
+ writeObject(ex.getStackTrace());
+ flush();
+ return true;
+ } catch (StoppedException ex) {
+ writeStatus(RESULT_STOPPED);
+ flush();
+ return true;
+ } catch (Throwable ex) {
+ writeStatus(RESULT_TERMINATED);
+ writeUTF(ex.getMessage());
+ flush();
+ return false;
+ }
+ }
+
+ void commandLoop() {
+ try {
+ while (processCommand()) {
+ // condition is loop action
+ }
+ } catch (IOException ex) {
+ // drop out of loop
+ }
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Internal.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.jshell.execution;
-
-/**
- * Temp class needed so the package exists.
- */
-class Internal {}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import com.sun.jdi.BooleanValue;
+import com.sun.jdi.ClassNotLoadedException;
+import com.sun.jdi.Field;
+import com.sun.jdi.IncompatibleThreadStateException;
+import com.sun.jdi.InvalidTypeException;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.StackFrame;
+import com.sun.jdi.ThreadReference;
+import com.sun.jdi.VMDisconnectedException;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.remoteInput;
+
+/**
+ * The implementation of {@link jdk.jshell.spi.ExecutionControl} that the
+ * JShell-core uses by default.
+ * Launches a remote process -- the "remote agent".
+ * Interfaces to the remote agent over a socket and via JDI.
+ * Designed to work with {@link RemoteExecutionControl}.
+ *
+ * @author Robert Field
+ * @author Jan Lahoda
+ */
+public class JDIDefaultExecutionControl extends JDIExecutionControl {
+
+ private static final String REMOTE_AGENT = RemoteExecutionControl.class.getName();
+
+ private VirtualMachine vm;
+ private Process process;
+
+ private final Object STOP_LOCK = new Object();
+ private boolean userCodeRunning = false;
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code LaunchingConnector}.
+ *
+ * @return the generator
+ */
+ public static ExecutionControl.Generator launch() {
+ return env -> create(env, true);
+ }
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code ListeningConnector}.
+ *
+ * @return the generator
+ */
+ public static ExecutionControl.Generator listen() {
+ return env -> create(env, false);
+ }
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code ListeningConnector} or {@code LaunchingConnector}.
+ *
+ * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
+ * commands and results. This socket also transports the user
+ * input/output/error.
+ *
+ * @param env the context passed by
+ * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
+ * @return the channel
+ * @throws IOException if there are errors in set-up
+ */
+ private static JDIDefaultExecutionControl create(ExecutionEnv env, boolean isLaunch) throws IOException {
+ try (final ServerSocket listener = new ServerSocket(0)) {
+ // timeout after 60 seconds
+ listener.setSoTimeout(60000);
+ int port = listener.getLocalPort();
+
+ // Set-up the JDI connection
+ JDIInitiator jdii = new JDIInitiator(port,
+ env.extraRemoteVMOptions(), REMOTE_AGENT, isLaunch);
+ VirtualMachine vm = jdii.vm();
+ Process process = jdii.process();
+
+ // Forward input to the remote agent
+ Util.forwardInputToRemote(env.userIn(), process.getOutputStream(),
+ ex -> debug(ex, "input forwarding failure"));
+
+ List<Consumer<String>> deathListeners = new ArrayList<>();
+ deathListeners.add(s -> env.closeDown());
+ Util.detectJDIExitEvent(vm, s -> {
+ for (Consumer<String> h : deathListeners) {
+ h.accept(s);
+ }
+ });
+
+ // Set-up the commands/reslts on the socket. Piggy-back snippet
+ // output.
+ Socket socket = listener.accept();
+ // out before in -- match remote creation so we don't hang
+ ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
+ Map<String, OutputStream> io = new HashMap<>();
+ io.put("out", env.userOut());
+ io.put("err", env.userErr());
+ ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
+ return new JDIDefaultExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param cmdout the output for commands
+ * @param cmdin the input for responses
+ */
+ private JDIDefaultExecutionControl(ObjectOutput cmdout, ObjectInput cmdin,
+ VirtualMachine vm, Process process, List<Consumer<String>> deathListeners) {
+ super(cmdout, cmdin);
+ this.vm = vm;
+ this.process = process;
+ deathListeners.add(s -> disposeVM());
+ }
+
+ @Override
+ public String invoke(String classname, String methodname)
+ throws RunException,
+ EngineTerminationException, InternalException {
+ String res;
+ synchronized (STOP_LOCK) {
+ userCodeRunning = true;
+ }
+ try {
+ res = super.invoke(classname, methodname);
+ } finally {
+ synchronized (STOP_LOCK) {
+ userCodeRunning = false;
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Interrupts a running remote invoke by manipulating remote variables
+ * and sending a stop via JDI.
+ *
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ @Override
+ public void stop() throws EngineTerminationException, InternalException {
+ synchronized (STOP_LOCK) {
+ if (!userCodeRunning) {
+ return;
+ }
+
+ vm().suspend();
+ try {
+ OUTER:
+ for (ThreadReference thread : vm().allThreads()) {
+ // could also tag the thread (e.g. using name), to find it easier
+ for (StackFrame frame : thread.frames()) {
+ if (REMOTE_AGENT.equals(frame.location().declaringType().name()) &&
+ ( "invoke".equals(frame.location().method().name())
+ || "varValue".equals(frame.location().method().name()))) {
+ ObjectReference thiz = frame.thisObject();
+ Field inClientCode = thiz.referenceType().fieldByName("inClientCode");
+ Field expectingStop = thiz.referenceType().fieldByName("expectingStop");
+ Field stopException = thiz.referenceType().fieldByName("stopException");
+ if (((BooleanValue) thiz.getValue(inClientCode)).value()) {
+ thiz.setValue(expectingStop, vm().mirrorOf(true));
+ ObjectReference stopInstance = (ObjectReference) thiz.getValue(stopException);
+
+ vm().resume();
+ debug("Attempting to stop the client code...\n");
+ thread.stop(stopInstance);
+ thiz.setValue(expectingStop, vm().mirrorOf(false));
+ }
+
+ break OUTER;
+ }
+ }
+ }
+ } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
+ throw new InternalException("Exception on remote stop: " + ex);
+ } finally {
+ vm().resume();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ disposeVM();
+ }
+
+ private synchronized void disposeVM() {
+ try {
+ if (vm != null) {
+ vm.dispose(); // This could NPE, so it is caught below
+ vm = null;
+ }
+ } catch (VMDisconnectedException ex) {
+ // Ignore if already closed
+ } catch (Throwable ex) {
+ debug(ex, "disposeVM");
+ } finally {
+ if (process != null) {
+ process.destroy();
+ process = null;
+ }
+ }
+ }
+
+ @Override
+ protected synchronized VirtualMachine vm() throws EngineTerminationException {
+ if (vm == null) {
+ throw new EngineTerminationException("VM closed");
+ } else {
+ return vm;
+ }
+ }
+
+ /**
+ * Log debugging information. Arguments as for {@code printf}.
+ *
+ * @param format a format string as described in Format string syntax
+ * @param args arguments referenced by the format specifiers in the format
+ * string.
+ */
+ private static void debug(String format, Object... args) {
+ // Reserved for future logging
+ }
+
+ /**
+ * Log a serious unexpected internal exception.
+ *
+ * @param ex the exception
+ * @param where a description of the context of the exception
+ */
+ private static void debug(Throwable ex, String where) {
+ // Reserved for future logging
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIEventHandler.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+import java.util.function.Consumer;
+import com.sun.jdi.*;
+import com.sun.jdi.event.*;
+
+/**
+ * Handler of Java Debug Interface events.
+ * Adapted from jdb EventHandler.
+ * Only exit and disconnect events processed.
+ */
+class JDIEventHandler implements Runnable {
+
+ private final Thread thread;
+ private volatile boolean connected = true;
+ private boolean completed = false;
+ private final VirtualMachine vm;
+ private final Consumer<String> reportVMExit;
+
+ /**
+ * Creates an event handler. Start with {@code start()}.
+ *
+ * @param vm the virtual machine for which to handle events
+ * @param reportVMExit callback to report exit/disconnect
+ * (passed true if the VM has died)
+ */
+ JDIEventHandler(VirtualMachine vm, Consumer<String> reportVMExit) {
+ this.vm = vm;
+ this.reportVMExit = reportVMExit;
+ this.thread = new Thread(this, "event-handler");
+ }
+
+ /**
+ * Starts the event handler.
+ */
+ void start() {
+ thread.start();
+ }
+
+ synchronized void shutdown() {
+ connected = false; // force run() loop termination
+ thread.interrupt();
+ while (!completed) {
+ try {wait();} catch (InterruptedException exc) {}
+ }
+ }
+
+ @Override
+ public void run() {
+ EventQueue queue = vm.eventQueue();
+ while (connected) {
+ try {
+ EventSet eventSet = queue.remove();
+ boolean resumeStoppedApp = false;
+ EventIterator it = eventSet.eventIterator();
+ while (it.hasNext()) {
+ resumeStoppedApp |= handleEvent(it.nextEvent());
+ }
+
+ if (resumeStoppedApp) {
+ eventSet.resume();
+ }
+ } catch (InterruptedException exc) {
+ // Do nothing. Any changes will be seen at top of loop.
+ } catch (VMDisconnectedException discExc) {
+ handleDisconnectedException();
+ break;
+ }
+ }
+ synchronized (this) {
+ completed = true;
+ notifyAll();
+ }
+ }
+
+ private boolean handleEvent(Event event) {
+ handleExitEvent(event);
+ return true;
+ }
+
+ private void handleExitEvent(Event event) {
+ if (event instanceof VMDeathEvent) {
+ reportVMExit.accept("VM Died");
+ } else if (event instanceof VMDisconnectEvent) {
+ connected = false;
+ reportVMExit.accept("VM Disconnected");
+ } else {
+ // ignore everything else
+ }
+ }
+
+ private synchronized void handleDisconnectedException() {
+ /*
+ * A VMDisconnectedException has happened while dealing with
+ * another event. We need to flush the event queue, dealing only
+ * with exit events (VMDeath, VMDisconnect) so that we terminate
+ * correctly.
+ */
+ EventQueue queue = vm.eventQueue();
+ while (connected) {
+ try {
+ EventSet eventSet = queue.remove();
+ EventIterator iter = eventSet.eventIterator();
+ while (iter.hasNext()) {
+ handleExitEvent(iter.next());
+ }
+ } catch (InterruptedException exc) {
+ // ignore
+ } catch (InternalError exc) {
+ // ignore
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import com.sun.jdi.ReferenceType;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * Abstract JDI implementation of {@link jdk.jshell.spi.ExecutionControl}
+ */
+public abstract class JDIExecutionControl extends StreamingExecutionControl implements ExecutionControl {
+
+ /**
+ * Mapping from class names to JDI {@link ReferenceType}.
+ */
+ private final Map<String, ReferenceType> toReferenceType = new HashMap<>();
+
+ /**
+ * Create an instance.
+ * @param out the output from the remote agent
+ * @param in the input to the remote agent
+ */
+ protected JDIExecutionControl(ObjectOutput out, ObjectInput in) {
+ super(out, in);
+ }
+
+ /**
+ * Returns the JDI {@link VirtualMachine} instance.
+ *
+ * @return the virtual machine
+ * @throws EngineTerminationException if the VM is dead/disconnected
+ */
+ protected abstract VirtualMachine vm() throws EngineTerminationException;
+
+ /**
+ * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
+ * an in-place replacement of the classes (preserving class identity) --
+ * that is, existing references to the class do not need to be recompiled.
+ * This implementation uses JDI
+ * {@link com.sun.jdi.VirtualMachine#redefineClasses(java.util.Map) }.
+ * It will be unsuccessful if
+ * the signature of the class has changed (see the JDI spec). The
+ * JShell-core is designed to adapt to unsuccessful redefine.
+ */
+ @Override
+ public void redefine(ClassBytecodes[] cbcs)
+ throws ClassInstallException, EngineTerminationException {
+ try {
+ // Convert to the JDI ReferenceType to class bytes map form needed
+ // by JDI.
+ VirtualMachine vm = vm();
+ Map<ReferenceType, byte[]> rmp = Stream.of(cbcs)
+ .collect(toMap(
+ cbc -> referenceType(vm, cbc.name()),
+ cbc -> cbc.bytecodes()));
+ // Attempt redefine. Throws exceptions on failure.
+ vm().redefineClasses(rmp);
+ } catch (EngineTerminationException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ClassInstallException("redefine: " + ex.getMessage(), new boolean[cbcs.length]);
+ }
+ }
+
+ /**
+ * Returns the JDI {@link ReferenceType} corresponding to the specified
+ * class name.
+ *
+ * @param vm the current JDI {@link VirtualMachine} as returned by
+ * {@code vm()}
+ * @param name the class name to look-up
+ * @return the corresponding {@link ReferenceType}
+ */
+ protected ReferenceType referenceType(VirtualMachine vm, String name) {
+ return toReferenceType.computeIfAbsent(name, n -> nameToRef(vm, n));
+ }
+
+ private static ReferenceType nameToRef(VirtualMachine vm, String name) {
+ List<ReferenceType> rtl = vm.classesByName(name);
+ if (rtl.size() != 1) {
+ return null;
+ }
+ return rtl.get(0);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIInitiator.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.LaunchingConnector;
+import com.sun.jdi.connect.ListeningConnector;
+
+/**
+ * Sets up a JDI connection, providing the resulting JDI {@link VirtualMachine}
+ * and the {@link Process} the remote agent is running in.
+ */
+public class JDIInitiator {
+
+ private VirtualMachine vm;
+ private Process process = null;
+ private final Connector connector;
+ private final String remoteAgent;
+ private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
+
+ /**
+ * Start the remote agent and establish a JDI connection to it.
+ *
+ * @param port the socket port for (non-JDI) commands
+ * @param remoteVMOptions any user requested VM options
+ * @param remoteAgent full class name of remote agent to launch
+ * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
+ * otherwise we start explicitly and use ListeningConnector
+ */
+ public JDIInitiator(int port, List<String> remoteVMOptions,
+ String remoteAgent, boolean isLaunch) {
+ this.remoteAgent = remoteAgent;
+ String connectorName
+ = isLaunch
+ ? "com.sun.jdi.CommandLineLaunch"
+ : "com.sun.jdi.SocketListen";
+ this.connector = findConnector(connectorName);
+ if (connector == null) {
+ throw new IllegalArgumentException("No connector named: " + connectorName);
+ }
+ Map<String, String> argumentName2Value
+ = isLaunch
+ ? launchArgs(port, String.join(" ", remoteVMOptions))
+ : new HashMap<>();
+ this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
+ this.vm = isLaunch
+ ? launchTarget()
+ : listenTarget(port, remoteVMOptions);
+
+ }
+
+ /**
+ * Returns the resulting {@code VirtualMachine} instance.
+ *
+ * @return the virtual machine
+ */
+ public VirtualMachine vm() {
+ return vm;
+ }
+
+ /**
+ * Returns the launched process.
+ *
+ * @return the remote agent process
+ */
+ public Process process() {
+ return process;
+ }
+
+ /* launch child target vm */
+ private VirtualMachine launchTarget() {
+ LaunchingConnector launcher = (LaunchingConnector) connector;
+ try {
+ VirtualMachine new_vm = launcher.launch(connectorArgs);
+ process = new_vm.process();
+ return new_vm;
+ } catch (Exception ex) {
+ reportLaunchFail(ex, "launch");
+ }
+ return null;
+ }
+
+ /**
+ * Directly launch the remote agent and connect JDI to it with a
+ * ListeningConnector.
+ */
+ private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) {
+ ListeningConnector listener = (ListeningConnector) connector;
+ try {
+ // Start listening, get the JDI connection address
+ String addr = listener.startListening(connectorArgs);
+ debug("Listening at address: " + addr);
+
+ // Launch the RemoteAgent requesting a connection on that address
+ String javaHome = System.getProperty("java.home");
+ List<String> args = new ArrayList<>();
+ args.add(javaHome == null
+ ? "java"
+ : javaHome + File.separator + "bin" + File.separator + "java");
+ args.add("-agentlib:jdwp=transport=" + connector.transport().name() +
+ ",address=" + addr);
+ args.addAll(remoteVMOptions);
+ args.add(remoteAgent);
+ args.add("" + port);
+ ProcessBuilder pb = new ProcessBuilder(args);
+ process = pb.start();
+
+ // Forward out, err, and in
+ // Accept the connection from the remote agent
+ vm = listener.accept(connectorArgs);
+ listener.stopListening(connectorArgs);
+ return vm;
+ } catch (Exception ex) {
+ reportLaunchFail(ex, "listen");
+ }
+ return null;
+ }
+
+ private Connector findConnector(String name) {
+ for (Connector cntor
+ : Bootstrap.virtualMachineManager().allConnectors()) {
+ if (cntor.name().equals(name)) {
+ return cntor;
+ }
+ }
+ return null;
+ }
+
+ private Map<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;
+ }
+
+ /**
+ * The JShell specific Connector args for the LaunchingConnector.
+ *
+ * @param portthe socket port for (non-JDI) commands
+ * @param remoteVMOptions any user requested VM options
+ * @return the argument map
+ */
+ private Map<String, String> launchArgs(int port, String remoteVMOptions) {
+ Map<String, String> argumentName2Value = new HashMap<>();
+ argumentName2Value.put("main", remoteAgent + " " + port);
+ argumentName2Value.put("options", remoteVMOptions);
+ return argumentName2Value;
+ }
+
+ private void reportLaunchFail(Exception ex, String context) {
+ throw new InternalError("Failed remote " + context + ": " + connector +
+ " -- " + connectorArgs, ex);
+ }
+
+ /**
+ * Log debugging information. Arguments as for {@code printf}.
+ *
+ * @param format a format string as described in Format string syntax
+ * @param args arguments referenced by the format specifiers in the format
+ * string.
+ */
+ private void debug(String format, Object... args) {
+ // Reserved for future logging
+ }
+
+ /**
+ * Log a serious unexpected internal exception.
+ *
+ * @param ex the exception
+ * @param where a description of the context of the exception
+ */
+ private void debug(Throwable ex, String where) {
+ // Reserved for future logging
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LoaderDelegate.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+
+/**
+ * This interface specifies the loading specific subset of
+ * {@link jdk.jshell.spi.ExecutionControl}. For use in encapsulating the
+ * {@link java.lang.ClassLoader} implementation.
+ */
+public interface LoaderDelegate {
+
+ /**
+ * Attempts to load new classes.
+ *
+ * @param cbcs the class name and bytecodes to load
+ * @throws ClassInstallException exception occurred loading the classes,
+ * some or all were not loaded
+ * @throws NotImplementedException if not implemented
+ * @throws EngineTerminationException the execution engine has terminated
+ */
+ void load(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+ /**
+ * Adds the path to the execution class path.
+ *
+ * @param path the path to add
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ void addToClasspath(String path)
+ throws EngineTerminationException, InternalException;
+
+ /**
+ * Sets the execution class path to the specified path.
+ *
+ * @param path the path to add
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ void setClasspath(String path)
+ throws EngineTerminationException, InternalException;
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * @param name the binary name of the class
+ * @return the Class Object
+ * @throws ClassNotFoundException if the class could not be found
+ */
+ Class<?> findClass(String name) throws ClassNotFoundException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+import jdk.jshell.spi.ExecutionControl;
+
+/**
+ * An implementation of {@link jdk.jshell.spi.ExecutionControl} which executes
+ * in the same JVM as the JShell-core.
+ *
+ * @author Grigory Ptashko
+ */
+public class LocalExecutionControl extends DirectExecutionControl {
+
+ private final Object STOP_LOCK = new Object();
+ private boolean userCodeRunning = false;
+ private ThreadGroup execThreadGroup;
+
+ /**
+ * Creates a local ExecutionControl instance.
+ *
+ * @return the generator
+ */
+ public static ExecutionControl.Generator create() {
+ return env -> new LocalExecutionControl();
+ }
+
+ /**
+ * Creates an instance, delegating loader operations to the specified
+ * delegate.
+ *
+ * @param loaderDelegate the delegate to handle loading classes
+ */
+ public LocalExecutionControl(LoaderDelegate loaderDelegate) {
+ super(loaderDelegate);
+ }
+
+ /**
+ * Create an instance using the default class loading.
+ */
+ public LocalExecutionControl() {
+ }
+
+ @Override
+ protected String invoke(Method doitMethod) throws Exception {
+ 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) {
+ if (thread != null) {
+ thread.join();
+ }
+ }
+
+ if (stopped.get()) {
+ throw new StoppedException();
+ }
+
+ if (iteEx.get() != null) {
+ throw iteEx.get();
+ } else if (nmeEx.get() != null) {
+ throw nmeEx.get();
+ } else if (iaeEx.get() != null) {
+ throw iaeEx.get();
+ }
+
+ return valueString(res[0]);
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void stop() throws EngineTerminationException, InternalException {
+ synchronized (STOP_LOCK) {
+ if (!userCodeRunning) {
+ return;
+ }
+ if (execThreadGroup == null) {
+ throw new InternalException("Process-local code snippets thread group is null. Aborting stop.");
+ }
+
+ execThreadGroup.stop();
+ }
+ }
+
+ @Override
+ protected void clientCodeEnter() {
+ synchronized (STOP_LOCK) {
+ userCodeRunning = true;
+ }
+ }
+
+ @Override
+ protected void clientCodeLeave() {
+ synchronized (STOP_LOCK) {
+ userCodeRunning = false;
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Packetize an OutputStream, dividing it into named channels.
+ *
+ * @author Jan Lahoda
+ */
+class MultiplexingOutputStream extends OutputStream {
+
+ private static final int PACKET_SIZE = 127;
+ private final byte[] name;
+ private final OutputStream delegate;
+
+ MultiplexingOutputStream(String name, OutputStream delegate) {
+ try {
+ this.name = name.getBytes("UTF-8");
+ this.delegate = delegate;
+ } catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException(ex); //should not happen
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ synchronized (delegate) {
+ delegate.write(name.length); //assuming the len is small enough to fit into byte
+ delegate.write(name);
+ delegate.write(1);
+ delegate.write(b);
+ delegate.flush();
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ synchronized (delegate) {
+ int i = 0;
+ while (len > 0) {
+ int size = Math.min(PACKET_SIZE, len);
+ delegate.write(name.length); //assuming the len is small enough to fit into byte
+ delegate.write(name);
+ delegate.write(size);
+ delegate.write(b, off + i, size);
+ i += size;
+ len -= size;
+ }
+ delegate.flush();
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ delegate.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ delegate.close();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.InputStream;
+
+/**
+ *
+ * @author Jan Lahoda
+ */
+class PipeInputStream extends InputStream {
+
+ private static final int INITIAL_SIZE = 128;
+ private int[] buffer = new int[INITIAL_SIZE];
+ private int start;
+ private int end;
+ private boolean closed;
+
+ @Override
+ public synchronized int read() {
+ while (start == end) {
+ if (closed) {
+ return -1;
+ }
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ //ignore
+ }
+ }
+ try {
+ return buffer[start];
+ } finally {
+ start = (start + 1) % buffer.length;
+ }
+ }
+
+ public synchronized void write(int b) {
+ if (closed) {
+ throw new IllegalStateException("Already closed.");
+ }
+ int newEnd = (end + 1) % buffer.length;
+ if (newEnd == start) {
+ //overflow:
+ int[] newBuffer = new int[buffer.length * 2];
+ int rightPart = (end > start ? end : buffer.length) - start;
+ int leftPart = end > start ? 0 : start - 1;
+ System.arraycopy(buffer, start, newBuffer, 0, rightPart);
+ System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
+ buffer = newBuffer;
+ start = 0;
+ end = rightPart + leftPart;
+ newEnd = end + 1;
+ }
+ buffer[end] = b;
+ end = newEnd;
+ notifyAll();
+ }
+
+ @Override
+ public synchronized void close() {
+ closed = true;
+ notifyAll();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteCodes.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.execution;
+
+/**
+ * Communication constants shared between the main process and the remote
+ * execution process. These are not enums to allow for future expansion, and
+ * remote/local of different versions.
+ *
+ * @author Robert Field
+ */
+class RemoteCodes {
+
+ /**
+ * Command prefix markers.
+ */
+ static final int COMMAND_PREFIX = 0xC03DC03D;
+
+ // Command codes
+
+ /**
+ * Exit the the agent.
+ */
+ static final String CMD_CLOSE = "CMD_CLOSE";
+ /**
+ * Load classes.
+ */
+ static final String CMD_LOAD = "CMD_LOAD";
+ /**
+ * Redefine classes.
+ */
+ static final String CMD_REDEFINE = "CMD_REDEFINE";
+ /**
+ * Invoke a method.
+ */
+ static final String CMD_INVOKE = "CMD_INVOKE";
+ /**
+ * Retrieve the value of a variable.
+ */
+ static final String CMD_VAR_VALUE = "CMD_VAR_VALUE";
+ /**
+ * Add to the class-path.
+ */
+ static final String CMD_ADD_CLASSPATH = "CMD_ADD_CLASSPATH";
+ /**
+ * Set the class-path.
+ */
+ static final String CMD_SET_CLASSPATH = "CMD_SET_CLASSPATH";
+ /**
+ * Stop an invoke.
+ */
+ static final String CMD_STOP = "CMD_STOP";
+
+ // Return result codes
+
+ /**
+ * The command succeeded.
+ */
+ static final int RESULT_SUCCESS = 100;
+ /**
+ * Unbidden execution engine termination.
+ */
+ static final int RESULT_TERMINATED = 101;
+ /**
+ * Command not implemented.
+ */
+ static final int RESULT_NOT_IMPLEMENTED = 102;
+ /**
+ * The command failed.
+ */
+ static final int RESULT_INTERNAL_PROBLEM = 103;
+ /**
+ * User exception encountered.
+ */
+ static final int RESULT_USER_EXCEPTION = 104;
+ /**
+ * Corralled code exception encountered.
+ */
+ static final int RESULT_CORRALLED = 105;
+ /**
+ * Exception encountered during class load/redefine.
+ */
+ static final int RESULT_CLASS_INSTALL_EXCEPTION = 106;
+ /**
+ * The invoke has been stopped.
+ */
+ static final int RESULT_STOPPED = 107;
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.net.Socket;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import jdk.jshell.spi.ExecutionControl;
+import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
+
+/**
+ * The remote agent runs in the execution process (separate from the main JShell
+ * process). This agent loads code over a socket from the main JShell process,
+ * executes the code, and other misc, Specialization of
+ * {@link DirectExecutionControl} which adds stop support controlled by
+ * an external process. Designed to work with {@link JDIDefaultExecutionControl}.
+ *
+ * @author Jan Lahoda
+ * @author Robert Field
+ */
+public class RemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
+
+ /**
+ * Launch the agent, connecting to the JShell-core over the socket specified
+ * in the command-line argument.
+ *
+ * @param args standard command-line arguments, expectation is the socket
+ * number is the only argument
+ * @throws Exception any unexpected exception
+ */
+ public static void main(String[] args) throws Exception {
+ String loopBack = null;
+ Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+ InputStream inStream = socket.getInputStream();
+ OutputStream outStream = socket.getOutputStream();
+ Map<String, Consumer<OutputStream>> chans = new HashMap<>();
+ chans.put("out", st -> System.setOut(new PrintStream(st, true)));
+ chans.put("err", st -> System.setErr(new PrintStream(st, true)));
+ forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, chans);
+ }
+
+ // These three variables are used by the main JShell process in interrupting
+ // the running process. Access is via JDI, so the reference is not visible
+ // to code inspection.
+ private boolean inClientCode; // Queried by the main process (in superclass)
+ private boolean expectingStop; // Set by the main process
+// Set by the main process
+
+ // thrown by the main process via JDI:
+ private final StopExecutionException stopException = new StopExecutionException();
+
+ /**
+ * Creates an instance, delegating loader operations to the specified
+ * delegate.
+ *
+ * @param loaderDelegate the delegate to handle loading classes
+ */
+ public RemoteExecutionControl(LoaderDelegate loaderDelegate) {
+ super(loaderDelegate);
+ }
+
+ /**
+ * Create an instance using the default class loading.
+ */
+ public RemoteExecutionControl() {
+ }
+
+ @Override
+ public void stop() throws EngineTerminationException, InternalException {
+ // handled by JDI
+ }
+
+ // Overridden only so this stack frame is seen
+ @Override
+ protected String invoke(Method doitMethod) throws Exception {
+ return super.invoke(doitMethod);
+ }
+
+ // Overridden only so this stack frame is seen
+ @Override
+ public String varValue(String className, String varName) throws RunException, EngineTerminationException, InternalException {
+ return super.varValue(className, varName);
+ }
+
+ @Override
+ protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException {
+ if (cause instanceof StopExecutionException) {
+ expectingStop = false;
+ throw new StoppedException();
+ } else {
+ return super.throwConvertedInvocationException(cause);
+ }
+ }
+
+ @Override
+ protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException {
+ if (ex instanceof StopExecutionException ||
+ ex.getCause() instanceof StopExecutionException) {
+ expectingStop = false;
+ throw new StoppedException();
+ }
+ return super.throwConvertedOtherException(ex);
+ }
+
+ @Override
+ protected void clientCodeEnter() {
+ expectingStop = false;
+ inClientCode = true;
+ }
+
+ @Override
+ protected void clientCodeLeave() throws InternalException {
+ inClientCode = false;
+ while (expectingStop) {
+ try {
+ Thread.sleep(0);
+ } catch (InterruptedException ex) {
+ throw new InternalException("*** Sleep interrupted while waiting for stop exception: " + ex);
+ }
+ }
+ }
+
+ @SuppressWarnings("serial") // serialVersionUID intentionally omitted
+ private class StopExecutionException extends ThreadDeath {
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ return this;
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import jdk.jshell.JShellException;
+import jdk.jshell.spi.ExecutionControl;
+import static jdk.jshell.execution.RemoteCodes.*;
+
+/**
+ * An implementation of the {@link jdk.jshell.spi.ExecutionControl}
+ * execution engine SPI which streams requests to a remote agent where
+ * execution takes place.
+ *
+ * @author Robert Field
+ */
+public class StreamingExecutionControl implements ExecutionControl {
+
+ private final ObjectOutput out;
+ private final ObjectInput in;
+
+ /**
+ * Creates an instance.
+ *
+ * @param out the output for commands
+ * @param in the input for command responses
+ */
+ public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
+ this.out = out;
+ this.in = in;
+ }
+
+ @Override
+ public void load(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException {
+ try {
+ // Send a load command to the remote agent.
+ writeCommand(CMD_LOAD);
+ out.writeObject(cbcs);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportClassInstallResult();
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote load: " + ex);
+ }
+ }
+
+ @Override
+ public void redefine(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException {
+ try {
+ // Send a load command to the remote agent.
+ writeCommand(CMD_REDEFINE);
+ out.writeObject(cbcs);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportClassInstallResult();
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote redefine: " + ex);
+ }
+ }
+
+ @Override
+ public String invoke(String classname, String methodname)
+ throws RunException, EngineTerminationException, InternalException {
+ try {
+ // Send the invoke command to the remote agent.
+ writeCommand(CMD_INVOKE);
+ out.writeUTF(classname);
+ out.writeUTF(methodname);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportExecutionResult();
+ String result = in.readUTF();
+ return result;
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote invoke: " + ex);
+ }
+ }
+
+ @Override
+ public String varValue(String classname, String varname)
+ throws RunException, EngineTerminationException, InternalException {
+ try {
+ // Send the variable-value command to the remote agent.
+ writeCommand(CMD_VAR_VALUE);
+ out.writeUTF(classname);
+ out.writeUTF(varname);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportExecutionResult();
+ String result = in.readUTF();
+ return result;
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote varValue: " + ex);
+ }
+ }
+
+
+ @Override
+ public void addToClasspath(String path)
+ throws EngineTerminationException, InternalException {
+ try {
+ // Send the classpath addition command to the remote agent.
+ writeCommand(CMD_ADD_CLASSPATH);
+ out.writeUTF(path);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportClassSimpleResult();
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote add to classpath: " + ex);
+ }
+ }
+
+ @Override
+ public void setClasspath(String path)
+ throws EngineTerminationException, InternalException {
+ try {
+ // Send the classpath addition command to the remote agent.
+ writeCommand(CMD_SET_CLASSPATH);
+ out.writeUTF(path);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportClassSimpleResult();
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote set classpath: " + ex);
+ }
+ }
+
+ @Override
+ public void stop()
+ throws EngineTerminationException, InternalException {
+ try {
+ // Send the variable-value command to the remote agent.
+ writeCommand(CMD_STOP);
+ out.flush();
+ } catch (IOException ex) {
+ throw new EngineTerminationException("Exception writing remote stop: " + ex);
+ }
+ }
+
+ @Override
+ public Object extensionCommand(String command, Object arg)
+ throws RunException, EngineTerminationException, InternalException {
+ try {
+ writeCommand(command);
+ out.writeObject(arg);
+ out.flush();
+ // Retrieve and report results from the remote agent.
+ readAndReportExecutionResult();
+ Object result = in.readObject();
+ return result;
+ } catch (IOException | ClassNotFoundException ex) {
+ throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
+ + command + " -- " + ex);
+ }
+ }
+
+ /**
+ * Closes the execution engine. Send an exit command to the remote agent.
+ */
+ @Override
+ public void close() {
+ try {
+ writeCommand(CMD_CLOSE);
+ out.flush();
+ } catch (IOException ex) {
+ // ignore;
+ }
+ }
+
+ private void writeCommand(String cmd) throws IOException {
+ out.writeInt(COMMAND_PREFIX);
+ out.writeUTF(cmd);
+ }
+
+ /**
+ * Reports results from a remote agent command that does not expect
+ * exceptions.
+ */
+ private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException {
+ try {
+ int status = in.readInt();
+ switch (status) {
+ case RESULT_SUCCESS:
+ return;
+ case RESULT_NOT_IMPLEMENTED: {
+ String message = in.readUTF();
+ throw new NotImplementedException(message);
+ }
+ case RESULT_INTERNAL_PROBLEM: {
+ String message = in.readUTF();
+ throw new InternalException(message);
+ }
+ case RESULT_TERMINATED: {
+ String message = in.readUTF();
+ throw new EngineTerminationException(message);
+ }
+ default: {
+ throw new EngineTerminationException("Bad remote result code: " + status);
+ }
+ }
+ } catch (IOException ex) {
+ throw new EngineTerminationException(ex.toString());
+ }
+ }
+
+ /**
+ * Reports results from a remote agent command that does not expect
+ * exceptions.
+ */
+ private void readAndReportClassInstallResult() throws ClassInstallException,
+ NotImplementedException, EngineTerminationException {
+ try {
+ int status = in.readInt();
+ switch (status) {
+ case RESULT_SUCCESS:
+ return;
+ case RESULT_NOT_IMPLEMENTED: {
+ String message = in.readUTF();
+ throw new NotImplementedException(message);
+ }
+ case RESULT_CLASS_INSTALL_EXCEPTION: {
+ String message = in.readUTF();
+ boolean[] loaded = (boolean[]) in.readObject();
+ throw new ClassInstallException(message, loaded);
+ }
+ case RESULT_TERMINATED: {
+ String message = in.readUTF();
+ throw new EngineTerminationException(message);
+ }
+ default: {
+ throw new EngineTerminationException("Bad remote result code: " + status);
+ }
+ }
+ } catch (IOException | ClassNotFoundException ex) {
+ throw new EngineTerminationException(ex.toString());
+ }
+ }
+
+ /**
+ * Reports results from a remote agent command that expects runtime
+ * exceptions.
+ *
+ * @return true if successful
+ * @throws IOException if the connection has dropped
+ * @throws JShellException {@link jdk.jshell.EvalException}, if a user
+ * exception was encountered on invoke;
+ * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved
+ * reference was encountered
+ * @throws java.lang.ClassNotFoundException
+ */
+ private void readAndReportExecutionResult() throws RunException,
+ EngineTerminationException, InternalException {
+ try {
+ int status = in.readInt();
+ switch (status) {
+ case RESULT_SUCCESS:
+ return;
+ case RESULT_NOT_IMPLEMENTED: {
+ String message = in.readUTF();
+ throw new NotImplementedException(message);
+ }
+ case RESULT_USER_EXCEPTION: {
+ // A user exception was encountered.
+ String message = in.readUTF();
+ String exceptionClassName = in.readUTF();
+ StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
+ throw new UserException(message, exceptionClassName, elems);
+ }
+ case RESULT_CORRALLED: {
+ // An unresolved reference was encountered.
+ int id = in.readInt();
+ StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
+ ResolutionException re = new ResolutionException(id, elems);
+ throw re;
+ }
+ case RESULT_STOPPED: {
+ // Execution was aborted by the stop()
+ throw new StoppedException();
+ }
+ case RESULT_INTERNAL_PROBLEM: {
+ // An internal error has occurred.
+ String message = in.readUTF();
+ throw new InternalException(message);
+ }
+ case RESULT_TERMINATED: {
+ String message = in.readUTF();
+ throw new EngineTerminationException(message);
+ }
+ default: {
+ throw new EngineTerminationException("Bad remote result code: " + status);
+ }
+ }
+ } catch (IOException | ClassNotFoundException ex) {
+ throw new EngineTerminationException(ex.toString());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import jdk.jshell.spi.ExecutionEnv;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+
+
+/**
+ * Miscellaneous utility methods for setting-up implementations of
+ * {@link ExecutionControl}. Particularly implementations with remote
+ * execution.
+ *
+ * @author Jan Lahoda
+ * @author Robert Field
+ */
+public class Util {
+
+ // never instanciated
+ private Util() {}
+
+ /**
+ * Create a composite {@link ExecutionControl.Generator} instance that, when
+ * generating, will try each specified generator until successfully creating
+ * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
+ * first exception.
+ *
+ * @param gec0 the first instance to try
+ * @param gecs the second through Nth instance to try
+ * @return the fail-over generator
+ */
+ public static ExecutionControl.Generator failOverExecutionControlGenerator(
+ ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
+ return (ExecutionEnv env) -> {
+ Throwable thrown;
+ try {
+ return gec0.generate(env);
+ } catch (Throwable ex) {
+ thrown = ex;
+ }
+ for (ExecutionControl.Generator gec : gecs) {
+ try {
+ return gec.generate(env);
+ } catch (Throwable ignore) {
+ // only care about the first, and only if they all fail
+ }
+ }
+ throw thrown;
+ };
+ }
+
+ /**
+ * Forward commands from the input to the specified {@link ExecutionControl}
+ * instance, then responses back on the output.
+ * @param ec the direct instance of {@link ExecutionControl} to process commands
+ * @param in the command input
+ * @param out the command response output
+ */
+ public static void forwardExecutionControl(ExecutionControl ec,
+ ObjectInput in, ObjectOutput out) {
+ new ExecutionControlForwarder(ec, in, out).commandLoop();
+ }
+
+ /**
+ * Forward commands from the input to the specified {@link ExecutionControl}
+ * instance, then responses back on the output.
+ * @param ec the direct instance of {@link ExecutionControl} to process commands
+ * @param inStream the stream from which to create the command input
+ * @param outStream the stream that will carry {@code System.out},
+ * {@code System.err}, any specified auxiliary channels, and the
+ * command response output.
+ * @param streamMap a map between names of additional streams to carry and setters
+ * for the stream
+ * @throws IOException if there are errors using the passed streams
+ */
+ public static void forwardExecutionControlAndIO(ExecutionControl ec,
+ InputStream inStream, OutputStream outStream,
+ Map<String, Consumer<OutputStream>> streamMap) throws IOException {
+ ObjectInputStream cmdIn = new ObjectInputStream(inStream);
+ for (Entry<String, Consumer<OutputStream>> e : streamMap.entrySet()) {
+ e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
+ }
+ ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream));
+ forwardExecutionControl(ec, cmdIn, cmdOut);
+ }
+
+ static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
+ return new MultiplexingOutputStream(label, outputStream);
+ }
+
+ /**
+ * Reads from an InputStream which has been packetized and write its contents
+ * to the out and err OutputStreams; Copies the command stream.
+ * @param input the packetized input stream
+ * @param streamMap a map between stream names and the output streams to forward
+ * @return the command stream
+ * @throws IOException if setting up the streams raised an exception
+ */
+ public static ObjectInput remoteInput(InputStream input,
+ Map<String, OutputStream> streamMap) throws IOException {
+ PipeInputStream commandIn = new PipeInputStream();
+ new DemultiplexInput(input, commandIn, streamMap).start();
+ return new ObjectInputStream(commandIn);
+ }
+
+ /**
+ * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
+ * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
+ * {@code unbiddenExitHandler}.
+ *
+ * @param vm the virtual machine to check
+ * @param unbiddenExitHandler the handler, which will accept the exit
+ * information
+ */
+ public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
+ if (vm.canBeModified()) {
+ new JDIEventHandler(vm, unbiddenExitHandler).start();
+ }
+ }
+
+ /**
+ * Creates a Thread that will ship all input to the remote agent.
+ *
+ * @param inputStream the user input
+ * @param outStream the input to the remote agent
+ * @param handler a failure handler
+ */
+ public static void forwardInputToRemote(final InputStream inputStream,
+ final OutputStream outStream, final Consumer<Exception> handler) {
+ Thread thr = new Thread("input reader") {
+ @Override
+ public void run() {
+ try {
+ byte[] buf = new byte[256];
+ int cnt;
+ while ((cnt = inputStream.read(buf)) != -1) {
+ outStream.write(buf, 0, cnt);
+ outStream.flush();
+ }
+ } catch (Exception ex) {
+ handler.accept(ex);
+ }
+ }
+ };
+ thr.setPriority(Thread.MAX_PRIORITY - 1);
+ thr.start();
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Wed Jul 20 23:19:09 2016 -0700
@@ -22,62 +22,152 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-
package jdk.jshell.spi;
-import java.util.Collection;
-import jdk.jshell.JShellException;
+import java.io.Serializable;
/**
- * This interface specifies the functionality that must provided to implement
- * a pluggable JShell execution engine.
+ * This interface specifies the functionality that must provided to implement a
+ * pluggable JShell execution engine.
* <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.
+ * 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.
* <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.
+ * 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) }.
+ * To install an {@code ExecutionControl}, its {@code Generator} is passed to
+ * {@link jdk.jshell.JShell.Builder#executionEngine(ExecutionControl.Generator) }.
*/
public interface ExecutionControl {
/**
- * Represents the current status of a class in the execution engine.
+ * Defines a functional interface for creating {@link ExecutionControl}
+ * instances.
*/
- 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,
+ public interface Generator {
/**
- * Class is loaded and loaded/redefined bytes match those
- * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
+ * Generates an execution engine, given an execution environment.
+ *
+ * @param env the context in which the {@link ExecutionControl} is to
+ * be created
+ * @return the created instance
+ * @throws Throwable if problems occurred
*/
- CURRENT
- };
+ ExecutionControl generate(ExecutionEnv env) throws Throwable;
+ }
+
+ /**
+ * Attempts to load new classes.
+ *
+ * @param cbcs the class name and bytecodes to load
+ * @throws ClassInstallException exception occurred loading the classes,
+ * some or all were not loaded
+ * @throws NotImplementedException if not implemented
+ * @throws EngineTerminationException the execution engine has terminated
+ */
+ void load(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+ /**
+ * Attempts to redefine previously loaded classes.
+ *
+ * @param cbcs the class name and bytecodes to redefine
+ * @throws ClassInstallException exception occurred redefining the classes,
+ * some or all were not redefined
+ * @throws NotImplementedException if not implemented
+ * @throws EngineTerminationException the execution engine has terminated
+ */
+ void redefine(ClassBytecodes[] cbcs)
+ throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+ /**
+ * Invokes an executable Snippet by calling a method on the specified
+ * wrapper class. The method must have no arguments and return String.
+ *
+ * @param className the class whose method should be invoked
+ * @param methodName the name of method to invoke
+ * @return the result of the execution or null if no result
+ * @throws UserException the invoke raised a user exception
+ * @throws ResolutionException the invoke attempted to directly or
+ * indirectly invoke an unresolved snippet
+ * @throws StoppedException if the {@code invoke()} was canceled by
+ * {@link ExecutionControl#stop}
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ String invoke(String className, String methodName)
+ throws RunException, EngineTerminationException, InternalException;
/**
- * Initializes the instance. No methods in this interface can be called
- * before this.
+ * Returns the value of a variable.
+ *
+ * @param className the name of the wrapper class of the variable
+ * @param varName the name of the variable
+ * @return the value of the variable
+ * @throws UserException formatting the value raised a user exception
+ * @throws ResolutionException formatting the value attempted to directly or
+ * indirectly invoke an unresolved snippet
+ * @throws StoppedException if the formatting the value was canceled by
+ * {@link ExecutionControl#stop}
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ String varValue(String className, String varName)
+ throws RunException, EngineTerminationException, InternalException;
+
+ /**
+ * Adds the path to the execution class path.
+ *
+ * @param path the path to add
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ void addToClasspath(String path)
+ throws EngineTerminationException, InternalException;
+
+ /**
+ * Sets the execution class path to the specified path.
*
- * @param env the execution environment information provided by JShell
- * @throws Exception if the instance is unable to initialize
+ * @param path the path to add
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
+ */
+ void setClasspath(String path)
+ throws EngineTerminationException, InternalException;
+
+ /**
+ * Interrupts a running invoke.
+ *
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws InternalException an internal problem occurred
*/
- void start(ExecutionEnv env) throws Exception;
+ void stop()
+ throws EngineTerminationException, InternalException;
+
+ /**
+ * Run a non-standard command (or a standard command from a newer version).
+ *
+ * @param command the non-standard command
+ * @param arg the commands argument
+ * @return the commands return value
+ * @throws UserException the command raised a user exception
+ * @throws ResolutionException the command attempted to directly or
+ * indirectly invoke an unresolved snippet
+ * @throws StoppedException if the command was canceled by
+ * {@link ExecutionControl#stop}
+ * @throws EngineTerminationException the execution engine has terminated
+ * @throws NotImplementedException if not implemented
+ * @throws InternalException an internal problem occurred
+ */
+ Object extensionCommand(String command, Object arg)
+ throws RunException, EngineTerminationException, InternalException;
/**
* Shuts down this execution engine. Implementation should free all
@@ -88,67 +178,206 @@
void close();
/**
- * Adds the path to the execution class path.
- *
- * @param path the path to add
- * @return true if successful
+ * Bundles class name with class bytecodes.
*/
- boolean addToClasspath(String path);
+ public static final class ClassBytecodes implements Serializable {
+
+ private static final long serialVersionUID = 0xC1A55B47EC0DE5L;
+ private final String name;
+ private final byte[] bytecodes;
+
+ /**
+ * Creates a name/bytecode pair.
+ * @param name the class name
+ * @param bytecodes the class bytecodes
+ */
+ public ClassBytecodes(String name, byte[] bytecodes) {
+ this.name = name;
+ this.bytecodes = bytecodes;
+ }
+
+ /**
+ * The bytecodes for the class.
+ *
+ * @return the bytecodes
+ */
+ public byte[] bytecodes() {
+ return bytecodes;
+ }
+
+ /**
+ * The class name.
+ *
+ * @return the class name
+ */
+ public String name() {
+ return name;
+ }
+ }
/**
- * 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
+ * The abstract base of all {@code ExecutionControl} exceptions.
*/
- String invoke(String classname, String methodname) throws JShellException;
+ public static abstract class ExecutionControlException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public ExecutionControlException(String message) {
+ super(message);
+ }
+ }
/**
- * 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
+ * Unbidden execution engine termination has occurred.
+ */
+ public static class EngineTerminationException extends ExecutionControlException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EngineTerminationException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * The command is not implemented.
*/
- boolean load(Collection<String> classes);
+ public static class NotImplementedException extends InternalException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NotImplementedException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * An internal problem has occurred.
+ */
+ public static class InternalException extends ExecutionControlException {
+
+ private static final long serialVersionUID = 1L;
+
+ public InternalException(String message) {
+ super(message);
+ }
+ }
/**
- * 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
+ * A class install (load or redefine) encountered a problem.
*/
- boolean redefine(Collection<String> classes);
+ public static class ClassInstallException extends ExecutionControlException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean[] installed;
+
+ public ClassInstallException(String message, boolean[] installed) {
+ super(message);
+ this.installed = installed;
+ }
+
+ /**
+ * Indicates which of the passed classes were successfully
+ * loaded/redefined.
+ * @return a one-to-one array with the {@link ClassBytecodes}{@code[]}
+ * array -- {@code true} if installed
+ */
+ public boolean[] installed() {
+ return installed;
+ }
+ }
+
+ /**
+ * The abstract base of of exceptions specific to running user code.
+ */
+ public static abstract class RunException extends ExecutionControlException {
+
+ private static final long serialVersionUID = 1L;
+
+ private RunException(String message) {
+ super(message);
+ }
+ }
/**
- * 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}
+ * A 'normal' user exception occurred.
*/
- ClassStatus getClassStatus(String classname);
+ public static class UserException extends RunException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String causeExceptionClass;
+
+ public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) {
+ super(message);
+ this.causeExceptionClass = causeExceptionClass;
+ this.setStackTrace(stackElements);
+ }
+
+ /**
+ * Returns the class of the user exception.
+ * @return the name of the user exception class
+ */
+ public String causeExceptionClass() {
+ return causeExceptionClass;
+ }
+ }
/**
- * Interrupt a running invoke.
+ * An exception indicating that a {@code DeclarationSnippet} with unresolved
+ * references has been encountered.
+ * <p>
+ * Contrast this with the initiating {@link SPIResolutionException}
+ * (a {@code RuntimeException}) which is embedded in generated corralled
+ * code. Also, contrast this with
+ * {@link jdk.jshell.UnresolvedReferenceException} the high-level
+ * exception (with {@code DeclarationSnippet} reference) provided in the
+ * main API.
*/
- void stop();
+ public static class ResolutionException extends RunException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final int id;
+
+ /**
+ * Constructs an exception indicating that a {@code DeclarationSnippet}
+ * with unresolved references has been encountered.
+ *
+ * @param id An internal identifier of the specific method
+ * @param stackElements the stack trace
+ */
+ public ResolutionException(int id, StackTraceElement[] stackElements) {
+ super("resolution exception: " + id);
+ this.id = id;
+ this.setStackTrace(stackElements);
+ }
+
+ /**
+ * Retrieves the internal identifier of the unresolved identifier.
+ *
+ * @return the internal identifier
+ */
+ public int id() {
+ return id;
+ }
+ }
/**
- * 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
+ * An exception indicating that an
+ * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
+ * (or theoretically a
+ * {@link ExecutionControl#varValue(java.lang.String, java.lang.String) })
+ * has been interrupted by a {@link ExecutionControl#stop() }.
*/
- String varValue(String classname, String varname);
+ public static class StoppedException extends RunException {
+
+ private static final long serialVersionUID = 1L;
+
+ public StoppedException() {
+ super("stopped by stop()");
+ }
+ }
+
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java Wed Jul 20 23:19:09 2016 -0700
@@ -28,14 +28,11 @@
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
-import jdk.jshell.EvalException;
import jdk.jshell.JShell;
-import jdk.jshell.UnresolvedReferenceException;
/**
* Functionality made available to a pluggable JShell execution engine. It is
- * provided to the execution engine by the core JShell implementation calling
- * {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }.
+ * provided to the execution engine by the core JShell implementation.
* <p>
* This interface is designed to provide the access to core JShell functionality
* needed to implement ExecutionControl.
@@ -66,11 +63,6 @@
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>
@@ -81,47 +73,8 @@
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();
+
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java Wed Jul 20 23:19:09 2016 -0700
@@ -33,9 +33,6 @@
* <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 {
@@ -57,11 +54,9 @@
}
/**
- * Retrieves the internal identifer of the unresolved identifer.
+ * Retrieves the internal identifier of the unresolved identifier.
*
- * @return the internal identifer
- * @see ExecutionEnv#createUnresolvedReferenceException(int,
- * java.lang.StackTraceElement[])
+ * @return the internal identifier
*/
public int id() {
return id;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java Wed Jul 20 23:19:09 2016 -0700
@@ -31,9 +31,9 @@
* implementation includes a default execution engine (currently a remote
* process which is JDI controlled). By implementing the
* {@link jdk.jshell.spi.ExecutionControl} interface and installing it with
- * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
+ * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl.Generator) }
* other execution engines can be used.
*
- * @see jdk.jshell.execution jdk.jshell.execution for execution implementation support
+ * @see jdk.jshell.execution for execution implementation support
*/
package jdk.jshell.spi;
--- a/langtools/test/jdk/jshell/ComputeFQNsTest.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/ComputeFQNsTest.java Wed Jul 20 23:19:09 2016 -0700
@@ -76,7 +76,7 @@
assertInferredFQNs("class X { ArrayList", "ArrayList".length(), false, "java.util.ArrayList");
}
- @Test
+ @Test(enabled = false) //TODO 8161165
public void testSuspendIndexing() throws Throwable {
compiler.compile(outDir, "package test; public class FQNTest { }");
String jarName = "test.jar";
--- a/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Wed Jul 20 23:19:09 2016 -0700
@@ -23,23 +23,20 @@
/*
* @test
- * @bug 8131029
- * @summary Test that fail-over works for FailOverExecutionControl
- * @modules jdk.jshell/jdk.internal.jshell.jdi
+ * @bug 8131029 8160127 8159935
+ * @summary Test that fail-over works for fail-over ExecutionControl generators.
+ * @modules jdk.jshell/jdk.jshell.execution
* jdk.jshell/jdk.jshell.spi
* @build KullaTesting ExecutionControlTestBase
* @run testng FailOverExecutionControlTest
*/
-
-import java.util.Collection;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.internal.jshell.jdi.FailOverExecutionControl;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
-import jdk.jshell.JShellException;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@Test
public class FailOverExecutionControlTest extends ExecutionControlTestBase {
@@ -47,56 +44,16 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(new FailOverExecutionControl(
- new AlwaysFailingExecutionControl(),
- new AlwaysFailingExecutionControl(),
- new JDIExecutionControl()));
+ setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
+ new AlwaysFailingGenerator(),
+ new AlwaysFailingGenerator(),
+ JDIDefaultExecutionControl.launch())));
}
- class AlwaysFailingExecutionControl implements ExecutionControl {
-
- @Override
- public void start(ExecutionEnv env) throws Exception {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public void close() {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public boolean addToClasspath(String path) {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
+ class AlwaysFailingGenerator implements ExecutionControl.Generator {
@Override
- public String invoke(String classname, String methodname) throws JShellException {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public boolean load(Collection<String> classes) {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public boolean redefine(Collection<String> classes) {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public ClassStatus getClassStatus(String classname) {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public void stop() {
- throw new UnsupportedOperationException("This operation intentionally broken.");
- }
-
- @Override
- public String varValue(String classname, String varname) {
+ public ExecutionControl generate(ExecutionEnv env) throws UnsupportedOperationException {
throw new UnsupportedOperationException("This operation intentionally broken.");
}
--- a/langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java Wed Jul 20 23:19:09 2016 -0700
@@ -23,9 +23,9 @@
/*
* @test
- * @bug 8131029
+ * @bug 8131029 8159935 8160127
* @summary Tests for alternate JDI connector -- listening
- * @modules jdk.jshell/jdk.internal.jshell.jdi
+ * @modules jdk.jshell/jdk.jshell.execution
* @build KullaTesting ExecutionControlTestBase
* @run testng JDIListeningExecutionControlTest
*/
@@ -33,7 +33,7 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
@Test
public class JDIListeningExecutionControlTest extends ExecutionControlTestBase {
@@ -41,6 +41,6 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(new JDIExecutionControl(false));
+ setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen()));
}
}
--- a/langtools/test/jdk/jshell/KullaTesting.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java Wed Jul 20 23:19:09 2016 -0700
@@ -73,7 +73,6 @@
import static jdk.jshell.Snippet.Status.*;
import static org.testng.Assert.*;
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
-import jdk.jshell.spi.ExecutionControl;
public class KullaTesting {
@@ -158,10 +157,6 @@
setUp(b -> {});
}
- public void setUp(ExecutionControl ec) {
- setUp(b -> b.executionEngine(ec));
- }
-
public void setUp(Consumer<JShell.Builder> bc) {
inStream = new TestingInputStream();
outStream = new ByteArrayOutputStream();
--- a/langtools/test/jdk/jshell/LocalExecutionControl.java Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-import jdk.jshell.spi.SPIResolutionException;
-import jdk.jshell.EvalException;
-import jdk.jshell.UnresolvedReferenceException;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * An implementation of ExecutionControl which executes in the same JVM as the
- * JShell core.
- *
- * @author Grigory Ptashko
- */
-class LocalExecutionControl implements ExecutionControl {
- private class REPLClassLoader extends URLClassLoader {
- REPLClassLoader() {
- super(new URL[0]);
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- debug("findClass %s\n", name);
- byte[] b = execEnv.getClassBytes(name);
- if (b == null) {
- return super.findClass(name);
- }
- return super.defineClass(name, b, 0, b.length, (CodeSource)null);
- }
-
- @Override
- public void addURL(URL url) {
- super.addURL(url);
- }
- }
-
- private ExecutionEnv execEnv;
- private final Object STOP_LOCK = new Object();
- private boolean userCodeRunning = false;
- private REPLClassLoader loader = new REPLClassLoader();
- private final Map<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) {
- if (thread != null)
- thread.join();
- }
-
- if (stopped.get()) {
- debug("Killed.");
-
- return "";
- }
-
- if (iteEx.get() != null) {
- throw iteEx.get();
- } else if (nmeEx.get() != null) {
- throw nmeEx.get();
- } else if (iaeEx.get() != null) {
- throw iaeEx.get();
- }
-
- return valueString(res[0]);
- } catch (InvocationTargetException ex) {
- Throwable cause = ex.getCause();
- StackTraceElement[] elems = cause.getStackTrace();
- if (cause instanceof SPIResolutionException) {
- int id = ((SPIResolutionException)cause).id();
-
- throw execEnv.createUnresolvedReferenceException(id, elems);
- } else {
- throw execEnv.createEvalException(cause.getMessage() == null ?
- "<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();
- }
- }
-}
--- a/langtools/test/jdk/jshell/UserExecutionControlTest.java Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/UserExecutionControlTest.java Wed Jul 20 23:19:09 2016 -0700
@@ -23,13 +23,14 @@
/*
* @test
- * @bug 8156101
+ * @bug 8156101 8159935 8159122
* @summary Tests for ExecutionControl SPI
- * @build KullaTesting LocalExecutionControl ExecutionControlTestBase
+ * @build KullaTesting ExecutionControlTestBase
* @run testng UserExecutionControlTest
*/
+import jdk.jshell.execution.LocalExecutionControl;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
@@ -40,7 +41,7 @@
@BeforeMethod
@Override
public void setUp() {
- setUp(new LocalExecutionControl());
+ setUp(builder -> builder.executionEngine(LocalExecutionControl.create()));
}
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/UserJDIUserRemoteTest.java Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @bug 8160128 8159935
+ * @summary Tests for Aux channel, custom remote agents, custom JDI implementations.
+ * @build KullaTesting ExecutionControlTestBase
+ * @run testng UserJDIUserRemoteTest
+ */
+import java.io.ByteArrayOutputStream;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.Snippet;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+import static jdk.jshell.Snippet.Status.VALID;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+import com.sun.jdi.VMDisconnectedException;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.execution.DirectExecutionControl;
+import jdk.jshell.execution.JDIExecutionControl;
+import jdk.jshell.execution.JDIInitiator;
+import jdk.jshell.execution.Util;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+import jdk.jshell.spi.ExecutionEnv;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
+import static jdk.jshell.execution.Util.remoteInput;
+
+@Test
+public class UserJDIUserRemoteTest extends ExecutionControlTestBase {
+
+ ExecutionControl currentEC;
+ ByteArrayOutputStream auxStream;
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ auxStream = new ByteArrayOutputStream();
+ setUp(builder -> builder.executionEngine(MyExecutionControl.create(this)));
+ }
+
+ public void testVarValue() {
+ VarSnippet dv = varKey(assertEval("double aDouble = 1.5;"));
+ String vd = getState().varValue(dv);
+ assertEquals(vd, "1.5");
+ assertEquals(auxStream.toString(), "aDouble");
+ }
+
+ public void testExtension() throws ExecutionControlException {
+ assertEval("42;");
+ Object res = currentEC.extensionCommand("FROG", "test");
+ assertEquals(res, "ribbit");
+ }
+
+ public void testRedefine() {
+ Snippet vx = varKey(assertEval("int x;"));
+ Snippet mu = methodKey(assertEval("int mu() { return x * 4; }"));
+ Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }"));
+ assertEval("C c0 = new C();");
+ assertEval("c0.v();", "\"#0\"");
+ assertEval("int x = 10;", "10",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c0.v();", "\"#40\"");
+ assertEval("C c = new C();");
+ assertEval("c.v();", "\"#40\"");
+ assertEval("int mu() { return x * 3; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c.v();", "\"#30\"");
+ assertEval("class C { String v() { return \"@\" + mu(); } }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c0.v();", "\"@30\"");
+ assertEval("c = new C();");
+ assertEval("c.v();", "\"@30\"");
+ assertActiveKeys();
+ }
+}
+
+class MyExecutionControl extends JDIExecutionControl {
+
+ private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName();
+
+ private VirtualMachine vm;
+ private Process process;
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code LaunchingConnector}.
+ *
+ * @return the generator
+ */
+ public static ExecutionControl.Generator create(UserJDIUserRemoteTest test) {
+ return env -> make(env, test);
+ }
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code ListeningConnector} or {@code LaunchingConnector}.
+ *
+ * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
+ * commands and results. This socket also transports the user
+ * input/output/error.
+ *
+ * @param env the context passed by
+ * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
+ * @return the channel
+ * @throws IOException if there are errors in set-up
+ */
+ static MyExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException {
+ try (final ServerSocket listener = new ServerSocket(0)) {
+ // timeout after 60 seconds
+ listener.setSoTimeout(60000);
+ int port = listener.getLocalPort();
+
+ // Set-up the JDI connection
+ List<String> opts = new ArrayList<>(env.extraRemoteVMOptions());
+ opts.add("-classpath");
+ opts.add(System.getProperty("java.class.path")
+ + System.getProperty("path.separator")
+ + System.getProperty("user.dir"));
+ JDIInitiator jdii = new JDIInitiator(port,
+ opts, REMOTE_AGENT, true);
+ VirtualMachine vm = jdii.vm();
+ Process process = jdii.process();
+
+ List<Consumer<String>> deathListeners = new ArrayList<>();
+ deathListeners.add(s -> env.closeDown());
+ Util.detectJDIExitEvent(vm, s -> {
+ for (Consumer<String> h : deathListeners) {
+ h.accept(s);
+ }
+ });
+
+ // Set-up the commands/reslts on the socket. Piggy-back snippet
+ // output.
+ Socket socket = listener.accept();
+ // out before in -- match remote creation so we don't hang
+ ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
+ Map<String, OutputStream> io = new HashMap<>();
+ io.put("out", env.userOut());
+ io.put("err", env.userErr());
+ io.put("aux", test.auxStream);
+ ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
+ MyExecutionControl myec = new MyExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+ test.currentEC = myec;
+ return myec;
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param out the output for commands
+ * @param in the input for responses
+ */
+ private MyExecutionControl(ObjectOutput out, ObjectInput in,
+ VirtualMachine vm, Process process,
+ List<Consumer<String>> deathListeners) {
+ super(out, in);
+ this.vm = vm;
+ this.process = process;
+ deathListeners.add(s -> disposeVM());
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ disposeVM();
+ }
+
+ private synchronized void disposeVM() {
+ try {
+ if (vm != null) {
+ vm.dispose(); // This could NPE, so it is caught below
+ vm = null;
+ }
+ } catch (VMDisconnectedException ex) {
+ // Ignore if already closed
+ } catch (Throwable e) {
+ fail("disposeVM threw: " + e);
+ } finally {
+ if (process != null) {
+ process.destroy();
+ process = null;
+ }
+ }
+ }
+
+ @Override
+ protected synchronized VirtualMachine vm() throws EngineTerminationException {
+ if (vm == null) {
+ throw new EngineTerminationException("VM closed");
+ } else {
+ return vm;
+ }
+ }
+
+}
+
+class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
+
+ static PrintStream auxPrint;
+
+ /**
+ * Launch the agent, connecting to the JShell-core over the socket specified
+ * in the command-line argument.
+ *
+ * @param args standard command-line arguments, expectation is the socket
+ * number is the only argument
+ * @throws Exception any unexpected exception
+ */
+ public static void main(String[] args) throws Exception {
+ try {
+ String loopBack = null;
+ Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+ InputStream inStream = socket.getInputStream();
+ OutputStream outStream = socket.getOutputStream();
+ Map<String, Consumer<OutputStream>> chans = new HashMap<>();
+ chans.put("out", st -> System.setOut(new PrintStream(st, true)));
+ chans.put("err", st -> System.setErr(new PrintStream(st, true)));
+ chans.put("aux", st -> { auxPrint = new PrintStream(st, true); });
+ forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, chans);
+ } catch (Throwable ex) {
+ throw ex;
+ }
+ }
+
+ @Override
+ public String varValue(String className, String varName)
+ throws RunException, EngineTerminationException, InternalException {
+ auxPrint.print(varName);
+ return super.varValue(className, varName);
+ }
+
+ @Override
+ public Object extensionCommand(String className, Object arg)
+ throws RunException, EngineTerminationException, InternalException {
+ if (!arg.equals("test")) {
+ throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg);
+ }
+ return "ribbit";
+ }
+
+}