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 ExecutionControl implementation support with simplified ExecutionControl interface Reviewed-by: jlahoda
This commit is contained in:
parent
0bba28db43
commit
918c010822
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -22,46 +22,39 @@
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.jshell.jdi;
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import java.util.List;
|
||||
import com.sun.jdi.VirtualMachine;
|
||||
import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
|
||||
|
||||
/**
|
||||
* Tracks the state of a class.
|
||||
*/
|
||||
class ClassTracker {
|
||||
|
||||
private final VirtualMachine vm;
|
||||
private final HashMap<String, ClassInfo> map;
|
||||
|
||||
ClassTracker(VirtualMachine vm) {
|
||||
this.vm = vm;
|
||||
ClassTracker() {
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a class name, class bytes, and ReferenceType.
|
||||
* Associates a class name, class bytes (current and loaded).
|
||||
*/
|
||||
class ClassInfo {
|
||||
|
||||
// The name of the class -- always set
|
||||
// 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[] bytes;
|
||||
private byte[] currentBytes;
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -74,37 +67,31 @@ class ClassTracker {
|
||||
return loadedBytes;
|
||||
}
|
||||
|
||||
byte[] getBytes() {
|
||||
return bytes;
|
||||
byte[] getCurrentBytes() {
|
||||
return currentBytes;
|
||||
}
|
||||
|
||||
private void setBytes(byte[] potentialBytes) {
|
||||
this.bytes = potentialBytes;
|
||||
void setCurrentBytes(byte[] bytes) {
|
||||
this.currentBytes = bytes;
|
||||
}
|
||||
|
||||
// The class has been successful loaded redefined. The class bytes
|
||||
// sent are now actually loaded.
|
||||
void markLoaded() {
|
||||
loadedBytes = bytes;
|
||||
void setLoadedBytes(byte[] bytes) {
|
||||
this.loadedBytes = bytes;
|
||||
}
|
||||
|
||||
// Ask JDI for the ReferenceType, null if not loaded.
|
||||
ReferenceType getReferenceTypeOrNull() {
|
||||
if (rt == null) {
|
||||
rt = nameToRef(className);
|
||||
}
|
||||
return rt;
|
||||
boolean isLoaded() {
|
||||
return loadedBytes != null;
|
||||
}
|
||||
|
||||
private ReferenceType nameToRef(String name) {
|
||||
List<ReferenceType> rtl = vm.classesByName(name);
|
||||
if (rtl.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return rtl.get(0);
|
||||
boolean isCurrent() {
|
||||
return Arrays.equals(currentBytes, loadedBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassBytecodes toClassBytecodes() {
|
||||
return new ClassBytecodes(className, currentBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof ClassInfo
|
||||
&& ((ClassInfo) o).className.equals(className);
|
||||
@ -116,11 +103,25 @@ class ClassTracker {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
ClassInfo classInfo(String className, byte[] bytes) {
|
||||
void setCurrentBytes(String className, byte[] bytes) {
|
||||
ClassInfo ci = get(className);
|
||||
ci.setBytes(bytes);
|
||||
return ci;
|
||||
ci.setCurrentBytes(bytes);
|
||||
}
|
||||
|
||||
// Lookup the ClassInfo by class name, create if it does not exist.
|
@ -61,6 +61,14 @@ import jdk.jshell.TaskFactory.ParseTask;
|
||||
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 @@ class Eval {
|
||||
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 @@ class Eval {
|
||||
|
||||
/**
|
||||
* 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 @@ class Eval {
|
||||
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) {
|
||||
|
@ -44,13 +44,15 @@ import java.util.function.Consumer;
|
||||
|
||||
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 @@ public class JShell implements AutoCloseable {
|
||||
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 @@ public class JShell implements AutoCloseable {
|
||||
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 @@ public class JShell implements AutoCloseable {
|
||||
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 @@ public class JShell implements AutoCloseable {
|
||||
* 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 @@ public class JShell implements AutoCloseable {
|
||||
* 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 @@ public class JShell implements AutoCloseable {
|
||||
* @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 @@ public class JShell implements AutoCloseable {
|
||||
* 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 @@ public class JShell implements AutoCloseable {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -695,44 +718,23 @@ public class JShell implements AutoCloseable {
|
||||
return err;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JShell state() {
|
||||
return JShell.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> extraRemoteVMOptions() {
|
||||
return extraRemoteVMOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getClassBytes(String classname) {
|
||||
return classnameToBytes.get(classname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvalException createEvalException(String message, String exceptionClass, StackTraceElement[] stackElements) {
|
||||
return new EvalException(message, exceptionClass, stackElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnresolvedReferenceException createUnresolvedReferenceException(int id, StackTraceElement[] stackElements) {
|
||||
DeclarationSnippet sn = (DeclarationSnippet) maps.getSnippetDeadOrAlive(id);
|
||||
return new UnresolvedReferenceException(sn, stackElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeDown() {
|
||||
JShell.this.closeDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- private / package-private implementation support ---
|
||||
ExecutionControl executionControl() {
|
||||
if (!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 @@ public class JShell implements AutoCloseable {
|
||||
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);
|
||||
}
|
||||
|
@ -223,7 +223,6 @@ class TaskFactory {
|
||||
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 @@ class TaskFactory {
|
||||
|
||||
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 @@ class TaskFactory {
|
||||
}
|
||||
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;
|
||||
|
@ -32,11 +32,16 @@ import java.util.LinkedHashSet;
|
||||
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 @@ final class Unit {
|
||||
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 @@ final class Unit {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ final class Unit {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 {}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.jshell.jdi;
|
||||
package jdk.jshell.execution;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import com.sun.jdi.*;
|
||||
@ -31,7 +31,8 @@ import com.sun.jdi.event.*;
|
||||
|
||||
/**
|
||||
* Handler of Java Debug Interface events.
|
||||
* Adapted from jdb EventHandler; Handling of events not used by JShell stubbed out.
|
||||
* Adapted from jdb EventHandler.
|
||||
* Only exit and disconnect events processed.
|
||||
*/
|
||||
class JDIEventHandler implements Runnable {
|
||||
|
||||
@ -39,14 +40,24 @@ class JDIEventHandler implements Runnable {
|
||||
private volatile boolean connected = true;
|
||||
private boolean completed = false;
|
||||
private final VirtualMachine vm;
|
||||
private final Consumer<Boolean> reportVMExit;
|
||||
private final Consumer<String> reportVMExit;
|
||||
|
||||
JDIEventHandler(VirtualMachine vm, Consumer<Boolean> 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();
|
||||
}
|
||||
@ -88,41 +99,19 @@ class JDIEventHandler implements Runnable {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
handleExitEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean vmDied = false;
|
||||
|
||||
private void handleExitEvent(Event event) {
|
||||
if (event instanceof VMDeathEvent) {
|
||||
vmDied = true;
|
||||
reportVMExit.accept("VM Died");
|
||||
} else if (event instanceof VMDisconnectEvent) {
|
||||
connected = false;
|
||||
reportVMExit.accept("VM Disconnected");
|
||||
} else {
|
||||
throw new InternalError("Unexpected event type: " +
|
||||
event.getClass());
|
||||
// ignore everything else
|
||||
}
|
||||
reportVMExit.accept(vmDied);
|
||||
}
|
||||
|
||||
private synchronized void handleDisconnectedException() {
|
||||
@ -147,36 +136,4 @@ class JDIEventHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
public interface Generator {
|
||||
|
||||
/**
|
||||
* Class is loaded, but the loaded/redefined bytes do not 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
|
||||
*/
|
||||
NOT_CURRENT,
|
||||
|
||||
/**
|
||||
* Class is loaded and loaded/redefined bytes match those
|
||||
* returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
|
||||
*/
|
||||
CURRENT
|
||||
};
|
||||
ExecutionControl generate(ExecutionEnv env) throws Throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the instance. No methods in this interface can be called
|
||||
* before this.
|
||||
* Attempts to load new classes.
|
||||
*
|
||||
* @param env the execution environment information provided by JShell
|
||||
* @throws Exception if the instance is unable to initialize
|
||||
* @param cbcs the class name and bytecodes to load
|
||||
* @throws ClassInstallException exception occurred loading the classes,
|
||||
* some or all were not loaded
|
||||
* @throws NotImplementedException if not implemented
|
||||
* @throws EngineTerminationException the execution engine has terminated
|
||||
*/
|
||||
void start(ExecutionEnv env) throws Exception;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 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 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 @@ public interface ExecutionControl {
|
||||
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.
|
||||
*/
|
||||
boolean load(Collection<String> classes);
|
||||
public static class EngineTerminationException extends ExecutionControlException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EngineTerminationException(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
|
||||
* The command is not implemented.
|
||||
*/
|
||||
boolean redefine(Collection<String> classes);
|
||||
public static class NotImplementedException extends InternalException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NotImplementedException(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}
|
||||
* An internal problem has occurred.
|
||||
*/
|
||||
ClassStatus getClassStatus(String classname);
|
||||
public static class InternalException extends ExecutionControlException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InternalException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupt a running invoke.
|
||||
* A class install (load or redefine) encountered a problem.
|
||||
*/
|
||||
void stop();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* The abstract base of of exceptions specific to running user code.
|
||||
*/
|
||||
String varValue(String classname, String varname);
|
||||
public static abstract class RunException extends ExecutionControlException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private RunException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 'normal' user exception occurred.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() }.
|
||||
*/
|
||||
public static class StoppedException extends RunException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public StoppedException() {
|
||||
super("stopped by stop()");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,14 +28,11 @@ package jdk.jshell.spi;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.List;
|
||||
import jdk.jshell.EvalException;
|
||||
import jdk.jshell.JShell;
|
||||
import jdk.jshell.UnresolvedReferenceException;
|
||||
|
||||
/**
|
||||
* Functionality made available to a pluggable JShell execution engine. It is
|
||||
* provided to the execution engine by the core JShell implementation calling
|
||||
* {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }.
|
||||
* 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.
|
||||
@ -65,11 +62,6 @@ public interface ExecutionEnv {
|
||||
*/
|
||||
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.
|
||||
@ -80,48 +72,9 @@ public interface ExecutionEnv {
|
||||
*/
|
||||
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();
|
||||
|
||||
}
|
||||
|
@ -33,9 +33,6 @@ package jdk.jshell.spi;
|
||||
* <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 @@ public class SPIResolutionException extends RuntimeException {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
@ -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;
|
||||
|
@ -76,7 +76,7 @@ public class ComputeFQNsTest extends KullaTesting {
|
||||
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";
|
||||
|
@ -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 @@ public class FailOverExecutionControlTest extends ExecutionControlTestBase {
|
||||
@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 {
|
||||
class AlwaysFailingGenerator implements ExecutionControl.Generator {
|
||||
|
||||
@Override
|
||||
public void start(ExecutionEnv env) throws Exception {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToClasspath(String path) {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String invoke(String classname, String methodname) throws JShellException {
|
||||
throw new UnsupportedOperationException("This operation intentionally broken.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(Collection<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.");
|
||||
}
|
||||
|
||||
|
@ -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 @@ public class JDIListeningExecutionControlTest extends ExecutionControlTestBase {
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new JDIExecutionControl(false));
|
||||
setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen()));
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,6 @@ import jdk.jshell.Diag;
|
||||
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 @@ public class KullaTesting {
|
||||
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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 @@ public class UserExecutionControlTest extends ExecutionControlTestBase {
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new LocalExecutionControl());
|
||||
setUp(builder -> builder.executionEngine(LocalExecutionControl.create()));
|
||||
}
|
||||
|
||||
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
|
284
langtools/test/jdk/jshell/UserJDIUserRemoteTest.java
Normal file
284
langtools/test/jdk/jshell/UserJDIUserRemoteTest.java
Normal file
@ -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";
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user