0001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
0002: *
0003: * ***** BEGIN LICENSE BLOCK *****
0004: * Version: MPL 1.1/GPL 2.0
0005: *
0006: * The contents of this file are subject to the Mozilla Public License Version
0007: * 1.1 (the "License"); you may not use this file except in compliance with
0008: * the License. You may obtain a copy of the License at
0009: * http://www.mozilla.org/MPL/
0010: *
0011: * Software distributed under the License is distributed on an "AS IS" basis,
0012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0013: * for the specific language governing rights and limitations under the
0014: * License.
0015: *
0016: * The Original Code is Rhino JavaScript Debugger code, released
0017: * November 21, 2000.
0018: *
0019: * The Initial Developer of the Original Code is
0020: * SeeBeyond Corporation.
0021: * Portions created by the Initial Developer are Copyright (C) 2000
0022: * the Initial Developer. All Rights Reserved.
0023: *
0024: * Contributor(s):
0025: * Igor Bukanov
0026: * Matt Gould
0027: * Christopher Oliver
0028: * Cameron McCormack
0029: *
0030: * Alternatively, the contents of this file may be used under the terms of
0031: * the GNU General Public License Version 2 or later (the "GPL"), in which
0032: * case the provisions of the GPL are applicable instead of those above. If
0033: * you wish to allow use of your version of this file only under the terms of
0034: * the GPL and not to allow others to use your version of this file under the
0035: * MPL, indicate your decision by deleting the provisions above and replacing
0036: * them with the notice and other provisions required by the GPL. If you do
0037: * not delete the provisions above, a recipient may use your version of this
0038: * file under either the MPL or the GPL.
0039: *
0040: * ***** END LICENSE BLOCK ***** */
0041: package org.mozilla.javascript.tools.debugger;
0042:
0043: import org.mozilla.javascript.*;
0044: import org.mozilla.javascript.debug.*;
0045: import java.util.*;
0046: import java.io.*;
0047: import java.net.URL;
0048:
0049: /**
0050: * Dim or Debugger Implementation for Rhino.
0051: */
0052: public class Dim {
0053:
0054: // Constants for instructing the debugger what action to perform
0055: // to end interruption. Used by 'returnValue'.
0056: public static final int STEP_OVER = 0;
0057: public static final int STEP_INTO = 1;
0058: public static final int STEP_OUT = 2;
0059: public static final int GO = 3;
0060: public static final int BREAK = 4;
0061: public static final int EXIT = 5;
0062:
0063: // Constants for the DimIProxy interface implementation class.
0064: private static final int IPROXY_DEBUG = 0;
0065: private static final int IPROXY_LISTEN = 1;
0066: private static final int IPROXY_COMPILE_SCRIPT = 2;
0067: private static final int IPROXY_EVAL_SCRIPT = 3;
0068: private static final int IPROXY_STRING_IS_COMPILABLE = 4;
0069: private static final int IPROXY_OBJECT_TO_STRING = 5;
0070: private static final int IPROXY_OBJECT_PROPERTY = 6;
0071: private static final int IPROXY_OBJECT_IDS = 7;
0072:
0073: /**
0074: * Interface to the debugger GUI.
0075: */
0076: private GuiCallback callback;
0077:
0078: /**
0079: * Whether the debugger should break.
0080: */
0081: private boolean breakFlag;
0082:
0083: /**
0084: * The ScopeProvider object that provides the scope in which to
0085: * evaluate script.
0086: */
0087: private ScopeProvider scopeProvider;
0088:
0089: /**
0090: * The index of the current stack frame.
0091: */
0092: private int frameIndex = -1;
0093:
0094: /**
0095: * Information about the current stack at the point of interruption.
0096: */
0097: private volatile ContextData interruptedContextData;
0098:
0099: /**
0100: * The ContextFactory to listen to for debugging information.
0101: */
0102: private ContextFactory contextFactory;
0103:
0104: /**
0105: * Synchronization object used to allow script evaluations to
0106: * happen when a thread is resumed.
0107: */
0108: private Object monitor = new Object();
0109:
0110: /**
0111: * Synchronization object used to wait for valid
0112: * {@link #interruptedContextData}.
0113: */
0114: private Object eventThreadMonitor = new Object();
0115:
0116: /**
0117: * The action to perform to end the interruption loop.
0118: */
0119: private volatile int returnValue = -1;
0120:
0121: /**
0122: * Whether the debugger is inside the interruption loop.
0123: */
0124: private boolean insideInterruptLoop;
0125:
0126: /**
0127: * The requested script string to be evaluated when the thread
0128: * has been resumed.
0129: */
0130: private String evalRequest;
0131:
0132: /**
0133: * The stack frame in which to evaluate {@link #evalRequest}.
0134: */
0135: private StackFrame evalFrame;
0136:
0137: /**
0138: * The result of evaluating {@link #evalRequest}.
0139: */
0140: private String evalResult;
0141:
0142: /**
0143: * Whether the debugger should break when a script exception is thrown.
0144: */
0145: private boolean breakOnExceptions;
0146:
0147: /**
0148: * Whether the debugger should break when a script function is entered.
0149: */
0150: private boolean breakOnEnter;
0151:
0152: /**
0153: * Whether the debugger should break when a script function is returned
0154: * from.
0155: */
0156: private boolean breakOnReturn;
0157:
0158: /**
0159: * Table mapping URLs to information about the script source.
0160: */
0161: private final Hashtable urlToSourceInfo = new Hashtable();
0162:
0163: /**
0164: * Table mapping function names to information about the function.
0165: */
0166: private final Hashtable functionNames = new Hashtable();
0167:
0168: /**
0169: * Table mapping functions to information about the function.
0170: */
0171: private final Hashtable functionToSource = new Hashtable();
0172:
0173: /**
0174: * ContextFactory.Listener instance attached to {@link #contextFactory}.
0175: */
0176: private DimIProxy listener;
0177:
0178: /**
0179: * Sets the GuiCallback object to use.
0180: */
0181: public void setGuiCallback(GuiCallback callback) {
0182: this .callback = callback;
0183: }
0184:
0185: /**
0186: * Tells the debugger to break at the next opportunity.
0187: */
0188: public void setBreak() {
0189: this .breakFlag = true;
0190: }
0191:
0192: /**
0193: * Sets the ScopeProvider to be used.
0194: */
0195: public void setScopeProvider(ScopeProvider scopeProvider) {
0196: this .scopeProvider = scopeProvider;
0197: }
0198:
0199: /**
0200: * Switches context to the stack frame with the given index.
0201: */
0202: public void contextSwitch(int frameIndex) {
0203: this .frameIndex = frameIndex;
0204: }
0205:
0206: /**
0207: * Sets whether the debugger should break on exceptions.
0208: */
0209: public void setBreakOnExceptions(boolean breakOnExceptions) {
0210: this .breakOnExceptions = breakOnExceptions;
0211: }
0212:
0213: /**
0214: * Sets whether the debugger should break on function entering.
0215: */
0216: public void setBreakOnEnter(boolean breakOnEnter) {
0217: this .breakOnEnter = breakOnEnter;
0218: }
0219:
0220: /**
0221: * Sets whether the debugger should break on function return.
0222: */
0223: public void setBreakOnReturn(boolean breakOnReturn) {
0224: this .breakOnReturn = breakOnReturn;
0225: }
0226:
0227: /**
0228: * Attaches the debugger to the given ContextFactory.
0229: */
0230: public void attachTo(ContextFactory factory) {
0231: detach();
0232: this .contextFactory = factory;
0233: this .listener = new DimIProxy(this , IPROXY_LISTEN);
0234: factory.addListener(this .listener);
0235: }
0236:
0237: /**
0238: * Detaches the debugger from the current ContextFactory.
0239: */
0240: public void detach() {
0241: if (listener != null) {
0242: contextFactory.removeListener(listener);
0243: contextFactory = null;
0244: listener = null;
0245: }
0246: }
0247:
0248: /**
0249: * Releases resources associated with this debugger.
0250: */
0251: public void dispose() {
0252: detach();
0253: }
0254:
0255: /**
0256: * Returns the FunctionSource object for the given script or function.
0257: */
0258: private FunctionSource getFunctionSource(DebuggableScript fnOrScript) {
0259: FunctionSource fsource = functionSource(fnOrScript);
0260: if (fsource == null) {
0261: String url = getNormalizedUrl(fnOrScript);
0262: SourceInfo si = sourceInfo(url);
0263: if (si == null) {
0264: if (!fnOrScript.isGeneratedScript()) {
0265: // Not eval or Function, try to load it from URL
0266: String source = loadSource(url);
0267: if (source != null) {
0268: DebuggableScript top = fnOrScript;
0269: for (;;) {
0270: DebuggableScript parent = top.getParent();
0271: if (parent == null) {
0272: break;
0273: }
0274: top = parent;
0275: }
0276: registerTopScript(top, source);
0277: fsource = functionSource(fnOrScript);
0278: }
0279: }
0280: }
0281: }
0282: return fsource;
0283: }
0284:
0285: /**
0286: * Loads the script at the given URL.
0287: */
0288: private String loadSource(String sourceUrl) {
0289: String source = null;
0290: int hash = sourceUrl.indexOf('#');
0291: if (hash >= 0) {
0292: sourceUrl = sourceUrl.substring(0, hash);
0293: }
0294: try {
0295: InputStream is;
0296: openStream: {
0297: if (sourceUrl.indexOf(':') < 0) {
0298: // Can be a file name
0299: try {
0300: if (sourceUrl.startsWith("~/")) {
0301: String home = SecurityUtilities
0302: .getSystemProperty("user.home");
0303: if (home != null) {
0304: String pathFromHome = sourceUrl
0305: .substring(2);
0306: File f = new File(new File(home),
0307: pathFromHome);
0308: if (f.exists()) {
0309: is = new FileInputStream(f);
0310: break openStream;
0311: }
0312: }
0313: }
0314: File f = new File(sourceUrl);
0315: if (f.exists()) {
0316: is = new FileInputStream(f);
0317: break openStream;
0318: }
0319: } catch (SecurityException ex) {
0320: }
0321: // No existing file, assume missed http://
0322: if (sourceUrl.startsWith("//")) {
0323: sourceUrl = "http:" + sourceUrl;
0324: } else if (sourceUrl.startsWith("/")) {
0325: sourceUrl = "http://127.0.0.1" + sourceUrl;
0326: } else {
0327: sourceUrl = "http://" + sourceUrl;
0328: }
0329: }
0330:
0331: is = (new URL(sourceUrl)).openStream();
0332: }
0333:
0334: try {
0335: source = Kit.readReader(new InputStreamReader(is));
0336: } finally {
0337: is.close();
0338: }
0339: } catch (IOException ex) {
0340: System.err.println("Failed to load source from "
0341: + sourceUrl + ": " + ex);
0342: }
0343: return source;
0344: }
0345:
0346: /**
0347: * Registers the given script as a top-level script in the debugger.
0348: */
0349: private void registerTopScript(DebuggableScript topScript,
0350: String source) {
0351: if (!topScript.isTopLevel()) {
0352: throw new IllegalArgumentException();
0353: }
0354: String url = getNormalizedUrl(topScript);
0355: DebuggableScript[] functions = getAllFunctions(topScript);
0356: final SourceInfo sourceInfo = new SourceInfo(source, functions,
0357: url);
0358:
0359: synchronized (urlToSourceInfo) {
0360: SourceInfo old = (SourceInfo) urlToSourceInfo.get(url);
0361: if (old != null) {
0362: sourceInfo.copyBreakpointsFrom(old);
0363: }
0364: urlToSourceInfo.put(url, sourceInfo);
0365: for (int i = 0; i != sourceInfo.functionSourcesTop(); ++i) {
0366: FunctionSource fsource = sourceInfo.functionSource(i);
0367: String name = fsource.name();
0368: if (name.length() != 0) {
0369: functionNames.put(name, fsource);
0370: }
0371: }
0372: }
0373:
0374: synchronized (functionToSource) {
0375: for (int i = 0; i != functions.length; ++i) {
0376: FunctionSource fsource = sourceInfo.functionSource(i);
0377: functionToSource.put(functions[i], fsource);
0378: }
0379: }
0380:
0381: callback.updateSourceText(sourceInfo);
0382: }
0383:
0384: /**
0385: * Returns the FunctionSource object for the given function or script.
0386: */
0387: private FunctionSource functionSource(DebuggableScript fnOrScript) {
0388: return (FunctionSource) functionToSource.get(fnOrScript);
0389: }
0390:
0391: /**
0392: * Returns an array of all function names.
0393: */
0394: public String[] functionNames() {
0395: String[] a;
0396: synchronized (urlToSourceInfo) {
0397: Enumeration e = functionNames.keys();
0398: a = new String[functionNames.size()];
0399: int i = 0;
0400: while (e.hasMoreElements()) {
0401: a[i++] = (String) e.nextElement();
0402: }
0403: }
0404: return a;
0405: }
0406:
0407: /**
0408: * Returns the FunctionSource object for the function with the given name.
0409: */
0410: public FunctionSource functionSourceByName(String functionName) {
0411: return (FunctionSource) functionNames.get(functionName);
0412: }
0413:
0414: /**
0415: * Returns the SourceInfo object for the given URL.
0416: */
0417: public SourceInfo sourceInfo(String url) {
0418: return (SourceInfo) urlToSourceInfo.get(url);
0419: }
0420:
0421: /**
0422: * Returns the source URL for the given script or function.
0423: */
0424: private String getNormalizedUrl(DebuggableScript fnOrScript) {
0425: String url = fnOrScript.getSourceName();
0426: if (url == null) {
0427: url = "<stdin>";
0428: } else {
0429: // Not to produce window for eval from different lines,
0430: // strip line numbers, i.e. replace all #[0-9]+\(eval\) by
0431: // (eval)
0432: // Option: similar teatment for Function?
0433: char evalSeparator = '#';
0434: StringBuffer sb = null;
0435: int urlLength = url.length();
0436: int cursor = 0;
0437: for (;;) {
0438: int searchStart = url.indexOf(evalSeparator, cursor);
0439: if (searchStart < 0) {
0440: break;
0441: }
0442: String replace = null;
0443: int i = searchStart + 1;
0444: while (i != urlLength) {
0445: int c = url.charAt(i);
0446: if (!('0' <= c && c <= '9')) {
0447: break;
0448: }
0449: ++i;
0450: }
0451: if (i != searchStart + 1) {
0452: // i points after #[0-9]+
0453: if ("(eval)".regionMatches(0, url, i, 6)) {
0454: cursor = i + 6;
0455: replace = "(eval)";
0456: }
0457: }
0458: if (replace == null) {
0459: break;
0460: }
0461: if (sb == null) {
0462: sb = new StringBuffer();
0463: sb.append(url.substring(0, searchStart));
0464: }
0465: sb.append(replace);
0466: }
0467: if (sb != null) {
0468: if (cursor != urlLength) {
0469: sb.append(url.substring(cursor));
0470: }
0471: url = sb.toString();
0472: }
0473: }
0474: return url;
0475: }
0476:
0477: /**
0478: * Returns an array of all functions in the given script.
0479: */
0480: private static DebuggableScript[] getAllFunctions(
0481: DebuggableScript function) {
0482: ObjArray functions = new ObjArray();
0483: collectFunctions_r(function, functions);
0484: DebuggableScript[] result = new DebuggableScript[functions
0485: .size()];
0486: functions.toArray(result);
0487: return result;
0488: }
0489:
0490: /**
0491: * Helper function for {@link #getAllFunctions(DebuggableScript)}.
0492: */
0493: private static void collectFunctions_r(DebuggableScript function,
0494: ObjArray array) {
0495: array.add(function);
0496: for (int i = 0; i != function.getFunctionCount(); ++i) {
0497: collectFunctions_r(function.getFunction(i), array);
0498: }
0499: }
0500:
0501: /**
0502: * Clears all breakpoints.
0503: */
0504: public void clearAllBreakpoints() {
0505: Enumeration e = urlToSourceInfo.elements();
0506: while (e.hasMoreElements()) {
0507: SourceInfo si = (SourceInfo) e.nextElement();
0508: si.removeAllBreakpoints();
0509: }
0510: }
0511:
0512: /**
0513: * Called when a breakpoint has been hit.
0514: */
0515: private void handleBreakpointHit(StackFrame frame, Context cx) {
0516: breakFlag = false;
0517: interrupted(cx, frame, null);
0518: }
0519:
0520: /**
0521: * Called when a script exception has been thrown.
0522: */
0523: private void handleExceptionThrown(Context cx, Throwable ex,
0524: StackFrame frame) {
0525: if (breakOnExceptions) {
0526: ContextData cd = frame.contextData();
0527: if (cd.lastProcessedException != ex) {
0528: interrupted(cx, frame, ex);
0529: cd.lastProcessedException = ex;
0530: }
0531: }
0532: }
0533:
0534: /**
0535: * Returns the current ContextData object.
0536: */
0537: public ContextData currentContextData() {
0538: return interruptedContextData;
0539: }
0540:
0541: /**
0542: * Sets the action to perform to end interruption.
0543: */
0544: public void setReturnValue(int returnValue) {
0545: synchronized (monitor) {
0546: this .returnValue = returnValue;
0547: monitor.notify();
0548: }
0549: }
0550:
0551: /**
0552: * Resumes execution of script.
0553: */
0554: public void go() {
0555: synchronized (monitor) {
0556: this .returnValue = GO;
0557: monitor.notifyAll();
0558: }
0559: }
0560:
0561: /**
0562: * Evaluates the given script.
0563: */
0564: public String eval(String expr) {
0565: String result = "undefined";
0566: if (expr == null) {
0567: return result;
0568: }
0569: ContextData contextData = currentContextData();
0570: if (contextData == null
0571: || frameIndex >= contextData.frameCount()) {
0572: return result;
0573: }
0574: StackFrame frame = contextData.getFrame(frameIndex);
0575: if (contextData.eventThreadFlag) {
0576: Context cx = Context.getCurrentContext();
0577: result = do_eval(cx, frame, expr);
0578: } else {
0579: synchronized (monitor) {
0580: if (insideInterruptLoop) {
0581: evalRequest = expr;
0582: evalFrame = frame;
0583: monitor.notify();
0584: do {
0585: try {
0586: monitor.wait();
0587: } catch (InterruptedException exc) {
0588: Thread.currentThread().interrupt();
0589: break;
0590: }
0591: } while (evalRequest != null);
0592: result = evalResult;
0593: }
0594: }
0595: }
0596: return result;
0597: }
0598:
0599: /**
0600: * Compiles the given script.
0601: */
0602: public void compileScript(String url, String text) {
0603: DimIProxy action = new DimIProxy(this , IPROXY_COMPILE_SCRIPT);
0604: action.url = url;
0605: action.text = text;
0606: action.withContext();
0607: }
0608:
0609: /**
0610: * Evaluates the given script.
0611: */
0612: public void evalScript(final String url, final String text) {
0613: DimIProxy action = new DimIProxy(this , IPROXY_EVAL_SCRIPT);
0614: action.url = url;
0615: action.text = text;
0616: action.withContext();
0617: }
0618:
0619: /**
0620: * Converts the given script object to a string.
0621: */
0622: public String objectToString(Object object) {
0623: DimIProxy action = new DimIProxy(this , IPROXY_OBJECT_TO_STRING);
0624: action.object = object;
0625: action.withContext();
0626: return action.stringResult;
0627: }
0628:
0629: /**
0630: * Returns whether the given string is syntactically valid script.
0631: */
0632: public boolean stringIsCompilableUnit(String str) {
0633: DimIProxy action = new DimIProxy(this ,
0634: IPROXY_STRING_IS_COMPILABLE);
0635: action.text = str;
0636: action.withContext();
0637: return action.booleanResult;
0638: }
0639:
0640: /**
0641: * Returns the value of a property on the given script object.
0642: */
0643: public Object getObjectProperty(Object object, Object id) {
0644: DimIProxy action = new DimIProxy(this , IPROXY_OBJECT_PROPERTY);
0645: action.object = object;
0646: action.id = id;
0647: action.withContext();
0648: return action.objectResult;
0649: }
0650:
0651: /**
0652: * Returns an array of the property names on the given script object.
0653: */
0654: public Object[] getObjectIds(Object object) {
0655: DimIProxy action = new DimIProxy(this , IPROXY_OBJECT_IDS);
0656: action.object = object;
0657: action.withContext();
0658: return action.objectArrayResult;
0659: }
0660:
0661: /**
0662: * Returns the value of a property on the given script object.
0663: */
0664: private Object getObjectPropertyImpl(Context cx, Object object,
0665: Object id) {
0666: Scriptable scriptable = (Scriptable) object;
0667: Object result;
0668: if (id instanceof String) {
0669: String name = (String) id;
0670: if (name.equals("this")) {
0671: result = scriptable;
0672: } else if (name.equals("__proto__")) {
0673: result = scriptable.getPrototype();
0674: } else if (name.equals("__parent__")) {
0675: result = scriptable.getParentScope();
0676: } else {
0677: result = ScriptableObject.getProperty(scriptable, name);
0678: if (result == ScriptableObject.NOT_FOUND) {
0679: result = Undefined.instance;
0680: }
0681: }
0682: } else {
0683: int index = ((Integer) id).intValue();
0684: result = ScriptableObject.getProperty(scriptable, index);
0685: if (result == ScriptableObject.NOT_FOUND) {
0686: result = Undefined.instance;
0687: }
0688: }
0689: return result;
0690: }
0691:
0692: /**
0693: * Returns an array of the property names on the given script object.
0694: */
0695: private Object[] getObjectIdsImpl(Context cx, Object object) {
0696: if (!(object instanceof Scriptable)
0697: || object == Undefined.instance) {
0698: return Context.emptyArgs;
0699: }
0700:
0701: Object[] ids;
0702: Scriptable scriptable = (Scriptable) object;
0703: if (scriptable instanceof DebuggableObject) {
0704: ids = ((DebuggableObject) scriptable).getAllIds();
0705: } else {
0706: ids = scriptable.getIds();
0707: }
0708:
0709: Scriptable proto = scriptable.getPrototype();
0710: Scriptable parent = scriptable.getParentScope();
0711: int extra = 0;
0712: if (proto != null) {
0713: ++extra;
0714: }
0715: if (parent != null) {
0716: ++extra;
0717: }
0718: if (extra != 0) {
0719: Object[] tmp = new Object[extra + ids.length];
0720: System.arraycopy(ids, 0, tmp, extra, ids.length);
0721: ids = tmp;
0722: extra = 0;
0723: if (proto != null) {
0724: ids[extra++] = "__proto__";
0725: }
0726: if (parent != null) {
0727: ids[extra++] = "__parent__";
0728: }
0729: }
0730:
0731: return ids;
0732: }
0733:
0734: /**
0735: * Interrupts script execution.
0736: */
0737: private void interrupted(Context cx, final StackFrame frame,
0738: Throwable scriptException) {
0739: ContextData contextData = frame.contextData();
0740: boolean eventThreadFlag = callback.isGuiEventThread();
0741: contextData.eventThreadFlag = eventThreadFlag;
0742:
0743: boolean recursiveEventThreadCall = false;
0744:
0745: interruptedCheck: synchronized (eventThreadMonitor) {
0746: if (eventThreadFlag) {
0747: if (interruptedContextData != null) {
0748: recursiveEventThreadCall = true;
0749: break interruptedCheck;
0750: }
0751: } else {
0752: while (interruptedContextData != null) {
0753: try {
0754: eventThreadMonitor.wait();
0755: } catch (InterruptedException exc) {
0756: return;
0757: }
0758: }
0759: }
0760: interruptedContextData = contextData;
0761: }
0762:
0763: if (recursiveEventThreadCall) {
0764: // XXX: For now the following is commented out as on Linux
0765: // too deep recursion of dispatchNextGuiEvent causes GUI lockout.
0766: // Note: it can make GUI unresponsive if long-running script
0767: // will be called on GUI thread while processing another interrupt
0768: if (false) {
0769: // Run event dispatch until gui sets a flag to exit the initial
0770: // call to interrupted.
0771: while (this .returnValue == -1) {
0772: try {
0773: callback.dispatchNextGuiEvent();
0774: } catch (InterruptedException exc) {
0775: }
0776: }
0777: }
0778: return;
0779: }
0780:
0781: if (interruptedContextData == null)
0782: Kit.codeBug();
0783:
0784: try {
0785: do {
0786: int frameCount = contextData.frameCount();
0787: this .frameIndex = frameCount - 1;
0788:
0789: final String threadTitle = Thread.currentThread()
0790: .toString();
0791: final String alertMessage;
0792: if (scriptException == null) {
0793: alertMessage = null;
0794: } else {
0795: alertMessage = scriptException.toString();
0796: }
0797:
0798: int returnValue = -1;
0799: if (!eventThreadFlag) {
0800: synchronized (monitor) {
0801: if (insideInterruptLoop)
0802: Kit.codeBug();
0803: this .insideInterruptLoop = true;
0804: this .evalRequest = null;
0805: this .returnValue = -1;
0806: callback.enterInterrupt(frame, threadTitle,
0807: alertMessage);
0808: try {
0809: for (;;) {
0810: try {
0811: monitor.wait();
0812: } catch (InterruptedException exc) {
0813: Thread.currentThread().interrupt();
0814: break;
0815: }
0816: if (evalRequest != null) {
0817: this .evalResult = null;
0818: try {
0819: evalResult = do_eval(cx,
0820: evalFrame, evalRequest);
0821: } finally {
0822: evalRequest = null;
0823: evalFrame = null;
0824: monitor.notify();
0825: }
0826: continue;
0827: }
0828: if (this .returnValue != -1) {
0829: returnValue = this .returnValue;
0830: break;
0831: }
0832: }
0833: } finally {
0834: insideInterruptLoop = false;
0835: }
0836: }
0837: } else {
0838: this .returnValue = -1;
0839: callback.enterInterrupt(frame, threadTitle,
0840: alertMessage);
0841: while (this .returnValue == -1) {
0842: try {
0843: callback.dispatchNextGuiEvent();
0844: } catch (InterruptedException exc) {
0845: }
0846: }
0847: returnValue = this .returnValue;
0848: }
0849: switch (returnValue) {
0850: case STEP_OVER:
0851: contextData.breakNextLine = true;
0852: contextData.stopAtFrameDepth = contextData
0853: .frameCount();
0854: break;
0855: case STEP_INTO:
0856: contextData.breakNextLine = true;
0857: contextData.stopAtFrameDepth = -1;
0858: break;
0859: case STEP_OUT:
0860: if (contextData.frameCount() > 1) {
0861: contextData.breakNextLine = true;
0862: contextData.stopAtFrameDepth = contextData
0863: .frameCount() - 1;
0864: }
0865: break;
0866: }
0867: } while (false);
0868: } finally {
0869: synchronized (eventThreadMonitor) {
0870: interruptedContextData = null;
0871: eventThreadMonitor.notifyAll();
0872: }
0873: }
0874:
0875: }
0876:
0877: /**
0878: * Evaluates script in the given stack frame.
0879: */
0880: private static String do_eval(Context cx, StackFrame frame,
0881: String expr) {
0882: String resultString;
0883: Debugger saved_debugger = cx.getDebugger();
0884: Object saved_data = cx.getDebuggerContextData();
0885: int saved_level = cx.getOptimizationLevel();
0886:
0887: cx.setDebugger(null, null);
0888: cx.setOptimizationLevel(-1);
0889: cx.setGeneratingDebug(false);
0890: try {
0891: Callable script = (Callable) cx.compileString(expr, "", 0,
0892: null);
0893: Object result = script.call(cx, frame.scope, frame.this Obj,
0894: ScriptRuntime.emptyArgs);
0895: if (result == Undefined.instance) {
0896: resultString = "";
0897: } else {
0898: resultString = ScriptRuntime.toString(result);
0899: }
0900: } catch (Exception exc) {
0901: resultString = exc.getMessage();
0902: } finally {
0903: cx.setGeneratingDebug(true);
0904: cx.setOptimizationLevel(saved_level);
0905: cx.setDebugger(saved_debugger, saved_data);
0906: }
0907: if (resultString == null) {
0908: resultString = "null";
0909: }
0910: return resultString;
0911: }
0912:
0913: /**
0914: * Proxy class to implement debug interfaces without bloat of class
0915: * files.
0916: */
0917: private static class DimIProxy implements ContextAction,
0918: ContextFactory.Listener, Debugger {
0919:
0920: /**
0921: * The debugger.
0922: */
0923: private Dim dim;
0924:
0925: /**
0926: * The interface implementation type. One of the IPROXY_* constants
0927: * defined in {@link Dim}.
0928: */
0929: private int type;
0930:
0931: /**
0932: * The URL origin of the script to compile or evaluate.
0933: */
0934: private String url;
0935:
0936: /**
0937: * The text of the script to compile, evaluate or test for compilation.
0938: */
0939: private String text;
0940:
0941: /**
0942: * The object to convert, get a property from or enumerate.
0943: */
0944: private Object object;
0945:
0946: /**
0947: * The property to look up in {@link #object}.
0948: */
0949: private Object id;
0950:
0951: /**
0952: * The boolean result of the action.
0953: */
0954: private boolean booleanResult;
0955:
0956: /**
0957: * The String result of the action.
0958: */
0959: private String stringResult;
0960:
0961: /**
0962: * The Object result of the action.
0963: */
0964: private Object objectResult;
0965:
0966: /**
0967: * The Object[] result of the action.
0968: */
0969: private Object[] objectArrayResult;
0970:
0971: /**
0972: * Creates a new DimIProxy.
0973: */
0974: private DimIProxy(Dim dim, int type) {
0975: this .dim = dim;
0976: this .type = type;
0977: }
0978:
0979: // ContextAction
0980:
0981: /**
0982: * Performs the action given by {@link #type}.
0983: */
0984: public Object run(Context cx) {
0985: switch (type) {
0986: case IPROXY_COMPILE_SCRIPT:
0987: cx.compileString(text, url, 1, null);
0988: break;
0989:
0990: case IPROXY_EVAL_SCRIPT: {
0991: Scriptable scope = null;
0992: if (dim.scopeProvider != null) {
0993: scope = dim.scopeProvider.getScope();
0994: }
0995: if (scope == null) {
0996: scope = new ImporterTopLevel(cx);
0997: }
0998: cx.evaluateString(scope, text, url, 1, null);
0999: }
1000: break;
1001:
1002: case IPROXY_STRING_IS_COMPILABLE:
1003: booleanResult = cx.stringIsCompilableUnit(text);
1004: break;
1005:
1006: case IPROXY_OBJECT_TO_STRING:
1007: if (object == Undefined.instance) {
1008: stringResult = "undefined";
1009: } else if (object == null) {
1010: stringResult = "null";
1011: } else if (object instanceof NativeCall) {
1012: stringResult = "[object Call]";
1013: } else {
1014: stringResult = Context.toString(object);
1015: }
1016: break;
1017:
1018: case IPROXY_OBJECT_PROPERTY:
1019: objectResult = dim
1020: .getObjectPropertyImpl(cx, object, id);
1021: break;
1022:
1023: case IPROXY_OBJECT_IDS:
1024: objectArrayResult = dim.getObjectIdsImpl(cx, object);
1025: break;
1026:
1027: default:
1028: throw Kit.codeBug();
1029: }
1030: return null;
1031: }
1032:
1033: /**
1034: * Performs the action given by {@link #type} with the attached
1035: * {@link ContextFactory}.
1036: */
1037: private void withContext() {
1038: dim.contextFactory.call(this );
1039: }
1040:
1041: // ContextFactory.Listener
1042:
1043: /**
1044: * Called when a Context is created.
1045: */
1046: public void contextCreated(Context cx) {
1047: if (type != IPROXY_LISTEN)
1048: Kit.codeBug();
1049: ContextData contextData = new ContextData();
1050: Debugger debugger = new DimIProxy(dim, IPROXY_DEBUG);
1051: cx.setDebugger(debugger, contextData);
1052: cx.setGeneratingDebug(true);
1053: cx.setOptimizationLevel(-1);
1054: }
1055:
1056: /**
1057: * Called when a Context is destroyed.
1058: */
1059: public void contextReleased(Context cx) {
1060: if (type != IPROXY_LISTEN)
1061: Kit.codeBug();
1062: }
1063:
1064: // Debugger
1065:
1066: /**
1067: * Returns a StackFrame for the given function or script.
1068: */
1069: public DebugFrame getFrame(Context cx,
1070: DebuggableScript fnOrScript) {
1071: if (type != IPROXY_DEBUG)
1072: Kit.codeBug();
1073:
1074: FunctionSource item = dim.getFunctionSource(fnOrScript);
1075: if (item == null) {
1076: // Can not debug if source is not available
1077: return null;
1078: }
1079: return new StackFrame(cx, dim, item);
1080: }
1081:
1082: /**
1083: * Called when compilation is finished.
1084: */
1085: public void handleCompilationDone(Context cx,
1086: DebuggableScript fnOrScript, String source) {
1087: if (type != IPROXY_DEBUG)
1088: Kit.codeBug();
1089:
1090: if (!fnOrScript.isTopLevel()) {
1091: return;
1092: }
1093: dim.registerTopScript(fnOrScript, source);
1094: }
1095: }
1096:
1097: /**
1098: * Class to store information about a stack.
1099: */
1100: public static class ContextData {
1101:
1102: /**
1103: * The stack frames.
1104: */
1105: private ObjArray frameStack = new ObjArray();
1106:
1107: /**
1108: * Whether the debugger should break at the next line in this context.
1109: */
1110: private boolean breakNextLine;
1111:
1112: /**
1113: * The frame depth the debugger should stop at. Used to implement
1114: * "step over" and "step out".
1115: */
1116: private int stopAtFrameDepth = -1;
1117:
1118: /**
1119: * Whether this context is in the event thread.
1120: */
1121: private boolean eventThreadFlag;
1122:
1123: /**
1124: * The last exception that was processed.
1125: */
1126: private Throwable lastProcessedException;
1127:
1128: /**
1129: * Returns the ContextData for the given Context.
1130: */
1131: public static ContextData get(Context cx) {
1132: return (ContextData) cx.getDebuggerContextData();
1133: }
1134:
1135: /**
1136: * Returns the number of stack frames.
1137: */
1138: public int frameCount() {
1139: return frameStack.size();
1140: }
1141:
1142: /**
1143: * Returns the stack frame with the given index.
1144: */
1145: public StackFrame getFrame(int frameNumber) {
1146: int num = frameStack.size() - frameNumber - 1;
1147: return (StackFrame) frameStack.get(num);
1148: }
1149:
1150: /**
1151: * Pushes a stack frame on to the stack.
1152: */
1153: private void pushFrame(StackFrame frame) {
1154: frameStack.push(frame);
1155: }
1156:
1157: /**
1158: * Pops a stack frame from the stack.
1159: */
1160: private void popFrame() {
1161: frameStack.pop();
1162: }
1163: }
1164:
1165: /**
1166: * Object to represent one stack frame.
1167: */
1168: public static class StackFrame implements DebugFrame {
1169:
1170: /**
1171: * The debugger.
1172: */
1173: private Dim dim;
1174:
1175: /**
1176: * The ContextData for the Context being debugged.
1177: */
1178: private ContextData contextData;
1179:
1180: /**
1181: * The scope.
1182: */
1183: private Scriptable scope;
1184:
1185: /**
1186: * The 'this' object.
1187: */
1188: private Scriptable this Obj;
1189:
1190: /**
1191: * Information about the function.
1192: */
1193: private FunctionSource fsource;
1194:
1195: /**
1196: * Array of breakpoint state for each source line.
1197: */
1198: private boolean[] breakpoints;
1199:
1200: /**
1201: * Current line number.
1202: */
1203: private int lineNumber;
1204:
1205: /**
1206: * Creates a new StackFrame.
1207: */
1208: private StackFrame(Context cx, Dim dim, FunctionSource fsource) {
1209: this .dim = dim;
1210: this .contextData = ContextData.get(cx);
1211: this .fsource = fsource;
1212: this .breakpoints = fsource.sourceInfo().breakpoints;
1213: this .lineNumber = fsource.firstLine();
1214: }
1215:
1216: /**
1217: * Called when the stack frame is entered.
1218: */
1219: public void onEnter(Context cx, Scriptable scope,
1220: Scriptable this Obj, Object[] args) {
1221: contextData.pushFrame(this );
1222: this .scope = scope;
1223: this .this Obj = this Obj;
1224: if (dim.breakOnEnter) {
1225: dim.handleBreakpointHit(this , cx);
1226: }
1227: }
1228:
1229: /**
1230: * Called when the current position has changed.
1231: */
1232: public void onLineChange(Context cx, int lineno) {
1233: this .lineNumber = lineno;
1234:
1235: if (!breakpoints[lineno] && !dim.breakFlag) {
1236: boolean lineBreak = contextData.breakNextLine;
1237: if (lineBreak && contextData.stopAtFrameDepth >= 0) {
1238: lineBreak = (contextData.frameCount() <= contextData.stopAtFrameDepth);
1239: }
1240: if (!lineBreak) {
1241: return;
1242: }
1243: contextData.stopAtFrameDepth = -1;
1244: contextData.breakNextLine = false;
1245: }
1246:
1247: dim.handleBreakpointHit(this , cx);
1248: }
1249:
1250: /**
1251: * Called when an exception has been thrown.
1252: */
1253: public void onExceptionThrown(Context cx, Throwable exception) {
1254: dim.handleExceptionThrown(cx, exception, this );
1255: }
1256:
1257: /**
1258: * Called when the stack frame has been left.
1259: */
1260: public void onExit(Context cx, boolean byThrow,
1261: Object resultOrException) {
1262: if (dim.breakOnReturn && !byThrow) {
1263: dim.handleBreakpointHit(this , cx);
1264: }
1265: contextData.popFrame();
1266: }
1267:
1268: /**
1269: * Called when a 'debugger' statement is executed.
1270: */
1271: public void onDebuggerStatement(Context cx) {
1272: dim.handleBreakpointHit(this , cx);
1273: }
1274:
1275: /**
1276: * Returns the SourceInfo object for the function.
1277: */
1278: public SourceInfo sourceInfo() {
1279: return fsource.sourceInfo();
1280: }
1281:
1282: /**
1283: * Returns the ContextData object for the Context.
1284: */
1285: public ContextData contextData() {
1286: return contextData;
1287: }
1288:
1289: /**
1290: * Returns the scope object for this frame.
1291: */
1292: public Object scope() {
1293: return scope;
1294: }
1295:
1296: /**
1297: * Returns the 'this' object for this frame.
1298: */
1299: public Object this Obj() {
1300: return this Obj;
1301: }
1302:
1303: /**
1304: * Returns the source URL.
1305: */
1306: public String getUrl() {
1307: return fsource.sourceInfo().url();
1308: }
1309:
1310: /**
1311: * Returns the current line number.
1312: */
1313: public int getLineNumber() {
1314: return lineNumber;
1315: }
1316: }
1317:
1318: /**
1319: * Class to store information about a function.
1320: */
1321: public static class FunctionSource {
1322:
1323: /**
1324: * Information about the source of the function.
1325: */
1326: private SourceInfo sourceInfo;
1327:
1328: /**
1329: * Line number of the first line of the function.
1330: */
1331: private int firstLine;
1332:
1333: /**
1334: * The function name.
1335: */
1336: private String name;
1337:
1338: /**
1339: * Creates a new FunctionSource.
1340: */
1341: private FunctionSource(SourceInfo sourceInfo, int firstLine,
1342: String name) {
1343: if (name == null)
1344: throw new IllegalArgumentException();
1345: this .sourceInfo = sourceInfo;
1346: this .firstLine = firstLine;
1347: this .name = name;
1348: }
1349:
1350: /**
1351: * Returns the SourceInfo object that describes the source of the
1352: * function.
1353: */
1354: public SourceInfo sourceInfo() {
1355: return sourceInfo;
1356: }
1357:
1358: /**
1359: * Returns the line number of the first line of the function.
1360: */
1361: public int firstLine() {
1362: return firstLine;
1363: }
1364:
1365: /**
1366: * Returns the name of the function.
1367: */
1368: public String name() {
1369: return name;
1370: }
1371: }
1372:
1373: /**
1374: * Class to store information about a script source.
1375: */
1376: public static class SourceInfo {
1377:
1378: /**
1379: * An empty array of booleans.
1380: */
1381: private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
1382:
1383: /**
1384: * The script.
1385: */
1386: private String source;
1387:
1388: /**
1389: * The URL of the script.
1390: */
1391: private String url;
1392:
1393: /**
1394: * Array indicating which lines can have breakpoints set.
1395: */
1396: private boolean[] breakableLines;
1397:
1398: /**
1399: * Array indicating whether a breakpoint is set on the line.
1400: */
1401: private boolean[] breakpoints;
1402:
1403: /**
1404: * Array of FunctionSource objects for the functions in the script.
1405: */
1406: private FunctionSource[] functionSources;
1407:
1408: /**
1409: * Creates a new SourceInfo object.
1410: */
1411: private SourceInfo(String source, DebuggableScript[] functions,
1412: String normilizedUrl) {
1413: this .source = source;
1414: this .url = normilizedUrl;
1415:
1416: int N = functions.length;
1417: int[][] lineArrays = new int[N][];
1418: for (int i = 0; i != N; ++i) {
1419: lineArrays[i] = functions[i].getLineNumbers();
1420: }
1421:
1422: int minAll = 0, maxAll = -1;
1423: int[] firstLines = new int[N];
1424: for (int i = 0; i != N; ++i) {
1425: int[] lines = lineArrays[i];
1426: if (lines == null || lines.length == 0) {
1427: firstLines[i] = -1;
1428: } else {
1429: int min, max;
1430: min = max = lines[0];
1431: for (int j = 1; j != lines.length; ++j) {
1432: int line = lines[j];
1433: if (line < min) {
1434: min = line;
1435: } else if (line > max) {
1436: max = line;
1437: }
1438: }
1439: firstLines[i] = min;
1440: if (minAll > maxAll) {
1441: minAll = min;
1442: maxAll = max;
1443: } else {
1444: if (min < minAll) {
1445: minAll = min;
1446: }
1447: if (max > maxAll) {
1448: maxAll = max;
1449: }
1450: }
1451: }
1452: }
1453:
1454: if (minAll > maxAll) {
1455: // No line information
1456: this .breakableLines = EMPTY_BOOLEAN_ARRAY;
1457: this .breakpoints = EMPTY_BOOLEAN_ARRAY;
1458: } else {
1459: if (minAll < 0) {
1460: // Line numbers can not be negative
1461: throw new IllegalStateException(String
1462: .valueOf(minAll));
1463: }
1464: int linesTop = maxAll + 1;
1465: this .breakableLines = new boolean[linesTop];
1466: this .breakpoints = new boolean[linesTop];
1467: for (int i = 0; i != N; ++i) {
1468: int[] lines = lineArrays[i];
1469: if (lines != null && lines.length != 0) {
1470: for (int j = 0; j != lines.length; ++j) {
1471: int line = lines[j];
1472: this .breakableLines[line] = true;
1473: }
1474: }
1475: }
1476: }
1477: this .functionSources = new FunctionSource[N];
1478: for (int i = 0; i != N; ++i) {
1479: String name = functions[i].getFunctionName();
1480: if (name == null) {
1481: name = "";
1482: }
1483: this .functionSources[i] = new FunctionSource(this ,
1484: firstLines[i], name);
1485: }
1486: }
1487:
1488: /**
1489: * Returns the source text.
1490: */
1491: public String source() {
1492: return this .source;
1493: }
1494:
1495: /**
1496: * Returns the script's origin URL.
1497: */
1498: public String url() {
1499: return this .url;
1500: }
1501:
1502: /**
1503: * Returns the number of FunctionSource objects stored in this object.
1504: */
1505: public int functionSourcesTop() {
1506: return functionSources.length;
1507: }
1508:
1509: /**
1510: * Returns the FunctionSource object with the given index.
1511: */
1512: public FunctionSource functionSource(int i) {
1513: return functionSources[i];
1514: }
1515:
1516: /**
1517: * Copies the breakpoints from the given SourceInfo object into this
1518: * one.
1519: */
1520: private void copyBreakpointsFrom(SourceInfo old) {
1521: int end = old.breakpoints.length;
1522: if (end > this .breakpoints.length) {
1523: end = this .breakpoints.length;
1524: }
1525: for (int line = 0; line != end; ++line) {
1526: if (old.breakpoints[line]) {
1527: this .breakpoints[line] = true;
1528: }
1529: }
1530: }
1531:
1532: /**
1533: * Returns whether the given line number can have a breakpoint set on
1534: * it.
1535: */
1536: public boolean breakableLine(int line) {
1537: return (line < this .breakableLines.length)
1538: && this .breakableLines[line];
1539: }
1540:
1541: /**
1542: * Returns whether there is a breakpoint set on the given line.
1543: */
1544: public boolean breakpoint(int line) {
1545: if (!breakableLine(line)) {
1546: throw new IllegalArgumentException(String.valueOf(line));
1547: }
1548: return line < this .breakpoints.length
1549: && this .breakpoints[line];
1550: }
1551:
1552: /**
1553: * Sets or clears the breakpoint flag for the given line.
1554: */
1555: public boolean breakpoint(int line, boolean value) {
1556: if (!breakableLine(line)) {
1557: throw new IllegalArgumentException(String.valueOf(line));
1558: }
1559: boolean changed;
1560: synchronized (breakpoints) {
1561: if (breakpoints[line] != value) {
1562: breakpoints[line] = value;
1563: changed = true;
1564: } else {
1565: changed = false;
1566: }
1567: }
1568: return changed;
1569: }
1570:
1571: /**
1572: * Removes all breakpoints from the script.
1573: */
1574: public void removeAllBreakpoints() {
1575: synchronized (breakpoints) {
1576: for (int line = 0; line != breakpoints.length; ++line) {
1577: breakpoints[line] = false;
1578: }
1579: }
1580: }
1581: }
1582: }
|