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