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