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