1 /* 2 * Copyright (c) 2016, 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 26 import jdk.jshell.spi.ExecutionControl; 27 import jdk.jshell.spi.ExecutionEnv; 28 import jdk.jshell.spi.SPIResolutionException; 29 import jdk.jshell.EvalException; 30 import jdk.jshell.UnresolvedReferenceException; 31 32 import java.io.File; 33 import java.lang.reflect.Field; 34 import java.lang.reflect.InvocationTargetException; 35 import java.lang.reflect.Method; 36 import java.net.MalformedURLException; 37 import java.net.URL; 38 import java.net.URLClassLoader; 39 import java.security.CodeSource; 40 import java.util.Arrays; 41 import java.util.Collection; 42 import java.util.HashMap; 43 import java.util.Map; 44 import java.util.TreeMap; 45 import java.util.concurrent.atomic.AtomicReference; 46 47 /** 48 * An implementation of ExecutionControl which executes in the same JVM as the 49 * JShell core. 50 * 51 * @author Grigory Ptashko 52 */ 53 class LocalExecutionControl implements ExecutionControl { 54 private class REPLClassLoader extends URLClassLoader { 55 REPLClassLoader() { 56 super(new URL[0]); 57 } 58 59 @Override 60 protected Class<?> findClass(String name) throws ClassNotFoundException { 61 debug("findClass %s\n", name); 62 byte[] b = execEnv.getClassBytes(name); 63 if (b == null) { 64 return super.findClass(name); 65 } 66 return super.defineClass(name, b, 0, b.length, (CodeSource)null); 67 } 68 69 @Override 70 public void addURL(URL url) { 71 super.addURL(url); 72 } 73 } 74 75 private ExecutionEnv execEnv; 76 private final Object STOP_LOCK = new Object(); 77 private boolean userCodeRunning = false; 78 private REPLClassLoader loader = new REPLClassLoader(); 79 private final Map<String, Class<?>> klasses = new TreeMap<>(); 80 private final Map<String, byte[]> classBytes = new HashMap<>(); 81 private ThreadGroup execThreadGroup; 82 83 @Override 84 public void start(ExecutionEnv execEnv) throws Exception { 85 this.execEnv = execEnv; 86 87 debug("Process-local code snippets execution control started"); 88 } 89 90 @Override 91 public void close() { 92 } 93 94 @Override 95 public boolean load(Collection<String> classes) { 96 try { 97 loadLocal(classes); 98 99 return true; 100 } catch (ClassNotFoundException | ClassCastException ex) { 101 debug(ex, "Exception on load operation"); 102 } 103 104 return false; 105 } 106 107 @Override 108 public String invoke(String classname, String methodname) throws EvalException, UnresolvedReferenceException { 109 try { 110 synchronized (STOP_LOCK) { 111 userCodeRunning = true; 112 } 113 114 // Invoke executable entry point in loaded code 115 Class<?> klass = klasses.get(classname); 116 if (klass == null) { 117 debug("Invoke failure: no such class loaded %s\n", classname); 118 119 return ""; 120 } 121 122 Method doitMethod; 123 try { 124 this.getClass().getModule().addReads(klass.getModule()); 125 this.getClass().getModule().addExports(SPIResolutionException.class.getPackage() 126 .getName(), klass.getModule()); 127 doitMethod = klass.getDeclaredMethod(methodname, new Class<?>[0]); 128 doitMethod.setAccessible(true); 129 130 execThreadGroup = new ThreadGroup("JShell process local execution"); 131 132 AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>(); 133 AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>(); 134 AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>(); 135 AtomicReference<Boolean> stopped = new AtomicReference<>(false); 136 137 Thread.setDefaultUncaughtExceptionHandler((t, e) -> { 138 if (e instanceof InvocationTargetException) { 139 if (e.getCause() instanceof ThreadDeath) { 140 stopped.set(true); 141 } else { 142 iteEx.set((InvocationTargetException)e); 143 } 144 } else if (e instanceof IllegalAccessException) { 145 iaeEx.set((IllegalAccessException)e); 146 } else if (e instanceof NoSuchMethodException) { 147 nmeEx.set((NoSuchMethodException)e); 148 } else if (e instanceof ThreadDeath) { 149 stopped.set(true); 150 } 151 }); 152 153 final Object[] res = new Object[1]; 154 Thread snippetThread = new Thread(execThreadGroup, () -> { 155 try { 156 res[0] = doitMethod.invoke(null, new Object[0]); 157 } catch (InvocationTargetException e) { 158 if (e.getCause() instanceof ThreadDeath) { 159 stopped.set(true); 160 } else { 161 iteEx.set(e); 162 } 163 } catch (IllegalAccessException e) { 164 iaeEx.set(e); 165 } catch (ThreadDeath e) { 166 stopped.set(true); 167 } 168 }); 169 170 snippetThread.start(); 171 Thread[] threadList = new Thread[execThreadGroup.activeCount()]; 172 execThreadGroup.enumerate(threadList); 173 for (Thread thread : threadList) { 174 if (thread != null) 175 thread.join(); 176 } 177 178 if (stopped.get()) { 179 debug("Killed."); 180 181 return ""; 182 } 183 184 if (iteEx.get() != null) { 185 throw iteEx.get(); 186 } else if (nmeEx.get() != null) { 187 throw nmeEx.get(); 188 } else if (iaeEx.get() != null) { 189 throw iaeEx.get(); 190 } 191 192 return valueString(res[0]); 193 } catch (InvocationTargetException ex) { 194 Throwable cause = ex.getCause(); 195 StackTraceElement[] elems = cause.getStackTrace(); 196 if (cause instanceof SPIResolutionException) { 197 int id = ((SPIResolutionException)cause).id(); 198 199 throw execEnv.createUnresolvedReferenceException(id, elems); 200 } else { 201 throw execEnv.createEvalException(cause.getMessage() == null ? 202 "<none>" : cause.getMessage(), cause.getClass().getName(), elems); 203 } 204 } catch (NoSuchMethodException | IllegalAccessException | InterruptedException ex) { 205 debug(ex, "Invoke failure"); 206 } 207 } finally { 208 synchronized (STOP_LOCK) { 209 userCodeRunning = false; 210 } 211 } 212 213 return ""; 214 } 215 216 @Override 217 @SuppressWarnings("deprecation") 218 public void stop() { 219 synchronized (STOP_LOCK) { 220 if (!userCodeRunning) 221 return; 222 223 if (execThreadGroup == null) { 224 debug("Process-local code snippets thread group is null. Aborting stop."); 225 226 return; 227 } 228 229 execThreadGroup.stop(); 230 } 231 } 232 233 @Override 234 public String varValue(String classname, String varname) { 235 Class<?> klass = klasses.get(classname); 236 if (klass == null) { 237 debug("Var value failure: no such class loaded %s\n", classname); 238 239 return ""; 240 } 241 try { 242 this.getClass().getModule().addReads(klass.getModule()); 243 Field var = klass.getDeclaredField(varname); 244 var.setAccessible(true); 245 Object res = var.get(null); 246 247 return valueString(res); 248 } catch (Exception ex) { 249 debug("Var value failure: no such field %s.%s\n", classname, varname); 250 } 251 252 return ""; 253 } 254 255 @Override 256 public boolean addToClasspath(String cp) { 257 // Append to the claspath 258 for (String path : cp.split(File.pathSeparator)) { 259 try { 260 loader.addURL(new File(path).toURI().toURL()); 261 } catch (MalformedURLException e) { 262 throw new InternalError("Classpath addition failed: " + cp, e); 263 } 264 } 265 266 return true; 267 } 268 269 @Override 270 public boolean redefine(Collection<String> classes) { 271 return false; 272 } 273 274 @Override 275 public ClassStatus getClassStatus(String classname) { 276 if (!classBytes.containsKey(classname)) { 277 return ClassStatus.UNKNOWN; 278 } else if (!Arrays.equals(classBytes.get(classname), execEnv.getClassBytes(classname))) { 279 return ClassStatus.NOT_CURRENT; 280 } else { 281 return ClassStatus.CURRENT; 282 } 283 } 284 285 private void loadLocal(Collection<String> classes) throws ClassNotFoundException { 286 for (String className : classes) { 287 Class<?> klass = loader.loadClass(className); 288 klasses.put(className, klass); 289 classBytes.put(className, execEnv.getClassBytes(className)); 290 klass.getDeclaredMethods(); 291 } 292 } 293 294 private void debug(String format, Object... args) { 295 //debug(execEnv.state(), execEnv.userErr(), flags, format, args); 296 } 297 298 private void debug(Exception ex, String where) { 299 //debug(execEnv.state(), execEnv.userErr(), ex, where); 300 } 301 302 private static String valueString(Object value) { 303 if (value == null) { 304 return "null"; 305 } else if (value instanceof String) { 306 return "\"" + (String)value + "\""; 307 } else if (value instanceof Character) { 308 return "'" + value + "'"; 309 } else { 310 return value.toString(); 311 } 312 } 313 }