/* * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ package sun.jvm.hotspot.utilities.soql; import java.io.*; import java.util.*; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import sun.jvm.hotspot.debugger.*; import sun.jvm.hotspot.oops.*; import sun.jvm.hotspot.runtime.*; import sun.jvm.hotspot.utilities.*; import sun.jvm.hotspot.tools.*; import sun.jvm.hotspot.tools.jcore.*; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * Simple wrapper around jsr-223 JavaScript script engine. * In addition to wrapping useful functionality of jsr-223 engine, * this class exposed certain "global" functions to the script. */ public abstract class JSJavaScriptEngine extends MapScriptObject { /** * Start a read-eval-print loop with this engine. */ public void startConsole() { start(true); } /** * Initialize the engine so that we can "eval" strings * and files later. */ public void start() { start(false); } /** * Define a global function that invokes given Method. */ public void defineFunction(Object target, Method method) { putFunction(target, method, false); } /** * Call the script function of given name passing the * given arguments. */ public Object call(String name, Object[] args) { Invocable invocable = (Invocable)engine; try { return invocable.invokeFunction(name, args); } catch (RuntimeException re) { throw re; } catch (Exception exp) { throw new RuntimeException(exp); } } /** address function returns address of JSJavaObject as String. For other type of objects, the result is undefined. */ public Object address(Object[] args) { if (args.length != 1) return UNDEFINED; Object o = args[0]; if (o != null && o instanceof JSJavaObject) { return ((JSJavaObject)o).getOop().getHandle().toString(); } else { return UNDEFINED; } } /** classof function gets type of given JSJavaInstance or JSJavaArray. Or given a string class name, this function gets the class object. For other type of objects, the result is undefined. */ public Object classof(Object[] args) { if (args.length != 1) { return UNDEFINED; } Object o = args[0]; if (o != null) { if (o instanceof JSJavaObject) { if (o instanceof JSJavaInstance) { return ((JSJavaInstance)o).getJSJavaClass(); } else if (o instanceof JSJavaArray) { return ((JSJavaArray)o).getJSJavaClass(); } else { return UNDEFINED; } } else if (o instanceof String) { InstanceKlass ik = SystemDictionaryHelper.findInstanceKlass((String) o); return getJSJavaFactory().newJSJavaKlass(ik).getJSJavaClass(); } else { return UNDEFINED; } } else { return UNDEFINED; } } /** * dumpClass function creates a .class file for a given Class object. * On success, returns true. Else, returns false. Second optional argument * specifies the directory in which .class content is dumped. This defaults * to '.' */ public Object dumpClass(Object[] args) { if (args.length == 0) { return Boolean.FALSE; } Object clazz = args[0]; if (clazz == null) { return Boolean.FALSE; } InstanceKlass ik = null; if (clazz instanceof String) { String name = (String) clazz; if (name.startsWith("0x")) { // treat it as address VM vm = VM.getVM(); Address addr = vm.getDebugger().parseAddress(name); Metadata metadata = Metadata.instantiateWrapperFor(addr.addOffsetTo(0)); if (metadata instanceof InstanceKlass) { ik = (InstanceKlass) metadata; } else { return Boolean.FALSE; } } else { ik = SystemDictionaryHelper.findInstanceKlass((String) clazz); } } else if (clazz instanceof JSJavaClass) { JSJavaKlass jk = ((JSJavaClass)clazz).getJSJavaKlass(); if (jk != null && jk instanceof JSJavaInstanceKlass) { ik = ((JSJavaInstanceKlass)jk).getInstanceKlass(); } } else { return Boolean.FALSE; } if (ik == null) return Boolean.FALSE; StringBuffer buf = new StringBuffer(); if (args.length > 1) { buf.append(args[1].toString()); } else { buf.append('.'); } buf.append(File.separatorChar); buf.append(ik.getName().asString().replace('/', File.separatorChar)); buf.append(".class"); String fileName = buf.toString(); File file = new File(fileName); try { int index = fileName.lastIndexOf(File.separatorChar); File dir = new File(fileName.substring(0, index)); dir.mkdirs(); FileOutputStream fos = new FileOutputStream(file); ClassWriter cw = new ClassWriter(ik, fos); cw.write(); fos.close(); } catch (IOException exp) { printError(exp.toString(), exp); return Boolean.FALSE; } return Boolean.TRUE; } /** * dumpHeap function creates a heap dump file. * On success, returns true. Else, returns false. */ public Object dumpHeap(Object[] args) { String fileName = "heap.bin"; if (args.length > 0) { fileName = args[0].toString(); } return new JMap().writeHeapHprofBin(fileName)? Boolean.TRUE: Boolean.FALSE; } /** help function prints help message for global functions and variables. */ public void help(Object[] args) { println("Function/Variable Description"); println("================= ==========="); println("address(jobject) returns the address of the Java object"); println("classof(jobject) returns the class object of the Java object"); println("dumpClass(jclass,[dir]) writes .class for the given Java Class"); println("dumpHeap([file]) writes heap in hprof binary format"); println("help() prints this help message"); println("identityHash(jobject) returns the hashCode of the Java object"); println("mirror(jobject) returns a local mirror of the Java object"); println("load([file1, file2,...]) loads JavaScript file(s). With no files, reads <stdin>"); println("object(string) converts a string address into Java object"); println("owner(jobject) returns the owner thread of this monitor or null"); println("sizeof(jobject) returns the size of Java object in bytes"); println("staticof(jclass, field) returns a static field of the given Java class"); println("read([prompt]) reads a single line from standard input"); println("quit() quits the interactive load call"); println("jvm the target jvm that is being debugged"); } /** identityHash function gets identity hash code value of given JSJavaObject. For other type of objects, the result is undefined. */ public Object identityHash(Object[] args) { if (args.length != 1) return UNDEFINED; Object o = args[0]; if (o != null && o instanceof JSJavaObject) { return new Long(((JSJavaObject)o).getOop().identityHash()); } else { return UNDEFINED; } } /** * Load and execute a set of JavaScript source files. * This method is defined as a JavaScript function. */ public void load(Object[] args) { for (int i = 0; i < args.length; i++) { processSource(args[i].toString()); } } /** mirror function creats local copy of the Oop wrapper supplied. if mirror can not be created, return undefined. For other types, mirror is undefined. */ public Object mirror(Object[] args) { Object o = args[0]; Object res = UNDEFINED; if (o != null) { if (o instanceof JSJavaObject) { Oop oop = ((JSJavaObject)o).getOop(); try { res = getObjectReader().readObject(oop); } catch (Exception e) { if (debug) e.printStackTrace(getErrorStream()); } } else if (o instanceof JSMetadata) { Metadata metadata = ((JSMetadata)o).getMetadata(); try { if (metadata instanceof InstanceKlass) { res = getObjectReader().readClass((InstanceKlass) metadata); } } catch (Exception e) { if (debug) e.printStackTrace(getErrorStream()); } } } return res; } /** owner function gets owning thread of given JSJavaObjec, if any, else returns null. For other type of objects, the result is undefined. */ public Object owner(Object[] args) { Object o = args[0]; if (o != null && o instanceof JSJavaObject) { return getOwningThread((JSJavaObject)o); } else { return UNDEFINED; } } /** object function takes a string address and returns a JSJavaObject. For other type of objects, the result is undefined. */ public Object object(Object[] args) { Object o = args[0]; if (o != null && o instanceof String) { VM vm = VM.getVM(); Address addr = vm.getDebugger().parseAddress((String)o); Oop oop = vm.getObjectHeap().newOop(addr.addOffsetToAsOopHandle(0)); return getJSJavaFactory().newJSJavaObject(oop); } else { return UNDEFINED; } } /** sizeof function returns size of a Java object in bytes. For other type of objects, the result is undefined. */ public Object sizeof(Object[] args) { if (args.length != 1) return UNDEFINED; Object o = args[0]; if (o != null && o instanceof JSJavaObject) { return new Long(((JSJavaObject)o).getOop().getObjectSize()); } else { return UNDEFINED; } } /** staticof function gets static field of given class. Both class and field name are specified as strings. undefined is returned if there is no such named field. */ public Object staticof(Object[] args) { Object classname = args[0]; Object fieldname = args[1]; if (fieldname == null || classname == null || !(fieldname instanceof String)) { return UNDEFINED; } InstanceKlass ik = null; if (classname instanceof JSJavaClass) { JSJavaClass jclass = (JSJavaClass) classname; JSJavaKlass jk = jclass.getJSJavaKlass(); if (jk != null && jk instanceof JSJavaInstanceKlass) { ik = ((JSJavaInstanceKlass)jk).getInstanceKlass(); } } else if (classname instanceof String) { ik = SystemDictionaryHelper.findInstanceKlass((String)classname); } else { return UNDEFINED; } if (ik == null) { return UNDEFINED; } JSJavaFactory factory = getJSJavaFactory(); try { return ((JSJavaInstanceKlass) factory.newJSJavaKlass(ik)).getStaticFieldValue((String)fieldname); } catch (NoSuchFieldException e) { return UNDEFINED; } } /** * read function reads a single line of input from standard input */ public Object read(Object[] args) { BufferedReader in = getInputReader(); if (in == null) { return null; } if (args.length > 0) { print(args[0].toString()); print(":"); } try { return in.readLine(); } catch (IOException exp) { exp.printStackTrace(); throw new RuntimeException(exp); } } /** * Quit the shell. * This only affects the interactive mode. */ public void quit(Object[] args) { quit(); } public void writeln(Object[] args) { for (int i = 0; i < args.length; i++) { print(args[i].toString()); print(" "); } println(""); } public void write(Object[] args) { for (int i = 0; i < args.length; i++) { print(args[i].toString()); print(" "); } } //-- Internals only below this point protected void start(boolean console) { ScriptContext context = engine.getContext(); OutputStream out = getOutputStream(); if (out != null) { context.setWriter(new PrintWriter(out)); } OutputStream err = getErrorStream(); if (err != null) { context.setErrorWriter(new PrintWriter(err)); } // load "sa.js" initialization file loadInitFile(); // load "~/jsdb.js" (if found) to perform user specific // initialization steps, if any. loadUserInitFile(); JSJavaFactory fac = getJSJavaFactory(); JSJavaVM jvm = (fac != null)? fac.newJSJavaVM() : null; // call "main" function from "sa.js" -- main expects // 'this' object and jvm object call("main", new Object[] { this, jvm }); // if asked, start read-eval-print console if (console) { processSource(null); } } protected JSJavaScriptEngine(boolean debug) { this.debug = debug; ScriptEngineManager manager = new ScriptEngineManager(); engine = manager.getEngineByName("javascript"); if (engine == null) { throw new RuntimeException("can't load JavaScript engine"); } Method[] methods = getClass().getMethods(); for (int i = 0; i < methods.length; i++) { Method m = methods[i]; if (! Modifier.isPublic(m.getModifiers())) { continue; } Class[] argTypes = m.getParameterTypes(); if (argTypes.length == 1 && argTypes[0] == Object[].class) { putFunction(this, m); } } } protected JSJavaScriptEngine() { this(false); } protected abstract ObjectReader getObjectReader(); protected abstract JSJavaFactory getJSJavaFactory(); protected void printPrompt(String str) { System.err.print(str); System.err.flush(); } protected void loadInitFile() { InputStream is = JSJavaScriptEngine.class.getResourceAsStream("sa.js"); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); evalReader(reader, "sa.js"); } protected void loadUserInitFile() { File initFile = new File(getUserInitFileDir(), getUserInitFileName()); if (initFile.exists() && initFile.isFile()) { // load the init script processSource(initFile.getAbsolutePath()); } } protected String getUserInitFileDir() { return System.getProperty("user.home"); } protected String getUserInitFileName() { return "jsdb.js"; } protected BufferedReader getInputReader() { if (inReader == null) { inReader = new BufferedReader(new InputStreamReader(System.in)); } return inReader; } protected PrintStream getOutputStream() { return System.out; } protected PrintStream getErrorStream() { return System.err; } protected void print(String name) { getOutputStream().print(name); } protected void println(String name) { getOutputStream().println(name); } protected void printError(String message) { printError(message, null); } protected void printError(String message, Exception exp) { getErrorStream().println(message); if (exp != null && debug) { exp.printStackTrace(getErrorStream()); } } protected boolean isQuitting() { return quitting; } protected void quit() { quitting = true; } protected ScriptEngine getScriptEngine() { return engine; } private JSJavaThread getOwningThread(JSJavaObject jo) { Oop oop = jo.getOop(); Mark mark = oop.getMark(); ObjectMonitor mon = null; Address owner = null; JSJavaThread owningThread = null; // check for heavyweight monitor if (! mark.hasMonitor()) { // check for lightweight monitor if (mark.hasLocker()) { owner = mark.locker().getAddress(); // save the address of the Lock word } // implied else: no owner } else { // this object has a heavyweight monitor mon = mark.monitor(); // The owner field of a heavyweight monitor may be NULL for no // owner, a JavaThread * or it may still be the address of the // Lock word in a JavaThread's stack. A monitor can be inflated // by a non-owning JavaThread, but only the owning JavaThread // can change the owner field from the Lock word to the // JavaThread * and it may not have done that yet. owner = mon.owner(); } // find the owning thread if (owner != null) { JSJavaFactory factory = getJSJavaFactory(); owningThread = (JSJavaThread) factory.newJSJavaThread(VM.getVM().getThreads().owningThreadFromMonitor(owner)); } return owningThread; } /** * Evaluate JavaScript source. * @param filename the name of the file to compile, or null * for interactive mode. */ private void processSource(String filename) { if (filename == null) { BufferedReader in = getInputReader(); String sourceName = "<stdin>"; int lineno = 0; boolean hitEOF = false; do { int startline = lineno; printPrompt("jsdb> "); Object source = read(EMPTY_ARRAY); if (source == null) { hitEOF = true; break; } lineno++; Object result = evalString(source.toString(), sourceName, startline); if (result != null) { printError(result.toString()); } if (isQuitting()) { // The user executed the quit() function. break; } } while (!hitEOF); } else { Reader in = null; try { in = new BufferedReader(new FileReader(filename)); evalReader(in, filename); } catch (FileNotFoundException ex) { println("File '" + filename + "' not found"); throw new RuntimeException(ex); } } } protected Object evalString(String source, String filename, int lineNum) { try { engine.put(ScriptEngine.FILENAME, filename); return engine.eval(source); } catch (ScriptException sexp) { printError(sexp.toString(), sexp); } catch (Exception exp) { printError(exp.toString(), exp); } return null; } private Object evalReader(Reader in, String filename) { try { engine.put(ScriptEngine.FILENAME, filename); return engine.eval(in); } catch (ScriptException sexp) { System.err.println(sexp); printError(sexp.toString(), sexp); } finally { try { in.close(); } catch (IOException ioe) { printError(ioe.toString(), ioe); } } return null; } // lazily initialized input reader private BufferedReader inReader; // debug mode or not protected final boolean debug; private boolean quitting; // underlying jsr-223 script engine private ScriptEngine engine; }