blob: 1775209b459a12367127df96be38fa319a74f07c [file] [log] [blame]
/*
* 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;
}