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:
Robert Field 2016-07-20 23:19:09 -07:00
parent 0bba28db43
commit 918c010822
40 changed files with 3344 additions and 2276 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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) {

View File

@ -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);
}

View File

@ -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;

View File

@ -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() {

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -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 {
}
}

View File

@ -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
}
}
}

View File

@ -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 {}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}

View File

@ -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();
}
}

View File

@ -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()");
}
}
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -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";

View File

@ -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.");
}

View File

@ -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()));
}
}

View File

@ -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();

View File

@ -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();
}
}
}

View File

@ -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 {

View 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";
}
}