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 }