8161983: JShell API: Clean-up following 8160127 et. al.
Reviewed-by: jlahoda
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Fri Nov 04 14:47:25 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Sun Nov 06 22:50:46 2016 -0800
@@ -47,7 +47,7 @@
import java.util.stream.Stream;
import jdk.internal.jshell.debug.InternalDebugControl;
import jdk.jshell.Snippet.Status;
-import jdk.jshell.execution.JDIDefaultExecutionControl;
+import jdk.jshell.execution.JdiDefaultExecutionControl;
import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import jdk.jshell.spi.ExecutionEnv;
@@ -117,10 +117,9 @@
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
this.extraCompilerOptions = b.extraCompilerOptions;
this.executionControlGenerator = b.executionControlGenerator==null
- ? failOverExecutionControlGenerator(
- JDIDefaultExecutionControl.launch(),
- JDIDefaultExecutionControl.listen("localhost"),
- JDIDefaultExecutionControl.listen(null))
+ ? failOverExecutionControlGenerator(JdiDefaultExecutionControl.launch(),
+ JdiDefaultExecutionControl.listen("localhost"),
+ JdiDefaultExecutionControl.listen(null))
: b.executionControlGenerator;
this.maps = new SnippetMaps(this);
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,282 +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;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-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.remoteInputOutput;
-
-/**
- * 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, null);
- }
-
- /**
- * Creates an ExecutionControl instance based on a JDI
- * {@code ListeningConnector}.
- *
- * @param host explicit hostname to use, if null use discovered
- * hostname, applies to listening only (!isLaunch)
- * @return the generator
- */
- public static ExecutionControl.Generator listen(String host) {
- return env -> create(env, false, host);
- }
-
- /**
- * 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) }
- * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
- * otherwise we start explicitly and use ListeningConnector
- * @param host explicit hostname to use, if null use discovered
- * hostname, applies to listening only (!isLaunch)
- * @return the channel
- * @throws IOException if there are errors in set-up
- */
- private static ExecutionControl create(ExecutionEnv env,
- boolean isLaunch, String host) 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, host);
- 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
- OutputStream out = socket.getOutputStream();
- Map<String, OutputStream> outputs = new HashMap<>();
- outputs.put("out", env.userOut());
- outputs.put("err", env.userErr());
- Map<String, InputStream> input = new HashMap<>();
- input.put("in", env.userIn());
- return remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new JDIDefaultExecutionControl(objOut, objIn, 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
- }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIEventHandler.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-/*
- * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.jshell.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");
- this.thread.setDaemon(true);
- }
-
- /**
- * 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
- }
- }
- }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIExecutionControl.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * 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);
- }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIInitiator.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +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;
-
-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
- * @param host explicit hostname to use, if null use discovered
- * hostname, applies to listening only (!isLaunch)
- */
- public JDIInitiator(int port, List<String> remoteVMOptions, String remoteAgent,
- boolean isLaunch, String host) {
- 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<>();
- if (host != null && !isLaunch) {
- argumentName2Value.put("localAddress", host);
- }
- 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/JdiDefaultExecutionControl.java Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+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.remoteInputOutput;
+
+/**
+ * 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, null);
+ }
+
+ /**
+ * Creates an ExecutionControl instance based on a JDI
+ * {@code ListeningConnector}.
+ *
+ * @param host explicit hostname to use, if null use discovered
+ * hostname, applies to listening only (!isLaunch)
+ * @return the generator
+ */
+ public static ExecutionControl.Generator listen(String host) {
+ return env -> create(env, false, host);
+ }
+
+ /**
+ * 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) }
+ * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
+ * otherwise we start explicitly and use ListeningConnector
+ * @param host explicit hostname to use, if null use discovered
+ * hostname, applies to listening only (!isLaunch)
+ * @return the channel
+ * @throws IOException if there are errors in set-up
+ */
+ private static ExecutionControl create(ExecutionEnv env,
+ boolean isLaunch, String host) 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, host);
+ 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
+ OutputStream out = socket.getOutputStream();
+ Map<String, OutputStream> outputs = new HashMap<>();
+ outputs.put("out", env.userOut());
+ outputs.put("err", env.userErr());
+ Map<String, InputStream> input = new HashMap<>();
+ input.put("in", env.userIn());
+ return remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new JdiDefaultExecutionControl(objOut, objIn, 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 Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,140 @@
+/*
+ * 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");
+ this.thread.setDaemon(true);
+ }
+
+ /**
+ * 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 Sun Nov 06 22:50:46 2016 -0800
@@ -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 Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,223 @@
+/*
+ * 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
+ * @param host explicit hostname to use, if null use discovered
+ * hostname, applies to listening only (!isLaunch)
+ */
+ public JdiInitiator(int port, List<String> remoteVMOptions, String remoteAgent,
+ boolean isLaunch, String host) {
+ 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<>();
+ if (host != null && !isLaunch) {
+ argumentName2Value.put("localAddress", host);
+ }
+ 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
+ }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java Fri Nov 04 14:47:25 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java Sun Nov 06 22:50:46 2016 -0800
@@ -41,7 +41,7 @@
* 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}.
+ * an external process. Designed to work with {@link JdiDefaultExecutionControl}.
*
* @author Jan Lahoda
* @author Robert Field
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java Fri Nov 04 14:47:25 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java Sun Nov 06 22:50:46 2016 -0800
@@ -239,9 +239,9 @@
* @param unbiddenExitHandler the handler, which will accept the exit
* information
*/
- public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
+ public static void detectJdiExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
if (vm.canBeModified()) {
- new JDIEventHandler(vm, unbiddenExitHandler).start();
+ new JdiEventHandler(vm, unbiddenExitHandler).start();
}
}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Fri Nov 04 14:47:25 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java Sun Nov 06 22:50:46 2016 -0800
@@ -44,12 +44,13 @@
* To install an {@code ExecutionControl}, its {@code Generator} is passed to
* {@link jdk.jshell.JShell.Builder#executionEngine(ExecutionControl.Generator) }.
*/
-public interface ExecutionControl {
+public interface ExecutionControl extends AutoCloseable {
/**
* Defines a functional interface for creating {@link ExecutionControl}
* instances.
*/
+ @FunctionalInterface
public interface Generator {
/**
--- a/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Fri Nov 04 14:47:25 2016 -0700
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlTest.java Sun Nov 06 22:50:46 2016 -0800
@@ -33,7 +33,7 @@
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JDIDefaultExecutionControl;
+import jdk.jshell.execution.JdiDefaultExecutionControl;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
@@ -47,7 +47,7 @@
setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
new AlwaysFailingGenerator(),
new AlwaysFailingGenerator(),
- JDIDefaultExecutionControl.launch())));
+ JdiDefaultExecutionControl.launch())));
}
class AlwaysFailingGenerator implements ExecutionControl.Generator {
--- a/langtools/test/jdk/jshell/JDILaunchingExecutionControlTest.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +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.
- *
- * 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 8164518
- * @summary Tests for standard JDI connector (without failover) -- launching
- * @modules jdk.jshell/jdk.jshell.execution
- * @build KullaTesting ExecutionControlTestBase
- * @run testng JDILaunchingExecutionControlTest
- */
-
-
-import org.testng.annotations.Test;
-import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JDIDefaultExecutionControl;
-
-@Test
-public class JDILaunchingExecutionControlTest extends ExecutionControlTestBase {
-
- @BeforeMethod
- @Override
- public void setUp() {
- setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.launch()));
- }
-}
--- a/langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +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.
- *
- * 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 8131029 8159935 8160127 8164518
- * @summary Tests for alternate JDI connector -- listening
- * @modules jdk.jshell/jdk.jshell.execution
- * @build KullaTesting ExecutionControlTestBase
- * @run testng JDIListeningExecutionControlTest
- */
-
-
-import org.testng.annotations.Test;
-import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JDIDefaultExecutionControl;
-
-@Test
-public class JDIListeningExecutionControlTest extends ExecutionControlTestBase {
-
- @BeforeMethod
- @Override
- public void setUp() {
- setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen(null)));
- }
-}
--- a/langtools/test/jdk/jshell/JDIListeningLocalhostExecutionControlTest.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +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.
- *
- * 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 8164518
- * @summary Tests for alternate JDI connector -- listening to "localhost"
- * @modules jdk.jshell/jdk.jshell.execution
- * @build KullaTesting ExecutionControlTestBase
- * @run testng JDIListeningLocalhostExecutionControlTest
- */
-
-
-import org.testng.annotations.Test;
-import org.testng.annotations.BeforeMethod;
-import jdk.jshell.execution.JDIDefaultExecutionControl;
-
-@Test
-public class JDIListeningLocalhostExecutionControlTest extends ExecutionControlTestBase {
-
- @BeforeMethod
- @Override
- public void setUp() {
- setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen("localhost")));
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JdiLaunchingExecutionControlTest.java Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,46 @@
+/*
+ * 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 8164518
+ * @summary Tests for standard JDI connector (without failover) -- launching
+ * @modules jdk.jshell/jdk.jshell.execution
+ * @build KullaTesting ExecutionControlTestBase
+ * @run testng JdiLaunchingExecutionControlTest
+ */
+
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.execution.JdiDefaultExecutionControl;
+
+@Test
+public class JdiLaunchingExecutionControlTest extends ExecutionControlTestBase {
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.launch()));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JdiListeningExecutionControlTest.java Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,46 @@
+/*
+ * 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 8131029 8159935 8160127 8164518
+ * @summary Tests for alternate JDI connector -- listening
+ * @modules jdk.jshell/jdk.jshell.execution
+ * @build KullaTesting ExecutionControlTestBase
+ * @run testng JdiListeningExecutionControlTest
+ */
+
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.execution.JdiDefaultExecutionControl;
+
+@Test
+public class JdiListeningExecutionControlTest extends ExecutionControlTestBase {
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.listen(null)));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JdiListeningLocalhostExecutionControlTest.java Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,46 @@
+/*
+ * 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 8164518
+ * @summary Tests for alternate JDI connector -- listening to "localhost"
+ * @modules jdk.jshell/jdk.jshell.execution
+ * @build KullaTesting ExecutionControlTestBase
+ * @run testng JdiListeningLocalhostExecutionControlTest
+ */
+
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.execution.JdiDefaultExecutionControl;
+
+@Test
+public class JdiListeningLocalhostExecutionControlTest extends ExecutionControlTestBase {
+
+ @BeforeMethod
+ @Override
+ public void setUp() {
+ setUp(builder -> builder.executionEngine(JdiDefaultExecutionControl.listen("localhost")));
+ }
+}
--- a/langtools/test/jdk/jshell/UserJDIUserRemoteTest.java Fri Nov 04 14:47:25 2016 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +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.
- *
- * 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.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.remoteInputOutput;
-
-@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 ExecutionControl 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, null);
- 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
- OutputStream out = socket.getOutputStream();
- Map<String, OutputStream> outputs = new HashMap<>();
- outputs.put("out", env.userOut());
- outputs.put("err", env.userErr());
- outputs.put("aux", test.auxStream);
- Map<String, InputStream> input = new HashMap<>();
- input.put("in", env.userIn());
- ExecutionControl myec = remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new MyExecutionControl(objOut, objIn, 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>> outputs = new HashMap<>();
- outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
- outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
- outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); });
- Map<String, Consumer<InputStream>> input = new HashMap<>();
- input.put("in", st -> System.setIn(st));
- forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input);
- } 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";
- }
-
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/UserJdiUserRemoteTest.java Sun Nov 06 22:50:46 2016 -0800
@@ -0,0 +1,286 @@
+/*
+ * 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.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.remoteInputOutput;
+
+@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 ExecutionControl 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, null);
+ 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
+ OutputStream out = socket.getOutputStream();
+ Map<String, OutputStream> outputs = new HashMap<>();
+ outputs.put("out", env.userOut());
+ outputs.put("err", env.userErr());
+ outputs.put("aux", test.auxStream);
+ Map<String, InputStream> input = new HashMap<>();
+ input.put("in", env.userIn());
+ ExecutionControl myec = remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new MyExecutionControl(objOut, objIn, 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>> outputs = new HashMap<>();
+ outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
+ outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
+ outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); });
+ Map<String, Consumer<InputStream>> input = new HashMap<>();
+ input.put("in", st -> System.setIn(st));
+ forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input);
+ } 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";
+ }
+
+}