1 /* 2 * Copyright (c) 2004, 2012, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 package sun.jvm.hotspot.utilities.soql; 26 27 import java.io.*; 28 import java.util.*; 29 import javax.script.Invocable; 30 import javax.script.ScriptContext; 31 import javax.script.ScriptEngine; 32 import javax.script.ScriptEngineManager; 33 import javax.script.ScriptException; 34 import sun.jvm.hotspot.debugger.*; 35 import sun.jvm.hotspot.oops.*; 36 import sun.jvm.hotspot.runtime.*; 37 import sun.jvm.hotspot.utilities.*; 38 import sun.jvm.hotspot.tools.*; 39 import sun.jvm.hotspot.tools.jcore.*; 40 import java.lang.reflect.Method; 41 import java.lang.reflect.Modifier; 42 43 /** 44 * Simple wrapper around jsr-223 JavaScript script engine. 45 * In addition to wrapping useful functionality of jsr-223 engine, 46 * this class exposed certain "global" functions to the script. 47 */ 48 public abstract class JSJavaScriptEngine extends MapScriptObject { 49 /** 50 * Start a read-eval-print loop with this engine. 51 */ 52 public void startConsole() { 53 start(true); 54 } 55 56 /** 57 * Initialize the engine so that we can "eval" strings 58 * and files later. 59 */ 60 public void start() { 61 start(false); 62 } 63 64 /** 65 * Define a global function that invokes given Method. 66 */ 67 public void defineFunction(Object target, Method method) { 68 putFunction(target, method, false); 69 } 70 71 /** 72 * Call the script function of given name passing the 73 * given arguments. 74 */ 75 public Object call(String name, Object[] args) { 76 Invocable invocable = (Invocable)engine; 77 try { 78 return invocable.invokeFunction(name, args); 79 } catch (RuntimeException re) { 80 throw re; 81 } catch (Exception exp) { 82 throw new RuntimeException(exp); 83 } 84 } 85 86 /** 87 address function returns address of JSJavaObject as String. For other 88 type of objects, the result is undefined. 89 */ 90 public Object address(Object[] args) { 91 if (args.length != 1) return UNDEFINED; 92 Object o = args[0]; 93 if (o != null && o instanceof JSJavaObject) { 94 return ((JSJavaObject)o).getOop().getHandle().toString(); 95 } else { 96 return UNDEFINED; 97 } 98 } 99 100 101 /** 102 classof function gets type of given JSJavaInstance or JSJavaArray. Or 103 given a string class name, this function gets the class object. For 104 other type of objects, the result is undefined. 105 */ 106 public Object classof(Object[] args) { 107 if (args.length != 1) { 108 return UNDEFINED; 109 } 110 Object o = args[0]; 111 if (o != null) { 112 if (o instanceof JSJavaObject) { 113 if (o instanceof JSJavaInstance) { 114 return ((JSJavaInstance)o).getJSJavaClass(); 115 } else if (o instanceof JSJavaArray) { 116 return ((JSJavaArray)o).getJSJavaClass(); 117 } else { 118 return UNDEFINED; 119 } 120 } else if (o instanceof String) { 121 InstanceKlass ik = SystemDictionaryHelper.findInstanceKlass((String) o); 122 return getJSJavaFactory().newJSJavaKlass(ik).getJSJavaClass(); 123 } else { 124 return UNDEFINED; 125 } 126 } else { 127 return UNDEFINED; 128 } 129 } 130 131 /** 132 * dumpClass function creates a .class file for a given Class object. 133 * On success, returns true. Else, returns false. Second optional argument 134 * specifies the directory in which .class content is dumped. This defaults 135 * to '.' 136 */ 137 public Object dumpClass(Object[] args) { 138 if (args.length == 0) { 139 return Boolean.FALSE; 140 } 141 Object clazz = args[0]; 142 if (clazz == null) { 143 return Boolean.FALSE; 144 } 145 InstanceKlass ik = null; 146 if (clazz instanceof String) { 147 String name = (String) clazz; 148 if (name.startsWith("0x")) { 149 // treat it as address 150 VM vm = VM.getVM(); 151 Address addr = vm.getDebugger().parseAddress(name); 152 Metadata metadata = Metadata.instantiateWrapperFor(addr.addOffsetTo(0)); 153 if (metadata instanceof InstanceKlass) { 154 ik = (InstanceKlass) metadata; 155 } else { 156 return Boolean.FALSE; 157 } 158 } else { 159 ik = SystemDictionaryHelper.findInstanceKlass((String) clazz); 160 } 161 } else if (clazz instanceof JSJavaClass) { 162 JSJavaKlass jk = ((JSJavaClass)clazz).getJSJavaKlass(); 163 if (jk != null && jk instanceof JSJavaInstanceKlass) { 164 ik = ((JSJavaInstanceKlass)jk).getInstanceKlass(); 165 } 166 } else { 167 return Boolean.FALSE; 168 } 169 170 if (ik == null) return Boolean.FALSE; 171 StringBuffer buf = new StringBuffer(); 172 if (args.length > 1) { 173 buf.append(args[1].toString()); 174 } else { 175 buf.append('.'); 176 } 177 178 buf.append(File.separatorChar); 179 buf.append(ik.getName().asString().replace('/', File.separatorChar)); 180 buf.append(".class"); 181 String fileName = buf.toString(); 182 File file = new File(fileName); 183 184 try { 185 int index = fileName.lastIndexOf(File.separatorChar); 186 File dir = new File(fileName.substring(0, index)); 187 dir.mkdirs(); 188 FileOutputStream fos = new FileOutputStream(file); 189 ClassWriter cw = new ClassWriter(ik, fos); 190 cw.write(); 191 fos.close(); 192 } catch (IOException exp) { 193 printError(exp.toString(), exp); 194 return Boolean.FALSE; 195 } 196 197 return Boolean.TRUE; 198 } 199 200 /** 201 * dumpHeap function creates a heap dump file. 202 * On success, returns true. Else, returns false. 203 */ 204 public Object dumpHeap(Object[] args) { 205 String fileName = "heap.bin"; 206 if (args.length > 0) { 207 fileName = args[0].toString(); 208 } 209 return new JMap().writeHeapHprofBin(fileName)? Boolean.TRUE: Boolean.FALSE; 210 } 211 212 /** 213 help function prints help message for global functions and variables. 214 */ 215 public void help(Object[] args) { 216 println("Function/Variable Description"); 217 println("================= ==========="); 218 println("address(jobject) returns the address of the Java object"); 219 println("classof(jobject) returns the class object of the Java object"); 220 println("dumpClass(jclass,[dir]) writes .class for the given Java Class"); 221 println("dumpHeap([file]) writes heap in hprof binary format"); 222 println("help() prints this help message"); 223 println("identityHash(jobject) returns the hashCode of the Java object"); 224 println("mirror(jobject) returns a local mirror of the Java object"); 225 println("load([file1, file2,...]) loads JavaScript file(s). With no files, reads <stdin>"); 226 println("object(string) converts a string address into Java object"); 227 println("owner(jobject) returns the owner thread of this monitor or null"); 228 println("sizeof(jobject) returns the size of Java object in bytes"); 229 println("staticof(jclass, field) returns a static field of the given Java class"); 230 println("read([prompt]) reads a single line from standard input"); 231 println("quit() quits the interactive load call"); 232 println("jvm the target jvm that is being debugged"); 233 } 234 235 /** 236 identityHash function gets identity hash code value of given 237 JSJavaObject. For other type of objects, the result is undefined. 238 */ 239 public Object identityHash(Object[] args) { 240 if (args.length != 1) return UNDEFINED; 241 Object o = args[0]; 242 if (o != null && o instanceof JSJavaObject) { 243 return new Long(((JSJavaObject)o).getOop().identityHash()); 244 } else { 245 return UNDEFINED; 246 } 247 } 248 249 250 /** 251 * Load and execute a set of JavaScript source files. 252 * This method is defined as a JavaScript function. 253 */ 254 public void load(Object[] args) { 255 for (int i = 0; i < args.length; i++) { 256 processSource(args[i].toString()); 257 } 258 } 259 260 /** 261 mirror function creats local copy of the Oop wrapper supplied. 262 if mirror can not be created, return undefined. For other types, 263 mirror is undefined. 264 */ 265 public Object mirror(Object[] args) { 266 Object o = args[0]; 267 Object res = UNDEFINED; 268 if (o != null) { 269 if (o instanceof JSJavaObject) { 270 Oop oop = ((JSJavaObject)o).getOop(); 271 try { 272 res = getObjectReader().readObject(oop); 273 } catch (Exception e) { 274 if (debug) e.printStackTrace(getErrorStream()); 275 } 276 } else if (o instanceof JSMetadata) { 277 Metadata metadata = ((JSMetadata)o).getMetadata(); 278 try { 279 if (metadata instanceof InstanceKlass) { 280 res = getObjectReader().readClass((InstanceKlass) metadata); 281 } 282 } catch (Exception e) { 283 if (debug) e.printStackTrace(getErrorStream()); 284 } 285 } 286 } 287 return res; 288 } 289 290 /** 291 owner function gets owning thread of given JSJavaObjec, if any, else 292 returns null. For other type of objects, the result is undefined. 293 */ 294 public Object owner(Object[] args) { 295 Object o = args[0]; 296 if (o != null && o instanceof JSJavaObject) { 297 return getOwningThread((JSJavaObject)o); 298 } else { 299 return UNDEFINED; 300 } 301 } 302 303 /** 304 object function takes a string address and returns a JSJavaObject. 305 For other type of objects, the result is undefined. 306 */ 307 public Object object(Object[] args) { 308 Object o = args[0]; 309 if (o != null && o instanceof String) { 310 VM vm = VM.getVM(); 311 Address addr = vm.getDebugger().parseAddress((String)o); 312 Oop oop = vm.getObjectHeap().newOop(addr.addOffsetToAsOopHandle(0)); 313 return getJSJavaFactory().newJSJavaObject(oop); 314 } else { 315 return UNDEFINED; 316 } 317 } 318 319 /** 320 sizeof function returns size of a Java object in bytes. For other type 321 of objects, the result is undefined. 322 */ 323 public Object sizeof(Object[] args) { 324 if (args.length != 1) return UNDEFINED; 325 Object o = args[0]; 326 if (o != null && o instanceof JSJavaObject) { 327 return new Long(((JSJavaObject)o).getOop().getObjectSize()); 328 } else { 329 return UNDEFINED; 330 } 331 } 332 333 /** 334 staticof function gets static field of given class. Both class and 335 field name are specified as strings. undefined is returned if there is 336 no such named field. 337 */ 338 public Object staticof(Object[] args) { 339 Object classname = args[0]; 340 Object fieldname = args[1]; 341 if (fieldname == null || classname == null || 342 !(fieldname instanceof String)) { 343 return UNDEFINED; 344 } 345 346 InstanceKlass ik = null; 347 if (classname instanceof JSJavaClass) { 348 JSJavaClass jclass = (JSJavaClass) classname; 349 JSJavaKlass jk = jclass.getJSJavaKlass(); 350 if (jk != null && jk instanceof JSJavaInstanceKlass) { 351 ik = ((JSJavaInstanceKlass)jk).getInstanceKlass(); 352 } 353 } else if (classname instanceof String) { 354 ik = SystemDictionaryHelper.findInstanceKlass((String)classname); 355 } else { 356 return UNDEFINED; 357 } 358 359 if (ik == null) { 360 return UNDEFINED; 361 } 362 JSJavaFactory factory = getJSJavaFactory(); 363 try { 364 return ((JSJavaInstanceKlass) factory.newJSJavaKlass(ik)).getStaticFieldValue((String)fieldname); 365 } catch (NoSuchFieldException e) { 366 return UNDEFINED; 367 } 368 } 369 370 /** 371 * read function reads a single line of input from standard input 372 */ 373 public Object read(Object[] args) { 374 BufferedReader in = getInputReader(); 375 if (in == null) { 376 return null; 377 } 378 if (args.length > 0) { 379 print(args[0].toString()); 380 print(":"); 381 } 382 try { 383 return in.readLine(); 384 } catch (IOException exp) { 385 exp.printStackTrace(); 386 throw new RuntimeException(exp); 387 } 388 } 389 390 /** 391 * Quit the shell. 392 * This only affects the interactive mode. 393 */ 394 public void quit(Object[] args) { 395 quit(); 396 } 397 398 public void writeln(Object[] args) { 399 for (int i = 0; i < args.length; i++) { 400 print(args[i].toString()); 401 print(" "); 402 } 403 println(""); 404 } 405 406 public void write(Object[] args) { 407 for (int i = 0; i < args.length; i++) { 408 print(args[i].toString()); 409 print(" "); 410 } 411 } 412 413 //-- Internals only below this point 414 protected void start(boolean console) { 415 ScriptContext context = engine.getContext(); 416 OutputStream out = getOutputStream(); 417 if (out != null) { 418 context.setWriter(new PrintWriter(out)); 419 } 420 OutputStream err = getErrorStream(); 421 if (err != null) { 422 context.setErrorWriter(new PrintWriter(err)); 423 } 424 // load "sa.js" initialization file 425 loadInitFile(); 426 // load "~/jsdb.js" (if found) to perform user specific 427 // initialization steps, if any. 428 loadUserInitFile(); 429 430 JSJavaFactory fac = getJSJavaFactory(); 431 JSJavaVM jvm = (fac != null)? fac.newJSJavaVM() : null; 432 // call "main" function from "sa.js" -- main expects 433 // 'this' object and jvm object 434 call("main", new Object[] { this, jvm }); 435 436 // if asked, start read-eval-print console 437 if (console) { 438 processSource(null); 439 } 440 } 441 442 protected JSJavaScriptEngine(boolean debug) { 443 this.debug = debug; 444 ScriptEngineManager manager = new ScriptEngineManager(); 445 engine = manager.getEngineByName("javascript"); 446 if (engine == null) { 447 throw new RuntimeException("can't load JavaScript engine"); 448 } 449 Method[] methods = getClass().getMethods(); 450 for (int i = 0; i < methods.length; i++) { 451 Method m = methods[i]; 452 if (! Modifier.isPublic(m.getModifiers())) { 453 continue; 454 } 455 Class[] argTypes = m.getParameterTypes(); 456 if (argTypes.length == 1 && 457 argTypes[0] == Object[].class) { 458 putFunction(this, m); 459 } 460 } 461 } 462 463 protected JSJavaScriptEngine() { 464 this(false); 465 } 466 467 protected abstract ObjectReader getObjectReader(); 468 protected abstract JSJavaFactory getJSJavaFactory(); 469 protected void printPrompt(String str) { 470 System.err.print(str); 471 System.err.flush(); 472 } 473 474 protected void loadInitFile() { 475 InputStream is = JSJavaScriptEngine.class.getResourceAsStream("sa.js"); 476 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 477 evalReader(reader, "sa.js"); 478 } 479 480 protected void loadUserInitFile() { 481 File initFile = new File(getUserInitFileDir(), getUserInitFileName()); 482 if (initFile.exists() && initFile.isFile()) { 483 // load the init script 484 processSource(initFile.getAbsolutePath()); 485 } 486 } 487 488 protected String getUserInitFileDir() { 489 return System.getProperty("user.home"); 490 } 491 492 protected String getUserInitFileName() { 493 return "jsdb.js"; 494 } 495 496 protected BufferedReader getInputReader() { 497 if (inReader == null) { 498 inReader = new BufferedReader(new InputStreamReader(System.in)); 499 } 500 return inReader; 501 } 502 503 protected PrintStream getOutputStream() { 504 return System.out; 505 } 506 507 protected PrintStream getErrorStream() { 508 return System.err; 509 } 510 511 protected void print(String name) { 512 getOutputStream().print(name); 513 } 514 515 protected void println(String name) { 516 getOutputStream().println(name); 517 } 518 519 protected void printError(String message) { 520 printError(message, null); 521 } 522 523 protected void printError(String message, Exception exp) { 524 getErrorStream().println(message); 525 if (exp != null && debug) { 526 exp.printStackTrace(getErrorStream()); 527 } 528 } 529 530 protected boolean isQuitting() { 531 return quitting; 532 } 533 534 protected void quit() { 535 quitting = true; 536 } 537 538 protected ScriptEngine getScriptEngine() { 539 return engine; 540 } 541 542 private JSJavaThread getOwningThread(JSJavaObject jo) { 543 Oop oop = jo.getOop(); 544 Mark mark = oop.getMark(); 545 ObjectMonitor mon = null; 546 Address owner = null; 547 JSJavaThread owningThread = null; 548 // check for heavyweight monitor 549 if (! mark.hasMonitor()) { 550 // check for lightweight monitor 551 if (mark.hasLocker()) { 552 owner = mark.locker().getAddress(); // save the address of the Lock word 553 } 554 // implied else: no owner 555 } else { 556 // this object has a heavyweight monitor 557 mon = mark.monitor(); 558 559 // The owner field of a heavyweight monitor may be NULL for no 560 // owner, a JavaThread * or it may still be the address of the 561 // Lock word in a JavaThread's stack. A monitor can be inflated 562 // by a non-owning JavaThread, but only the owning JavaThread 563 // can change the owner field from the Lock word to the 564 // JavaThread * and it may not have done that yet. 565 owner = mon.owner(); 566 } 567 568 // find the owning thread 569 if (owner != null) { 570 JSJavaFactory factory = getJSJavaFactory(); 571 owningThread = (JSJavaThread) factory.newJSJavaThread(VM.getVM().getThreads().owningThreadFromMonitor(owner)); 572 } 573 return owningThread; 574 } 575 576 /** 577 * Evaluate JavaScript source. 578 * @param filename the name of the file to compile, or null 579 * for interactive mode. 580 */ 581 private void processSource(String filename) { 582 if (filename == null) { 583 BufferedReader in = getInputReader(); 584 String sourceName = "<stdin>"; 585 int lineno = 0; 586 boolean hitEOF = false; 587 do { 588 int startline = lineno; 589 printPrompt("jsdb> "); 590 Object source = read(EMPTY_ARRAY); 591 if (source == null) { 592 hitEOF = true; 593 break; 594 } 595 lineno++; 596 Object result = evalString(source.toString(), sourceName, startline); 597 if (result != null) { 598 printError(result.toString()); 599 } 600 if (isQuitting()) { 601 // The user executed the quit() function. 602 break; 603 } 604 } while (!hitEOF); 605 } else { 606 Reader in = null; 607 try { 608 in = new BufferedReader(new FileReader(filename)); 609 evalReader(in, filename); 610 } catch (FileNotFoundException ex) { 611 println("File '" + filename + "' not found"); 612 throw new RuntimeException(ex); 613 } 614 } 615 } 616 617 protected Object evalString(String source, String filename, int lineNum) { 618 try { 619 engine.put(ScriptEngine.FILENAME, filename); 620 return engine.eval(source); 621 } catch (ScriptException sexp) { 622 printError(sexp.toString(), sexp); 623 } catch (Exception exp) { 624 printError(exp.toString(), exp); 625 } 626 return null; 627 } 628 629 private Object evalReader(Reader in, String filename) { 630 try { 631 engine.put(ScriptEngine.FILENAME, filename); 632 return engine.eval(in); 633 } catch (ScriptException sexp) { 634 System.err.println(sexp); 635 printError(sexp.toString(), sexp); 636 } finally { 637 try { 638 in.close(); 639 } catch (IOException ioe) { 640 printError(ioe.toString(), ioe); 641 } 642 } 643 return null; 644 } 645 646 // lazily initialized input reader 647 private BufferedReader inReader; 648 // debug mode or not 649 protected final boolean debug; 650 private boolean quitting; 651 // underlying jsr-223 script engine 652 private ScriptEngine engine; 653 }