8156101: JShell SPI: Provide a pluggable execution control SPI
Reviewed-by: jlahoda
This commit is contained in:
parent
8c88656e09
commit
5361169fbd
@ -23,24 +23,60 @@
|
||||
|
||||
package jdk.internal.jshell.debug;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jdk.jshell.JShell;
|
||||
|
||||
/**
|
||||
* Used to externally control output messages for debugging the implementation
|
||||
* of the JShell API. This is NOT a supported interface,
|
||||
* @author Robert Field
|
||||
/**
|
||||
* This class is used to externally control output messages for debugging the
|
||||
* implementation of the JShell API.
|
||||
* <p>
|
||||
* This is not part of the SPI, not API.
|
||||
*/
|
||||
public class InternalDebugControl {
|
||||
public static final int DBG_GEN = 0b0000001;
|
||||
public static final int DBG_FMGR = 0b0000010;
|
||||
|
||||
/**
|
||||
* This is a static only class; The constructor should never be called.
|
||||
*/
|
||||
private InternalDebugControl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* General debugging.
|
||||
*/
|
||||
public static final int DBG_GEN = 0b0000001;
|
||||
|
||||
/**
|
||||
* File manager debuging.
|
||||
*/
|
||||
public static final int DBG_FMGR = 0b0000010;
|
||||
|
||||
/**
|
||||
* Completion analysis debugging.
|
||||
*/
|
||||
public static final int DBG_COMPA = 0b0000100;
|
||||
public static final int DBG_DEP = 0b0001000;
|
||||
public static final int DBG_EVNT = 0b0010000;
|
||||
|
||||
/**
|
||||
* Dependency debugging.
|
||||
*/
|
||||
public static final int DBG_DEP = 0b0001000;
|
||||
|
||||
/**
|
||||
* Event debugging.
|
||||
*/
|
||||
public static final int DBG_EVNT = 0b0010000;
|
||||
|
||||
private static Map<JShell, Integer> debugMap = null;
|
||||
|
||||
/**
|
||||
* Sets which debug flags are enabled for a given JShell instance. The flags
|
||||
* are or'ed bits as defined in {@code DBG_*}.
|
||||
*
|
||||
* @param state the JShell instance
|
||||
* @param flags the or'ed debug bits
|
||||
*/
|
||||
public static void setDebugFlags(JShell state, int flags) {
|
||||
if (debugMap == null) {
|
||||
debugMap = new HashMap<>();
|
||||
@ -48,7 +84,14 @@ public class InternalDebugControl {
|
||||
debugMap.put(state, flags);
|
||||
}
|
||||
|
||||
public static boolean debugEnabled(JShell state, int flag) {
|
||||
/**
|
||||
* Tests if any of the specified debug flags are enabled.
|
||||
*
|
||||
* @param state the JShell instance
|
||||
* @param flag the {@code DBG_*} bits to check
|
||||
* @return true if any of the flags are enabled
|
||||
*/
|
||||
public static boolean isDebugEnabled(JShell state, int flag) {
|
||||
if (debugMap == null) {
|
||||
return false;
|
||||
}
|
||||
@ -58,4 +101,34 @@ public class InternalDebugControl {
|
||||
}
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays debug info if the specified debug flags are enabled.
|
||||
*
|
||||
* @param state the current JShell instance
|
||||
* @param err the {@code PrintStream} to report on
|
||||
* @param flags {@code DBG_*} flag bits to check
|
||||
* @param format format string for the output
|
||||
* @param args args for the format string
|
||||
*/
|
||||
public static void debug(JShell state, PrintStream err, int flags, String format, Object... args) {
|
||||
if (isDebugEnabled(state, flags)) {
|
||||
err.printf(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a fatal exception as debug info.
|
||||
*
|
||||
* @param state the current JShell instance
|
||||
* @param err the {@code PrintStream} to report on
|
||||
* @param ex the fatal Exception
|
||||
* @param where additional context
|
||||
*/
|
||||
public static void debug(JShell state, PrintStream err, Exception ex, String where) {
|
||||
if (isDebugEnabled(state, 0xFFFFFFFF)) {
|
||||
err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
|
||||
ex.printStackTrace(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -22,35 +22,43 @@
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Tracks the state of a class through compilation and loading in the remote
|
||||
* environment.
|
||||
*
|
||||
* @author Robert Field
|
||||
* Tracks the state of a class.
|
||||
*/
|
||||
class ClassTracker {
|
||||
|
||||
private final JShell state;
|
||||
private final JDIEnv jdiEnv;
|
||||
private final HashMap<String, ClassInfo> map;
|
||||
|
||||
ClassTracker(JShell state) {
|
||||
this.state = state;
|
||||
ClassTracker(JDIEnv jdiEnv) {
|
||||
this.jdiEnv = jdiEnv;
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a class name, class bytes, and ReferenceType.
|
||||
*/
|
||||
class ClassInfo {
|
||||
|
||||
// The name of the class -- always set
|
||||
private final String className;
|
||||
|
||||
// The corresponding compiled class bytes when a load or redefine
|
||||
// is started. May not be the loaded bytes. May be null.
|
||||
private byte[] bytes;
|
||||
|
||||
// The class bytes successfully loaded/redefined into the remote VM.
|
||||
private byte[] loadedBytes;
|
||||
|
||||
// The corresponding JDI ReferenceType. Used by redefineClasses and
|
||||
// acts as indicator of successful load (null if not loaded).
|
||||
private ReferenceType rt;
|
||||
|
||||
private ClassInfo(String className) {
|
||||
@ -61,33 +69,44 @@ class ClassTracker {
|
||||
return className;
|
||||
}
|
||||
|
||||
byte[] getLoadedBytes() {
|
||||
return loadedBytes;
|
||||
}
|
||||
|
||||
byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
private void setBytes(byte[] potentialBytes) {
|
||||
this.bytes = potentialBytes;
|
||||
}
|
||||
|
||||
void setLoaded() {
|
||||
// The class has been successful loaded redefined. The class bytes
|
||||
// sent are now actually loaded.
|
||||
void markLoaded() {
|
||||
loadedBytes = bytes;
|
||||
}
|
||||
|
||||
boolean isLoaded() {
|
||||
return Arrays.equals(loadedBytes, bytes);
|
||||
}
|
||||
|
||||
// Ask JDI for the ReferenceType, null if not loaded.
|
||||
ReferenceType getReferenceTypeOrNull() {
|
||||
if (rt == null) {
|
||||
rt = state.executionControl().nameToRef(className);
|
||||
rt = nameToRef(className);
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
private ReferenceType nameToRef(String name) {
|
||||
List<ReferenceType> rtl = jdiEnv.vm().classesByName(name);
|
||||
if (rtl.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return rtl.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof ClassInfo &&
|
||||
((ClassInfo) o).className.equals(className);
|
||||
return o instanceof ClassInfo
|
||||
&& ((ClassInfo) o).className.equals(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -96,13 +115,15 @@ class ClassTracker {
|
||||
}
|
||||
}
|
||||
|
||||
// Map a class name to the current compiled class bytes.
|
||||
ClassInfo classInfo(String className, byte[] bytes) {
|
||||
ClassInfo ci = map.computeIfAbsent(className, k -> new ClassInfo(k));
|
||||
ClassInfo ci = get(className);
|
||||
ci.setBytes(bytes);
|
||||
return ci;
|
||||
}
|
||||
|
||||
// Lookup the ClassInfo by class name, create if it does not exist.
|
||||
ClassInfo get(String className) {
|
||||
return map.get(className);
|
||||
return map.computeIfAbsent(className, k -> new ClassInfo(k));
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -32,7 +32,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package jdk.jshell;
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
import com.sun.jdi.connect.*;
|
||||
@ -40,7 +40,6 @@ 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;
|
||||
|
||||
/**
|
||||
@ -54,8 +53,7 @@ class JDIConnection {
|
||||
private Process process = null;
|
||||
private int outputCompleteCount = 0;
|
||||
|
||||
private final JShell proc;
|
||||
private final JDIEnv env;
|
||||
private final JDIExecutionControl ec;
|
||||
private final Connector connector;
|
||||
private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
|
||||
private final int traceFlags;
|
||||
@ -103,9 +101,9 @@ class JDIConnection {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
JDIConnection(JDIEnv env, String connectorName, Map<String, String> argumentName2Value, int traceFlags, JShell proc) {
|
||||
this.env = env;
|
||||
this.proc = proc;
|
||||
|
||||
JDIConnection(JDIExecutionControl ec, String connectorName, Map<String, String> argumentName2Value, int traceFlags) {
|
||||
this.ec = ec;
|
||||
this.connector = findConnector(connectorName);
|
||||
|
||||
if (connector == null) {
|
||||
@ -260,8 +258,8 @@ class JDIConnection {
|
||||
try {
|
||||
dumpStream(inStream, pStream);
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "Failed reading output");
|
||||
env.shutdown();
|
||||
ec.debug(ex, "Failed reading output");
|
||||
ec.jdiEnv.shutdown();
|
||||
} finally {
|
||||
notifyOutputComplete();
|
||||
}
|
||||
@ -287,8 +285,8 @@ class JDIConnection {
|
||||
outStream.flush();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "Failed reading output");
|
||||
env.shutdown();
|
||||
ec.debug(ex, "Failed reading output");
|
||||
ec.jdiEnv.shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -302,9 +300,9 @@ class JDIConnection {
|
||||
try {
|
||||
VirtualMachine new_vm = launcher.launch(connectorArgs);
|
||||
process = new_vm.process();
|
||||
displayRemoteOutput(process.getErrorStream(), proc.err);
|
||||
displayRemoteOutput(process.getInputStream(), proc.out);
|
||||
readRemoteInput(process.getOutputStream(), proc.in);
|
||||
displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
|
||||
displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
|
||||
readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
|
||||
return new_vm;
|
||||
} catch (Exception ex) {
|
||||
reportLaunchFail(ex, "launch");
|
||||
@ -330,7 +328,7 @@ class JDIConnection {
|
||||
ListeningConnector listener = (ListeningConnector)connector;
|
||||
try {
|
||||
String retAddress = listener.startListening(connectorArgs);
|
||||
proc.debug(DBG_GEN, "Listening at address: " + retAddress);
|
||||
ec.debug(DBG_GEN, "Listening at address: " + retAddress);
|
||||
vm = listener.accept(connectorArgs);
|
||||
listener.stopListening(connectorArgs);
|
||||
return vm;
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -37,14 +37,14 @@ import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
class JDIEnv {
|
||||
|
||||
private JDIConnection connection;
|
||||
private final JShell state;
|
||||
private final JDIExecutionControl ec;
|
||||
|
||||
JDIEnv(JShell state) {
|
||||
this.state = state;
|
||||
JDIEnv(JDIExecutionControl ec) {
|
||||
this.ec = ec;
|
||||
}
|
||||
|
||||
void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
|
||||
connection = new JDIConnection(this, connectorName, argumentName2Value, flags, state);
|
||||
connection = new JDIConnection(ec, connectorName, argumentName2Value, flags);
|
||||
if (!connection.isLaunch() || openNow) {
|
||||
connection.open();
|
||||
}
|
||||
@ -66,14 +66,14 @@ class JDIEnv {
|
||||
// Shutting down after the VM has gone away. This is
|
||||
// not an error, and we just ignore it.
|
||||
} catch (Throwable e) {
|
||||
state.debug(DBG_GEN, null, "disposeVM threw: " + e);
|
||||
ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
|
||||
}
|
||||
}
|
||||
if (state != null) { // If state has been set-up
|
||||
if (ec.execEnv.state() != null) { // If state has been set-up
|
||||
try {
|
||||
state.closeDown();
|
||||
ec.execEnv.closeDown();
|
||||
} catch (Throwable e) {
|
||||
state.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
|
||||
ec.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
import com.sun.jdi.event.*;
|
@ -0,0 +1,595 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import com.sun.jdi.*;
|
||||
import java.io.EOFException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import jdk.jshell.JShellException;
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import jdk.jshell.spi.ExecutionEnv;
|
||||
import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import jdk.internal.jshell.debug.InternalDebugControl;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
|
||||
/**
|
||||
* Controls the remote execution environment.
|
||||
* Interfaces to the JShell-core by implementing ExecutionControl SPI.
|
||||
* Interfaces to RemoteAgent over a socket and via JDI.
|
||||
* Launches a remote process.
|
||||
*/
|
||||
public class JDIExecutionControl implements ExecutionControl {
|
||||
|
||||
ExecutionEnv execEnv;
|
||||
JDIEnv jdiEnv;
|
||||
private ClassTracker tracker;
|
||||
private JDIEventHandler handler;
|
||||
private Socket socket;
|
||||
private ObjectInputStream remoteIn;
|
||||
private ObjectOutputStream remoteOut;
|
||||
private String remoteVMOptions;
|
||||
|
||||
/**
|
||||
* Initializes the launching JDI execution engine. Initialize JDI and use it
|
||||
* to launch the remote JVM. Set-up control and result communications socket
|
||||
* to the remote execution environment. This socket also transports the
|
||||
* input/output channels.
|
||||
*
|
||||
* @param execEnv the execution environment provided by the JShell-core
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void start(ExecutionEnv execEnv) throws IOException {
|
||||
this.execEnv = execEnv;
|
||||
this.jdiEnv = new JDIEnv(this);
|
||||
this.tracker = new ClassTracker(jdiEnv);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
execEnv.extraRemoteVMOptions().stream()
|
||||
.forEach(s -> {
|
||||
sb.append(" ");
|
||||
sb.append(s);
|
||||
});
|
||||
this.remoteVMOptions = sb.toString();
|
||||
try (ServerSocket listener = new ServerSocket(0)) {
|
||||
// timeout after 60 seconds
|
||||
listener.setSoTimeout(60000);
|
||||
int port = listener.getLocalPort();
|
||||
jdiGo(port);
|
||||
this.socket = listener.accept();
|
||||
// out before in -- match remote creation so we don't hang
|
||||
this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
|
||||
PipeInputStream commandIn = new PipeInputStream();
|
||||
new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
|
||||
this.remoteIn = new ObjectInputStream(commandIn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the execution engine. Send an exit command to the remote agent.
|
||||
* Shuts down the JDI connection. Should this close the socket?
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (remoteOut != null) {
|
||||
remoteOut.writeInt(CMD_EXIT);
|
||||
remoteOut.flush();
|
||||
}
|
||||
JDIConnection c = jdiEnv.connection();
|
||||
if (c != null) {
|
||||
c.disposeVM();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of classes specified. Sends a load command to the remote
|
||||
* agent with pairs of classname/bytes.
|
||||
*
|
||||
* @param classes the names of the wrapper classes to loaded
|
||||
* @return true if all classes loaded successfully
|
||||
*/
|
||||
@Override
|
||||
public boolean load(Collection<String> classes) {
|
||||
try {
|
||||
// Create corresponding ClassInfo instances to track the classes.
|
||||
// Each ClassInfo has the current class bytes associated with it.
|
||||
List<ClassInfo> infos = withBytes(classes);
|
||||
// Send a load command to the remote agent.
|
||||
remoteOut.writeInt(CMD_LOAD);
|
||||
remoteOut.writeInt(classes.size());
|
||||
for (ClassInfo ci : infos) {
|
||||
remoteOut.writeUTF(ci.getClassName());
|
||||
remoteOut.writeObject(ci.getBytes());
|
||||
}
|
||||
remoteOut.flush();
|
||||
// Retrieve and report results from the remote agent.
|
||||
boolean result = readAndReportResult();
|
||||
// For each class that now has a JDI ReferenceType, mark the bytes
|
||||
// as loaded.
|
||||
infos.stream()
|
||||
.filter(ci -> ci.getReferenceTypeOrNull() != null)
|
||||
.forEach(ci -> ci.markLoaded());
|
||||
return result;
|
||||
} catch (IOException ex) {
|
||||
debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the doit method on the specified class.
|
||||
*
|
||||
* @param classname name of the wrapper class whose doit should be invoked
|
||||
* @return return the result value of the doit
|
||||
* @throws JShellException if a user exception was thrown (EvalException) or
|
||||
* an unresolved reference was encountered (UnresolvedReferenceException)
|
||||
*/
|
||||
@Override
|
||||
public String invoke(String classname, String methodname) throws JShellException {
|
||||
try {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = true;
|
||||
}
|
||||
// Send the invoke command to the remote agent.
|
||||
remoteOut.writeInt(CMD_INVOKE);
|
||||
remoteOut.writeUTF(classname);
|
||||
remoteOut.writeUTF(methodname);
|
||||
remoteOut.flush();
|
||||
// Retrieve and report results from the remote agent.
|
||||
if (readAndReportExecutionResult()) {
|
||||
String result = remoteIn.readUTF();
|
||||
return result;
|
||||
}
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
if (!jdiEnv.connection().isRunning()) {
|
||||
// The JDI connection is no longer live, shutdown.
|
||||
jdiEnv.shutdown();
|
||||
} else {
|
||||
debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
}
|
||||
} finally {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = false;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a JShell variable.
|
||||
*
|
||||
* @param classname name of the wrapper class holding the variable
|
||||
* @param varname name of the variable
|
||||
* @return the value as a String
|
||||
*/
|
||||
@Override
|
||||
public String varValue(String classname, String varname) {
|
||||
try {
|
||||
// Send the variable-value command to the remote agent.
|
||||
remoteOut.writeInt(CMD_VARVALUE);
|
||||
remoteOut.writeUTF(classname);
|
||||
remoteOut.writeUTF(varname);
|
||||
remoteOut.flush();
|
||||
// Retrieve and report results from the remote agent.
|
||||
if (readAndReportResult()) {
|
||||
String result = remoteIn.readUTF();
|
||||
return result;
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
jdiEnv.shutdown();
|
||||
} catch (IOException ex) {
|
||||
debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a path to the remote classpath.
|
||||
*
|
||||
* @param cp the additional path element
|
||||
* @return true if succesful
|
||||
*/
|
||||
@Override
|
||||
public boolean addToClasspath(String cp) {
|
||||
try {
|
||||
// Send the classpath addition command to the remote agent.
|
||||
remoteOut.writeInt(CMD_CLASSPATH);
|
||||
remoteOut.writeUTF(cp);
|
||||
remoteOut.flush();
|
||||
// Retrieve and report results from the remote agent.
|
||||
return readAndReportResult();
|
||||
} catch (IOException ex) {
|
||||
throw new InternalError("Classpath addition failed: " + cp, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
|
||||
* an in-place replacement of the classes (preserving class identity) --
|
||||
* that is, existing references to the class do not need to be recompiled.
|
||||
* This implementation uses JDI redefineClasses. It will be unsuccessful if
|
||||
* the signature of the class has changed (see the JDI spec). The
|
||||
* JShell-core is designed to adapt to unsuccessful redefine.
|
||||
*
|
||||
* @param classes the names of the classes to redefine
|
||||
* @return true if all the classes were redefined
|
||||
*/
|
||||
@Override
|
||||
public boolean redefine(Collection<String> classes) {
|
||||
try {
|
||||
// Create corresponding ClassInfo instances to track the classes.
|
||||
// Each ClassInfo has the current class bytes associated with it.
|
||||
List<ClassInfo> infos = withBytes(classes);
|
||||
// Convert to the JDI ReferenceType to class bytes map form needed
|
||||
// by JDI.
|
||||
Map<ReferenceType, byte[]> rmp = infos.stream()
|
||||
.collect(toMap(
|
||||
ci -> ci.getReferenceTypeOrNull(),
|
||||
ci -> ci.getBytes()));
|
||||
// Attempt redefine. Throws exceptions on failure.
|
||||
jdiEnv.vm().redefineClasses(rmp);
|
||||
// Successful: mark the bytes as loaded.
|
||||
infos.stream()
|
||||
.forEach(ci -> ci.markLoaded());
|
||||
return true;
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
// A form of class transformation not supported by JDI
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a collection of class names into ClassInfo instances associated
|
||||
* with the most recently compiled class bytes.
|
||||
*
|
||||
* @param classes names of the classes
|
||||
* @return a list of corresponding ClassInfo instances
|
||||
*/
|
||||
private List<ClassInfo> withBytes(Collection<String> classes) {
|
||||
return classes.stream()
|
||||
.map(cn -> tracker.classInfo(cn, execEnv.getClassBytes(cn)))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
|
||||
* the most recent successfully loaded/redefined bytes match the current
|
||||
* compiled bytes.
|
||||
*
|
||||
* @param classname the name of the class to test
|
||||
* @return the status
|
||||
*/
|
||||
@Override
|
||||
public ClassStatus getClassStatus(String classname) {
|
||||
ClassInfo ci = tracker.get(classname);
|
||||
if (ci.getReferenceTypeOrNull() == null) {
|
||||
// If the class does not have a JDI ReferenceType it has not been loaded
|
||||
return ClassStatus.UNKNOWN;
|
||||
}
|
||||
// Compare successfully loaded with last compiled bytes.
|
||||
return (Arrays.equals(execEnv.getClassBytes(classname), ci.getLoadedBytes()))
|
||||
? ClassStatus.CURRENT
|
||||
: ClassStatus.NOT_CURRENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports results from a remote agent command that does not expect
|
||||
* exceptions.
|
||||
*
|
||||
* @return true if successful
|
||||
* @throws IOException if the connection has dropped
|
||||
*/
|
||||
private boolean readAndReportResult() throws IOException {
|
||||
int ok = remoteIn.readInt();
|
||||
switch (ok) {
|
||||
case RESULT_SUCCESS:
|
||||
return true;
|
||||
case RESULT_FAIL: {
|
||||
String ex = remoteIn.readUTF();
|
||||
debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
debug(DBG_GEN, "Bad remote result code: %s\n", ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports results from a remote agent command that expects runtime
|
||||
* exceptions.
|
||||
*
|
||||
* @return true if successful
|
||||
* @throws IOException if the connection has dropped
|
||||
* @throws EvalException if a user exception was encountered on invoke
|
||||
* @throws UnresolvedReferenceException if an unresolved reference was
|
||||
* encountered
|
||||
*/
|
||||
private boolean readAndReportExecutionResult() throws IOException, JShellException {
|
||||
int ok = remoteIn.readInt();
|
||||
switch (ok) {
|
||||
case RESULT_SUCCESS:
|
||||
return true;
|
||||
case RESULT_FAIL: {
|
||||
// An internal error has occurred.
|
||||
String ex = remoteIn.readUTF();
|
||||
return false;
|
||||
}
|
||||
case RESULT_EXCEPTION: {
|
||||
// A user exception was encountered.
|
||||
String exceptionClassName = remoteIn.readUTF();
|
||||
String message = remoteIn.readUTF();
|
||||
StackTraceElement[] elems = readStackTrace();
|
||||
throw execEnv.createEvalException(message, exceptionClassName, elems);
|
||||
}
|
||||
case RESULT_CORRALLED: {
|
||||
// An unresolved reference was encountered.
|
||||
int id = remoteIn.readInt();
|
||||
StackTraceElement[] elems = readStackTrace();
|
||||
throw execEnv.createUnresolvedReferenceException(id, elems);
|
||||
}
|
||||
case RESULT_KILLED: {
|
||||
// Execution was aborted by the stop()
|
||||
debug(DBG_GEN, "Killed.");
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
debug(DBG_GEN, "Bad remote result code: %s\n", ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StackTraceElement[] readStackTrace() throws IOException {
|
||||
int elemCount = remoteIn.readInt();
|
||||
StackTraceElement[] elems = new StackTraceElement[elemCount];
|
||||
for (int i = 0; i < elemCount; ++i) {
|
||||
String className = remoteIn.readUTF();
|
||||
String methodName = remoteIn.readUTF();
|
||||
String fileName = remoteIn.readUTF();
|
||||
int line = remoteIn.readInt();
|
||||
elems[i] = new StackTraceElement(className, methodName, fileName, line);
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the remote agent as a JDI connection.
|
||||
*
|
||||
* @param port the socket port for (non-JDI) commands
|
||||
*/
|
||||
private void jdiGo(int port) {
|
||||
//MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
|
||||
// Locale.getDefault());
|
||||
|
||||
// Set-up for a fresh launch of a remote agent with any user-specified VM options.
|
||||
String connectorName = "com.sun.jdi.CommandLineLaunch";
|
||||
Map<String, String> argumentName2Value = new HashMap<>();
|
||||
argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
|
||||
argumentName2Value.put("options", remoteVMOptions);
|
||||
|
||||
boolean launchImmediately = true;
|
||||
int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
|
||||
|
||||
// Launch.
|
||||
jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
|
||||
|
||||
if (jdiEnv.connection().isOpen() && jdiEnv.vm().canBeModified()) {
|
||||
/*
|
||||
* Connection opened on startup. Start event handler
|
||||
* immediately, telling it (through arg 2) to stop on the
|
||||
* VM start event.
|
||||
*/
|
||||
handler = new JDIEventHandler(jdiEnv);
|
||||
}
|
||||
}
|
||||
|
||||
private final Object STOP_LOCK = new Object();
|
||||
private boolean userCodeRunning = false;
|
||||
|
||||
/**
|
||||
* Interrupt a running invoke.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
synchronized (STOP_LOCK) {
|
||||
if (!userCodeRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualMachine vm = handler.env.vm();
|
||||
vm.suspend();
|
||||
try {
|
||||
OUTER:
|
||||
for (ThreadReference thread : vm.allThreads()) {
|
||||
// could also tag the thread (e.g. using name), to find it easier
|
||||
for (StackFrame frame : thread.frames()) {
|
||||
String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent";
|
||||
if (remoteAgentName.equals(frame.location().declaringType().name())
|
||||
&& "commandLoop".equals(frame.location().method().name())) {
|
||||
ObjectReference thiz = frame.thisObject();
|
||||
if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) {
|
||||
thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true));
|
||||
ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException"));
|
||||
|
||||
vm.resume();
|
||||
debug(DBG_GEN, "Attempting to stop the client code...\n");
|
||||
thread.stop(stopInstance);
|
||||
thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false));
|
||||
}
|
||||
|
||||
break OUTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
|
||||
debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
|
||||
} finally {
|
||||
vm.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void debug(int flags, String format, Object... args) {
|
||||
InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), flags, format, args);
|
||||
}
|
||||
|
||||
void debug(Exception ex, String where) {
|
||||
InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where);
|
||||
}
|
||||
|
||||
private final class DemultiplexInput extends Thread {
|
||||
|
||||
private final DataInputStream delegate;
|
||||
private final PipeInputStream command;
|
||||
private final PrintStream out;
|
||||
private final PrintStream err;
|
||||
|
||||
public DemultiplexInput(InputStream input,
|
||||
PipeInputStream command,
|
||||
PrintStream out,
|
||||
PrintStream err) {
|
||||
super("output reader");
|
||||
this.delegate = new DataInputStream(input);
|
||||
this.command = command;
|
||||
this.out = out;
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
int nameLen = delegate.read();
|
||||
if (nameLen == (-1))
|
||||
break;
|
||||
byte[] name = new byte[nameLen];
|
||||
DemultiplexInput.this.delegate.readFully(name);
|
||||
int dataLen = delegate.read();
|
||||
byte[] data = new byte[dataLen];
|
||||
DemultiplexInput.this.delegate.readFully(data);
|
||||
switch (new String(name, "UTF-8")) {
|
||||
case "err":
|
||||
err.write(data);
|
||||
break;
|
||||
case "out":
|
||||
out.write(data);
|
||||
break;
|
||||
case "command":
|
||||
for (byte b : data) {
|
||||
command.write(Byte.toUnsignedInt(b));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
debug(ex, "Failed reading output");
|
||||
} finally {
|
||||
command.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class PipeInputStream extends InputStream {
|
||||
public static final int INITIAL_SIZE = 128;
|
||||
|
||||
private int[] buffer = new int[INITIAL_SIZE];
|
||||
private int start;
|
||||
private int end;
|
||||
private boolean closed;
|
||||
|
||||
@Override
|
||||
public synchronized int read() {
|
||||
while (start == end) {
|
||||
if (closed) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
try {
|
||||
return buffer[start];
|
||||
} finally {
|
||||
start = (start + 1) % buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void write(int b) {
|
||||
if (closed)
|
||||
throw new IllegalStateException("Already closed.");
|
||||
int newEnd = (end + 1) % buffer.length;
|
||||
if (newEnd == start) {
|
||||
//overflow:
|
||||
int[] newBuffer = new int[buffer.length * 2];
|
||||
int rightPart = (end > start ? end : buffer.length) - start;
|
||||
int leftPart = end > start ? 0 : start - 1;
|
||||
System.arraycopy(buffer, start, newBuffer, 0, rightPart);
|
||||
System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
|
||||
buffer = newBuffer;
|
||||
start = 0;
|
||||
end = rightPart + leftPart;
|
||||
newEnd = end + 1;
|
||||
}
|
||||
buffer[end] = b;
|
||||
end = newEnd;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
closed = true;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
package jdk.internal.jshell.jdi;
|
||||
|
||||
/**
|
||||
* Internal exception when Java Debug Interface VirtualMacine is not connected.
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
package jdk.internal.jshell.remote;
|
||||
import jdk.jshell.spi.SPIResolutionException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
@ -111,10 +112,11 @@ class RemoteAgent {
|
||||
out.flush();
|
||||
break;
|
||||
}
|
||||
String methodName = in.readUTF();
|
||||
Method doitMethod;
|
||||
try {
|
||||
this.getClass().getModule().addExports(RemoteResolutionException.class.getPackage().getName(), klass.getModule());
|
||||
doitMethod = klass.getDeclaredMethod(DOIT_METHOD_NAME, new Class<?>[0]);
|
||||
this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
|
||||
doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
|
||||
doitMethod.setAccessible(true);
|
||||
Object res;
|
||||
try {
|
||||
@ -138,9 +140,9 @@ class RemoteAgent {
|
||||
} catch (InvocationTargetException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
StackTraceElement[] elems = cause.getStackTrace();
|
||||
if (cause instanceof RemoteResolutionException) {
|
||||
if (cause instanceof SPIResolutionException) {
|
||||
out.writeInt(RESULT_CORRALLED);
|
||||
out.writeInt(((RemoteResolutionException) cause).id);
|
||||
out.writeInt(((SPIResolutionException) cause).id());
|
||||
} else {
|
||||
out.writeInt(RESULT_EXCEPTION);
|
||||
out.writeUTF(cause.getClass().getName());
|
||||
@ -254,27 +256,14 @@ class RemoteAgent {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
} else if (value instanceof String) {
|
||||
return "\"" + expunge((String)value) + "\"";
|
||||
return "\"" + (String)value + "\"";
|
||||
} else if (value instanceof Character) {
|
||||
return "'" + value + "'";
|
||||
} else {
|
||||
return expunge(value.toString());
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expunge internal info from string
|
||||
* @param s string to process
|
||||
* @return string the display, JShell package and wrapper class names removed
|
||||
*/
|
||||
static String expunge(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String comp : PREFIX_PATTERN.split(s)) {
|
||||
sb.append(comp);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static final class MultiplexingOutputStream extends OutputStream {
|
||||
|
||||
private static final int PACKET_SIZE = 127;
|
||||
|
@ -25,8 +25,6 @@
|
||||
|
||||
package jdk.internal.jshell.remote;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Communication constants shared between the main process and the remote
|
||||
* execution process
|
||||
@ -46,13 +44,4 @@ public class RemoteCodes {
|
||||
public static final int RESULT_EXCEPTION = 102;
|
||||
public static final int RESULT_CORRALLED = 103;
|
||||
public static final int RESULT_KILLED = 104;
|
||||
|
||||
// String constants
|
||||
public static final String REPL_PACKAGE = "REPL";
|
||||
public static final String REPL_CLASS_PREFIX = "$JShell$";
|
||||
public static final String DOIT_METHOD_NAME = "do_it$";
|
||||
public static final Pattern PREFIX_PATTERN = Pattern.compile(
|
||||
"(" + REPL_PACKAGE + "\\.)?" +
|
||||
"(?<class>" + Pattern.quote(REPL_CLASS_PREFIX) +
|
||||
"\\w+" + ")" + "[\\$\\.]?");
|
||||
}
|
||||
|
@ -101,8 +101,13 @@ import jdk.internal.jshell.tool.Feedback.FormatResolve;
|
||||
import jdk.internal.jshell.tool.Feedback.FormatUnresolved;
|
||||
import jdk.internal.jshell.tool.Feedback.FormatWhen;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
|
||||
/**
|
||||
* Command line REPL tool for Java using the JShell API.
|
||||
@ -1344,7 +1349,7 @@ public class JShellTool implements MessageHandler {
|
||||
boolean cmdDebug(String arg) {
|
||||
if (arg.isEmpty()) {
|
||||
debug = !debug;
|
||||
InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
|
||||
InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0);
|
||||
fluff("Debugging %s", debug ? "on" : "off");
|
||||
} else {
|
||||
int flags = 0;
|
||||
@ -1360,23 +1365,23 @@ public class JShellTool implements MessageHandler {
|
||||
fluff("REPL tool debugging on");
|
||||
break;
|
||||
case 'g':
|
||||
flags |= InternalDebugControl.DBG_GEN;
|
||||
flags |= DBG_GEN;
|
||||
fluff("General debugging on");
|
||||
break;
|
||||
case 'f':
|
||||
flags |= InternalDebugControl.DBG_FMGR;
|
||||
flags |= DBG_FMGR;
|
||||
fluff("File manager debugging on");
|
||||
break;
|
||||
case 'c':
|
||||
flags |= InternalDebugControl.DBG_COMPA;
|
||||
flags |= DBG_COMPA;
|
||||
fluff("Completion analysis debugging on");
|
||||
break;
|
||||
case 'd':
|
||||
flags |= InternalDebugControl.DBG_DEP;
|
||||
flags |= DBG_DEP;
|
||||
fluff("Dependency debugging on");
|
||||
break;
|
||||
case 'e':
|
||||
flags |= InternalDebugControl.DBG_EVNT;
|
||||
flags |= DBG_EVNT;
|
||||
fluff("Event debugging on");
|
||||
break;
|
||||
default:
|
||||
|
@ -49,6 +49,8 @@ import static com.sun.tools.javac.code.Flags.STATIC;
|
||||
import static com.sun.tools.javac.code.Flags.INTERFACE;
|
||||
import static com.sun.tools.javac.code.Flags.ENUM;
|
||||
import static com.sun.tools.javac.code.Flags.PUBLIC;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import jdk.jshell.spi.SPIResolutionException;
|
||||
|
||||
/**
|
||||
* Produce a corralled version of the Wrap for a snippet.
|
||||
@ -129,16 +131,22 @@ class Corraller extends Pretty {
|
||||
super.visitClassDef(tree);
|
||||
}
|
||||
|
||||
// Build a compiler tree for an exception throwing block, e.g.:
|
||||
// {
|
||||
// throw new jdk.jshell.spi.SPIResolutionException(9);
|
||||
// }
|
||||
private JCBlock resolutionExceptionBlock() {
|
||||
if (resolutionExceptionBlock == null) {
|
||||
JCExpression expClass
|
||||
= make.Select(make.Select(make.Select(make.Select(
|
||||
make.Ident(names.fromString("jdk")),
|
||||
names.fromString("internal")),
|
||||
names.fromString("jshell")),
|
||||
names.fromString("remote")),
|
||||
names.fromString("RemoteResolutionException")
|
||||
);
|
||||
JCExpression expClass = null;
|
||||
// Split the exception class name at dots
|
||||
for (String id : SPIResolutionException.class.getName().split("\\.")) {
|
||||
Name nm = names.fromString(id);
|
||||
if (expClass == null) {
|
||||
expClass = make.Ident(nm);
|
||||
} else {
|
||||
expClass = make.Select(expClass, nm);
|
||||
}
|
||||
}
|
||||
JCNewClass exp = make.NewClass(null,
|
||||
null, expClass, List.of(make.Literal(keyIndex)), null);
|
||||
resolutionExceptionBlock = make.Block(0L, List.<JCStatement>of(
|
||||
|
@ -49,7 +49,6 @@ import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
import jdk.jshell.Key.ErroneousKey;
|
||||
import jdk.jshell.Key.MethodKey;
|
||||
import jdk.jshell.Key.TypeDeclKey;
|
||||
@ -64,9 +63,9 @@ import jdk.jshell.Snippet.Status;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
import static jdk.jshell.Util.*;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
|
||||
import static jdk.jshell.Util.DOIT_METHOD_NAME;
|
||||
import static jdk.jshell.Util.PREFIX_PATTERN;
|
||||
import static jdk.jshell.Util.expunge;
|
||||
import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
|
||||
import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
|
||||
import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
|
||||
@ -456,7 +455,7 @@ class Eval {
|
||||
if (si.status().isDefined) {
|
||||
if (si.isExecutable()) {
|
||||
try {
|
||||
value = state.executionControl().commandInvoke(si.classFullName());
|
||||
value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
|
||||
value = si.subKind().hasValue()
|
||||
? expunge(value)
|
||||
: "";
|
||||
@ -578,7 +577,7 @@ class Eval {
|
||||
|
||||
// load all new classes
|
||||
load(legit.stream()
|
||||
.flatMap(u -> u.classesToLoad(ct.classInfoList(u.snippet().outerWrap())))
|
||||
.flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
|
||||
.collect(toSet()));
|
||||
// attempt to redefine the remaining classes
|
||||
List<Unit> toReplace = legit.stream()
|
||||
@ -613,9 +612,13 @@ class Eval {
|
||||
}
|
||||
}
|
||||
|
||||
private void load(Set<ClassInfo> cil) {
|
||||
if (!cil.isEmpty()) {
|
||||
state.executionControl().commandLoad(cil);
|
||||
/**
|
||||
* If there are classes to load, loads by calling the execution engine.
|
||||
* @param classnames names of the classes to load.
|
||||
*/
|
||||
private void load(Collection<String> classnames) {
|
||||
if (!classnames.isEmpty()) {
|
||||
state.executionControl().load(classnames);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,434 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import com.sun.jdi.*;
|
||||
import java.io.EOFException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
|
||||
/**
|
||||
* Controls the remote execution environment.
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
class ExecutionControl {
|
||||
|
||||
private final JDIEnv env;
|
||||
private final SnippetMaps maps;
|
||||
private JDIEventHandler handler;
|
||||
private Socket socket;
|
||||
private ObjectInputStream in;
|
||||
private ObjectOutputStream out;
|
||||
private final JShell proc;
|
||||
private final String remoteVMOptions;
|
||||
|
||||
ExecutionControl(JDIEnv env, SnippetMaps maps, JShell proc, List<String> extraRemoteVMOptions) {
|
||||
this.env = env;
|
||||
this.maps = maps;
|
||||
this.proc = proc;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
extraRemoteVMOptions.stream()
|
||||
.forEach(s -> {
|
||||
sb.append(" ");
|
||||
sb.append(s);
|
||||
});
|
||||
this.remoteVMOptions = sb.toString();
|
||||
}
|
||||
|
||||
void launch() throws IOException {
|
||||
try (ServerSocket listener = new ServerSocket(0)) {
|
||||
// timeout after 60 seconds
|
||||
listener.setSoTimeout(60000);
|
||||
int port = listener.getLocalPort();
|
||||
jdiGo(port);
|
||||
socket = listener.accept();
|
||||
// out before in -- match remote creation so we don't hang
|
||||
out = new ObjectOutputStream(socket.getOutputStream());
|
||||
PipeInputStream commandIn = new PipeInputStream();
|
||||
new DemultiplexInput(socket.getInputStream(), commandIn, proc.out, proc.err).start();
|
||||
in = new ObjectInputStream(commandIn);
|
||||
}
|
||||
}
|
||||
|
||||
void commandExit() {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.writeInt(CMD_EXIT);
|
||||
out.flush();
|
||||
}
|
||||
JDIConnection c = env.connection();
|
||||
if (c != null) {
|
||||
c.disposeVM();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean commandLoad(Collection<ClassInfo> cil) {
|
||||
try {
|
||||
out.writeInt(CMD_LOAD);
|
||||
out.writeInt(cil.size());
|
||||
for (ClassInfo ci : cil) {
|
||||
out.writeUTF(ci.getClassName());
|
||||
out.writeObject(ci.getBytes());
|
||||
}
|
||||
out.flush();
|
||||
return readAndReportResult();
|
||||
} catch (IOException ex) {
|
||||
proc.debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String commandInvoke(String classname) throws JShellException {
|
||||
try {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = true;
|
||||
}
|
||||
out.writeInt(CMD_INVOKE);
|
||||
out.writeUTF(classname);
|
||||
out.flush();
|
||||
if (readAndReportExecutionResult()) {
|
||||
String result = in.readUTF();
|
||||
return result;
|
||||
}
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
if (!env.connection().isRunning()) {
|
||||
env.shutdown();
|
||||
} else {
|
||||
proc.debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
}
|
||||
} finally {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = false;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String commandVarValue(String classname, String varname) {
|
||||
try {
|
||||
out.writeInt(CMD_VARVALUE);
|
||||
out.writeUTF(classname);
|
||||
out.writeUTF(varname);
|
||||
out.flush();
|
||||
if (readAndReportResult()) {
|
||||
String result = in.readUTF();
|
||||
return result;
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
env.shutdown();
|
||||
} catch (IOException ex) {
|
||||
proc.debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
|
||||
return "Execution failure: " + ex.getMessage();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
boolean commandAddToClasspath(String cp) {
|
||||
try {
|
||||
out.writeInt(CMD_CLASSPATH);
|
||||
out.writeUTF(cp);
|
||||
out.flush();
|
||||
return readAndReportResult();
|
||||
} catch (IOException ex) {
|
||||
throw new InternalError("Classpath addition failed: " + cp, ex);
|
||||
}
|
||||
}
|
||||
|
||||
boolean commandRedefine(Map<ReferenceType, byte[]> mp) {
|
||||
try {
|
||||
env.vm().redefineClasses(mp);
|
||||
return true;
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
proc.debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ReferenceType nameToRef(String name) {
|
||||
List<ReferenceType> rtl = env.vm().classesByName(name);
|
||||
if (rtl.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return rtl.get(0);
|
||||
}
|
||||
|
||||
private boolean readAndReportResult() throws IOException {
|
||||
int ok = in.readInt();
|
||||
switch (ok) {
|
||||
case RESULT_SUCCESS:
|
||||
return true;
|
||||
case RESULT_FAIL: {
|
||||
String ex = in.readUTF();
|
||||
proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readAndReportExecutionResult() throws IOException, JShellException {
|
||||
int ok = in.readInt();
|
||||
switch (ok) {
|
||||
case RESULT_SUCCESS:
|
||||
return true;
|
||||
case RESULT_FAIL: {
|
||||
String ex = in.readUTF();
|
||||
proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
|
||||
return false;
|
||||
}
|
||||
case RESULT_EXCEPTION: {
|
||||
String exceptionClassName = in.readUTF();
|
||||
String message = in.readUTF();
|
||||
StackTraceElement[] elems = readStackTrace();
|
||||
EvalException ee = new EvalException(message, exceptionClassName, elems);
|
||||
throw ee;
|
||||
}
|
||||
case RESULT_CORRALLED: {
|
||||
int id = in.readInt();
|
||||
StackTraceElement[] elems = readStackTrace();
|
||||
Snippet si = maps.getSnippetDeadOrAlive(id);
|
||||
throw new UnresolvedReferenceException((DeclarationSnippet) si, elems);
|
||||
}
|
||||
case RESULT_KILLED: {
|
||||
proc.out.println("Killed.");
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StackTraceElement[] readStackTrace() throws IOException {
|
||||
int elemCount = in.readInt();
|
||||
StackTraceElement[] elems = new StackTraceElement[elemCount];
|
||||
for (int i = 0; i < elemCount; ++i) {
|
||||
String className = in.readUTF();
|
||||
String methodName = in.readUTF();
|
||||
String fileName = in.readUTF();
|
||||
int line = in.readInt();
|
||||
elems[i] = new StackTraceElement(className, methodName, fileName, line);
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
private void jdiGo(int port) {
|
||||
//MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
|
||||
// Locale.getDefault());
|
||||
|
||||
String connectorName = "com.sun.jdi.CommandLineLaunch";
|
||||
Map<String, String> argumentName2Value = new HashMap<>();
|
||||
argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
|
||||
argumentName2Value.put("options", remoteVMOptions);
|
||||
|
||||
boolean launchImmediately = true;
|
||||
int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
|
||||
|
||||
env.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
|
||||
|
||||
if (env.connection().isOpen() && env.vm().canBeModified()) {
|
||||
/*
|
||||
* Connection opened on startup. Start event handler
|
||||
* immediately, telling it (through arg 2) to stop on the
|
||||
* VM start event.
|
||||
*/
|
||||
handler = new JDIEventHandler(env);
|
||||
}
|
||||
}
|
||||
|
||||
private final Object STOP_LOCK = new Object();
|
||||
private boolean userCodeRunning = false;
|
||||
|
||||
void commandStop() {
|
||||
synchronized (STOP_LOCK) {
|
||||
if (!userCodeRunning)
|
||||
return ;
|
||||
|
||||
VirtualMachine vm = handler.env.vm();
|
||||
vm.suspend();
|
||||
try {
|
||||
OUTER: for (ThreadReference thread : vm.allThreads()) {
|
||||
// could also tag the thread (e.g. using name), to find it easier
|
||||
for (StackFrame frame : thread.frames()) {
|
||||
String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent";
|
||||
if (remoteAgentName.equals(frame.location().declaringType().name()) &&
|
||||
"commandLoop".equals(frame.location().method().name())) {
|
||||
ObjectReference thiz = frame.thisObject();
|
||||
if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) {
|
||||
thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true));
|
||||
ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException"));
|
||||
|
||||
vm.resume();
|
||||
proc.debug(DBG_GEN, "Attempting to stop the client code...\n");
|
||||
thread.stop(stopInstance);
|
||||
thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false));
|
||||
}
|
||||
|
||||
break OUTER;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
|
||||
proc.debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
|
||||
} finally {
|
||||
vm.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DemultiplexInput extends Thread {
|
||||
|
||||
private final DataInputStream delegate;
|
||||
private final PipeInputStream command;
|
||||
private final PrintStream out;
|
||||
private final PrintStream err;
|
||||
|
||||
public DemultiplexInput(InputStream input,
|
||||
PipeInputStream command,
|
||||
PrintStream out,
|
||||
PrintStream err) {
|
||||
super("output reader");
|
||||
this.delegate = new DataInputStream(input);
|
||||
this.command = command;
|
||||
this.out = out;
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
int nameLen = delegate.read();
|
||||
if (nameLen == (-1))
|
||||
break;
|
||||
byte[] name = new byte[nameLen];
|
||||
DemultiplexInput.this.delegate.readFully(name);
|
||||
int dataLen = delegate.read();
|
||||
byte[] data = new byte[dataLen];
|
||||
DemultiplexInput.this.delegate.readFully(data);
|
||||
switch (new String(name, "UTF-8")) {
|
||||
case "err":
|
||||
err.write(data);
|
||||
break;
|
||||
case "out":
|
||||
out.write(data);
|
||||
break;
|
||||
case "command":
|
||||
for (byte b : data) {
|
||||
command.write(Byte.toUnsignedInt(b));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
proc.debug(ex, "Failed reading output");
|
||||
} finally {
|
||||
command.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class PipeInputStream extends InputStream {
|
||||
public static final int INITIAL_SIZE = 128;
|
||||
|
||||
private int[] buffer = new int[INITIAL_SIZE];
|
||||
private int start;
|
||||
private int end;
|
||||
private boolean closed;
|
||||
|
||||
@Override
|
||||
public synchronized int read() {
|
||||
while (start == end) {
|
||||
if (closed) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
try {
|
||||
return buffer[start];
|
||||
} finally {
|
||||
start = (start + 1) % buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void write(int b) {
|
||||
if (closed)
|
||||
throw new IllegalStateException("Already closed.");
|
||||
int newEnd = (end + 1) % buffer.length;
|
||||
if (newEnd == start) {
|
||||
//overflow:
|
||||
int[] newBuffer = new int[buffer.length * 2];
|
||||
int rightPart = (end > start ? end : buffer.length) - start;
|
||||
int leftPart = end > start ? 0 : start - 1;
|
||||
System.arraycopy(buffer, start, newBuffer, 0, rightPart);
|
||||
System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
|
||||
buffer = newBuffer;
|
||||
start = 0;
|
||||
end = rightPart + leftPart;
|
||||
newEnd = end + 1;
|
||||
}
|
||||
buffer[end] = b;
|
||||
end = newEnd;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
closed = true;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
@ -47,6 +48,8 @@ import static java.util.stream.Collectors.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
|
||||
@ -71,7 +74,6 @@ import jdk.jshell.Snippet.Status;
|
||||
* <p>
|
||||
* This class is not thread safe, except as noted, all access should be through
|
||||
* a single thread.
|
||||
* @see jdk.jshell
|
||||
* @author Robert Field
|
||||
*/
|
||||
public class JShell implements AutoCloseable {
|
||||
@ -86,16 +88,17 @@ public class JShell implements AutoCloseable {
|
||||
final Supplier<String> tempVariableNameGenerator;
|
||||
final BiFunction<Snippet, Integer, String> idGenerator;
|
||||
final List<String> extraRemoteVMOptions;
|
||||
final ExecutionControl executionControl;
|
||||
|
||||
private int nextKeyIndex = 1;
|
||||
|
||||
final Eval eval;
|
||||
final ClassTracker classTracker;
|
||||
private final Map<String, byte[]> classnameToBytes = new HashMap<>();
|
||||
private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
|
||||
private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
|
||||
private boolean closed = false;
|
||||
|
||||
private ExecutionControl executionControl = null;
|
||||
private boolean executionControlLaunched = false;
|
||||
private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
|
||||
|
||||
private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n";
|
||||
@ -108,13 +111,15 @@ public class JShell implements AutoCloseable {
|
||||
this.tempVariableNameGenerator = b.tempVariableNameGenerator;
|
||||
this.idGenerator = b.idGenerator;
|
||||
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
|
||||
this.executionControl = b.executionControl==null
|
||||
? new JDIExecutionControl()
|
||||
: b.executionControl;
|
||||
|
||||
this.maps = new SnippetMaps(this);
|
||||
this.keyMap = new KeyMap(this);
|
||||
this.outerMap = new OuterWrapMap(this);
|
||||
this.taskFactory = new TaskFactory(this);
|
||||
this.eval = new Eval(this);
|
||||
this.classTracker = new ClassTracker(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,22 +148,23 @@ public class JShell implements AutoCloseable {
|
||||
Supplier<String> tempVariableNameGenerator = null;
|
||||
BiFunction<Snippet, Integer, String> idGenerator = null;
|
||||
List<String> extraRemoteVMOptions = new ArrayList<>();
|
||||
ExecutionControl executionControl;
|
||||
|
||||
Builder() { }
|
||||
|
||||
/**
|
||||
* Input for the running evaluation (it's <code>System.in</code>). Note:
|
||||
* applications that use <code>System.in</code> for snippet or other
|
||||
* user input cannot use <code>System.in</code> as the input stream for
|
||||
* Sets the input for the running evaluation (it's {@code System.in}). Note:
|
||||
* applications that use {@code System.in} for snippet or other
|
||||
* user input cannot use {@code System.in} as the input stream for
|
||||
* the remote process.
|
||||
* <p>
|
||||
* The default, if this is not set, is to provide an empty input stream
|
||||
* -- <code>new ByteArrayInputStream(new byte[0])</code>.
|
||||
* -- {@code new ByteArrayInputStream(new byte[0])}.
|
||||
*
|
||||
* @param in the <code>InputStream</code> to be channelled to
|
||||
* <code>System.in</code> in the remote execution process.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* @param in the {@code InputStream} to be channelled to
|
||||
* {@code System.in} in the remote execution process
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder in(InputStream in) {
|
||||
this.in = in;
|
||||
@ -166,16 +172,16 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Output for the running evaluation (it's <code>System.out</code>).
|
||||
* Sets the output for the running evaluation (it's {@code System.out}).
|
||||
* The controlling process and
|
||||
* the remote process can share <code>System.out</code>.
|
||||
* the remote process can share {@code System.out}.
|
||||
* <p>
|
||||
* The default, if this is not set, is <code>System.out</code>.
|
||||
* The default, if this is not set, is {@code System.out}.
|
||||
*
|
||||
* @param out the <code>PrintStream</code> to be channelled to
|
||||
* <code>System.out</code> in the remote execution process.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* @param out the {@code PrintStream} to be channelled to
|
||||
* {@code System.out} in the remote execution process
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder out(PrintStream out) {
|
||||
this.out = out;
|
||||
@ -183,16 +189,16 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Error output for the running evaluation (it's
|
||||
* <code>System.err</code>). The controlling process and the remote
|
||||
* process can share <code>System.err</code>.
|
||||
* Sets the error output for the running evaluation (it's
|
||||
* {@code System.err}). The controlling process and the remote
|
||||
* process can share {@code System.err}.
|
||||
* <p>
|
||||
* The default, if this is not set, is <code>System.err</code>.
|
||||
* The default, if this is not set, is {@code System.err}.
|
||||
*
|
||||
* @param err the <code>PrintStream</code> to be channelled to
|
||||
* <code>System.err</code> in the remote execution process.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* @param err the {@code PrintStream} to be channelled to
|
||||
* {@code System.err} in the remote execution process
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder err(PrintStream err) {
|
||||
this.err = err;
|
||||
@ -200,7 +206,7 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a generator of temp variable names for
|
||||
* Sets a generator of temp variable names for
|
||||
* {@link jdk.jshell.VarSnippet} of
|
||||
* {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
|
||||
* <p>
|
||||
@ -221,9 +227,9 @@ public class JShell implements AutoCloseable {
|
||||
* prefixing dollar sign ("$").
|
||||
*
|
||||
* @param generator the <code>Supplier</code> to generate the temporary
|
||||
* variable name string or <code>null</code>.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* variable name string or <code>null</code>
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder tempVariableNameGenerator(Supplier<String> generator) {
|
||||
this.tempVariableNameGenerator = generator;
|
||||
@ -231,7 +237,7 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the generator of identifying names for Snippets.
|
||||
* Sets the generator of identifying names for Snippets.
|
||||
* <p>
|
||||
* Do not use this method unless you have explicit need for it.
|
||||
* <p>
|
||||
@ -258,9 +264,9 @@ public class JShell implements AutoCloseable {
|
||||
* is null) is to generate the id as the integer converted to a string.
|
||||
*
|
||||
* @param generator the <code>BiFunction</code> to generate the id
|
||||
* string or <code>null</code>.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* string or <code>null</code>
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
|
||||
this.idGenerator = generator;
|
||||
@ -268,11 +274,11 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set additional VM options for launching the VM.
|
||||
* Sets additional VM options for launching the VM.
|
||||
*
|
||||
* @param options The options for the remote VM.
|
||||
* @return the <code>Builder</code> instance (for use in chained
|
||||
* initialization).
|
||||
* @param options The options for the remote VM
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder remoteVMOptions(String... options) {
|
||||
this.extraRemoteVMOptions.addAll(Arrays.asList(options));
|
||||
@ -280,11 +286,24 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JShell state engine. This is the entry-point to all JShell
|
||||
* Sets the custom engine for execution. Snippet execution will be
|
||||
* provided by the specified {@link ExecutionControl} instance.
|
||||
*
|
||||
* @param execEngine the execution engine
|
||||
* @return the {@code Builder} instance (for use in chained
|
||||
* initialization)
|
||||
*/
|
||||
public Builder executionEngine(ExecutionControl execEngine) {
|
||||
this.executionControl = execEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JShell state engine. This is the entry-point to all JShell
|
||||
* functionality. This creates a remote process for execution. It is
|
||||
* thus important to close the returned instance.
|
||||
*
|
||||
* @return the state engine.
|
||||
* @return the state engine
|
||||
*/
|
||||
public JShell build() {
|
||||
return new JShell(this);
|
||||
@ -406,7 +425,7 @@ public class JShell implements AutoCloseable {
|
||||
*/
|
||||
public void addToClasspath(String path) {
|
||||
taskFactory.addToClasspath(path); // Compiler
|
||||
executionControl().commandAddToClasspath(path); // Runtime
|
||||
executionControl().addToClasspath(path); // Runtime
|
||||
if (sourceCodeAnalysis != null) {
|
||||
sourceCodeAnalysis.classpathChanged();
|
||||
}
|
||||
@ -427,7 +446,7 @@ public class JShell implements AutoCloseable {
|
||||
*/
|
||||
public void stop() {
|
||||
if (executionControl != null)
|
||||
executionControl.commandStop();
|
||||
executionControl.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -438,7 +457,7 @@ public class JShell implements AutoCloseable {
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
closeDown();
|
||||
executionControl().commandExit();
|
||||
executionControl().close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,7 +599,7 @@ public class JShell implements AutoCloseable {
|
||||
throw new IllegalArgumentException(
|
||||
messageFormat("jshell.exc.var.not.valid", snippet, snippet.status()));
|
||||
}
|
||||
String value = executionControl().commandVarValue(snippet.classFullName(), snippet.name());
|
||||
String value = executionControl().varValue(snippet.classFullName(), snippet.name());
|
||||
return expunge(value);
|
||||
}
|
||||
|
||||
@ -633,35 +652,86 @@ public class JShell implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
// --- private / package-private implementation support ---
|
||||
/**
|
||||
* Provide the environment for a execution engine.
|
||||
*/
|
||||
class ExecutionEnvImpl implements ExecutionEnv {
|
||||
|
||||
@Override
|
||||
public InputStream userIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintStream userOut() {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintStream userErr() {
|
||||
return err;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JShell state() {
|
||||
return JShell.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> extraRemoteVMOptions() {
|
||||
return extraRemoteVMOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getClassBytes(String classname) {
|
||||
return classnameToBytes.get(classname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvalException createEvalException(String message, String exceptionClass, StackTraceElement[] stackElements) {
|
||||
return new EvalException(message, exceptionClass, stackElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnresolvedReferenceException createUnresolvedReferenceException(int id, StackTraceElement[] stackElements) {
|
||||
DeclarationSnippet sn = (DeclarationSnippet) maps.getSnippetDeadOrAlive(id);
|
||||
return new UnresolvedReferenceException(sn, stackElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeDown() {
|
||||
JShell.this.closeDown();
|
||||
}
|
||||
}
|
||||
|
||||
// --- private / package-private implementation support ---
|
||||
ExecutionControl executionControl() {
|
||||
if (executionControl == null) {
|
||||
this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this, extraRemoteVMOptions);
|
||||
if (!executionControlLaunched) {
|
||||
try {
|
||||
executionControl.launch();
|
||||
executionControlLaunched = true;
|
||||
executionControl.start(new ExecutionEnvImpl());
|
||||
} catch (Throwable ex) {
|
||||
throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
|
||||
throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
return executionControl;
|
||||
}
|
||||
|
||||
void setClassnameToBytes(String classname, byte[] bytes) {
|
||||
classnameToBytes.put(classname, bytes);
|
||||
}
|
||||
|
||||
void debug(int flags, String format, Object... args) {
|
||||
if (InternalDebugControl.debugEnabled(this, flags)) {
|
||||
err.printf(format, args);
|
||||
}
|
||||
InternalDebugControl.debug(this, err, flags, format, args);
|
||||
}
|
||||
|
||||
void debug(Exception ex, String where) {
|
||||
if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
|
||||
err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
|
||||
ex.printStackTrace(err);
|
||||
}
|
||||
InternalDebugControl.debug(this, err, ex, where);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the next key index, indicating a unique snippet signature.
|
||||
*
|
||||
* @return the next key index
|
||||
*/
|
||||
int nextKeyIndex() {
|
||||
|
@ -28,9 +28,11 @@ package jdk.jshell;
|
||||
import java.util.Locale;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import jdk.internal.jshell.remote.RemoteCodes;
|
||||
import static jdk.jshell.Util.*;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
|
||||
import static jdk.jshell.Util.PARSED_LOCALE;
|
||||
import static jdk.jshell.Util.REPL_CLASS_PREFIX;
|
||||
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
|
||||
import static jdk.jshell.Util.REPL_PACKAGE;
|
||||
import static jdk.jshell.Util.expunge;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -163,7 +165,7 @@ class OuterWrap implements GeneralWrap {
|
||||
}
|
||||
for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
|
||||
if (line.trim().startsWith("location:")) {
|
||||
if (!line.contains(RemoteCodes.REPL_CLASS_PREFIX)) {
|
||||
if (!line.contains(REPL_CLASS_PREFIX)) {
|
||||
// Resolution error must occur within a REPL class or it is not resolvable
|
||||
return false;
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.stream.Collectors;
|
||||
import jdk.jshell.Wrap.CompoundWrap;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
|
||||
import static jdk.jshell.Util.PREFIX_PATTERN;
|
||||
import static jdk.jshell.Util.REPL_CLASS_PREFIX;
|
||||
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
|
||||
import static jdk.jshell.Util.asLetters;
|
||||
|
||||
|
@ -38,9 +38,9 @@ import java.util.regex.Matcher;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
|
||||
import static jdk.jshell.Util.PREFIX_PATTERN;
|
||||
import static jdk.jshell.Util.REPL_PACKAGE;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
|
||||
|
||||
/**
|
||||
* Maintain relationships between the significant entities: Snippets,
|
||||
|
@ -104,7 +104,6 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
@ -130,6 +129,7 @@ import javax.tools.StandardLocation;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
/**
|
||||
* The concrete implementation of SourceCodeAnalysis.
|
||||
|
@ -60,7 +60,6 @@ import java.util.stream.Stream;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.tools.FileObject;
|
||||
import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
import java.lang.Runtime.Version;
|
||||
|
||||
/**
|
||||
@ -278,13 +277,19 @@ class TaskFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
List<ClassInfo> classInfoList(OuterWrap w) {
|
||||
// Returns the list of classes generated during this compile.
|
||||
// Stores the mapping between class name and current compiled bytes.
|
||||
List<String> classList(OuterWrap w) {
|
||||
List<OutputMemoryJavaFileObject> l = classObjs.get(w);
|
||||
if (l == null) return Collections.emptyList();
|
||||
return l.stream()
|
||||
.map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
|
||||
.collect(Collectors.toList());
|
||||
if (l == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
for (OutputMemoryJavaFileObject fo : l) {
|
||||
state.setClassnameToBytes(fo.getName(), fo.getBytes());
|
||||
list.add(fo.getName());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
|
||||
|
@ -30,18 +30,14 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import jdk.jshell.Snippet.Kind;
|
||||
import jdk.jshell.Snippet.Status;
|
||||
import jdk.jshell.Snippet.SubKind;
|
||||
import jdk.jshell.TaskFactory.AnalyzeTask;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
import jdk.jshell.TaskFactory.CompileTask;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
@ -79,7 +75,7 @@ final class Unit {
|
||||
private SnippetEvent replaceOldEvent;
|
||||
private List<SnippetEvent> secondaryEvents;
|
||||
private boolean isAttemptingCorral;
|
||||
private List<ClassInfo> toRedefine;
|
||||
private List<String> toRedefine;
|
||||
private boolean dependenciesNeeded;
|
||||
|
||||
Unit(JShell state, Snippet si, Snippet causalSnippet,
|
||||
@ -260,10 +256,6 @@ final class Unit {
|
||||
si, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called for each unit
|
||||
* @return
|
||||
*/
|
||||
boolean isDefined() {
|
||||
return status.isDefined;
|
||||
}
|
||||
@ -273,21 +265,28 @@ final class Unit {
|
||||
* Requires loading of returned list.
|
||||
* @return the list of classes to load
|
||||
*/
|
||||
Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
|
||||
Stream<String> classesToLoad(List<String> classnames) {
|
||||
toRedefine = new ArrayList<>();
|
||||
List<ClassInfo> toLoad = new ArrayList<>();
|
||||
List<String> toLoad = new ArrayList<>();
|
||||
if (status.isDefined && !isImport()) {
|
||||
cil.stream().forEach(ci -> {
|
||||
if (!ci.isLoaded()) {
|
||||
if (ci.getReferenceTypeOrNull() == null) {
|
||||
toLoad.add(ci);
|
||||
ci.setLoaded();
|
||||
// Classes should only be loaded/redefined if the compile left them
|
||||
// in a defined state. Imports do not have code and are not loaded.
|
||||
for (String cn : classnames) {
|
||||
switch (state.executionControl().getClassStatus(cn)) {
|
||||
case UNKNOWN:
|
||||
// If not loaded, add to the list of classes to load.
|
||||
toLoad.add(cn);
|
||||
dependenciesNeeded = true;
|
||||
} else {
|
||||
toRedefine.add(ci);
|
||||
}
|
||||
break;
|
||||
case NOT_CURRENT:
|
||||
// If loaded but out of date, add to the list of classes to attempt redefine.
|
||||
toRedefine.add(cn);
|
||||
break;
|
||||
case CURRENT:
|
||||
// Loaded and current, so nothing to do
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return toLoad.stream();
|
||||
}
|
||||
@ -298,19 +297,9 @@ final class Unit {
|
||||
* @return true if all redefines succeeded (can be vacuously true)
|
||||
*/
|
||||
boolean doRedefines() {
|
||||
if (toRedefine.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
Map<ReferenceType, byte[]> mp = toRedefine.stream()
|
||||
.collect(toMap(ci -> ci.getReferenceTypeOrNull(), ci -> ci.getBytes()));
|
||||
if (state.executionControl().commandRedefine(mp)) {
|
||||
// success, mark as loaded
|
||||
toRedefine.stream().forEach(ci -> ci.setLoaded());
|
||||
return true;
|
||||
} else {
|
||||
// failed to redefine
|
||||
return false;
|
||||
}
|
||||
return toRedefine.isEmpty()
|
||||
? true
|
||||
: state.executionControl().redefine(toRedefine);
|
||||
}
|
||||
|
||||
void markForReplacement() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -29,20 +29,40 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.lang.model.element.Name;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
|
||||
|
||||
/**
|
||||
* Assorted shared utilities.
|
||||
* @author Robert Field
|
||||
* Assorted shared utilities and constants.
|
||||
*/
|
||||
class Util {
|
||||
|
||||
static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"DOESNOTMATTER";
|
||||
/**
|
||||
* The package name of all wrapper classes.
|
||||
*/
|
||||
static final String REPL_PACKAGE = "REPL";
|
||||
|
||||
/**
|
||||
* The prefix for all wrapper class names.
|
||||
*/
|
||||
static final String REPL_CLASS_PREFIX = "$JShell$";
|
||||
|
||||
/**
|
||||
* The name of the invoke method.
|
||||
*/
|
||||
static final String DOIT_METHOD_NAME = "do_it$";
|
||||
|
||||
/**
|
||||
* A pattern matching the full or simple class name of a wrapper class.
|
||||
*/
|
||||
static final Pattern PREFIX_PATTERN = Pattern.compile(
|
||||
"(" + REPL_PACKAGE + "\\.)?"
|
||||
+ "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX)
|
||||
+ "\\w+" + ")" + "[\\$\\.]?");
|
||||
|
||||
static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX + "DOESNOTMATTER";
|
||||
|
||||
static final Locale PARSED_LOCALE = Locale.ROOT;
|
||||
|
||||
|
@ -27,7 +27,7 @@ package jdk.jshell;
|
||||
|
||||
import java.util.Arrays;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
|
||||
import static jdk.jshell.Util.DOIT_METHOD_NAME;
|
||||
|
||||
/**
|
||||
* Wrapping of source into Java methods, fields, etc. All but outer layer
|
||||
|
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell.spi;
|
||||
|
||||
import java.util.Collection;
|
||||
import jdk.jshell.JShellException;
|
||||
|
||||
/**
|
||||
* This interface specifies the functionality that must provided to implement
|
||||
* a pluggable JShell execution engine.
|
||||
* <p>
|
||||
* The audience for this Service Provider Interface is engineers
|
||||
* wishing to implement their own version of the execution engine in support
|
||||
* of the JShell API. This is NOT a part of the JShell API.
|
||||
* <p>
|
||||
* A Snippet is compiled into code wrapped in a 'wrapper class'. The execution
|
||||
* engine is used by the core JShell implementation to load and, for
|
||||
* executable Snippets, execute the Snippet.
|
||||
* <p>
|
||||
* Methods defined in this interface should only be called by the core JShell
|
||||
* implementation.
|
||||
* <p>
|
||||
* To install an instance of ExecutionControl, it is passed to
|
||||
* {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }.
|
||||
*/
|
||||
public interface ExecutionControl {
|
||||
|
||||
/**
|
||||
* Represents the current status of a class in the execution engine.
|
||||
*/
|
||||
public enum ClassStatus {
|
||||
/**
|
||||
* Class is not known to the execution engine (not loaded).
|
||||
*/
|
||||
UNKNOWN,
|
||||
|
||||
/**
|
||||
* Class is loaded, but the loaded/redefined bytes do not match those
|
||||
* returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
|
||||
*/
|
||||
NOT_CURRENT,
|
||||
|
||||
/**
|
||||
* Class is loaded and loaded/redefined bytes match those
|
||||
* returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
|
||||
*/
|
||||
CURRENT
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the instance. No methods in this interface can be called
|
||||
* before this.
|
||||
*
|
||||
* @param env the execution environment information provided by JShell
|
||||
* @throws Exception if the instance is unable to initialize
|
||||
*/
|
||||
void start(ExecutionEnv env) throws Exception;
|
||||
|
||||
/**
|
||||
* Shuts down this execution engine. Implementation should free all
|
||||
* resources held by this execution engine.
|
||||
* <p>
|
||||
* No calls to methods on this interface should be made after close.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Adds the path to the execution class path.
|
||||
*
|
||||
* @param path the path to add
|
||||
* @return true if successful
|
||||
*/
|
||||
boolean addToClasspath(String path);
|
||||
|
||||
/**
|
||||
* Invokes an executable Snippet by calling a method on the specified
|
||||
* wrapper class. The method must have no arguments and return String.
|
||||
*
|
||||
* @param classname the class whose method should be invoked
|
||||
* @param methodname the name of method to invoke
|
||||
* @return the result of the execution or null if no result
|
||||
* @throws JShellException if a user exception if thrown,
|
||||
* {@link jdk.jshell.EvalException EvalException} will be thrown; if an
|
||||
* unresolved reference is encountered,
|
||||
* {@link jdk.jshell.UnresolvedReferenceException UnresolvedReferenceException}
|
||||
* will be thrown
|
||||
*/
|
||||
String invoke(String classname, String methodname) throws JShellException;
|
||||
|
||||
/**
|
||||
* Attempts to load new classes. Class bytes are retrieved from
|
||||
* {@link ExecutionEnv#getClassBytes(java.lang.String) }
|
||||
*
|
||||
* @param classes list of class names to load
|
||||
* @return true if load succeeded
|
||||
*/
|
||||
boolean load(Collection<String> classes);
|
||||
|
||||
/**
|
||||
* Attempts to redefine previously loaded classes. Class bytes are retrieved
|
||||
* from {@link ExecutionEnv#getClassBytes(java.lang.String) }
|
||||
*
|
||||
* @param classes list of class names to redefine
|
||||
* @return true if redefine succeeded
|
||||
*/
|
||||
boolean redefine(Collection<String> classes);
|
||||
|
||||
/**
|
||||
* Queries if the class is loaded and the class bytes are current.
|
||||
*
|
||||
* @param classname name of the wrapper class to query
|
||||
* @return {@code UNKNOWN} if the class is not loaded; {@code CURRENT} if
|
||||
* the loaded/redefined bytes are equal to the most recent bytes for this
|
||||
* wrapper class; otherwise {@code NOT_CURRENT}
|
||||
*/
|
||||
ClassStatus getClassStatus(String classname);
|
||||
|
||||
/**
|
||||
* Interrupt a running invoke.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Returns the value of a variable.
|
||||
*
|
||||
* @param classname the name of the wrapper class of the variable
|
||||
* @param varname the name of the variable
|
||||
* @return the value of the variable
|
||||
*/
|
||||
String varValue(String classname, String varname);
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell.spi;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.List;
|
||||
import jdk.jshell.EvalException;
|
||||
import jdk.jshell.JShell;
|
||||
import jdk.jshell.UnresolvedReferenceException;
|
||||
|
||||
/**
|
||||
* Functionality made available to a pluggable JShell execution engine. It is
|
||||
* provided to the execution engine by the core JShell implementation calling
|
||||
* {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }.
|
||||
* <p>
|
||||
* This interface is designed to provide the access to core JShell functionality
|
||||
* needed to implement ExecutionControl.
|
||||
*
|
||||
* @see ExecutionControl
|
||||
*/
|
||||
public interface ExecutionEnv {
|
||||
|
||||
/**
|
||||
* Returns the user's input stream.
|
||||
*
|
||||
* @return the user's input stream
|
||||
*/
|
||||
InputStream userIn();
|
||||
|
||||
/**
|
||||
* Returns the user's output stream.
|
||||
*
|
||||
* @return the user's output stream
|
||||
*/
|
||||
PrintStream userOut();
|
||||
|
||||
/**
|
||||
* Returns the user's error stream.
|
||||
*
|
||||
* @return the user's error stream
|
||||
*/
|
||||
PrintStream userErr();
|
||||
|
||||
/**
|
||||
* @return the JShell instance
|
||||
*/
|
||||
JShell state();
|
||||
|
||||
/**
|
||||
* Returns the additional VM options to be used when launching the remote
|
||||
* JVM. This is advice to the execution engine.
|
||||
* <p>
|
||||
* Note: an execution engine need not launch a remote JVM.
|
||||
*
|
||||
* @return the additional options with which to launch the remote JVM
|
||||
*/
|
||||
List<String> extraRemoteVMOptions();
|
||||
|
||||
/**
|
||||
* Retrieves the class file bytes for the specified wrapper class.
|
||||
*
|
||||
* @param className the name of the wrapper class
|
||||
* @return the current class file bytes as a byte array
|
||||
*/
|
||||
byte[] getClassBytes(String className);
|
||||
|
||||
/**
|
||||
* Creates an {@code EvalException} corresponding to a user exception. An
|
||||
* user exception thrown during
|
||||
* {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
|
||||
* should be converted to an {@code EvalException} using this method.
|
||||
*
|
||||
* @param message the exception message to use (from the user exception)
|
||||
* @param exceptionClass the class name of the user exception
|
||||
* @param stackElements the stack trace elements to install
|
||||
* @return a user API EvalException for the user exception
|
||||
*/
|
||||
EvalException createEvalException(String message, String exceptionClass,
|
||||
StackTraceElement[] stackElements);
|
||||
|
||||
/**
|
||||
* Creates an {@code UnresolvedReferenceException} for the Snippet identifed
|
||||
* by the specified identifier. An {@link SPIResolutionException} thrown
|
||||
* during {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
|
||||
* should be converted to an {@code UnresolvedReferenceException} using
|
||||
* this method.
|
||||
* <p>
|
||||
* The identifier is an internal id, different from the id in the API. This
|
||||
* internal id is returned by {@link SPIResolutionException#id()}.
|
||||
*
|
||||
* @param id the internal integer identifier
|
||||
* @param stackElements the stack trace elements to install
|
||||
* @return an {@code UnresolvedReferenceException} for the unresolved
|
||||
* reference
|
||||
*/
|
||||
UnresolvedReferenceException createUnresolvedReferenceException(int id,
|
||||
StackTraceElement[] stackElements);
|
||||
|
||||
/**
|
||||
* Reports that the execution engine has shutdown.
|
||||
*/
|
||||
void closeDown();
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell.spi;
|
||||
|
||||
/**
|
||||
* The construction and throw of this exception is embedded in code generated by
|
||||
* the JShell core implementation in such a way that, upon executing a
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* user method, this exception is thrown.
|
||||
* <p>
|
||||
* This exception is seen by the execution engine, but not seen by
|
||||
* the end user nor through the JShell API.
|
||||
*
|
||||
* @see ExecutionEnv#createUnresolvedReferenceException(int,
|
||||
* java.lang.StackTraceElement[])
|
||||
*/
|
||||
@SuppressWarnings("serial") // serialVersionUID intentionally omitted
|
||||
public class SPIResolutionException extends RuntimeException {
|
||||
|
||||
private final int id;
|
||||
|
||||
/**
|
||||
* Constructs an SPI layer exception indicating that a
|
||||
* {@code DeclarationSnippet} with unresolved references has been
|
||||
* encountered. The throw of this exception is generated into the body of a
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* method.
|
||||
*
|
||||
* @param id An internal identifier of the specific method
|
||||
*/
|
||||
public SPIResolutionException(int id) {
|
||||
super("resolution exception");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the internal identifer of the unresolved identifer.
|
||||
*
|
||||
* @return the internal identifer
|
||||
* @see ExecutionEnv#createUnresolvedReferenceException(int,
|
||||
* java.lang.StackTraceElement[])
|
||||
*/
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -23,27 +23,17 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.jshell.remote;
|
||||
|
||||
/**
|
||||
* The exception thrown on the remote side upon executing a
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* user method. This exception is not seen by the end user nor through the API.
|
||||
* @author Robert Field
|
||||
* Provides support for alternate implementations of the JShell execution
|
||||
* engine. The JShell core tracks and compiles Snippets then sends them
|
||||
* (represented in a wrapper class) to the execution engine for loading,
|
||||
* and in the case of executable Snippets, execution. The JShell
|
||||
* implementation includes a default execution engine (currently a remote
|
||||
* process which is JDI controlled). By implementing the
|
||||
* {@link ExecutionControl} interface and installing it with
|
||||
* {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
|
||||
* other execution engines can be used.
|
||||
* <p>
|
||||
* This is not a part of the JShell API.
|
||||
*/
|
||||
@SuppressWarnings("serial") // serialVersionUID intentionally omitted
|
||||
public class RemoteResolutionException extends RuntimeException {
|
||||
|
||||
final int id;
|
||||
|
||||
/**
|
||||
* The throw of this exception is generated into the body of a
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* method.
|
||||
* @param id An internal identifier of the specific method
|
||||
*/
|
||||
public RemoteResolutionException(int id) {
|
||||
super("RemoteResolutionException");
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
package jdk.jshell.spi;
|
@ -32,4 +32,5 @@ module jdk.jshell {
|
||||
requires jdk.jdi;
|
||||
|
||||
exports jdk.jshell;
|
||||
exports jdk.jshell.spi;
|
||||
}
|
||||
|
150
langtools/test/jdk/jshell/ExecutionControlTest.java
Normal file
150
langtools/test/jdk/jshell/ExecutionControlTest.java
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8156101
|
||||
* @summary Tests for ExecutionControl SPI
|
||||
* @build KullaTesting LocalExecutionControl
|
||||
* @run testng ExecutionControlTest
|
||||
*/
|
||||
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import jdk.jshell.VarSnippet;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static jdk.jshell.Snippet.Status.VALID;
|
||||
import static jdk.jshell.Snippet.SubKind.*;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
@Test
|
||||
public class ExecutionControlTest extends KullaTesting {
|
||||
|
||||
@BeforeMethod
|
||||
@Override
|
||||
public void setUp() {
|
||||
setUp(new LocalExecutionControl());
|
||||
}
|
||||
|
||||
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
|
||||
System.setProperty("LOCAL_CHECK", "TBD");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
|
||||
assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
|
||||
assertEquals(System.getProperty("LOCAL_CHECK"), "local");
|
||||
}
|
||||
|
||||
public void classesDeclaration() {
|
||||
assertEval("interface A { }");
|
||||
assertEval("class B implements A { }");
|
||||
assertEval("interface C extends A { }");
|
||||
assertEval("enum D implements C { }");
|
||||
assertEval("@interface E { }");
|
||||
assertClasses(
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "A"),
|
||||
clazz(KullaTesting.ClassType.CLASS, "B"),
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "C"),
|
||||
clazz(KullaTesting.ClassType.ENUM, "D"),
|
||||
clazz(KullaTesting.ClassType.ANNOTATION, "E"));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceTest() {
|
||||
String interfaceSource
|
||||
= "interface A {\n"
|
||||
+ " default int defaultMethod() { return 1; }\n"
|
||||
+ " static int staticMethod() { return 2; }\n"
|
||||
+ " int method();\n"
|
||||
+ " class Inner1 {}\n"
|
||||
+ " static class Inner2 {}\n"
|
||||
+ "}";
|
||||
assertEval(interfaceSource);
|
||||
assertEval("A.staticMethod();", "2");
|
||||
String classSource
|
||||
= "class B implements A {\n"
|
||||
+ " public int method() { return 3; }\n"
|
||||
+ "}";
|
||||
assertEval(classSource);
|
||||
assertEval("B b = new B();");
|
||||
assertEval("b.defaultMethod();", "1");
|
||||
assertDeclareFail("B.staticMethod();",
|
||||
new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertEval("b.method();", "3");
|
||||
assertEval("new A.Inner1();");
|
||||
assertEval("new A.Inner2();");
|
||||
assertEval("new B.Inner1();");
|
||||
assertEval("new B.Inner2();");
|
||||
}
|
||||
|
||||
public void variables() {
|
||||
VarSnippet snx = varKey(assertEval("int x = 10;"));
|
||||
VarSnippet sny = varKey(assertEval("String y = \"hi\";"));
|
||||
VarSnippet snz = varKey(assertEval("long z;"));
|
||||
assertVariables(variable("int", "x"), variable("String", "y"), variable("long", "z"));
|
||||
assertVarValue(snx, "10");
|
||||
assertVarValue(sny, "\"hi\"");
|
||||
assertVarValue(snz, "0");
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void methodOverload() {
|
||||
assertEval("int m() { return 1; }");
|
||||
assertEval("int m(int x) { return 2; }");
|
||||
assertEval("int m(String s) { return 3; }");
|
||||
assertEval("int m(int x, int y) { return 4; }");
|
||||
assertEval("int m(int x, String z) { return 5; }");
|
||||
assertEval("int m(int x, String z, long g) { return 6; }");
|
||||
assertMethods(
|
||||
method("()int", "m"),
|
||||
method("(int)int", "m"),
|
||||
method("(String)int", "m"),
|
||||
method("(int,int)int", "m"),
|
||||
method("(int,String)int", "m"),
|
||||
method("(int,String,long)int", "m")
|
||||
);
|
||||
assertEval("m();", "1");
|
||||
assertEval("m(3);", "2");
|
||||
assertEval("m(\"hi\");", "3");
|
||||
assertEval("m(7, 8);", "4");
|
||||
assertEval("m(7, \"eight\");", "5");
|
||||
assertEval("m(7, \"eight\", 9L);", "6");
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testExprSanity() {
|
||||
assertEval("int x = 3;", "3");
|
||||
assertEval("int y = 4;", "4");
|
||||
assertEval("x + y;", "7");
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testImportOnDemand() {
|
||||
assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
|
||||
assertEval("List<Integer> list = new ArrayList<>();");
|
||||
assertEval("list.add(45);");
|
||||
assertEval("list.size();", "1");
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@ 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 {
|
||||
|
||||
@ -166,6 +167,21 @@ public class KullaTesting {
|
||||
classpath = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setUp(ExecutionControl ec) {
|
||||
inStream = new TestingInputStream();
|
||||
outStream = new ByteArrayOutputStream();
|
||||
errStream = new ByteArrayOutputStream();
|
||||
state = JShell.builder()
|
||||
.executionEngine(ec)
|
||||
.in(inStream)
|
||||
.out(new PrintStream(outStream))
|
||||
.err(new PrintStream(errStream))
|
||||
.build();
|
||||
allSnippets = new LinkedHashSet<>();
|
||||
idToSnippet = new LinkedHashMap<>();
|
||||
classpath = new ArrayList<>();
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
public void tearDown() {
|
||||
if (state != null) state.close();
|
||||
|
312
langtools/test/jdk/jshell/LocalExecutionControl.java
Normal file
312
langtools/test/jdk/jshell/LocalExecutionControl.java
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import jdk.jshell.spi.ExecutionEnv;
|
||||
import jdk.jshell.spi.SPIResolutionException;
|
||||
import jdk.jshell.EvalException;
|
||||
import jdk.jshell.UnresolvedReferenceException;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* An implementation of ExecutionControl which executes in the same JVM as the
|
||||
* JShell core.
|
||||
*
|
||||
* @author Grigory Ptashko
|
||||
*/
|
||||
class LocalExecutionControl implements ExecutionControl {
|
||||
private class REPLClassLoader extends URLClassLoader {
|
||||
REPLClassLoader() {
|
||||
super(new URL[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
debug("findClass %s\n", name);
|
||||
byte[] b = execEnv.getClassBytes(name);
|
||||
if (b == null) {
|
||||
return super.findClass(name);
|
||||
}
|
||||
return super.defineClass(name, b, 0, b.length, (CodeSource)null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addURL(URL url) {
|
||||
super.addURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutionEnv execEnv;
|
||||
private final Object STOP_LOCK = new Object();
|
||||
private boolean userCodeRunning = false;
|
||||
private REPLClassLoader loader = new REPLClassLoader();
|
||||
private final Map<String, Class<?>> klasses = new TreeMap<>();
|
||||
private final Map<String, byte[]> classBytes = new HashMap<>();
|
||||
private ThreadGroup execThreadGroup;
|
||||
|
||||
@Override
|
||||
public void start(ExecutionEnv execEnv) throws Exception {
|
||||
this.execEnv = execEnv;
|
||||
|
||||
debug("Process-local code snippets execution control started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(Collection<String> classes) {
|
||||
try {
|
||||
loadLocal(classes);
|
||||
|
||||
return true;
|
||||
} catch (ClassNotFoundException | ClassCastException ex) {
|
||||
debug(ex, "Exception on load operation");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String invoke(String classname, String methodname) throws EvalException, UnresolvedReferenceException {
|
||||
try {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = true;
|
||||
}
|
||||
|
||||
// Invoke executable entry point in loaded code
|
||||
Class<?> klass = klasses.get(classname);
|
||||
if (klass == null) {
|
||||
debug("Invoke failure: no such class loaded %s\n", classname);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Method doitMethod;
|
||||
try {
|
||||
this.getClass().getModule().addReads(klass.getModule());
|
||||
this.getClass().getModule().addExports(SPIResolutionException.class.getPackage()
|
||||
.getName(), klass.getModule());
|
||||
doitMethod = klass.getDeclaredMethod(methodname, new Class<?>[0]);
|
||||
doitMethod.setAccessible(true);
|
||||
|
||||
execThreadGroup = new ThreadGroup("JShell process local execution");
|
||||
|
||||
AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>();
|
||||
AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
|
||||
AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
|
||||
AtomicReference<Boolean> stopped = new AtomicReference<>(false);
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
if (e instanceof InvocationTargetException) {
|
||||
if (e.getCause() instanceof ThreadDeath) {
|
||||
stopped.set(true);
|
||||
} else {
|
||||
iteEx.set((InvocationTargetException)e);
|
||||
}
|
||||
} else if (e instanceof IllegalAccessException) {
|
||||
iaeEx.set((IllegalAccessException)e);
|
||||
} else if (e instanceof NoSuchMethodException) {
|
||||
nmeEx.set((NoSuchMethodException)e);
|
||||
} else if (e instanceof ThreadDeath) {
|
||||
stopped.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
final Object[] res = new Object[1];
|
||||
Thread snippetThread = new Thread(execThreadGroup, () -> {
|
||||
try {
|
||||
res[0] = doitMethod.invoke(null, new Object[0]);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof ThreadDeath) {
|
||||
stopped.set(true);
|
||||
} else {
|
||||
iteEx.set(e);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
iaeEx.set(e);
|
||||
} catch (ThreadDeath e) {
|
||||
stopped.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
snippetThread.start();
|
||||
Thread[] threadList = new Thread[execThreadGroup.activeCount()];
|
||||
execThreadGroup.enumerate(threadList);
|
||||
for (Thread thread : threadList) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
if (stopped.get()) {
|
||||
debug("Killed.");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
if (iteEx.get() != null) {
|
||||
throw iteEx.get();
|
||||
} else if (nmeEx.get() != null) {
|
||||
throw nmeEx.get();
|
||||
} else if (iaeEx.get() != null) {
|
||||
throw iaeEx.get();
|
||||
}
|
||||
|
||||
return valueString(res[0]);
|
||||
} catch (InvocationTargetException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
StackTraceElement[] elems = cause.getStackTrace();
|
||||
if (cause instanceof SPIResolutionException) {
|
||||
int id = ((SPIResolutionException)cause).id();
|
||||
|
||||
throw execEnv.createUnresolvedReferenceException(id, elems);
|
||||
} else {
|
||||
throw execEnv.createEvalException(cause.getMessage() == null ?
|
||||
"<none>" : cause.getMessage(), cause.getClass().getName(), elems);
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InterruptedException ex) {
|
||||
debug(ex, "Invoke failure");
|
||||
}
|
||||
} finally {
|
||||
synchronized (STOP_LOCK) {
|
||||
userCodeRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void stop() {
|
||||
synchronized (STOP_LOCK) {
|
||||
if (!userCodeRunning)
|
||||
return;
|
||||
|
||||
if (execThreadGroup == null) {
|
||||
debug("Process-local code snippets thread group is null. Aborting stop.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
execThreadGroup.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String varValue(String classname, String varname) {
|
||||
Class<?> klass = klasses.get(classname);
|
||||
if (klass == null) {
|
||||
debug("Var value failure: no such class loaded %s\n", classname);
|
||||
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
this.getClass().getModule().addReads(klass.getModule());
|
||||
Field var = klass.getDeclaredField(varname);
|
||||
var.setAccessible(true);
|
||||
Object res = var.get(null);
|
||||
|
||||
return valueString(res);
|
||||
} catch (Exception ex) {
|
||||
debug("Var value failure: no such field %s.%s\n", classname, varname);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addToClasspath(String cp) {
|
||||
// Append to the claspath
|
||||
for (String path : cp.split(File.pathSeparator)) {
|
||||
try {
|
||||
loader.addURL(new File(path).toURI().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new InternalError("Classpath addition failed: " + cp, e);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean redefine(Collection<String> classes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassStatus getClassStatus(String classname) {
|
||||
if (!classBytes.containsKey(classname)) {
|
||||
return ClassStatus.UNKNOWN;
|
||||
} else if (!Arrays.equals(classBytes.get(classname), execEnv.getClassBytes(classname))) {
|
||||
return ClassStatus.NOT_CURRENT;
|
||||
} else {
|
||||
return ClassStatus.CURRENT;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadLocal(Collection<String> classes) throws ClassNotFoundException {
|
||||
for (String className : classes) {
|
||||
Class<?> klass = loader.loadClass(className);
|
||||
klasses.put(className, klass);
|
||||
classBytes.put(className, execEnv.getClassBytes(className));
|
||||
klass.getDeclaredMethods();
|
||||
}
|
||||
}
|
||||
|
||||
private void debug(String format, Object... args) {
|
||||
//debug(execEnv.state(), execEnv.userErr(), flags, format, args);
|
||||
}
|
||||
|
||||
private void debug(Exception ex, String where) {
|
||||
//debug(execEnv.state(), execEnv.userErr(), ex, where);
|
||||
}
|
||||
|
||||
private static String valueString(Object value) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
} else if (value instanceof String) {
|
||||
return "\"" + (String)value + "\"";
|
||||
} else if (value instanceof Character) {
|
||||
return "'" + value + "'";
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user