1 /* 2 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.jshell.execution; 26 27 import java.io.EOFException; 28 import java.io.IOException; 29 import java.io.ObjectInput; 30 import java.io.ObjectOutput; 31 import jdk.jshell.JShellException; 32 import jdk.jshell.spi.ExecutionControl; 33 import static jdk.jshell.execution.ExecutionControlForwarder.NULL_MARKER; 34 import static jdk.jshell.execution.RemoteCodes.*; 35 36 /** 37 * An implementation of the {@link jdk.jshell.spi.ExecutionControl} 38 * execution engine SPI which streams requests to a remote agent where 39 * execution takes place. 40 * 41 * @author Robert Field 42 * @since 9 43 */ 44 public class StreamingExecutionControl implements ExecutionControl { 45 46 private final ObjectOutput out; 47 private final ObjectInput in; 48 49 /** 50 * Creates an instance. 51 * 52 * @param out the output for commands 53 * @param in the input for command responses 54 */ StreamingExecutionControl(ObjectOutput out, ObjectInput in)55 public StreamingExecutionControl(ObjectOutput out, ObjectInput in) { 56 this.out = out; 57 this.in = in; 58 } 59 60 @Override load(ClassBytecodes[] cbcs)61 public void load(ClassBytecodes[] cbcs) 62 throws ClassInstallException, NotImplementedException, EngineTerminationException { 63 try { 64 // Send a load command to the remote agent. 65 writeCommand(CMD_LOAD); 66 out.writeObject(cbcs); 67 out.flush(); 68 // Retrieve and report results from the remote agent. 69 readAndReportClassInstallResult(); 70 } catch (IOException ex) { 71 throw new EngineTerminationException("Exception writing remote load: " + ex); 72 } 73 } 74 75 @Override redefine(ClassBytecodes[] cbcs)76 public void redefine(ClassBytecodes[] cbcs) 77 throws ClassInstallException, NotImplementedException, EngineTerminationException { 78 try { 79 // Send a load command to the remote agent. 80 writeCommand(CMD_REDEFINE); 81 out.writeObject(cbcs); 82 out.flush(); 83 // Retrieve and report results from the remote agent. 84 readAndReportClassInstallResult(); 85 } catch (IOException ex) { 86 throw new EngineTerminationException("Exception writing remote redefine: " + ex); 87 } 88 } 89 90 @Override invoke(String classname, String methodname)91 public String invoke(String classname, String methodname) 92 throws RunException, EngineTerminationException, InternalException { 93 try { 94 // Send the invoke command to the remote agent. 95 writeCommand(CMD_INVOKE); 96 out.writeUTF(classname); 97 out.writeUTF(methodname); 98 out.flush(); 99 // Retrieve and report results from the remote agent. 100 readAndReportExecutionResult(); 101 String result = in.readUTF(); 102 return result; 103 } catch (IOException ex) { 104 throw new EngineTerminationException("Exception writing remote invoke: " + ex); 105 } 106 } 107 108 @Override varValue(String classname, String varname)109 public String varValue(String classname, String varname) 110 throws RunException, EngineTerminationException, InternalException { 111 try { 112 // Send the variable-value command to the remote agent. 113 writeCommand(CMD_VAR_VALUE); 114 out.writeUTF(classname); 115 out.writeUTF(varname); 116 out.flush(); 117 // Retrieve and report results from the remote agent. 118 readAndReportExecutionResult(); 119 String result = in.readUTF(); 120 return result; 121 } catch (IOException ex) { 122 throw new EngineTerminationException("Exception writing remote varValue: " + ex); 123 } 124 } 125 126 127 @Override addToClasspath(String path)128 public void addToClasspath(String path) 129 throws EngineTerminationException, InternalException { 130 try { 131 // Send the classpath addition command to the remote agent. 132 writeCommand(CMD_ADD_CLASSPATH); 133 out.writeUTF(path); 134 out.flush(); 135 // Retrieve and report results from the remote agent. 136 readAndReportClassSimpleResult(); 137 } catch (IOException ex) { 138 throw new EngineTerminationException("Exception writing remote add to classpath: " + ex); 139 } 140 } 141 142 @Override stop()143 public void stop() 144 throws EngineTerminationException, InternalException { 145 try { 146 // Send the variable-value command to the remote agent. 147 writeCommand(CMD_STOP); 148 out.flush(); 149 } catch (IOException ex) { 150 throw new EngineTerminationException("Exception writing remote stop: " + ex); 151 } 152 } 153 154 @Override extensionCommand(String command, Object arg)155 public Object extensionCommand(String command, Object arg) 156 throws RunException, EngineTerminationException, InternalException { 157 try { 158 writeCommand(command); 159 out.writeObject(arg); 160 out.flush(); 161 // Retrieve and report results from the remote agent. 162 readAndReportExecutionResult(); 163 Object result = in.readObject(); 164 return result; 165 } catch (IOException | ClassNotFoundException ex) { 166 throw new EngineTerminationException("Exception transmitting remote extensionCommand: " 167 + command + " -- " + ex); 168 } 169 } 170 171 /** 172 * Closes the execution engine. Send an exit command to the remote agent. 173 */ 174 @Override close()175 public void close() { 176 try { 177 writeCommand(CMD_CLOSE); 178 out.flush(); 179 } catch (IOException ex) { 180 // ignore; 181 } 182 } 183 writeCommand(String cmd)184 private void writeCommand(String cmd) throws IOException { 185 out.writeInt(COMMAND_PREFIX); 186 out.writeUTF(cmd); 187 } 188 189 /** 190 * Read a UTF or a null encoded as a null marker. 191 * @return a string or null 192 * @throws IOException passed through from readUTF() 193 */ readNullOrUTF()194 private String readNullOrUTF() throws IOException { 195 String s = in.readUTF(); 196 return s.equals(NULL_MARKER) ? null : s; 197 } 198 199 /** 200 * Reports results from a remote agent command that does not expect 201 * exceptions. 202 */ readAndReportClassSimpleResult()203 private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException { 204 try { 205 int status = in.readInt(); 206 switch (status) { 207 case RESULT_SUCCESS: 208 return; 209 case RESULT_NOT_IMPLEMENTED: { 210 String message = in.readUTF(); 211 throw new NotImplementedException(message); 212 } 213 case RESULT_INTERNAL_PROBLEM: { 214 String message = in.readUTF(); 215 throw new InternalException(message); 216 } 217 case RESULT_TERMINATED: { 218 String message = in.readUTF(); 219 throw new EngineTerminationException(message); 220 } 221 default: { 222 throw new EngineTerminationException("Bad remote result code: " + status); 223 } 224 } 225 } catch (IOException ex) { 226 throw new EngineTerminationException(ex.toString()); 227 } 228 } 229 230 /** 231 * Reports results from a remote agent command that does not expect 232 * exceptions. 233 */ readAndReportClassInstallResult()234 private void readAndReportClassInstallResult() throws ClassInstallException, 235 NotImplementedException, EngineTerminationException { 236 try { 237 int status = in.readInt(); 238 switch (status) { 239 case RESULT_SUCCESS: 240 return; 241 case RESULT_NOT_IMPLEMENTED: { 242 String message = in.readUTF(); 243 throw new NotImplementedException(message); 244 } 245 case RESULT_CLASS_INSTALL_EXCEPTION: { 246 String message = in.readUTF(); 247 boolean[] loaded = (boolean[]) in.readObject(); 248 throw new ClassInstallException(message, loaded); 249 } 250 case RESULT_TERMINATED: { 251 String message = in.readUTF(); 252 throw new EngineTerminationException(message); 253 } 254 default: { 255 throw new EngineTerminationException("Bad remote result code: " + status); 256 } 257 } 258 } catch (IOException | ClassNotFoundException ex) { 259 throw new EngineTerminationException(ex.toString()); 260 } 261 } 262 263 /** 264 * Reports results from a remote agent command that expects runtime 265 * exceptions. 266 * 267 * @return true if successful 268 * @throws IOException if the connection has dropped 269 * @throws JShellException {@link jdk.jshell.EvalException}, if a user 270 * exception was encountered on invoke; 271 * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved 272 * reference was encountered 273 * @throws java.lang.ClassNotFoundException 274 */ readAndReportExecutionResult()275 private void readAndReportExecutionResult() throws RunException, 276 EngineTerminationException, InternalException { 277 try { 278 int status = in.readInt(); 279 switch (status) { 280 case RESULT_SUCCESS: 281 return; 282 case RESULT_NOT_IMPLEMENTED: { 283 String message = in.readUTF(); 284 throw new NotImplementedException(message); 285 } 286 case RESULT_USER_EXCEPTION: { 287 // A user exception was encountered. Handle pre JDK 11 back-ends 288 throw readUserException(); 289 } 290 case RESULT_CORRALLED: { 291 // An unresolved reference was encountered. 292 throw readResolutionException(); 293 } 294 case RESULT_USER_EXCEPTION_CHAINED: { 295 // A user exception was encountered -- transmit chained. 296 in.readInt(); // always RESULT_USER_EXCEPTION 297 UserException result = readUserException(); 298 RunException caused = result; 299 // Loop through the chained causes (if any) building a chained exception 300 loop: while (true) { 301 RunException ex; 302 int cstatus = in.readInt(); 303 switch (cstatus) { 304 case RESULT_USER_EXCEPTION: { 305 // A user exception was the proximal cause. 306 ex = readUserException(); 307 break; 308 } 309 case RESULT_CORRALLED: { 310 // An unresolved reference was the underlying cause. 311 ex = readResolutionException(); 312 break; 313 } 314 case RESULT_SUCCESS: { 315 // End of chained exceptions 316 break loop; 317 } 318 default: { 319 throw new EngineTerminationException("Bad chained remote result code: " + cstatus); 320 } 321 } 322 caused.initCause(ex); 323 caused = ex; 324 } 325 caused.initCause(null); // root cause has no cause 326 throw result; 327 } 328 case RESULT_STOPPED: { 329 // Execution was aborted by the stop() 330 throw new StoppedException(); 331 } 332 case RESULT_INTERNAL_PROBLEM: { 333 // An internal error has occurred. 334 String message = in.readUTF(); 335 throw new InternalException(message); 336 } 337 case RESULT_TERMINATED: { 338 String message = in.readUTF(); 339 throw new EngineTerminationException(message); 340 } 341 default: { 342 throw new EngineTerminationException("Bad remote result code: " + status); 343 } 344 } 345 } catch (EOFException ex) { 346 throw new EngineTerminationException("Terminated."); 347 } catch (IOException | ClassNotFoundException ex) { 348 ex.printStackTrace(); 349 throw new EngineTerminationException(ex.toString()); 350 } 351 } 352 readUserException()353 private UserException readUserException() throws IOException, ClassNotFoundException { 354 String message = readNullOrUTF(); 355 String exceptionClassName = in.readUTF(); 356 StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); 357 return new UserException(message, exceptionClassName, elems); 358 } 359 readResolutionException()360 private ResolutionException readResolutionException() throws IOException, ClassNotFoundException { 361 int id = in.readInt(); 362 StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); 363 return new ResolutionException(id, elems); 364 } 365 } 366