aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java')
-rw-r--r--trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java1560
1 files changed, 1560 insertions, 0 deletions
diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java
new file mode 100644
index 0000000..de8fcde
--- /dev/null
+++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java
@@ -0,0 +1,1560 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino JavaScript Debugger code, released
+ * November 21, 2000.
+ *
+ * The Initial Developer of the Original Code is
+ * SeeBeyond Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Matt Gould
+ * Christopher Oliver
+ * Cameron McCormack
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+package org.mozilla.javascript.tools.debugger;
+
+import org.mozilla.javascript.*;
+import org.mozilla.javascript.debug.*;
+import java.util.*;
+import java.io.*;
+import java.net.URL;
+
+/**
+ * Dim or Debugger Implementation for Rhino.
+ */
+public class Dim {
+
+ // Constants for instructing the debugger what action to perform
+ // to end interruption. Used by 'returnValue'.
+ public static final int STEP_OVER = 0;
+ public static final int STEP_INTO = 1;
+ public static final int STEP_OUT = 2;
+ public static final int GO = 3;
+ public static final int BREAK = 4;
+ public static final int EXIT = 5;
+
+ // Constants for the DimIProxy interface implementation class.
+ private static final int IPROXY_DEBUG = 0;
+ private static final int IPROXY_LISTEN = 1;
+ private static final int IPROXY_COMPILE_SCRIPT = 2;
+ private static final int IPROXY_EVAL_SCRIPT = 3;
+ private static final int IPROXY_STRING_IS_COMPILABLE = 4;
+ private static final int IPROXY_OBJECT_TO_STRING = 5;
+ private static final int IPROXY_OBJECT_PROPERTY = 6;
+ private static final int IPROXY_OBJECT_IDS = 7;
+
+ /**
+ * Interface to the debugger GUI.
+ */
+ private GuiCallback callback;
+
+ /**
+ * Whether the debugger should break.
+ */
+ private boolean breakFlag;
+
+ /**
+ * The ScopeProvider object that provides the scope in which to
+ * evaluate script.
+ */
+ private ScopeProvider scopeProvider;
+
+ /**
+ * The index of the current stack frame.
+ */
+ private int frameIndex = -1;
+
+ /**
+ * Information about the current stack at the point of interruption.
+ */
+ private volatile ContextData interruptedContextData;
+
+ /**
+ * The ContextFactory to listen to for debugging information.
+ */
+ private ContextFactory contextFactory;
+
+ /**
+ * Synchronization object used to allow script evaluations to
+ * happen when a thread is resumed.
+ */
+ private Object monitor = new Object();
+
+ /**
+ * Synchronization object used to wait for valid
+ * {@link #interruptedContextData}.
+ */
+ private Object eventThreadMonitor = new Object();
+
+ /**
+ * The action to perform to end the interruption loop.
+ */
+ private volatile int returnValue = -1;
+
+ /**
+ * Whether the debugger is inside the interruption loop.
+ */
+ private boolean insideInterruptLoop;
+
+ /**
+ * The requested script string to be evaluated when the thread
+ * has been resumed.
+ */
+ private String evalRequest;
+
+ /**
+ * The stack frame in which to evaluate {@link #evalRequest}.
+ */
+ private StackFrame evalFrame;
+
+ /**
+ * The result of evaluating {@link #evalRequest}.
+ */
+ private String evalResult;
+
+ /**
+ * Whether the debugger should break when a script exception is thrown.
+ */
+ private boolean breakOnExceptions;
+
+ /**
+ * Whether the debugger should break when a script function is entered.
+ */
+ private boolean breakOnEnter;
+
+ /**
+ * Whether the debugger should break when a script function is returned
+ * from.
+ */
+ private boolean breakOnReturn;
+
+ /**
+ * Table mapping URLs to information about the script source.
+ */
+ private final Hashtable urlToSourceInfo = new Hashtable();
+
+ /**
+ * Table mapping function names to information about the function.
+ */
+ private final Hashtable functionNames = new Hashtable();
+
+ /**
+ * Table mapping functions to information about the function.
+ */
+ private final Hashtable functionToSource = new Hashtable();
+
+ /**
+ * ContextFactory.Listener instance attached to {@link #contextFactory}.
+ */
+ private DimIProxy listener;
+
+ /**
+ * Sets the GuiCallback object to use.
+ */
+ public void setGuiCallback(GuiCallback callback) {
+ this.callback = callback;
+ }
+
+ /**
+ * Tells the debugger to break at the next opportunity.
+ */
+ public void setBreak() {
+ this.breakFlag = true;
+ }
+
+ /**
+ * Sets the ScopeProvider to be used.
+ */
+ public void setScopeProvider(ScopeProvider scopeProvider) {
+ this.scopeProvider = scopeProvider;
+ }
+
+ /**
+ * Switches context to the stack frame with the given index.
+ */
+ public void contextSwitch(int frameIndex) {
+ this.frameIndex = frameIndex;
+ }
+
+ /**
+ * Sets whether the debugger should break on exceptions.
+ */
+ public void setBreakOnExceptions(boolean breakOnExceptions) {
+ this.breakOnExceptions = breakOnExceptions;
+ }
+
+ /**
+ * Sets whether the debugger should break on function entering.
+ */
+ public void setBreakOnEnter(boolean breakOnEnter) {
+ this.breakOnEnter = breakOnEnter;
+ }
+
+ /**
+ * Sets whether the debugger should break on function return.
+ */
+ public void setBreakOnReturn(boolean breakOnReturn) {
+ this.breakOnReturn = breakOnReturn;
+ }
+
+ /**
+ * Attaches the debugger to the given ContextFactory.
+ */
+ public void attachTo(ContextFactory factory) {
+ detach();
+ this.contextFactory = factory;
+ this.listener = new DimIProxy(this, IPROXY_LISTEN);
+ factory.addListener(this.listener);
+ }
+
+ /**
+ * Detaches the debugger from the current ContextFactory.
+ */
+ public void detach() {
+ if (listener != null) {
+ contextFactory.removeListener(listener);
+ contextFactory = null;
+ listener = null;
+ }
+ }
+
+ /**
+ * Releases resources associated with this debugger.
+ */
+ public void dispose() {
+ detach();
+ }
+
+ /**
+ * Returns the FunctionSource object for the given script or function.
+ */
+ private FunctionSource getFunctionSource(DebuggableScript fnOrScript) {
+ FunctionSource fsource = functionSource(fnOrScript);
+ if (fsource == null) {
+ String url = getNormalizedUrl(fnOrScript);
+ SourceInfo si = sourceInfo(url);
+ if (si == null) {
+ if (!fnOrScript.isGeneratedScript()) {
+ // Not eval or Function, try to load it from URL
+ String source = loadSource(url);
+ if (source != null) {
+ DebuggableScript top = fnOrScript;
+ for (;;) {
+ DebuggableScript parent = top.getParent();
+ if (parent == null) {
+ break;
+ }
+ top = parent;
+ }
+ registerTopScript(top, source);
+ fsource = functionSource(fnOrScript);
+ }
+ }
+ }
+ }
+ return fsource;
+ }
+
+ /**
+ * Loads the script at the given URL.
+ */
+ private String loadSource(String sourceUrl) {
+ String source = null;
+ int hash = sourceUrl.indexOf('#');
+ if (hash >= 0) {
+ sourceUrl = sourceUrl.substring(0, hash);
+ }
+ try {
+ InputStream is;
+ openStream:
+ {
+ if (sourceUrl.indexOf(':') < 0) {
+ // Can be a file name
+ try {
+ if (sourceUrl.startsWith("~/")) {
+ String home = SecurityUtilities.getSystemProperty("user.home");
+ if (home != null) {
+ String pathFromHome = sourceUrl.substring(2);
+ File f = new File(new File(home), pathFromHome);
+ if (f.exists()) {
+ is = new FileInputStream(f);
+ break openStream;
+ }
+ }
+ }
+ File f = new File(sourceUrl);
+ if (f.exists()) {
+ is = new FileInputStream(f);
+ break openStream;
+ }
+ } catch (SecurityException ex) { }
+ // No existing file, assume missed http://
+ if (sourceUrl.startsWith("//")) {
+ sourceUrl = "http:" + sourceUrl;
+ } else if (sourceUrl.startsWith("/")) {
+ sourceUrl = "http://127.0.0.1" + sourceUrl;
+ } else {
+ sourceUrl = "http://" + sourceUrl;
+ }
+ }
+
+ is = (new URL(sourceUrl)).openStream();
+ }
+
+ try {
+ source = Kit.readReader(new InputStreamReader(is));
+ } finally {
+ is.close();
+ }
+ } catch (IOException ex) {
+ System.err.println
+ ("Failed to load source from "+sourceUrl+": "+ ex);
+ }
+ return source;
+ }
+
+ /**
+ * Registers the given script as a top-level script in the debugger.
+ */
+ private void registerTopScript(DebuggableScript topScript, String source) {
+ if (!topScript.isTopLevel()) {
+ throw new IllegalArgumentException();
+ }
+ String url = getNormalizedUrl(topScript);
+ DebuggableScript[] functions = getAllFunctions(topScript);
+ final SourceInfo sourceInfo = new SourceInfo(source, functions, url);
+
+ synchronized (urlToSourceInfo) {
+ SourceInfo old = (SourceInfo)urlToSourceInfo.get(url);
+ if (old != null) {
+ sourceInfo.copyBreakpointsFrom(old);
+ }
+ urlToSourceInfo.put(url, sourceInfo);
+ for (int i = 0; i != sourceInfo.functionSourcesTop(); ++i) {
+ FunctionSource fsource = sourceInfo.functionSource(i);
+ String name = fsource.name();
+ if (name.length() != 0) {
+ functionNames.put(name, fsource);
+ }
+ }
+ }
+
+ synchronized (functionToSource) {
+ for (int i = 0; i != functions.length; ++i) {
+ FunctionSource fsource = sourceInfo.functionSource(i);
+ functionToSource.put(functions[i], fsource);
+ }
+ }
+
+ callback.updateSourceText(sourceInfo);
+ }
+
+ /**
+ * Returns the FunctionSource object for the given function or script.
+ */
+ private FunctionSource functionSource(DebuggableScript fnOrScript) {
+ return (FunctionSource)functionToSource.get(fnOrScript);
+ }
+
+ /**
+ * Returns an array of all function names.
+ */
+ public String[] functionNames() {
+ String[] a;
+ synchronized (urlToSourceInfo) {
+ Enumeration e = functionNames.keys();
+ a = new String[functionNames.size()];
+ int i = 0;
+ while (e.hasMoreElements()) {
+ a[i++] = (String)e.nextElement();
+ }
+ }
+ return a;
+ }
+
+ /**
+ * Returns the FunctionSource object for the function with the given name.
+ */
+ public FunctionSource functionSourceByName(String functionName) {
+ return (FunctionSource)functionNames.get(functionName);
+ }
+
+ /**
+ * Returns the SourceInfo object for the given URL.
+ */
+ public SourceInfo sourceInfo(String url) {
+ return (SourceInfo)urlToSourceInfo.get(url);
+ }
+
+ /**
+ * Returns the source URL for the given script or function.
+ */
+ private String getNormalizedUrl(DebuggableScript fnOrScript) {
+ String url = fnOrScript.getSourceName();
+ if (url == null) { url = "<stdin>"; }
+ else {
+ // Not to produce window for eval from different lines,
+ // strip line numbers, i.e. replace all #[0-9]+\(eval\) by
+ // (eval)
+ // Option: similar teatment for Function?
+ char evalSeparator = '#';
+ StringBuffer sb = null;
+ int urlLength = url.length();
+ int cursor = 0;
+ for (;;) {
+ int searchStart = url.indexOf(evalSeparator, cursor);
+ if (searchStart < 0) {
+ break;
+ }
+ String replace = null;
+ int i = searchStart + 1;
+ while (i != urlLength) {
+ int c = url.charAt(i);
+ if (!('0' <= c && c <= '9')) {
+ break;
+ }
+ ++i;
+ }
+ if (i != searchStart + 1) {
+ // i points after #[0-9]+
+ if ("(eval)".regionMatches(0, url, i, 6)) {
+ cursor = i + 6;
+ replace = "(eval)";
+ }
+ }
+ if (replace == null) {
+ break;
+ }
+ if (sb == null) {
+ sb = new StringBuffer();
+ sb.append(url.substring(0, searchStart));
+ }
+ sb.append(replace);
+ }
+ if (sb != null) {
+ if (cursor != urlLength) {
+ sb.append(url.substring(cursor));
+ }
+ url = sb.toString();
+ }
+ }
+ return url;
+ }
+
+ /**
+ * Returns an array of all functions in the given script.
+ */
+ private static DebuggableScript[] getAllFunctions
+ (DebuggableScript function) {
+ ObjArray functions = new ObjArray();
+ collectFunctions_r(function, functions);
+ DebuggableScript[] result = new DebuggableScript[functions.size()];
+ functions.toArray(result);
+ return result;
+ }
+
+ /**
+ * Helper function for {@link #getAllFunctions(DebuggableScript)}.
+ */
+ private static void collectFunctions_r(DebuggableScript function,
+ ObjArray array) {
+ array.add(function);
+ for (int i = 0; i != function.getFunctionCount(); ++i) {
+ collectFunctions_r(function.getFunction(i), array);
+ }
+ }
+
+ /**
+ * Clears all breakpoints.
+ */
+ public void clearAllBreakpoints() {
+ Enumeration e = urlToSourceInfo.elements();
+ while (e.hasMoreElements()) {
+ SourceInfo si = (SourceInfo)e.nextElement();
+ si.removeAllBreakpoints();
+ }
+ }
+
+ /**
+ * Called when a breakpoint has been hit.
+ */
+ private void handleBreakpointHit(StackFrame frame, Context cx) {
+ breakFlag = false;
+ interrupted(cx, frame, null);
+ }
+
+ /**
+ * Called when a script exception has been thrown.
+ */
+ private void handleExceptionThrown(Context cx, Throwable ex,
+ StackFrame frame) {
+ if (breakOnExceptions) {
+ ContextData cd = frame.contextData();
+ if (cd.lastProcessedException != ex) {
+ interrupted(cx, frame, ex);
+ cd.lastProcessedException = ex;
+ }
+ }
+ }
+
+ /**
+ * Returns the current ContextData object.
+ */
+ public ContextData currentContextData() {
+ return interruptedContextData;
+ }
+
+ /**
+ * Sets the action to perform to end interruption.
+ */
+ public void setReturnValue(int returnValue) {
+ synchronized (monitor) {
+ this.returnValue = returnValue;
+ monitor.notify();
+ }
+ }
+
+ /**
+ * Resumes execution of script.
+ */
+ public void go() {
+ synchronized (monitor) {
+ this.returnValue = GO;
+ monitor.notifyAll();
+ }
+ }
+
+ /**
+ * Evaluates the given script.
+ */
+ public String eval(String expr) {
+ String result = "undefined";
+ if (expr == null) {
+ return result;
+ }
+ ContextData contextData = currentContextData();
+ if (contextData == null || frameIndex >= contextData.frameCount()) {
+ return result;
+ }
+ StackFrame frame = contextData.getFrame(frameIndex);
+ if (contextData.eventThreadFlag) {
+ Context cx = Context.getCurrentContext();
+ result = do_eval(cx, frame, expr);
+ } else {
+ synchronized (monitor) {
+ if (insideInterruptLoop) {
+ evalRequest = expr;
+ evalFrame = frame;
+ monitor.notify();
+ do {
+ try {
+ monitor.wait();
+ } catch (InterruptedException exc) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ } while (evalRequest != null);
+ result = evalResult;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compiles the given script.
+ */
+ public void compileScript(String url, String text) {
+ DimIProxy action = new DimIProxy(this, IPROXY_COMPILE_SCRIPT);
+ action.url = url;
+ action.text = text;
+ action.withContext();
+ }
+
+ /**
+ * Evaluates the given script.
+ */
+ public void evalScript(final String url, final String text) {
+ DimIProxy action = new DimIProxy(this, IPROXY_EVAL_SCRIPT);
+ action.url = url;
+ action.text = text;
+ action.withContext();
+ }
+
+ /**
+ * Converts the given script object to a string.
+ */
+ public String objectToString(Object object) {
+ DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_TO_STRING);
+ action.object = object;
+ action.withContext();
+ return action.stringResult;
+ }
+
+ /**
+ * Returns whether the given string is syntactically valid script.
+ */
+ public boolean stringIsCompilableUnit(String str) {
+ DimIProxy action = new DimIProxy(this, IPROXY_STRING_IS_COMPILABLE);
+ action.text = str;
+ action.withContext();
+ return action.booleanResult;
+ }
+
+ /**
+ * Returns the value of a property on the given script object.
+ */
+ public Object getObjectProperty(Object object, Object id) {
+ DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_PROPERTY);
+ action.object = object;
+ action.id = id;
+ action.withContext();
+ return action.objectResult;
+ }
+
+ /**
+ * Returns an array of the property names on the given script object.
+ */
+ public Object[] getObjectIds(Object object) {
+ DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_IDS);
+ action.object = object;
+ action.withContext();
+ return action.objectArrayResult;
+ }
+
+ /**
+ * Returns the value of a property on the given script object.
+ */
+ private Object getObjectPropertyImpl(Context cx, Object object,
+ Object id) {
+ Scriptable scriptable = (Scriptable)object;
+ Object result;
+ if (id instanceof String) {
+ String name = (String)id;
+ if (name.equals("this")) {
+ result = scriptable;
+ } else if (name.equals("__proto__")) {
+ result = scriptable.getPrototype();
+ } else if (name.equals("__parent__")) {
+ result = scriptable.getParentScope();
+ } else {
+ result = ScriptableObject.getProperty(scriptable, name);
+ if (result == ScriptableObject.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+ }
+ } else {
+ int index = ((Integer)id).intValue();
+ result = ScriptableObject.getProperty(scriptable, index);
+ if (result == ScriptableObject.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns an array of the property names on the given script object.
+ */
+ private Object[] getObjectIdsImpl(Context cx, Object object) {
+ if (!(object instanceof Scriptable) || object == Undefined.instance) {
+ return Context.emptyArgs;
+ }
+
+ Object[] ids;
+ Scriptable scriptable = (Scriptable)object;
+ if (scriptable instanceof DebuggableObject) {
+ ids = ((DebuggableObject)scriptable).getAllIds();
+ } else {
+ ids = scriptable.getIds();
+ }
+
+ Scriptable proto = scriptable.getPrototype();
+ Scriptable parent = scriptable.getParentScope();
+ int extra = 0;
+ if (proto != null) {
+ ++extra;
+ }
+ if (parent != null) {
+ ++extra;
+ }
+ if (extra != 0) {
+ Object[] tmp = new Object[extra + ids.length];
+ System.arraycopy(ids, 0, tmp, extra, ids.length);
+ ids = tmp;
+ extra = 0;
+ if (proto != null) {
+ ids[extra++] = "__proto__";
+ }
+ if (parent != null) {
+ ids[extra++] = "__parent__";
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * Interrupts script execution.
+ */
+ private void interrupted(Context cx, final StackFrame frame,
+ Throwable scriptException) {
+ ContextData contextData = frame.contextData();
+ boolean eventThreadFlag = callback.isGuiEventThread();
+ contextData.eventThreadFlag = eventThreadFlag;
+
+ boolean recursiveEventThreadCall = false;
+
+interruptedCheck:
+ synchronized (eventThreadMonitor) {
+ if (eventThreadFlag) {
+ if (interruptedContextData != null) {
+ recursiveEventThreadCall = true;
+ break interruptedCheck;
+ }
+ } else {
+ while (interruptedContextData != null) {
+ try {
+ eventThreadMonitor.wait();
+ } catch (InterruptedException exc) {
+ return;
+ }
+ }
+ }
+ interruptedContextData = contextData;
+ }
+
+ if (recursiveEventThreadCall) {
+ // XXX: For now the following is commented out as on Linux
+ // too deep recursion of dispatchNextGuiEvent causes GUI lockout.
+ // Note: it can make GUI unresponsive if long-running script
+ // will be called on GUI thread while processing another interrupt
+ if (false) {
+ // Run event dispatch until gui sets a flag to exit the initial
+ // call to interrupted.
+ while (this.returnValue == -1) {
+ try {
+ callback.dispatchNextGuiEvent();
+ } catch (InterruptedException exc) {
+ }
+ }
+ }
+ return;
+ }
+
+ if (interruptedContextData == null) Kit.codeBug();
+
+ try {
+ do {
+ int frameCount = contextData.frameCount();
+ this.frameIndex = frameCount -1;
+
+ final String threadTitle = Thread.currentThread().toString();
+ final String alertMessage;
+ if (scriptException == null) {
+ alertMessage = null;
+ } else {
+ alertMessage = scriptException.toString();
+ }
+
+ int returnValue = -1;
+ if (!eventThreadFlag) {
+ synchronized (monitor) {
+ if (insideInterruptLoop) Kit.codeBug();
+ this.insideInterruptLoop = true;
+ this.evalRequest = null;
+ this.returnValue = -1;
+ callback.enterInterrupt(frame, threadTitle,
+ alertMessage);
+ try {
+ for (;;) {
+ try {
+ monitor.wait();
+ } catch (InterruptedException exc) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ if (evalRequest != null) {
+ this.evalResult = null;
+ try {
+ evalResult = do_eval(cx, evalFrame,
+ evalRequest);
+ } finally {
+ evalRequest = null;
+ evalFrame = null;
+ monitor.notify();
+ }
+ continue;
+ }
+ if (this.returnValue != -1) {
+ returnValue = this.returnValue;
+ break;
+ }
+ }
+ } finally {
+ insideInterruptLoop = false;
+ }
+ }
+ } else {
+ this.returnValue = -1;
+ callback.enterInterrupt(frame, threadTitle, alertMessage);
+ while (this.returnValue == -1) {
+ try {
+ callback.dispatchNextGuiEvent();
+ } catch (InterruptedException exc) {
+ }
+ }
+ returnValue = this.returnValue;
+ }
+ switch (returnValue) {
+ case STEP_OVER:
+ contextData.breakNextLine = true;
+ contextData.stopAtFrameDepth = contextData.frameCount();
+ break;
+ case STEP_INTO:
+ contextData.breakNextLine = true;
+ contextData.stopAtFrameDepth = -1;
+ break;
+ case STEP_OUT:
+ if (contextData.frameCount() > 1) {
+ contextData.breakNextLine = true;
+ contextData.stopAtFrameDepth
+ = contextData.frameCount() -1;
+ }
+ break;
+ }
+ } while (false);
+ } finally {
+ synchronized (eventThreadMonitor) {
+ interruptedContextData = null;
+ eventThreadMonitor.notifyAll();
+ }
+ }
+
+ }
+
+ /**
+ * Evaluates script in the given stack frame.
+ */
+ private static String do_eval(Context cx, StackFrame frame, String expr) {
+ String resultString;
+ Debugger saved_debugger = cx.getDebugger();
+ Object saved_data = cx.getDebuggerContextData();
+ int saved_level = cx.getOptimizationLevel();
+
+ cx.setDebugger(null, null);
+ cx.setOptimizationLevel(-1);
+ cx.setGeneratingDebug(false);
+ try {
+ Callable script = (Callable)cx.compileString(expr, "", 0, null);
+ Object result = script.call(cx, frame.scope, frame.thisObj,
+ ScriptRuntime.emptyArgs);
+ if (result == Undefined.instance) {
+ resultString = "";
+ } else {
+ resultString = ScriptRuntime.toString(result);
+ }
+ } catch (Exception exc) {
+ resultString = exc.getMessage();
+ } finally {
+ cx.setGeneratingDebug(true);
+ cx.setOptimizationLevel(saved_level);
+ cx.setDebugger(saved_debugger, saved_data);
+ }
+ if (resultString == null) {
+ resultString = "null";
+ }
+ return resultString;
+ }
+
+ /**
+ * Proxy class to implement debug interfaces without bloat of class
+ * files.
+ */
+ private static class DimIProxy
+ implements ContextAction, ContextFactory.Listener, Debugger {
+
+ /**
+ * The debugger.
+ */
+ private Dim dim;
+
+ /**
+ * The interface implementation type. One of the IPROXY_* constants
+ * defined in {@link Dim}.
+ */
+ private int type;
+
+ /**
+ * The URL origin of the script to compile or evaluate.
+ */
+ private String url;
+
+ /**
+ * The text of the script to compile, evaluate or test for compilation.
+ */
+ private String text;
+
+ /**
+ * The object to convert, get a property from or enumerate.
+ */
+ private Object object;
+
+ /**
+ * The property to look up in {@link #object}.
+ */
+ private Object id;
+
+ /**
+ * The boolean result of the action.
+ */
+ private boolean booleanResult;
+
+ /**
+ * The String result of the action.
+ */
+ private String stringResult;
+
+ /**
+ * The Object result of the action.
+ */
+ private Object objectResult;
+
+ /**
+ * The Object[] result of the action.
+ */
+ private Object[] objectArrayResult;
+
+ /**
+ * Creates a new DimIProxy.
+ */
+ private DimIProxy(Dim dim, int type) {
+ this.dim = dim;
+ this.type = type;
+ }
+
+ // ContextAction
+
+ /**
+ * Performs the action given by {@link #type}.
+ */
+ public Object run(Context cx) {
+ switch (type) {
+ case IPROXY_COMPILE_SCRIPT:
+ cx.compileString(text, url, 1, null);
+ break;
+
+ case IPROXY_EVAL_SCRIPT:
+ {
+ Scriptable scope = null;
+ if (dim.scopeProvider != null) {
+ scope = dim.scopeProvider.getScope();
+ }
+ if (scope == null) {
+ scope = new ImporterTopLevel(cx);
+ }
+ cx.evaluateString(scope, text, url, 1, null);
+ }
+ break;
+
+ case IPROXY_STRING_IS_COMPILABLE:
+ booleanResult = cx.stringIsCompilableUnit(text);
+ break;
+
+ case IPROXY_OBJECT_TO_STRING:
+ if (object == Undefined.instance) {
+ stringResult = "undefined";
+ } else if (object == null) {
+ stringResult = "null";
+ } else if (object instanceof NativeCall) {
+ stringResult = "[object Call]";
+ } else {
+ stringResult = Context.toString(object);
+ }
+ break;
+
+ case IPROXY_OBJECT_PROPERTY:
+ objectResult = dim.getObjectPropertyImpl(cx, object, id);
+ break;
+
+ case IPROXY_OBJECT_IDS:
+ objectArrayResult = dim.getObjectIdsImpl(cx, object);
+ break;
+
+ default:
+ throw Kit.codeBug();
+ }
+ return null;
+ }
+
+ /**
+ * Performs the action given by {@link #type} with the attached
+ * {@link ContextFactory}.
+ */
+ private void withContext() {
+ dim.contextFactory.call(this);
+ }
+
+ // ContextFactory.Listener
+
+ /**
+ * Called when a Context is created.
+ */
+ public void contextCreated(Context cx) {
+ if (type != IPROXY_LISTEN) Kit.codeBug();
+ ContextData contextData = new ContextData();
+ Debugger debugger = new DimIProxy(dim, IPROXY_DEBUG);
+ cx.setDebugger(debugger, contextData);
+ cx.setGeneratingDebug(true);
+ cx.setOptimizationLevel(-1);
+ }
+
+ /**
+ * Called when a Context is destroyed.
+ */
+ public void contextReleased(Context cx) {
+ if (type != IPROXY_LISTEN) Kit.codeBug();
+ }
+
+ // Debugger
+
+ /**
+ * Returns a StackFrame for the given function or script.
+ */
+ public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) {
+ if (type != IPROXY_DEBUG) Kit.codeBug();
+
+ FunctionSource item = dim.getFunctionSource(fnOrScript);
+ if (item == null) {
+ // Can not debug if source is not available
+ return null;
+ }
+ return new StackFrame(cx, dim, item);
+ }
+
+ /**
+ * Called when compilation is finished.
+ */
+ public void handleCompilationDone(Context cx,
+ DebuggableScript fnOrScript,
+ String source) {
+ if (type != IPROXY_DEBUG) Kit.codeBug();
+
+ if (!fnOrScript.isTopLevel()) {
+ return;
+ }
+ dim.registerTopScript(fnOrScript, source);
+ }
+ }
+
+ /**
+ * Class to store information about a stack.
+ */
+ public static class ContextData {
+
+ /**
+ * The stack frames.
+ */
+ private ObjArray frameStack = new ObjArray();
+
+ /**
+ * Whether the debugger should break at the next line in this context.
+ */
+ private boolean breakNextLine;
+
+ /**
+ * The frame depth the debugger should stop at. Used to implement
+ * "step over" and "step out".
+ */
+ private int stopAtFrameDepth = -1;
+
+ /**
+ * Whether this context is in the event thread.
+ */
+ private boolean eventThreadFlag;
+
+ /**
+ * The last exception that was processed.
+ */
+ private Throwable lastProcessedException;
+
+ /**
+ * Returns the ContextData for the given Context.
+ */
+ public static ContextData get(Context cx) {
+ return (ContextData) cx.getDebuggerContextData();
+ }
+
+ /**
+ * Returns the number of stack frames.
+ */
+ public int frameCount() {
+ return frameStack.size();
+ }
+
+ /**
+ * Returns the stack frame with the given index.
+ */
+ public StackFrame getFrame(int frameNumber) {
+ int num = frameStack.size() - frameNumber - 1;
+ return (StackFrame) frameStack.get(num);
+ }
+
+ /**
+ * Pushes a stack frame on to the stack.
+ */
+ private void pushFrame(StackFrame frame) {
+ frameStack.push(frame);
+ }
+
+ /**
+ * Pops a stack frame from the stack.
+ */
+ private void popFrame() {
+ frameStack.pop();
+ }
+ }
+
+ /**
+ * Object to represent one stack frame.
+ */
+ public static class StackFrame implements DebugFrame {
+
+ /**
+ * The debugger.
+ */
+ private Dim dim;
+
+ /**
+ * The ContextData for the Context being debugged.
+ */
+ private ContextData contextData;
+
+ /**
+ * The scope.
+ */
+ private Scriptable scope;
+
+ /**
+ * The 'this' object.
+ */
+ private Scriptable thisObj;
+
+ /**
+ * Information about the function.
+ */
+ private FunctionSource fsource;
+
+ /**
+ * Array of breakpoint state for each source line.
+ */
+ private boolean[] breakpoints;
+
+ /**
+ * Current line number.
+ */
+ private int lineNumber;
+
+ /**
+ * Creates a new StackFrame.
+ */
+ private StackFrame(Context cx, Dim dim, FunctionSource fsource) {
+ this.dim = dim;
+ this.contextData = ContextData.get(cx);
+ this.fsource = fsource;
+ this.breakpoints = fsource.sourceInfo().breakpoints;
+ this.lineNumber = fsource.firstLine();
+ }
+
+ /**
+ * Called when the stack frame is entered.
+ */
+ public void onEnter(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args) {
+ contextData.pushFrame(this);
+ this.scope = scope;
+ this.thisObj = thisObj;
+ if (dim.breakOnEnter) {
+ dim.handleBreakpointHit(this, cx);
+ }
+ }
+
+ /**
+ * Called when the current position has changed.
+ */
+ public void onLineChange(Context cx, int lineno) {
+ this.lineNumber = lineno;
+
+ if (!breakpoints[lineno] && !dim.breakFlag) {
+ boolean lineBreak = contextData.breakNextLine;
+ if (lineBreak && contextData.stopAtFrameDepth >= 0) {
+ lineBreak = (contextData.frameCount()
+ <= contextData.stopAtFrameDepth);
+ }
+ if (!lineBreak) {
+ return;
+ }
+ contextData.stopAtFrameDepth = -1;
+ contextData.breakNextLine = false;
+ }
+
+ dim.handleBreakpointHit(this, cx);
+ }
+
+ /**
+ * Called when an exception has been thrown.
+ */
+ public void onExceptionThrown(Context cx, Throwable exception) {
+ dim.handleExceptionThrown(cx, exception, this);
+ }
+
+ /**
+ * Called when the stack frame has been left.
+ */
+ public void onExit(Context cx, boolean byThrow,
+ Object resultOrException) {
+ if (dim.breakOnReturn && !byThrow) {
+ dim.handleBreakpointHit(this, cx);
+ }
+ contextData.popFrame();
+ }
+
+ /**
+ * Called when a 'debugger' statement is executed.
+ */
+ public void onDebuggerStatement(Context cx) {
+ dim.handleBreakpointHit(this, cx);
+ }
+
+ /**
+ * Returns the SourceInfo object for the function.
+ */
+ public SourceInfo sourceInfo() {
+ return fsource.sourceInfo();
+ }
+
+ /**
+ * Returns the ContextData object for the Context.
+ */
+ public ContextData contextData() {
+ return contextData;
+ }
+
+ /**
+ * Returns the scope object for this frame.
+ */
+ public Object scope() {
+ return scope;
+ }
+
+ /**
+ * Returns the 'this' object for this frame.
+ */
+ public Object thisObj() {
+ return thisObj;
+ }
+
+ /**
+ * Returns the source URL.
+ */
+ public String getUrl() {
+ return fsource.sourceInfo().url();
+ }
+
+ /**
+ * Returns the current line number.
+ */
+ public int getLineNumber() {
+ return lineNumber;
+ }
+ }
+
+ /**
+ * Class to store information about a function.
+ */
+ public static class FunctionSource {
+
+ /**
+ * Information about the source of the function.
+ */
+ private SourceInfo sourceInfo;
+
+ /**
+ * Line number of the first line of the function.
+ */
+ private int firstLine;
+
+ /**
+ * The function name.
+ */
+ private String name;
+
+ /**
+ * Creates a new FunctionSource.
+ */
+ private FunctionSource(SourceInfo sourceInfo, int firstLine,
+ String name) {
+ if (name == null) throw new IllegalArgumentException();
+ this.sourceInfo = sourceInfo;
+ this.firstLine = firstLine;
+ this.name = name;
+ }
+
+ /**
+ * Returns the SourceInfo object that describes the source of the
+ * function.
+ */
+ public SourceInfo sourceInfo() {
+ return sourceInfo;
+ }
+
+ /**
+ * Returns the line number of the first line of the function.
+ */
+ public int firstLine() {
+ return firstLine;
+ }
+
+ /**
+ * Returns the name of the function.
+ */
+ public String name() {
+ return name;
+ }
+ }
+
+ /**
+ * Class to store information about a script source.
+ */
+ public static class SourceInfo {
+
+ /**
+ * An empty array of booleans.
+ */
+ private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
+
+ /**
+ * The script.
+ */
+ private String source;
+
+ /**
+ * The URL of the script.
+ */
+ private String url;
+
+ /**
+ * Array indicating which lines can have breakpoints set.
+ */
+ private boolean[] breakableLines;
+
+ /**
+ * Array indicating whether a breakpoint is set on the line.
+ */
+ private boolean[] breakpoints;
+
+ /**
+ * Array of FunctionSource objects for the functions in the script.
+ */
+ private FunctionSource[] functionSources;
+
+ /**
+ * Creates a new SourceInfo object.
+ */
+ private SourceInfo(String source, DebuggableScript[] functions,
+ String normilizedUrl) {
+ this.source = source;
+ this.url = normilizedUrl;
+
+ int N = functions.length;
+ int[][] lineArrays = new int[N][];
+ for (int i = 0; i != N; ++i) {
+ lineArrays[i] = functions[i].getLineNumbers();
+ }
+
+ int minAll = 0, maxAll = -1;
+ int[] firstLines = new int[N];
+ for (int i = 0; i != N; ++i) {
+ int[] lines = lineArrays[i];
+ if (lines == null || lines.length == 0) {
+ firstLines[i] = -1;
+ } else {
+ int min, max;
+ min = max = lines[0];
+ for (int j = 1; j != lines.length; ++j) {
+ int line = lines[j];
+ if (line < min) {
+ min = line;
+ } else if (line > max) {
+ max = line;
+ }
+ }
+ firstLines[i] = min;
+ if (minAll > maxAll) {
+ minAll = min;
+ maxAll = max;
+ } else {
+ if (min < minAll) {
+ minAll = min;
+ }
+ if (max > maxAll) {
+ maxAll = max;
+ }
+ }
+ }
+ }
+
+ if (minAll > maxAll) {
+ // No line information
+ this.breakableLines = EMPTY_BOOLEAN_ARRAY;
+ this.breakpoints = EMPTY_BOOLEAN_ARRAY;
+ } else {
+ if (minAll < 0) {
+ // Line numbers can not be negative
+ throw new IllegalStateException(String.valueOf(minAll));
+ }
+ int linesTop = maxAll + 1;
+ this.breakableLines = new boolean[linesTop];
+ this.breakpoints = new boolean[linesTop];
+ for (int i = 0; i != N; ++i) {
+ int[] lines = lineArrays[i];
+ if (lines != null && lines.length != 0) {
+ for (int j = 0; j != lines.length; ++j) {
+ int line = lines[j];
+ this.breakableLines[line] = true;
+ }
+ }
+ }
+ }
+ this.functionSources = new FunctionSource[N];
+ for (int i = 0; i != N; ++i) {
+ String name = functions[i].getFunctionName();
+ if (name == null) {
+ name = "";
+ }
+ this.functionSources[i]
+ = new FunctionSource(this, firstLines[i], name);
+ }
+ }
+
+ /**
+ * Returns the source text.
+ */
+ public String source() {
+ return this.source;
+ }
+
+ /**
+ * Returns the script's origin URL.
+ */
+ public String url() {
+ return this.url;
+ }
+
+ /**
+ * Returns the number of FunctionSource objects stored in this object.
+ */
+ public int functionSourcesTop() {
+ return functionSources.length;
+ }
+
+ /**
+ * Returns the FunctionSource object with the given index.
+ */
+ public FunctionSource functionSource(int i) {
+ return functionSources[i];
+ }
+
+ /**
+ * Copies the breakpoints from the given SourceInfo object into this
+ * one.
+ */
+ private void copyBreakpointsFrom(SourceInfo old) {
+ int end = old.breakpoints.length;
+ if (end > this.breakpoints.length) {
+ end = this.breakpoints.length;
+ }
+ for (int line = 0; line != end; ++line) {
+ if (old.breakpoints[line]) {
+ this.breakpoints[line] = true;
+ }
+ }
+ }
+
+ /**
+ * Returns whether the given line number can have a breakpoint set on
+ * it.
+ */
+ public boolean breakableLine(int line) {
+ return (line < this.breakableLines.length)
+ && this.breakableLines[line];
+ }
+
+ /**
+ * Returns whether there is a breakpoint set on the given line.
+ */
+ public boolean breakpoint(int line) {
+ if (!breakableLine(line)) {
+ throw new IllegalArgumentException(String.valueOf(line));
+ }
+ return line < this.breakpoints.length && this.breakpoints[line];
+ }
+
+ /**
+ * Sets or clears the breakpoint flag for the given line.
+ */
+ public boolean breakpoint(int line, boolean value) {
+ if (!breakableLine(line)) {
+ throw new IllegalArgumentException(String.valueOf(line));
+ }
+ boolean changed;
+ synchronized (breakpoints) {
+ if (breakpoints[line] != value) {
+ breakpoints[line] = value;
+ changed = true;
+ } else {
+ changed = false;
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Removes all breakpoints from the script.
+ */
+ public void removeAllBreakpoints() {
+ synchronized (breakpoints) {
+ for (int line = 0; line != breakpoints.length; ++line) {
+ breakpoints[line] = false;
+ }
+ }
+ }
+ }
+}