1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 package org.mozilla.javascript.tools.debugger; 7 8 import org.mozilla.javascript.*; 9 import org.mozilla.javascript.debug.*; 10 import java.util.*; 11 import java.io.*; 12 import java.net.URL; 13 14 /** 15 * Dim or Debugger Implementation for Rhino. 16 */ 17 public class Dim { 18 19 // Constants for instructing the debugger what action to perform 20 // to end interruption. Used by 'returnValue'. 21 public static final int STEP_OVER = 0; 22 public static final int STEP_INTO = 1; 23 public static final int STEP_OUT = 2; 24 public static final int GO = 3; 25 public static final int BREAK = 4; 26 public static final int EXIT = 5; 27 28 // Constants for the DimIProxy interface implementation class. 29 private static final int IPROXY_DEBUG = 0; 30 private static final int IPROXY_LISTEN = 1; 31 private static final int IPROXY_COMPILE_SCRIPT = 2; 32 private static final int IPROXY_EVAL_SCRIPT = 3; 33 private static final int IPROXY_STRING_IS_COMPILABLE = 4; 34 private static final int IPROXY_OBJECT_TO_STRING = 5; 35 private static final int IPROXY_OBJECT_PROPERTY = 6; 36 private static final int IPROXY_OBJECT_IDS = 7; 37 38 /** 39 * Interface to the debugger GUI. 40 */ 41 private GuiCallback callback; 42 43 /** 44 * Whether the debugger should break. 45 */ 46 private boolean breakFlag; 47 48 /** 49 * The ScopeProvider object that provides the scope in which to 50 * evaluate script. 51 */ 52 private ScopeProvider scopeProvider; 53 54 /** 55 * The SourceProvider object that provides the source of evaluated scripts. 56 */ 57 private SourceProvider sourceProvider; 58 59 /** 60 * The index of the current stack frame. 61 */ 62 private int frameIndex = -1; 63 64 /** 65 * Information about the current stack at the point of interruption. 66 */ 67 private volatile ContextData interruptedContextData; 68 69 /** 70 * The ContextFactory to listen to for debugging information. 71 */ 72 private ContextFactory contextFactory; 73 74 /** 75 * Synchronization object used to allow script evaluations to 76 * happen when a thread is resumed. 77 */ 78 private Object monitor = new Object(); 79 80 /** 81 * Synchronization object used to wait for valid 82 * {@link #interruptedContextData}. 83 */ 84 private Object eventThreadMonitor = new Object(); 85 86 /** 87 * The action to perform to end the interruption loop. 88 */ 89 private volatile int returnValue = -1; 90 91 /** 92 * Whether the debugger is inside the interruption loop. 93 */ 94 private boolean insideInterruptLoop; 95 96 /** 97 * The requested script string to be evaluated when the thread 98 * has been resumed. 99 */ 100 private String evalRequest; 101 102 /** 103 * The stack frame in which to evaluate {@link #evalRequest}. 104 */ 105 private StackFrame evalFrame; 106 107 /** 108 * The result of evaluating {@link #evalRequest}. 109 */ 110 private String evalResult; 111 112 /** 113 * Whether the debugger should break when a script exception is thrown. 114 */ 115 private boolean breakOnExceptions; 116 117 /** 118 * Whether the debugger should break when a script function is entered. 119 */ 120 private boolean breakOnEnter; 121 122 /** 123 * Whether the debugger should break when a script function is returned 124 * from. 125 */ 126 private boolean breakOnReturn; 127 128 /** 129 * Table mapping URLs to information about the script source. 130 */ 131 private final Map<String,SourceInfo> urlToSourceInfo = 132 Collections.synchronizedMap(new HashMap<String,SourceInfo>()); 133 134 /** 135 * Table mapping function names to information about the function. 136 */ 137 private final Map<String,FunctionSource> functionNames = 138 Collections.synchronizedMap(new HashMap<String,FunctionSource>()); 139 140 /** 141 * Table mapping functions to information about the function. 142 */ 143 private final Map<DebuggableScript,FunctionSource> functionToSource = 144 Collections.synchronizedMap(new HashMap<DebuggableScript,FunctionSource>()); 145 146 /** 147 * ContextFactory.Listener instance attached to {@link #contextFactory}. 148 */ 149 private DimIProxy listener; 150 151 /** 152 * Sets the GuiCallback object to use. 153 */ setGuiCallback(GuiCallback callback)154 public void setGuiCallback(GuiCallback callback) { 155 this.callback = callback; 156 } 157 158 /** 159 * Tells the debugger to break at the next opportunity. 160 */ setBreak()161 public void setBreak() { 162 this.breakFlag = true; 163 } 164 165 /** 166 * Sets the ScopeProvider to be used. 167 */ setScopeProvider(ScopeProvider scopeProvider)168 public void setScopeProvider(ScopeProvider scopeProvider) { 169 this.scopeProvider = scopeProvider; 170 } 171 172 /** 173 * Sets the ScopeProvider to be used. 174 */ setSourceProvider(final SourceProvider sourceProvider)175 public void setSourceProvider(final SourceProvider sourceProvider) { 176 this.sourceProvider = sourceProvider; 177 } 178 179 /** 180 * Switches context to the stack frame with the given index. 181 */ contextSwitch(int frameIndex)182 public void contextSwitch(int frameIndex) { 183 this.frameIndex = frameIndex; 184 } 185 186 /** 187 * Sets whether the debugger should break on exceptions. 188 */ setBreakOnExceptions(boolean breakOnExceptions)189 public void setBreakOnExceptions(boolean breakOnExceptions) { 190 this.breakOnExceptions = breakOnExceptions; 191 } 192 193 /** 194 * Sets whether the debugger should break on function entering. 195 */ setBreakOnEnter(boolean breakOnEnter)196 public void setBreakOnEnter(boolean breakOnEnter) { 197 this.breakOnEnter = breakOnEnter; 198 } 199 200 /** 201 * Sets whether the debugger should break on function return. 202 */ setBreakOnReturn(boolean breakOnReturn)203 public void setBreakOnReturn(boolean breakOnReturn) { 204 this.breakOnReturn = breakOnReturn; 205 } 206 207 /** 208 * Attaches the debugger to the given ContextFactory. 209 */ attachTo(ContextFactory factory)210 public void attachTo(ContextFactory factory) { 211 detach(); 212 this.contextFactory = factory; 213 this.listener = new DimIProxy(this, IPROXY_LISTEN); 214 factory.addListener(this.listener); 215 } 216 217 /** 218 * Detaches the debugger from the current ContextFactory. 219 */ detach()220 public void detach() { 221 if (listener != null) { 222 contextFactory.removeListener(listener); 223 contextFactory = null; 224 listener = null; 225 } 226 } 227 228 /** 229 * Releases resources associated with this debugger. 230 */ dispose()231 public void dispose() { 232 detach(); 233 } 234 235 /** 236 * Returns the FunctionSource object for the given script or function. 237 */ getFunctionSource(DebuggableScript fnOrScript)238 private FunctionSource getFunctionSource(DebuggableScript fnOrScript) { 239 FunctionSource fsource = functionSource(fnOrScript); 240 if (fsource == null) { 241 String url = getNormalizedUrl(fnOrScript); 242 SourceInfo si = sourceInfo(url); 243 if (si == null) { 244 if (!fnOrScript.isGeneratedScript()) { 245 // Not eval or Function, try to load it from URL 246 String source = loadSource(url); 247 if (source != null) { 248 DebuggableScript top = fnOrScript; 249 for (;;) { 250 DebuggableScript parent = top.getParent(); 251 if (parent == null) { 252 break; 253 } 254 top = parent; 255 } 256 registerTopScript(top, source); 257 fsource = functionSource(fnOrScript); 258 } 259 } 260 } 261 } 262 return fsource; 263 } 264 265 /** 266 * Loads the script at the given URL. 267 */ loadSource(String sourceUrl)268 private String loadSource(String sourceUrl) { 269 String source = null; 270 int hash = sourceUrl.indexOf('#'); 271 if (hash >= 0) { 272 sourceUrl = sourceUrl.substring(0, hash); 273 } 274 try { 275 InputStream is; 276 openStream: 277 { 278 if (sourceUrl.indexOf(':') < 0) { 279 // Can be a file name 280 try { 281 if (sourceUrl.startsWith("~/")) { 282 String home = SecurityUtilities.getSystemProperty("user.home"); 283 if (home != null) { 284 String pathFromHome = sourceUrl.substring(2); 285 File f = new File(new File(home), pathFromHome); 286 if (f.exists()) { 287 is = new FileInputStream(f); 288 break openStream; 289 } 290 } 291 } 292 File f = new File(sourceUrl); 293 if (f.exists()) { 294 is = new FileInputStream(f); 295 break openStream; 296 } 297 } catch (SecurityException ex) { } 298 // No existing file, assume missed http:// 299 if (sourceUrl.startsWith("//")) { 300 sourceUrl = "http:" + sourceUrl; 301 } else if (sourceUrl.startsWith("/")) { 302 sourceUrl = "http://127.0.0.1" + sourceUrl; 303 } else { 304 sourceUrl = "http://" + sourceUrl; 305 } 306 } 307 308 is = (new URL(sourceUrl)).openStream(); 309 } 310 311 try { 312 source = Kit.readReader(new InputStreamReader(is)); 313 } finally { 314 is.close(); 315 } 316 } catch (IOException ex) { 317 System.err.println 318 ("Failed to load source from "+sourceUrl+": "+ ex); 319 } 320 return source; 321 } 322 323 /** 324 * Registers the given script as a top-level script in the debugger. 325 */ registerTopScript(DebuggableScript topScript, String source)326 private void registerTopScript(DebuggableScript topScript, String source) { 327 if (!topScript.isTopLevel()) { 328 throw new IllegalArgumentException(); 329 } 330 String url = getNormalizedUrl(topScript); 331 DebuggableScript[] functions = getAllFunctions(topScript); 332 if (sourceProvider != null) { 333 final String providedSource = sourceProvider.getSource(topScript); 334 if(providedSource != null) { 335 source = providedSource; 336 } 337 } 338 339 final SourceInfo sourceInfo = new SourceInfo(source, functions, url); 340 341 synchronized (urlToSourceInfo) { 342 SourceInfo old = urlToSourceInfo.get(url); 343 if (old != null) { 344 sourceInfo.copyBreakpointsFrom(old); 345 } 346 urlToSourceInfo.put(url, sourceInfo); 347 for (int i = 0; i != sourceInfo.functionSourcesTop(); ++i) { 348 FunctionSource fsource = sourceInfo.functionSource(i); 349 String name = fsource.name(); 350 if (name.length() != 0) { 351 functionNames.put(name, fsource); 352 } 353 } 354 } 355 356 synchronized (functionToSource) { 357 for (int i = 0; i != functions.length; ++i) { 358 FunctionSource fsource = sourceInfo.functionSource(i); 359 functionToSource.put(functions[i], fsource); 360 } 361 } 362 363 callback.updateSourceText(sourceInfo); 364 } 365 366 /** 367 * Returns the FunctionSource object for the given function or script. 368 */ functionSource(DebuggableScript fnOrScript)369 private FunctionSource functionSource(DebuggableScript fnOrScript) { 370 return functionToSource.get(fnOrScript); 371 } 372 373 /** 374 * Returns an array of all function names. 375 */ functionNames()376 public String[] functionNames() { 377 synchronized (urlToSourceInfo) { 378 return functionNames.keySet().toArray(new String[functionNames.size()]); 379 } 380 } 381 382 /** 383 * Returns the FunctionSource object for the function with the given name. 384 */ functionSourceByName(String functionName)385 public FunctionSource functionSourceByName(String functionName) { 386 return functionNames.get(functionName); 387 } 388 389 /** 390 * Returns the SourceInfo object for the given URL. 391 */ sourceInfo(String url)392 public SourceInfo sourceInfo(String url) { 393 return urlToSourceInfo.get(url); 394 } 395 396 /** 397 * Returns the source URL for the given script or function. 398 */ getNormalizedUrl(DebuggableScript fnOrScript)399 private String getNormalizedUrl(DebuggableScript fnOrScript) { 400 String url = fnOrScript.getSourceName(); 401 if (url == null) { url = "<stdin>"; } 402 else { 403 // Not to produce window for eval from different lines, 404 // strip line numbers, i.e. replace all #[0-9]+\(eval\) by 405 // (eval) 406 // Option: similar teatment for Function? 407 char evalSeparator = '#'; 408 StringBuffer sb = null; 409 int urlLength = url.length(); 410 int cursor = 0; 411 for (;;) { 412 int searchStart = url.indexOf(evalSeparator, cursor); 413 if (searchStart < 0) { 414 break; 415 } 416 String replace = null; 417 int i = searchStart + 1; 418 while (i != urlLength) { 419 int c = url.charAt(i); 420 if (!('0' <= c && c <= '9')) { 421 break; 422 } 423 ++i; 424 } 425 if (i != searchStart + 1) { 426 // i points after #[0-9]+ 427 if ("(eval)".regionMatches(0, url, i, 6)) { 428 cursor = i + 6; 429 replace = "(eval)"; 430 } 431 } 432 if (replace == null) { 433 break; 434 } 435 if (sb == null) { 436 sb = new StringBuffer(); 437 sb.append(url.substring(0, searchStart)); 438 } 439 sb.append(replace); 440 } 441 if (sb != null) { 442 if (cursor != urlLength) { 443 sb.append(url.substring(cursor)); 444 } 445 url = sb.toString(); 446 } 447 } 448 return url; 449 } 450 451 /** 452 * Returns an array of all functions in the given script. 453 */ getAllFunctions(DebuggableScript function)454 private static DebuggableScript[] getAllFunctions 455 (DebuggableScript function) { 456 ObjArray functions = new ObjArray(); 457 collectFunctions_r(function, functions); 458 DebuggableScript[] result = new DebuggableScript[functions.size()]; 459 functions.toArray(result); 460 return result; 461 } 462 463 /** 464 * Helper function for {@link #getAllFunctions(DebuggableScript)}. 465 */ collectFunctions_r(DebuggableScript function, ObjArray array)466 private static void collectFunctions_r(DebuggableScript function, 467 ObjArray array) { 468 array.add(function); 469 for (int i = 0; i != function.getFunctionCount(); ++i) { 470 collectFunctions_r(function.getFunction(i), array); 471 } 472 } 473 474 /** 475 * Clears all breakpoints. 476 */ clearAllBreakpoints()477 public void clearAllBreakpoints() { 478 for (SourceInfo si: urlToSourceInfo.values()) { 479 si.removeAllBreakpoints(); 480 } 481 } 482 483 /** 484 * Called when a breakpoint has been hit. 485 */ handleBreakpointHit(StackFrame frame, Context cx)486 private void handleBreakpointHit(StackFrame frame, Context cx) { 487 breakFlag = false; 488 interrupted(cx, frame, null); 489 } 490 491 /** 492 * Called when a script exception has been thrown. 493 */ handleExceptionThrown(Context cx, Throwable ex, StackFrame frame)494 private void handleExceptionThrown(Context cx, Throwable ex, 495 StackFrame frame) { 496 if (breakOnExceptions) { 497 ContextData cd = frame.contextData(); 498 if (cd.lastProcessedException != ex) { 499 interrupted(cx, frame, ex); 500 cd.lastProcessedException = ex; 501 } 502 } 503 } 504 505 /** 506 * Returns the current ContextData object. 507 */ currentContextData()508 public ContextData currentContextData() { 509 return interruptedContextData; 510 } 511 512 /** 513 * Sets the action to perform to end interruption. 514 */ setReturnValue(int returnValue)515 public void setReturnValue(int returnValue) { 516 synchronized (monitor) { 517 this.returnValue = returnValue; 518 monitor.notify(); 519 } 520 } 521 522 /** 523 * Resumes execution of script. 524 */ go()525 public void go() { 526 synchronized (monitor) { 527 this.returnValue = GO; 528 monitor.notifyAll(); 529 } 530 } 531 532 /** 533 * Evaluates the given script. 534 */ eval(String expr)535 public String eval(String expr) { 536 String result = "undefined"; 537 if (expr == null) { 538 return result; 539 } 540 ContextData contextData = currentContextData(); 541 if (contextData == null || frameIndex >= contextData.frameCount()) { 542 return result; 543 } 544 StackFrame frame = contextData.getFrame(frameIndex); 545 if (contextData.eventThreadFlag) { 546 Context cx = Context.getCurrentContext(); 547 result = do_eval(cx, frame, expr); 548 } else { 549 synchronized (monitor) { 550 if (insideInterruptLoop) { 551 evalRequest = expr; 552 evalFrame = frame; 553 monitor.notify(); 554 do { 555 try { 556 monitor.wait(); 557 } catch (InterruptedException exc) { 558 Thread.currentThread().interrupt(); 559 break; 560 } 561 } while (evalRequest != null); 562 result = evalResult; 563 } 564 } 565 } 566 return result; 567 } 568 569 /** 570 * Compiles the given script. 571 */ compileScript(String url, String text)572 public void compileScript(String url, String text) { 573 DimIProxy action = new DimIProxy(this, IPROXY_COMPILE_SCRIPT); 574 action.url = url; 575 action.text = text; 576 action.withContext(); 577 } 578 579 /** 580 * Evaluates the given script. 581 */ evalScript(final String url, final String text)582 public void evalScript(final String url, final String text) { 583 DimIProxy action = new DimIProxy(this, IPROXY_EVAL_SCRIPT); 584 action.url = url; 585 action.text = text; 586 action.withContext(); 587 } 588 589 /** 590 * Converts the given script object to a string. 591 */ objectToString(Object object)592 public String objectToString(Object object) { 593 DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_TO_STRING); 594 action.object = object; 595 action.withContext(); 596 return action.stringResult; 597 } 598 599 /** 600 * Returns whether the given string is syntactically valid script. 601 */ stringIsCompilableUnit(String str)602 public boolean stringIsCompilableUnit(String str) { 603 DimIProxy action = new DimIProxy(this, IPROXY_STRING_IS_COMPILABLE); 604 action.text = str; 605 action.withContext(); 606 return action.booleanResult; 607 } 608 609 /** 610 * Returns the value of a property on the given script object. 611 */ getObjectProperty(Object object, Object id)612 public Object getObjectProperty(Object object, Object id) { 613 DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_PROPERTY); 614 action.object = object; 615 action.id = id; 616 action.withContext(); 617 return action.objectResult; 618 } 619 620 /** 621 * Returns an array of the property names on the given script object. 622 */ getObjectIds(Object object)623 public Object[] getObjectIds(Object object) { 624 DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_IDS); 625 action.object = object; 626 action.withContext(); 627 return action.objectArrayResult; 628 } 629 630 /** 631 * Returns the value of a property on the given script object. 632 */ getObjectPropertyImpl(Context cx, Object object, Object id)633 private Object getObjectPropertyImpl(Context cx, Object object, 634 Object id) { 635 Scriptable scriptable = (Scriptable)object; 636 Object result; 637 if (id instanceof String) { 638 String name = (String)id; 639 if (name.equals("this")) { 640 result = scriptable; 641 } else if (name.equals("__proto__")) { 642 result = scriptable.getPrototype(); 643 } else if (name.equals("__parent__")) { 644 result = scriptable.getParentScope(); 645 } else { 646 result = ScriptableObject.getProperty(scriptable, name); 647 if (result == ScriptableObject.NOT_FOUND) { 648 result = Undefined.instance; 649 } 650 } 651 } else { 652 int index = ((Integer)id).intValue(); 653 result = ScriptableObject.getProperty(scriptable, index); 654 if (result == ScriptableObject.NOT_FOUND) { 655 result = Undefined.instance; 656 } 657 } 658 return result; 659 } 660 661 /** 662 * Returns an array of the property names on the given script object. 663 */ getObjectIdsImpl(Context cx, Object object)664 private Object[] getObjectIdsImpl(Context cx, Object object) { 665 if (!(object instanceof Scriptable) || object == Undefined.instance) { 666 return Context.emptyArgs; 667 } 668 669 Object[] ids; 670 Scriptable scriptable = (Scriptable)object; 671 if (scriptable instanceof DebuggableObject) { 672 ids = ((DebuggableObject)scriptable).getAllIds(); 673 } else { 674 ids = scriptable.getIds(); 675 } 676 677 Scriptable proto = scriptable.getPrototype(); 678 Scriptable parent = scriptable.getParentScope(); 679 int extra = 0; 680 if (proto != null) { 681 ++extra; 682 } 683 if (parent != null) { 684 ++extra; 685 } 686 if (extra != 0) { 687 Object[] tmp = new Object[extra + ids.length]; 688 System.arraycopy(ids, 0, tmp, extra, ids.length); 689 ids = tmp; 690 extra = 0; 691 if (proto != null) { 692 ids[extra++] = "__proto__"; 693 } 694 if (parent != null) { 695 ids[extra++] = "__parent__"; 696 } 697 } 698 699 return ids; 700 } 701 702 /** 703 * Interrupts script execution. 704 */ interrupted(Context cx, final StackFrame frame, Throwable scriptException)705 private void interrupted(Context cx, final StackFrame frame, 706 Throwable scriptException) { 707 ContextData contextData = frame.contextData(); 708 boolean eventThreadFlag = callback.isGuiEventThread(); 709 contextData.eventThreadFlag = eventThreadFlag; 710 711 boolean recursiveEventThreadCall = false; 712 713 interruptedCheck: 714 synchronized (eventThreadMonitor) { 715 if (eventThreadFlag) { 716 if (interruptedContextData != null) { 717 recursiveEventThreadCall = true; 718 break interruptedCheck; 719 } 720 } else { 721 while (interruptedContextData != null) { 722 try { 723 eventThreadMonitor.wait(); 724 } catch (InterruptedException exc) { 725 return; 726 } 727 } 728 } 729 interruptedContextData = contextData; 730 } 731 732 if (recursiveEventThreadCall) { 733 // XXX: For now the following is commented out as on Linux 734 // too deep recursion of dispatchNextGuiEvent causes GUI lockout. 735 // Note: it can make GUI unresponsive if long-running script 736 // will be called on GUI thread while processing another interrupt 737 if (false) { 738 // Run event dispatch until gui sets a flag to exit the initial 739 // call to interrupted. 740 while (this.returnValue == -1) { 741 try { 742 callback.dispatchNextGuiEvent(); 743 } catch (InterruptedException exc) { 744 } 745 } 746 } 747 return; 748 } 749 750 if (interruptedContextData == null) Kit.codeBug(); 751 752 try { 753 do { 754 int frameCount = contextData.frameCount(); 755 this.frameIndex = frameCount -1; 756 757 final String threadTitle = Thread.currentThread().toString(); 758 final String alertMessage; 759 if (scriptException == null) { 760 alertMessage = null; 761 } else { 762 alertMessage = scriptException.toString(); 763 } 764 765 int returnValue = -1; 766 if (!eventThreadFlag) { 767 synchronized (monitor) { 768 if (insideInterruptLoop) Kit.codeBug(); 769 this.insideInterruptLoop = true; 770 this.evalRequest = null; 771 this.returnValue = -1; 772 callback.enterInterrupt(frame, threadTitle, 773 alertMessage); 774 try { 775 for (;;) { 776 try { 777 monitor.wait(); 778 } catch (InterruptedException exc) { 779 Thread.currentThread().interrupt(); 780 break; 781 } 782 if (evalRequest != null) { 783 this.evalResult = null; 784 try { 785 evalResult = do_eval(cx, evalFrame, 786 evalRequest); 787 } finally { 788 evalRequest = null; 789 evalFrame = null; 790 monitor.notify(); 791 } 792 continue; 793 } 794 if (this.returnValue != -1) { 795 returnValue = this.returnValue; 796 break; 797 } 798 } 799 } finally { 800 insideInterruptLoop = false; 801 } 802 } 803 } else { 804 this.returnValue = -1; 805 callback.enterInterrupt(frame, threadTitle, alertMessage); 806 while (this.returnValue == -1) { 807 try { 808 callback.dispatchNextGuiEvent(); 809 } catch (InterruptedException exc) { 810 } 811 } 812 returnValue = this.returnValue; 813 } 814 switch (returnValue) { 815 case STEP_OVER: 816 contextData.breakNextLine = true; 817 contextData.stopAtFrameDepth = contextData.frameCount(); 818 break; 819 case STEP_INTO: 820 contextData.breakNextLine = true; 821 contextData.stopAtFrameDepth = -1; 822 break; 823 case STEP_OUT: 824 if (contextData.frameCount() > 1) { 825 contextData.breakNextLine = true; 826 contextData.stopAtFrameDepth 827 = contextData.frameCount() -1; 828 } 829 break; 830 } 831 } while (false); 832 } finally { 833 synchronized (eventThreadMonitor) { 834 interruptedContextData = null; 835 eventThreadMonitor.notifyAll(); 836 } 837 } 838 839 } 840 841 /** 842 * Evaluates script in the given stack frame. 843 */ do_eval(Context cx, StackFrame frame, String expr)844 private static String do_eval(Context cx, StackFrame frame, String expr) { 845 String resultString; 846 Debugger saved_debugger = cx.getDebugger(); 847 Object saved_data = cx.getDebuggerContextData(); 848 int saved_level = cx.getOptimizationLevel(); 849 850 cx.setDebugger(null, null); 851 cx.setOptimizationLevel(-1); 852 cx.setGeneratingDebug(false); 853 try { 854 Callable script = (Callable)cx.compileString(expr, "", 0, null); 855 Object result = script.call(cx, frame.scope, frame.thisObj, 856 ScriptRuntime.emptyArgs); 857 if (result == Undefined.instance) { 858 resultString = ""; 859 } else { 860 resultString = ScriptRuntime.toString(result); 861 } 862 } catch (Exception exc) { 863 resultString = exc.getMessage(); 864 } finally { 865 cx.setGeneratingDebug(true); 866 cx.setOptimizationLevel(saved_level); 867 cx.setDebugger(saved_debugger, saved_data); 868 } 869 if (resultString == null) { 870 resultString = "null"; 871 } 872 return resultString; 873 } 874 875 /** 876 * Proxy class to implement debug interfaces without bloat of class 877 * files. 878 */ 879 private static class DimIProxy 880 implements ContextAction, ContextFactory.Listener, Debugger { 881 882 /** 883 * The debugger. 884 */ 885 private Dim dim; 886 887 /** 888 * The interface implementation type. One of the IPROXY_* constants 889 * defined in {@link Dim}. 890 */ 891 private int type; 892 893 /** 894 * The URL origin of the script to compile or evaluate. 895 */ 896 private String url; 897 898 /** 899 * The text of the script to compile, evaluate or test for compilation. 900 */ 901 private String text; 902 903 /** 904 * The object to convert, get a property from or enumerate. 905 */ 906 private Object object; 907 908 /** 909 * The property to look up in {@link #object}. 910 */ 911 private Object id; 912 913 /** 914 * The boolean result of the action. 915 */ 916 private boolean booleanResult; 917 918 /** 919 * The String result of the action. 920 */ 921 private String stringResult; 922 923 /** 924 * The Object result of the action. 925 */ 926 private Object objectResult; 927 928 /** 929 * The Object[] result of the action. 930 */ 931 private Object[] objectArrayResult; 932 933 /** 934 * Creates a new DimIProxy. 935 */ DimIProxy(Dim dim, int type)936 private DimIProxy(Dim dim, int type) { 937 this.dim = dim; 938 this.type = type; 939 } 940 941 // ContextAction 942 943 /** 944 * Performs the action given by {@link #type}. 945 */ run(Context cx)946 public Object run(Context cx) { 947 switch (type) { 948 case IPROXY_COMPILE_SCRIPT: 949 cx.compileString(text, url, 1, null); 950 break; 951 952 case IPROXY_EVAL_SCRIPT: 953 { 954 Scriptable scope = null; 955 if (dim.scopeProvider != null) { 956 scope = dim.scopeProvider.getScope(); 957 } 958 if (scope == null) { 959 scope = new ImporterTopLevel(cx); 960 } 961 cx.evaluateString(scope, text, url, 1, null); 962 } 963 break; 964 965 case IPROXY_STRING_IS_COMPILABLE: 966 booleanResult = cx.stringIsCompilableUnit(text); 967 break; 968 969 case IPROXY_OBJECT_TO_STRING: 970 if (object == Undefined.instance) { 971 stringResult = "undefined"; 972 } else if (object == null) { 973 stringResult = "null"; 974 } else if (object instanceof NativeCall) { 975 stringResult = "[object Call]"; 976 } else { 977 stringResult = Context.toString(object); 978 } 979 break; 980 981 case IPROXY_OBJECT_PROPERTY: 982 objectResult = dim.getObjectPropertyImpl(cx, object, id); 983 break; 984 985 case IPROXY_OBJECT_IDS: 986 objectArrayResult = dim.getObjectIdsImpl(cx, object); 987 break; 988 989 default: 990 throw Kit.codeBug(); 991 } 992 return null; 993 } 994 995 /** 996 * Performs the action given by {@link #type} with the attached 997 * {@link ContextFactory}. 998 */ withContext()999 private void withContext() { 1000 dim.contextFactory.call(this); 1001 } 1002 1003 // ContextFactory.Listener 1004 1005 /** 1006 * Called when a Context is created. 1007 */ contextCreated(Context cx)1008 public void contextCreated(Context cx) { 1009 if (type != IPROXY_LISTEN) Kit.codeBug(); 1010 ContextData contextData = new ContextData(); 1011 Debugger debugger = new DimIProxy(dim, IPROXY_DEBUG); 1012 cx.setDebugger(debugger, contextData); 1013 cx.setGeneratingDebug(true); 1014 cx.setOptimizationLevel(-1); 1015 } 1016 1017 /** 1018 * Called when a Context is destroyed. 1019 */ contextReleased(Context cx)1020 public void contextReleased(Context cx) { 1021 if (type != IPROXY_LISTEN) Kit.codeBug(); 1022 } 1023 1024 // Debugger 1025 1026 /** 1027 * Returns a StackFrame for the given function or script. 1028 */ getFrame(Context cx, DebuggableScript fnOrScript)1029 public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) { 1030 if (type != IPROXY_DEBUG) Kit.codeBug(); 1031 1032 FunctionSource item = dim.getFunctionSource(fnOrScript); 1033 if (item == null) { 1034 // Can not debug if source is not available 1035 return null; 1036 } 1037 return new StackFrame(cx, dim, item); 1038 } 1039 1040 /** 1041 * Called when compilation is finished. 1042 */ handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source)1043 public void handleCompilationDone(Context cx, 1044 DebuggableScript fnOrScript, 1045 String source) { 1046 if (type != IPROXY_DEBUG) Kit.codeBug(); 1047 1048 if (!fnOrScript.isTopLevel()) { 1049 return; 1050 } 1051 dim.registerTopScript(fnOrScript, source); 1052 } 1053 } 1054 1055 /** 1056 * Class to store information about a stack. 1057 */ 1058 public static class ContextData { 1059 1060 /** 1061 * The stack frames. 1062 */ 1063 private ObjArray frameStack = new ObjArray(); 1064 1065 /** 1066 * Whether the debugger should break at the next line in this context. 1067 */ 1068 private boolean breakNextLine; 1069 1070 /** 1071 * The frame depth the debugger should stop at. Used to implement 1072 * "step over" and "step out". 1073 */ 1074 private int stopAtFrameDepth = -1; 1075 1076 /** 1077 * Whether this context is in the event thread. 1078 */ 1079 private boolean eventThreadFlag; 1080 1081 /** 1082 * The last exception that was processed. 1083 */ 1084 private Throwable lastProcessedException; 1085 1086 /** 1087 * Returns the ContextData for the given Context. 1088 */ get(Context cx)1089 public static ContextData get(Context cx) { 1090 return (ContextData) cx.getDebuggerContextData(); 1091 } 1092 1093 /** 1094 * Returns the number of stack frames. 1095 */ frameCount()1096 public int frameCount() { 1097 return frameStack.size(); 1098 } 1099 1100 /** 1101 * Returns the stack frame with the given index. 1102 */ getFrame(int frameNumber)1103 public StackFrame getFrame(int frameNumber) { 1104 int num = frameStack.size() - frameNumber - 1; 1105 return (StackFrame) frameStack.get(num); 1106 } 1107 1108 /** 1109 * Pushes a stack frame on to the stack. 1110 */ pushFrame(StackFrame frame)1111 private void pushFrame(StackFrame frame) { 1112 frameStack.push(frame); 1113 } 1114 1115 /** 1116 * Pops a stack frame from the stack. 1117 */ popFrame()1118 private void popFrame() { 1119 frameStack.pop(); 1120 } 1121 } 1122 1123 /** 1124 * Object to represent one stack frame. 1125 */ 1126 public static class StackFrame implements DebugFrame { 1127 1128 /** 1129 * The debugger. 1130 */ 1131 private Dim dim; 1132 1133 /** 1134 * The ContextData for the Context being debugged. 1135 */ 1136 private ContextData contextData; 1137 1138 /** 1139 * The scope. 1140 */ 1141 private Scriptable scope; 1142 1143 /** 1144 * The 'this' object. 1145 */ 1146 private Scriptable thisObj; 1147 1148 /** 1149 * Information about the function. 1150 */ 1151 private FunctionSource fsource; 1152 1153 /** 1154 * Array of breakpoint state for each source line. 1155 */ 1156 private boolean[] breakpoints; 1157 1158 /** 1159 * Current line number. 1160 */ 1161 private int lineNumber; 1162 1163 /** 1164 * Creates a new StackFrame. 1165 */ StackFrame(Context cx, Dim dim, FunctionSource fsource)1166 private StackFrame(Context cx, Dim dim, FunctionSource fsource) { 1167 this.dim = dim; 1168 this.contextData = ContextData.get(cx); 1169 this.fsource = fsource; 1170 this.breakpoints = fsource.sourceInfo().breakpoints; 1171 this.lineNumber = fsource.firstLine(); 1172 } 1173 1174 /** 1175 * Called when the stack frame is entered. 1176 */ onEnter(Context cx, Scriptable scope, Scriptable thisObj, Object[] args)1177 public void onEnter(Context cx, Scriptable scope, 1178 Scriptable thisObj, Object[] args) { 1179 contextData.pushFrame(this); 1180 this.scope = scope; 1181 this.thisObj = thisObj; 1182 if (dim.breakOnEnter) { 1183 dim.handleBreakpointHit(this, cx); 1184 } 1185 } 1186 1187 /** 1188 * Called when the current position has changed. 1189 */ onLineChange(Context cx, int lineno)1190 public void onLineChange(Context cx, int lineno) { 1191 this.lineNumber = lineno; 1192 1193 if (!breakpoints[lineno] && !dim.breakFlag) { 1194 boolean lineBreak = contextData.breakNextLine; 1195 if (lineBreak && contextData.stopAtFrameDepth >= 0) { 1196 lineBreak = (contextData.frameCount() 1197 <= contextData.stopAtFrameDepth); 1198 } 1199 if (!lineBreak) { 1200 return; 1201 } 1202 contextData.stopAtFrameDepth = -1; 1203 contextData.breakNextLine = false; 1204 } 1205 1206 dim.handleBreakpointHit(this, cx); 1207 } 1208 1209 /** 1210 * Called when an exception has been thrown. 1211 */ onExceptionThrown(Context cx, Throwable exception)1212 public void onExceptionThrown(Context cx, Throwable exception) { 1213 dim.handleExceptionThrown(cx, exception, this); 1214 } 1215 1216 /** 1217 * Called when the stack frame has been left. 1218 */ onExit(Context cx, boolean byThrow, Object resultOrException)1219 public void onExit(Context cx, boolean byThrow, 1220 Object resultOrException) { 1221 if (dim.breakOnReturn && !byThrow) { 1222 dim.handleBreakpointHit(this, cx); 1223 } 1224 contextData.popFrame(); 1225 } 1226 1227 /** 1228 * Called when a 'debugger' statement is executed. 1229 */ onDebuggerStatement(Context cx)1230 public void onDebuggerStatement(Context cx) { 1231 dim.handleBreakpointHit(this, cx); 1232 } 1233 1234 /** 1235 * Returns the SourceInfo object for the function. 1236 */ sourceInfo()1237 public SourceInfo sourceInfo() { 1238 return fsource.sourceInfo(); 1239 } 1240 1241 /** 1242 * Returns the ContextData object for the Context. 1243 */ contextData()1244 public ContextData contextData() { 1245 return contextData; 1246 } 1247 1248 /** 1249 * Returns the scope object for this frame. 1250 */ scope()1251 public Object scope() { 1252 return scope; 1253 } 1254 1255 /** 1256 * Returns the 'this' object for this frame. 1257 */ thisObj()1258 public Object thisObj() { 1259 return thisObj; 1260 } 1261 1262 /** 1263 * Returns the source URL. 1264 */ getUrl()1265 public String getUrl() { 1266 return fsource.sourceInfo().url(); 1267 } 1268 1269 /** 1270 * Returns the current line number. 1271 */ getLineNumber()1272 public int getLineNumber() { 1273 return lineNumber; 1274 } 1275 1276 /** 1277 * Returns the current function name. 1278 */ getFunctionName()1279 public String getFunctionName() { 1280 return fsource.name(); 1281 } 1282 } 1283 1284 /** 1285 * Class to store information about a function. 1286 */ 1287 public static class FunctionSource { 1288 1289 /** 1290 * Information about the source of the function. 1291 */ 1292 private SourceInfo sourceInfo; 1293 1294 /** 1295 * Line number of the first line of the function. 1296 */ 1297 private int firstLine; 1298 1299 /** 1300 * The function name. 1301 */ 1302 private String name; 1303 1304 /** 1305 * Creates a new FunctionSource. 1306 */ FunctionSource(SourceInfo sourceInfo, int firstLine, String name)1307 private FunctionSource(SourceInfo sourceInfo, int firstLine, 1308 String name) { 1309 if (name == null) throw new IllegalArgumentException(); 1310 this.sourceInfo = sourceInfo; 1311 this.firstLine = firstLine; 1312 this.name = name; 1313 } 1314 1315 /** 1316 * Returns the SourceInfo object that describes the source of the 1317 * function. 1318 */ sourceInfo()1319 public SourceInfo sourceInfo() { 1320 return sourceInfo; 1321 } 1322 1323 /** 1324 * Returns the line number of the first line of the function. 1325 */ firstLine()1326 public int firstLine() { 1327 return firstLine; 1328 } 1329 1330 /** 1331 * Returns the name of the function. 1332 */ name()1333 public String name() { 1334 return name; 1335 } 1336 } 1337 1338 /** 1339 * Class to store information about a script source. 1340 */ 1341 public static class SourceInfo { 1342 1343 /** 1344 * An empty array of booleans. 1345 */ 1346 private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; 1347 1348 /** 1349 * The script. 1350 */ 1351 private String source; 1352 1353 /** 1354 * The URL of the script. 1355 */ 1356 private String url; 1357 1358 /** 1359 * Array indicating which lines can have breakpoints set. 1360 */ 1361 private boolean[] breakableLines; 1362 1363 /** 1364 * Array indicating whether a breakpoint is set on the line. 1365 */ 1366 private boolean[] breakpoints; 1367 1368 /** 1369 * Array of FunctionSource objects for the functions in the script. 1370 */ 1371 private FunctionSource[] functionSources; 1372 1373 /** 1374 * Creates a new SourceInfo object. 1375 */ SourceInfo(String source, DebuggableScript[] functions, String normilizedUrl)1376 private SourceInfo(String source, DebuggableScript[] functions, 1377 String normilizedUrl) { 1378 this.source = source; 1379 this.url = normilizedUrl; 1380 1381 int N = functions.length; 1382 int[][] lineArrays = new int[N][]; 1383 for (int i = 0; i != N; ++i) { 1384 lineArrays[i] = functions[i].getLineNumbers(); 1385 } 1386 1387 int minAll = 0, maxAll = -1; 1388 int[] firstLines = new int[N]; 1389 for (int i = 0; i != N; ++i) { 1390 int[] lines = lineArrays[i]; 1391 if (lines == null || lines.length == 0) { 1392 firstLines[i] = -1; 1393 } else { 1394 int min, max; 1395 min = max = lines[0]; 1396 for (int j = 1; j != lines.length; ++j) { 1397 int line = lines[j]; 1398 if (line < min) { 1399 min = line; 1400 } else if (line > max) { 1401 max = line; 1402 } 1403 } 1404 firstLines[i] = min; 1405 if (minAll > maxAll) { 1406 minAll = min; 1407 maxAll = max; 1408 } else { 1409 if (min < minAll) { 1410 minAll = min; 1411 } 1412 if (max > maxAll) { 1413 maxAll = max; 1414 } 1415 } 1416 } 1417 } 1418 1419 if (minAll > maxAll) { 1420 // No line information 1421 this.breakableLines = EMPTY_BOOLEAN_ARRAY; 1422 this.breakpoints = EMPTY_BOOLEAN_ARRAY; 1423 } else { 1424 if (minAll < 0) { 1425 // Line numbers can not be negative 1426 throw new IllegalStateException(String.valueOf(minAll)); 1427 } 1428 int linesTop = maxAll + 1; 1429 this.breakableLines = new boolean[linesTop]; 1430 this.breakpoints = new boolean[linesTop]; 1431 for (int i = 0; i != N; ++i) { 1432 int[] lines = lineArrays[i]; 1433 if (lines != null && lines.length != 0) { 1434 for (int j = 0; j != lines.length; ++j) { 1435 int line = lines[j]; 1436 this.breakableLines[line] = true; 1437 } 1438 } 1439 } 1440 } 1441 this.functionSources = new FunctionSource[N]; 1442 for (int i = 0; i != N; ++i) { 1443 String name = functions[i].getFunctionName(); 1444 if (name == null) { 1445 name = ""; 1446 } 1447 this.functionSources[i] 1448 = new FunctionSource(this, firstLines[i], name); 1449 } 1450 } 1451 1452 /** 1453 * Returns the source text. 1454 */ source()1455 public String source() { 1456 return this.source; 1457 } 1458 1459 /** 1460 * Returns the script's origin URL. 1461 */ url()1462 public String url() { 1463 return this.url; 1464 } 1465 1466 /** 1467 * Returns the number of FunctionSource objects stored in this object. 1468 */ functionSourcesTop()1469 public int functionSourcesTop() { 1470 return functionSources.length; 1471 } 1472 1473 /** 1474 * Returns the FunctionSource object with the given index. 1475 */ functionSource(int i)1476 public FunctionSource functionSource(int i) { 1477 return functionSources[i]; 1478 } 1479 1480 /** 1481 * Copies the breakpoints from the given SourceInfo object into this 1482 * one. 1483 */ copyBreakpointsFrom(SourceInfo old)1484 private void copyBreakpointsFrom(SourceInfo old) { 1485 int end = old.breakpoints.length; 1486 if (end > this.breakpoints.length) { 1487 end = this.breakpoints.length; 1488 } 1489 for (int line = 0; line != end; ++line) { 1490 if (old.breakpoints[line]) { 1491 this.breakpoints[line] = true; 1492 } 1493 } 1494 } 1495 1496 /** 1497 * Returns whether the given line number can have a breakpoint set on 1498 * it. 1499 */ breakableLine(int line)1500 public boolean breakableLine(int line) { 1501 return (line < this.breakableLines.length) 1502 && this.breakableLines[line]; 1503 } 1504 1505 /** 1506 * Returns whether there is a breakpoint set on the given line. 1507 */ breakpoint(int line)1508 public boolean breakpoint(int line) { 1509 if (!breakableLine(line)) { 1510 throw new IllegalArgumentException(String.valueOf(line)); 1511 } 1512 return line < this.breakpoints.length && this.breakpoints[line]; 1513 } 1514 1515 /** 1516 * Sets or clears the breakpoint flag for the given line. 1517 */ breakpoint(int line, boolean value)1518 public boolean breakpoint(int line, boolean value) { 1519 if (!breakableLine(line)) { 1520 throw new IllegalArgumentException(String.valueOf(line)); 1521 } 1522 boolean changed; 1523 synchronized (breakpoints) { 1524 if (breakpoints[line] != value) { 1525 breakpoints[line] = value; 1526 changed = true; 1527 } else { 1528 changed = false; 1529 } 1530 } 1531 return changed; 1532 } 1533 1534 /** 1535 * Removes all breakpoints from the script. 1536 */ removeAllBreakpoints()1537 public void removeAllBreakpoints() { 1538 synchronized (breakpoints) { 1539 for (int line = 0; line != breakpoints.length; ++line) { 1540 breakpoints[line] = false; 1541 } 1542 } 1543 } 1544 } 1545 } 1546