comparison graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugEngine.java @ 21568:3b8bbf51d320

Truffle/Debugging: add the Truffle DebugEngine and supporting code, as well as add a crude command-line debugging tool used mainly to test the DebugEngine. Migrate the small tols out of project com.oracle.truffle.api into the new project com.oracle.truffle.tools.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 26 May 2015 16:38:13 -0700
parents
children c072fbce5756
comparison
equal deleted inserted replaced
21470:1bbef57f9a38 21568:3b8bbf51d320
1 /*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.oracle.truffle.tools.debug.engine;
26
27 import java.io.*;
28 import java.util.*;
29
30 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
31 import com.oracle.truffle.api.*;
32 import com.oracle.truffle.api.frame.*;
33 import com.oracle.truffle.api.instrument.*;
34 import com.oracle.truffle.api.nodes.*;
35 import com.oracle.truffle.api.source.*;
36 import com.oracle.truffle.tools.debug.engine.SourceExecutionProvider.ExecutionListener;
37
38 /**
39 * Language-agnostic engine for running Truffle languages under debugging control.
40 */
41 public final class DebugEngine {
42
43 private static final boolean TRACE = false;
44 private static final boolean TRACE_PROBES = false;
45 private static final String TRACE_PREFIX = "DEBUG ENGINE: ";
46
47 private static final PrintStream OUT = System.out;
48
49 private static final SyntaxTag STEPPING_TAG = StandardSyntaxTag.STATEMENT;
50 private static final SyntaxTag CALL_TAG = StandardSyntaxTag.CALL;
51
52 @SuppressWarnings("unused")
53 private static void trace(String format, Object... args) {
54 if (TRACE || TRACE_PROBES) {
55 OUT.println(TRACE_PREFIX + String.format(format, args));
56 }
57 }
58
59 interface BreakpointCallback {
60
61 /**
62 * Passes control to the debugger with execution suspended.
63 */
64 void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason);
65 }
66
67 interface WarningLog {
68
69 /**
70 * Logs a warning that is kept until the start of the next execution.
71 */
72 void addWarning(String warning);
73 }
74
75 /**
76 * The client of this engine.
77 */
78 private final DebugClient debugClient;
79
80 private final SourceExecutionProvider sourceExecutionProvider;
81
82 /**
83 * Implementation of line-oriented breakpoints.
84 */
85 private final LineBreakpointFactory lineBreaks;
86
87 /**
88 * Implementation of tag-oriented breakpoints.
89 */
90 private final TagBreakpointFactory tagBreaks;
91
92 /**
93 * Head of the stack of executions.
94 */
95 private DebugExecutionContext debugContext;
96
97 /**
98 * @param debugClient
99 * @param sourceExecutionProvider
100 */
101 private DebugEngine(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) {
102 this.debugClient = debugClient;
103 this.sourceExecutionProvider = sourceExecutionProvider;
104
105 Source.setFileCaching(true);
106
107 // Initialize execution context stack
108 debugContext = new DebugExecutionContext(null, null);
109 prepareContinue();
110 debugContext.contextTrace("START EXEC DEFAULT");
111
112 sourceExecutionProvider.addExecutionListener(new ExecutionListener() {
113
114 public void executionStarted(Source source, boolean stepInto) {
115 // Push a new execution context onto stack
116 DebugEngine.this.debugContext = new DebugExecutionContext(source, DebugEngine.this.debugContext);
117 if (stepInto) {
118 DebugEngine.this.prepareStepInto(1);
119 } else {
120 DebugEngine.this.prepareContinue();
121 }
122 DebugEngine.this.debugContext.contextTrace("START EXEC ");
123 }
124
125 public void executionEnded() {
126 DebugEngine.this.lineBreaks.disposeOneShots();
127 DebugEngine.this.tagBreaks.disposeOneShots();
128 DebugEngine.this.debugContext.clearStrategy();
129 DebugEngine.this.debugContext.contextTrace("END EXEC ");
130 // Pop the stack of execution contexts.
131 DebugEngine.this.debugContext = DebugEngine.this.debugContext.predecessor;
132 }
133 });
134
135 final BreakpointCallback breakpointCallback = new BreakpointCallback() {
136
137 @TruffleBoundary
138 public void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason) {
139 debugContext.halt(astNode, mFrame, true, haltReason);
140 }
141 };
142
143 final WarningLog warningLog = new WarningLog() {
144
145 public void addWarning(String warning) {
146 assert debugContext != null;
147 debugContext.logWarning(warning);
148 }
149 };
150
151 this.lineBreaks = new LineBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog);
152
153 this.tagBreaks = new TagBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog);
154
155 if (TRACE_PROBES) {
156 Probe.addProbeListener(new ProbeListener() {
157
158 private Source beingProbed = null;
159
160 @Override
161 public void startASTProbing(Source source) {
162 final String sourceName = source == null ? "<?>" : source.getShortName();
163 trace("START PROBING %s", sourceName);
164 beingProbed = source;
165 }
166
167 @Override
168 public void newProbeInserted(Probe probe) {
169 trace("PROBE ADDED %s", probe.getShortDescription());
170 }
171
172 @Override
173 public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
174 trace("PROBE TAGGED as %s: %s", tag, probe.getShortDescription());
175 }
176
177 @Override
178 public void endASTProbing(Source source) {
179 final String sourceName = source == null ? "<?>" : source.getShortName();
180 trace("FINISHED PROBING %s", sourceName);
181 assert source == beingProbed;
182 beingProbed = null;
183 }
184 });
185 }
186 }
187
188 public static DebugEngine create(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) {
189 return new DebugEngine(debugClient, sourceExecutionProvider);
190 }
191
192 /**
193 * Runs a script. If "StepInto" is requested, halts at the first location tagged as a
194 * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}.
195 *
196 * @throws DebugException if an unexpected failure occurs
197 */
198 public void run(Source source, boolean stepInto) throws DebugException {
199 sourceExecutionProvider.run(source, stepInto);
200 }
201
202 /**
203 * Sets a breakpoint to halt at a source line.
204 *
205 * @param groupId
206 * @param ignoreCount number of hits to ignore before halting
207 * @param lineLocation where to set the breakpoint (source, line number)
208 * @param oneShot breakpoint disposes itself after fist hit, if {@code true}
209 * @return a new breakpoint, initially enabled
210 * @throws DebugException if the breakpoint can not be set.
211 */
212 @TruffleBoundary
213 public LineBreakpoint setLineBreakpoint(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) throws DebugException {
214 return lineBreaks.create(groupId, ignoreCount, lineLocation, oneShot);
215 }
216
217 /**
218 * Sets a breakpoint to halt at any node holding a specified {@link SyntaxTag}.
219 *
220 * @param groupId
221 * @param ignoreCount number of hits to ignore before halting
222 * @param oneShot if {@code true} breakpoint removes it self after a hit
223 * @return a new breakpoint, initially enabled
224 * @throws DebugException if the breakpoint already set
225 */
226 @TruffleBoundary
227 public Breakpoint setTagBreakpoint(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) throws DebugException {
228 return tagBreaks.create(groupId, ignoreCount, tag, oneShot);
229 }
230
231 /**
232 * Finds a breakpoint created by this engine, but not yet disposed, by id.
233 */
234 @TruffleBoundary
235 public Breakpoint findBreakpoint(long id) {
236 final Breakpoint breakpoint = lineBreaks.find(id);
237 return breakpoint == null ? tagBreaks.find(id) : breakpoint;
238 }
239
240 /**
241 * Gets all existing breakpoints, whatever their status, in natural sorted order. Modification
242 * save.
243 */
244 @TruffleBoundary
245 public Collection<Breakpoint> getBreakpoints() {
246 final Collection<Breakpoint> result = new ArrayList<>();
247 result.addAll(lineBreaks.getAll());
248 result.addAll(tagBreaks.getAll());
249 return result;
250 }
251
252 /**
253 * Prepare to execute in Continue mode when guest language program execution resumes. In this
254 * mode:
255 * <ul>
256 * <li>Execution will continue until either:
257 * <ol>
258 * <li>execution arrives at a node to which an enabled breakpoint is attached,
259 * <strong>or:</strong></li>
260 * <li>execution completes.</li>
261 * </ol>
262 * </ul>
263 */
264 @TruffleBoundary
265 public void prepareContinue() {
266 debugContext.setStrategy(new Continue());
267 }
268
269 /**
270 * Prepare to execute in StepInto mode when guest language program execution resumes. In this
271 * mode:
272 * <ul>
273 * <li>User breakpoints are disabled.</li>
274 * <li>Execution will continue until either:
275 * <ol>
276 * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
277 * STATMENT}, <strong>or:</strong></li>
278 * <li>execution completes.</li>
279 * </ol>
280 * <li>
281 * StepInto mode persists only through one resumption (i.e. {@code stepIntoCount} steps), and
282 * reverts by default to Continue mode.</li>
283 * </ul>
284 *
285 * @param stepCount the number of times to perform StepInto before halting
286 * @throws IllegalArgumentException if the specified number is {@code <= 0}
287 */
288 @TruffleBoundary
289 public void prepareStepInto(int stepCount) {
290 if (stepCount <= 0) {
291 throw new IllegalArgumentException();
292 }
293 debugContext.setStrategy(new StepInto(stepCount));
294 }
295
296 /**
297 * Prepare to execute in StepOut mode when guest language program execution resumes. In this
298 * mode:
299 * <ul>
300 * <li>User breakpoints are enabled.</li>
301 * <li>Execution will continue until either:
302 * <ol>
303 * <li>execution arrives at the nearest enclosing call site on the stack, <strong>or</strong></li>
304 * <li>execution completes.</li>
305 * </ol>
306 * <li>StepOut mode persists only through one resumption, and reverts by default to Continue
307 * mode.</li>
308 * </ul>
309 */
310 @TruffleBoundary
311 public void prepareStepOut() {
312 debugContext.setStrategy(new StepOut());
313 }
314
315 /**
316 * Prepare to execute in StepOver mode when guest language program execution resumes. In this
317 * mode:
318 * <ul>
319 * <li>Execution will continue until either:
320 * <ol>
321 * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
322 * STATEMENT} when not nested in one or more function/method calls, <strong>or:</strong></li>
323 * <li>execution arrives at a node to which a breakpoint is attached and when nested in one or
324 * more function/method calls, <strong>or:</strong></li>
325 * <li>execution completes.</li>
326 * </ol>
327 * <li>StepOver mode persists only through one resumption (i.e. {@code stepOverCount} steps),
328 * and reverts by default to Continue mode.</li>
329 * </ul>
330 *
331 * @param stepCount the number of times to perform StepInto before halting
332 * @throws IllegalArgumentException if the specified number is {@code <= 0}
333 */
334 @TruffleBoundary
335 public void prepareStepOver(int stepCount) {
336 if (stepCount <= 0) {
337 throw new IllegalArgumentException();
338 }
339 debugContext.setStrategy(new StepOver(stepCount));
340 }
341
342 /**
343 * Gets the stack frames from the (topmost) halted Truffle execution; {@code null} null if no
344 * execution.
345 */
346 @TruffleBoundary
347 public List<FrameDebugDescription> getStack() {
348 return debugContext == null ? null : debugContext.getFrames();
349 }
350
351 /**
352 * Evaluates code in a halted execution context, at top-level if <code>mFrame==null</code>.
353 */
354 public Object eval(Source source, Node node, MaterializedFrame mFrame) {
355 return sourceExecutionProvider.eval(source, node, mFrame);
356 }
357
358 /**
359 * A mode of user navigation from a current code location to another, e.g "step in" vs.
360 * "step over".
361 */
362 private abstract class StepStrategy {
363
364 private DebugExecutionContext context;
365 protected final String strategyName;
366
367 protected StepStrategy() {
368 this.strategyName = getClass().getSimpleName();
369 }
370
371 final String getName() {
372 return strategyName;
373 }
374
375 /**
376 * Reconfigure the debugger so that when execution continues the program will halt at the
377 * location specified by this strategy.
378 */
379 final void enable(DebugExecutionContext c, int stackDepth) {
380 this.context = c;
381 setStrategy(stackDepth);
382 }
383
384 /**
385 * Return the debugger to the default navigation mode.
386 */
387 final void disable() {
388 unsetStrategy();
389 }
390
391 @TruffleBoundary
392 final void halt(Node astNode, MaterializedFrame mFrame, boolean before) {
393 context.halt(astNode, mFrame, before, this.getClass().getSimpleName());
394 }
395
396 @TruffleBoundary
397 final void replaceStrategy(StepStrategy newStrategy) {
398 context.setStrategy(newStrategy);
399 }
400
401 @TruffleBoundary
402 protected final void strategyTrace(String action, String format, Object... args) {
403 if (TRACE) {
404 context.contextTrace("%s (%s) %s", action, strategyName, String.format(format, args));
405 }
406 }
407
408 @TruffleBoundary
409 protected final void suspendUserBreakpoints() {
410 lineBreaks.setActive(false);
411 tagBreaks.setActive(false);
412 }
413
414 @SuppressWarnings("unused")
415 protected final void restoreUserBreakpoints() {
416 lineBreaks.setActive(true);
417 tagBreaks.setActive(true);
418 }
419
420 /**
421 * Reconfigure the debugger so that when execution continues, it will do so using this mode
422 * of navigation.
423 */
424 protected abstract void setStrategy(int stackDepth);
425
426 /**
427 * Return to the debugger to the default mode of navigation.
428 */
429 protected abstract void unsetStrategy();
430 }
431
432 /**
433 * Strategy: the null stepping strategy.
434 * <ul>
435 * <li>User breakpoints are enabled.</li>
436 * <li>Execution continues until either:
437 * <ol>
438 * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
439 * <li>execution completes.</li>
440 * </ol>
441 * </ul>
442 */
443 private final class Continue extends StepStrategy {
444
445 @Override
446 protected void setStrategy(int stackDepth) {
447 }
448
449 @Override
450 protected void unsetStrategy() {
451 }
452 }
453
454 /**
455 * Strategy: per-statement stepping.
456 * <ul>
457 * <li>User breakpoints are enabled.</li>
458 * <li>Execution continues until either:
459 * <ol>
460 * <li>execution <em>arrives</em> at a STATEMENT node, <strong>or:</strong></li>
461 * <li>execution <em>returns</em> to a CALL node and the call stack is smaller then when
462 * execution started, <strong>or:</strong></li>
463 * <li>execution completes.</li>
464 * </ol>
465 * </ul>
466 *
467 * @see DebugEngine#prepareStepInto(int)
468 */
469 private final class StepInto extends StepStrategy {
470 private int unfinishedStepCount;
471
472 StepInto(int stepCount) {
473 super();
474 this.unfinishedStepCount = stepCount;
475 }
476
477 @Override
478 protected void setStrategy(final int stackDepth) {
479 Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
480
481 @Override
482 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
483 // HALT: just before statement
484 --unfinishedStepCount;
485 strategyTrace("TRAP BEFORE", "unfinished steps=%d", unfinishedStepCount);
486 // Should run in fast path
487 if (unfinishedStepCount <= 0) {
488 halt(node, mFrame, true);
489 }
490 strategyTrace("RESUME BEFORE", "");
491 }
492 });
493 Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
494
495 @Override
496 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
497 --unfinishedStepCount;
498 strategyTrace(null, "TRAP AFTER unfinished steps=%d", unfinishedStepCount);
499 if (currentStackDepth() < stackDepth) {
500 // HALT: just "stepped out"
501 if (unfinishedStepCount <= 0) {
502 halt(node, mFrame, false);
503 }
504 }
505 strategyTrace("RESUME AFTER", "");
506 }
507 });
508 }
509
510 @Override
511 protected void unsetStrategy() {
512 Probe.setBeforeTagTrap(null);
513 Probe.setAfterTagTrap(null);
514 }
515 }
516
517 /**
518 * Strategy: execution to nearest enclosing call site.
519 * <ul>
520 * <li>User breakpoints are enabled.</li>
521 * <li>Execution continues until either:
522 * <ol>
523 * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
524 * <li>execution <em>returns</em> to a CALL node and the call stack is smaller than when
525 * execution started, <strong>or:</strong></li>
526 * <li>execution completes.</li>
527 * </ol>
528 * </ul>
529 *
530 * @see DebugEngine#prepareStepOut()
531 */
532 private final class StepOut extends StepStrategy {
533
534 @Override
535 protected void setStrategy(final int stackDepth) {
536 Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
537
538 @TruffleBoundary
539 @Override
540 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
541 // HALT:
542 final int currentStackDepth = currentStackDepth();
543 strategyTrace("TRAP AFTER", "stackDepth: start=%d current=%d", stackDepth, currentStackDepth);
544 if (currentStackDepth < stackDepth) {
545 halt(node, mFrame, false);
546 }
547 strategyTrace("RESUME AFTER", "");
548 }
549 });
550 }
551
552 @Override
553 protected void unsetStrategy() {
554 Probe.setAfterTagTrap(null);
555 }
556 }
557
558 /**
559 * Strategy: per-statement stepping, so long as not nested in method calls (i.e. at original
560 * stack depth).
561 * <ul>
562 * <li>User breakpoints are enabled.</li>
563 * <li>Execution continues until either:
564 * <ol>
565 * <li>execution arrives at a STATEMENT node with stack depth no more than when started
566 * <strong>or:</strong></li>
567 * <li>the program completes.</li>
568 * </ol>
569 * </ul>
570 */
571 private final class StepOver extends StepStrategy {
572 private int unfinishedStepCount;
573
574 StepOver(int stepCount) {
575 this.unfinishedStepCount = stepCount;
576 }
577
578 @Override
579 protected void setStrategy(int stackDepth) {
580 Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
581
582 @Override
583 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
584 final int currentStackDepth = currentStackDepth();
585 if (currentStackDepth <= stackDepth) {
586 // HALT: stack depth unchanged or smaller; treat like StepInto
587 --unfinishedStepCount;
588 if (TRACE) {
589 strategyTrace("TRAP BEFORE", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
590 }
591 // Test should run in fast path
592 if (unfinishedStepCount <= 0) {
593 halt(node, mFrame, true);
594 }
595 } else {
596 // CONTINUE: Stack depth increased; don't count as a step
597 strategyTrace("STEP INTO", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
598 // Stop treating like StepInto, start treating like StepOut
599 replaceStrategy(new StepOverNested(unfinishedStepCount, stackDepth));
600 }
601 strategyTrace("RESUME BEFORE", "");
602 }
603 });
604
605 Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) {
606
607 @Override
608 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
609 final int currentStackDepth = currentStackDepth();
610 if (currentStackDepth < stackDepth) {
611 // HALT: just "stepped out"
612 --unfinishedStepCount;
613 strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth: start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
614 // Should run in fast path
615 if (unfinishedStepCount <= 0) {
616 halt(node, mFrame, false);
617 }
618 strategyTrace("RESUME AFTER", "");
619 }
620 }
621 });
622 }
623
624 @Override
625 protected void unsetStrategy() {
626 Probe.setBeforeTagTrap(null);
627 Probe.setAfterTagTrap(null);
628 }
629 }
630
631 /**
632 * Strategy: per-statement stepping, not into method calls, in effect while at increased stack
633 * depth
634 * <ul>
635 * <li>User breakpoints are enabled.</li>
636 * <li>Execution continues until either:
637 * <ol>
638 * <li>execution arrives at a STATEMENT node with stack depth no more than when started
639 * <strong>or:</strong></li>
640 * <li>the program completes <strong>or:</strong></li>
641 * </ol>
642 * </ul>
643 */
644 private final class StepOverNested extends StepStrategy {
645 private int unfinishedStepCount;
646 private final int startStackDepth;
647
648 StepOverNested(int stepCount, int startStackDepth) {
649 this.unfinishedStepCount = stepCount;
650 this.startStackDepth = startStackDepth;
651 }
652
653 @Override
654 protected void setStrategy(int stackDepth) {
655 Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) {
656
657 @Override
658 public void tagTrappedAt(Node node, MaterializedFrame mFrame) {
659 final int currentStackDepth = currentStackDepth();
660 if (currentStackDepth <= startStackDepth) {
661 // At original step depth (or smaller) after being nested
662 --unfinishedStepCount;
663 strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth);
664 if (unfinishedStepCount <= 0) {
665 halt(node, mFrame, false);
666 }
667 // TODO (mlvdv) fixme for multiple steps
668 strategyTrace("RESUME BEFORE", "");
669 }
670 }
671 });
672 }
673
674 @Override
675 protected void unsetStrategy() {
676 Probe.setBeforeTagTrap(null);
677 }
678 }
679
680 /**
681 * Information and debugging state for a single Truffle execution (which make take place over
682 * one or more suspended executions). This holds interaction state, for example what is
683 * executing (e.g. some {@link Source}), what the execution mode is ("stepping" or
684 * "continuing"). When not running, this holds a cache of the Truffle stack for this particular
685 * execution, effectively hiding the Truffle stack for any currently suspended executions (down
686 * the stack).
687 */
688 private final class DebugExecutionContext {
689
690 // Previous halted context in stack
691 private final DebugExecutionContext predecessor;
692
693 // The current execution level; first is 0.
694 private final int level; // Number of contexts suspended below
695 private final Source source;
696 private final int contextStackBase; // Where the stack for this execution starts
697 private final List<String> warnings = new ArrayList<>();
698
699 private boolean running;
700
701 /**
702 * The stepping strategy currently configured in the debugger.
703 */
704 private StepStrategy strategy;
705
706 /**
707 * Where halted; null if running.
708 */
709 private Node haltedNode;
710
711 /**
712 * Where halted; null if running.
713 */
714 private MaterializedFrame haltedFrame;
715
716 /**
717 * Cached list of stack frames when halted; null if running.
718 */
719 private List<FrameDebugDescription> frames = new ArrayList<>();
720
721 private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) {
722 this.source = executionSource;
723 this.predecessor = previousContext;
724 this.level = previousContext == null ? 0 : previousContext.level + 1;
725
726 // "Base" is the number of stack frames for all nested (halted) executions.
727 this.contextStackBase = currentStackDepth();
728 this.running = true;
729 contextTrace("NEW CONTEXT");
730 }
731
732 /**
733 * Sets up a strategy for the next resumption of execution.
734 *
735 * @param stepStrategy
736 */
737 void setStrategy(StepStrategy stepStrategy) {
738 if (this.strategy == null) {
739 this.strategy = stepStrategy;
740 this.strategy.enable(this, currentStackDepth());
741 if (TRACE) {
742 contextTrace("SET MODE <none>-->" + stepStrategy.getName());
743 }
744 } else {
745 strategy.disable();
746 strategy = stepStrategy;
747 strategy.enable(this, currentStackDepth());
748 contextTrace("SWITCH MODE %s-->%s", strategy.getName(), stepStrategy.getName());
749 }
750 }
751
752 void clearStrategy() {
753 if (strategy != null) {
754 final StepStrategy oldStrategy = strategy;
755 strategy.disable();
756 strategy = null;
757 contextTrace("CLEAR MODE %s--><none>", oldStrategy.getName());
758 }
759 }
760
761 /**
762 * Handle a program halt, caused by a breakpoint, stepping strategy, or other cause.
763 *
764 * @param astNode the guest language node at which execution is halted
765 * @param mFrame the current execution frame where execution is halted
766 * @param before {@code true} if halted <em>before</em> the node, else <em>after</em>.
767 */
768 @TruffleBoundary
769 void halt(Node astNode, MaterializedFrame mFrame, boolean before, String haltReason) {
770 assert running;
771 assert frames.isEmpty();
772 assert haltedNode == null;
773 assert haltedFrame == null;
774
775 haltedNode = astNode;
776 haltedFrame = mFrame;
777 running = false;
778
779 clearStrategy();
780
781 // Clean up, just in cased the one-shot breakpoints got confused
782 lineBreaks.disposeOneShots();
783
784 // Map the Truffle stack for this execution, ignore nested executions
785 // The top (current) frame is not produced by the iterator.
786 frames.add(new FrameDebugDescription(0, haltedNode, Truffle.getRuntime().getCurrentFrame()));
787 final int contextStackDepth = currentStackDepth() - contextStackBase;
788 final int[] frameCount = {1};
789 Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<FrameInstance>() {
790 @Override
791 public FrameInstance visitFrame(FrameInstance frameInstance) {
792 if (frameCount[0] < contextStackDepth) {
793 frames.add(new FrameDebugDescription(frameCount[0], frameInstance.getCallNode(), frameInstance));
794 frameCount[0] = frameCount[0] + 1;
795 return null;
796 }
797 return frameInstance;
798 }
799 });
800
801 if (TRACE) {
802 final String reason = haltReason == null ? "" : haltReason + "";
803 final String where = before ? "BEFORE" : "AFTER";
804 contextTrace("HALT %s : (%s) stack base=%d", where, reason, contextStackBase);
805 contextTrace("CURRENT STACK:");
806 printStack(OUT);
807 }
808
809 final List<String> recentWarnings = new ArrayList<>(warnings);
810 warnings.clear();
811
812 try {
813 // Pass control to the debug client with current execution suspended
814 debugClient.haltedAt(astNode, mFrame, recentWarnings);
815 // Debug client finished normally, execution resumes
816 // Presume that the client has set a new strategy (or default to Continue)
817 running = true;
818 } catch (KillException e) {
819 contextTrace("KILL");
820 throw e;
821 } finally {
822 haltedNode = null;
823 haltedFrame = null;
824 frames.clear();
825 }
826
827 }
828
829 List<FrameDebugDescription> getFrames() {
830 return Collections.unmodifiableList(frames);
831 }
832
833 void logWarning(String warning) {
834 warnings.add(warning);
835 }
836
837 // For tracing
838 private void printStack(PrintStream stream) {
839 getFrames();
840 if (frames == null) {
841 stream.println("<empty stack>");
842 } else {
843 // TODO (mlvdv) get visualizer via the (to be developed) Truffle langauge API
844 final Visualizer visualizer = debugClient.getExecutionContext().getVisualizer();
845
846 for (FrameDebugDescription frameDesc : frames) {
847 final StringBuilder sb = new StringBuilder(" frame " + Integer.toString(frameDesc.index()));
848 sb.append(":at " + visualizer.displaySourceLocation(frameDesc.node()));
849 sb.append(":in '" + visualizer.displayMethodName(frameDesc.node()) + "'");
850 stream.println(sb.toString());
851 }
852 }
853 }
854
855 void contextTrace(String format, Object... args) {
856 if (TRACE) {
857 final String srcName = (source != null) ? source.getName() : "no source";
858 DebugEngine.trace("<%d> %s (%s)", level, String.format(format, args), srcName);
859 }
860 }
861 }
862
863 // TODO (mlvdv) wish there were fast-path access to stack depth
864 /**
865 * Depth of current Truffle stack, including nested executions. Includes the top/current frame,
866 * which the standard iterator does not count: {@code 0} if no executions.
867 */
868 @TruffleBoundary
869 private static int currentStackDepth() {
870 final int[] count = {0};
871 Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Void>() {
872 @Override
873 public Void visitFrame(FrameInstance frameInstance) {
874 count[0] = count[0] + 1;
875 return null;
876 }
877 });
878 return count[0] == 0 ? 0 : count[0] + 1;
879
880 }
881 }