Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.oracle.truffle.api.debug.DebuggerSession Maven / Gradle / Ivy
Go to download
Truffle is a multi-language framework for executing dynamic languages
that achieves high performance when combined with Graal.
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api.debug;
import java.io.Closeable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Scope;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.debug.Breakpoint.BreakpointConditionFailure;
import com.oracle.truffle.api.debug.DebuggerNode.InputValuesProvider;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter.Builder;
import com.oracle.truffle.api.instrumentation.StandardTags.CallTag;
import com.oracle.truffle.api.instrumentation.StandardTags.RootTag;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
/**
* Represents a single debugging session of a Debugger.
*
* Session lifetime
*
*
* A debugging client {@linkplain Debugger#startSession(SuspendedCallback) requests} a new
* {@linkplain DebuggerSession session} from the {@linkplain Debugger Debugger}.
*
* A client uses a session to request suspension of guest language execution threads, for
* example by setting breakpoints or stepping.
*
* When a session suspends a guest language execution thread, it passes its client a new
* {@link SuspendedEvent} via synchronous {@linkplain SuspendedCallback callback} on the execution
* thread.
*
* A suspended guest language execution thread resumes language execution only after the client
* callback returns.
*
* Sessions that are no longer needed should be {@linkplain #close() closed}; a closed session
* has no further affect on engine execution.
*
*
*
* Debugging requests
*
* Session clients can manage guest language execution in several ways:
*
* {@linkplain #install(Breakpoint) Install} a newly created {@link Breakpoint}.
*
* {@linkplain #suspendNextExecution() Request} suspension of the next execution on the first
* thread that is encountered.
*
* Request a stepping action (e.g. {@linkplain SuspendedEvent#prepareStepInto(int) step into},
* {@linkplain SuspendedEvent#prepareStepOver(int) step over},
* {@linkplain SuspendedEvent#prepareKill() kill}) on a suspended execution thread, to take effect
* after the client callback returns.
*
*
*
* Event merging
*
* A session may suspend a guest language execution thread in response to more than one request from
* its client. For example:
*
* A stepping action may land where a breakpoint is installed.
* Multiple installed breakpoints may apply to a particular location.
*
* In such cases the client receives a single merged event. A call to
* {@linkplain SuspendedEvent#getBreakpoints()} lists all breakpoints (possibly none) that apply to
* the suspended event's location.
*
*
* Multiple sessions
*
* There can be multiple sessions associated with a single engine, which are independent of one
* another in the following ways:
*
* Breakpoints created by a session are not visible to clients of other sessions.
*
* A client receives no notification when guest language execution threads are suspended by
* sessions other than its own.
*
* Events are not merged across sessions. For example, when a guest language execution
* thread hits a location where two sessions have installed breakpoints, each session notifies its
* client with a new {@link SuspendedEvent} instance.
*
* Because all sessions can control engine execution, some interactions are inherently possible. For
* example:
*
* A session's client can {@linkplain SuspendedEvent#prepareKill() kill} an execution at just
* about any time.
* A session's client can starve execution by not returning from the synchronous
* {@linkplain SuspendedCallback callback} on the guest language execution thread.
*
*
*
* Usage example: {@link DebuggerSessionSnippets#example}
*
* @since 0.17
*/
/*
* Javadoc for package-protected APIs:
*
*
{@link #suspend(Thread)} suspends the next or current execution on a particular thread.
* {@link #suspendAll()} suspends the next or current execution on all threads.
*/
public final class DebuggerSession implements Closeable {
private static final AtomicInteger SESSIONS = new AtomicInteger(0);
static final Set ANCHOR_SET_BEFORE = Collections.singleton(SuspendAnchor.BEFORE);
static final Set ANCHOR_SET_AFTER = Collections.singleton(SuspendAnchor.AFTER);
static final Set ANCHOR_SET_ALL = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SuspendAnchor.BEFORE, SuspendAnchor.AFTER)));
private final Debugger debugger;
private final SuspendedCallback callback;
private final Set sourceElements;
private final boolean hasExpressionElement;
private final List breakpoints = Collections.synchronizedList(new ArrayList<>());
private EventBinding extends ExecutionEventNodeFactory> callBinding;
private EventBinding extends ExecutionEventNodeFactory> syntaxElementsBinding;
private EventBinding extends ExecutionEventNodeFactory> rootBinding;
final Set> allBindings = Collections.synchronizedSet(new HashSet<>());
private final ConcurrentHashMap currentSuspendedEventMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap strategyMap = new ConcurrentHashMap<>();
private volatile boolean suspendNext;
private volatile boolean suspendAll;
private final StableBoolean stepping = new StableBoolean(false);
private final StableBoolean ignoreLanguageContextInitialization = new StableBoolean(false);
private boolean includeInternal = false;
private Predicate sourceFilter;
private final StableBoolean alwaysHaltBreakpointsActive = new StableBoolean(true);
private final StableBoolean locationBreakpointsActive = new StableBoolean(true);
private final StableBoolean exceptionBreakpointsActive = new StableBoolean(true);
private final DebuggerExecutionLifecycle executionLifecycle;
final ThreadLocal threadSuspensions = new ThreadLocal<>();
private final int sessionId;
private volatile boolean closed;
DebuggerSession(Debugger debugger, SuspendedCallback callback, SourceElement... sourceElements) {
this.sessionId = SESSIONS.incrementAndGet();
this.debugger = debugger;
this.callback = callback;
switch (sourceElements.length) {
case 0:
this.sourceElements = Collections.emptySet();
break;
case 1:
this.sourceElements = Collections.singleton(sourceElements[0]);
break;
default:
this.sourceElements = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(sourceElements)));
break;
}
this.hasExpressionElement = this.sourceElements.contains(SourceElement.EXPRESSION);
if (Debugger.TRACE) {
trace("open with callback %s", callback);
}
addBindings(includeInternal, sourceFilter);
executionLifecycle = new DebuggerExecutionLifecycle(debugger);
}
private void trace(String msg, Object... parameters) {
Debugger.trace(this + ": " + msg, parameters);
}
/**
* {@inheritDoc}
*
* @since 0.17
*/
@Override
public String toString() {
return String.format("Session[id=%s]", sessionId);
}
/**
* Returns the {@link Debugger debugger} instance that this session is associated with. Can be
* used also after the session has already been closed.
*
* @since 0.17
*/
public Debugger getDebugger() {
return debugger;
}
/**
* Returns a language top scope. The top scopes have global validity and unlike
* {@link DebugStackFrame#getScope()} have no relation to the suspended location.
*
* @throws DebugException when guest language code throws an exception
* @since 0.30
*/
public DebugScope getTopScope(String languageId) throws DebugException {
LanguageInfo info = debugger.getEnv().getLanguages().get(languageId);
if (info == null) {
return null;
}
try {
Iterable scopes = debugger.getEnv().findTopScopes(languageId);
Iterator it = scopes.iterator();
if (!it.hasNext()) {
return null;
}
return new DebugScope(it.next(), it, debugger, info);
} catch (ThreadDeath td) {
throw td;
} catch (Throwable ex) {
throw new DebugException(debugger, ex, info, null, true, null);
}
}
/**
* Returns a polyglot scope - symbols explicitly exported by languages.
*
* @since 0.30
*/
public Map getExportedSymbols() {
return new AbstractMap() {
@Override
public Set> entrySet() {
Set> entries = new LinkedHashSet<>();
for (Map.Entry symbol : debugger.getEnv().getExportedSymbols().entrySet()) {
DebugValue value = new DebugValue.HeapValue(debugger, symbol.getKey(), symbol.getValue());
entries.add(new SimpleImmutableEntry<>(symbol.getKey(), value));
}
return Collections.unmodifiableSet(entries);
}
@Override
public DebugValue get(Object key) {
if (!(key instanceof String)) {
return null;
}
String name = (String) key;
Object value = debugger.getEnv().getExportedSymbols().get(name);
if (value == null) {
return null;
}
return new DebugValue.HeapValue(debugger, name, value);
}
};
}
/**
* Set a stepping suspension filter. Prepared steps skip code that does not match this filter.
*
* @since 0.26
*/
public void setSteppingFilter(SuspensionFilter steppingFilter) {
this.ignoreLanguageContextInitialization.set(steppingFilter.isIgnoreLanguageContextInitialization());
synchronized (this) {
boolean oldIncludeInternal = this.includeInternal;
this.includeInternal = steppingFilter.isInternalIncluded();
Predicate oldSourceFilter = this.sourceFilter;
this.sourceFilter = steppingFilter.getSourcePredicate();
if (oldIncludeInternal != this.includeInternal || oldSourceFilter != this.sourceFilter) {
removeBindings();
addBindings(this.includeInternal, this.sourceFilter);
}
}
}
/**
* Suspends the next execution on the first thread that is encountered. After the first thread
* was suspended no further executions are suspended unless {@link #suspendNextExecution()} is
* called again. If multiple threads are executing at the same time then there are no guarantees
* on which thread is going to be suspended. Will throw an {@link IllegalStateException} if the
* session is already closed.
*
* @since 0.17
*/
public synchronized void suspendNextExecution() {
if (Debugger.TRACE) {
trace("suspend next execution");
}
if (closed) {
throw new IllegalStateException("session closed");
}
suspendNext = true;
updateStepping();
}
/**
* Suspends the current or the next execution of a given thread. Will throw an
* {@link IllegalStateException} if the session is already closed.
*/
// TODO make part of public API as soon as PolyglotEngine is thread-safe
void suspend(Thread t) {
if (Debugger.TRACE) {
trace("suspend thread %s ", t);
}
if (closed) {
throw new IllegalStateException("session closed");
}
setSteppingStrategy(t, SteppingStrategy.createAlwaysHalt(), true);
}
/**
* Suspends the current or the next execution on all threads. All new executing threads will
* start suspended until {@link #resumeAll()} is called or the session is closed. Will throw an
* {@link IllegalStateException} if the session is already closed.
*/
// TODO make part of public API as soon as PolyglotEngine is thread-safe
synchronized void suspendAll() {
if (Debugger.TRACE) {
trace("suspend all threads");
}
if (closed) {
throw new IllegalStateException("session closed");
}
suspendAll = true;
// iterating concurrent hashmap should be save
for (Thread t : strategyMap.keySet()) {
SteppingStrategy s = strategyMap.get(t);
assert s != null;
if (s.isDone() || s.isConsumed()) {
setSteppingStrategy(t, SteppingStrategy.createAlwaysHalt(), false);
}
}
updateStepping();
}
/**
* Resumes all suspended executions that have not yet been notified.
*
* @since 0.17
*/
public synchronized void resumeAll() {
if (Debugger.TRACE) {
trace("resume all threads");
}
if (closed) {
throw new IllegalStateException("session closed");
}
clearStrategies();
}
/**
* Resumes the execution on a given thread if it has not been suspended yet.
*
* @param t the thread to resume
*/
// TODO make part of public API as soon as PolyglotEngine is thread-safe
synchronized void resume(Thread t) {
if (Debugger.TRACE) {
trace("resume threads", t);
}
if (closed) {
throw new IllegalStateException("session closed");
}
setSteppingStrategy(t, SteppingStrategy.createContinue(), true);
}
private synchronized void setSteppingStrategy(Thread thread, SteppingStrategy strategy, boolean updateStepping) {
if (closed) {
return;
}
assert strategy != null;
SteppingStrategy oldStrategy = this.strategyMap.put(thread, strategy);
if (oldStrategy != strategy) {
if (Debugger.TRACE) {
trace("set stepping for thread: %s with strategy: %s", thread, strategy);
}
if (updateStepping) {
updateStepping();
}
}
}
private synchronized void clearStrategies() {
suspendAll = false;
suspendNext = false;
strategyMap.clear();
updateStepping();
}
private SteppingStrategy getSteppingStrategy(Thread value) {
return strategyMap.get(value);
}
private void updateStepping() {
assert Thread.holdsLock(this);
boolean needsStepping = suspendNext || suspendAll;
if (!needsStepping) {
// iterating concurrent hashmap should be save
for (Thread t : strategyMap.keySet()) {
SteppingStrategy s = strategyMap.get(t);
assert s != null;
if (!s.isDone()) {
needsStepping = true;
break;
}
}
}
stepping.set(needsStepping);
}
@TruffleBoundary
void setThreadSuspendEnabled(boolean enabled) {
if (!enabled) {
// temporarily disable suspensions in the given thread
threadSuspensions.set(ThreadSuspension.DISABLED);
} else {
threadSuspensions.remove();
}
}
private void addBindings(boolean includeInternalCode, Predicate sFilter) {
if (syntaxElementsBinding == null && !sourceElements.isEmpty()) {
// The order of registered instrumentations matters.
// It's important to instrument root nodes first to intercept stack changes,
// then instrument statements, and
// call bindings need to be called after statements.
this.rootBinding = createBinding(includeInternalCode, sFilter, new ExecutionEventNodeFactory() {
@Override
public ExecutionEventNode create(EventContext context) {
return new RootSteppingDepthNode();
}
}, false, RootTag.class);
Class>[] syntaxTags = new Class>[this.sourceElements.size()];
Iterator elementsIterator = this.sourceElements.iterator();
for (int i = 0; i < syntaxTags.length; i++) {
syntaxTags[i] = elementsIterator.next().getTag();
}
this.syntaxElementsBinding = createBinding(includeInternalCode, sFilter, new ExecutionEventNodeFactory() {
@Override
public ExecutionEventNode create(EventContext context) {
return new SteppingNode(context);
}
}, hasExpressionElement, syntaxTags);
this.callBinding = createBinding(includeInternalCode, sFilter, new ExecutionEventNodeFactory() {
@Override
public ExecutionEventNode create(EventContext context) {
return new CallSteppingNode(context);
}
}, false, CallTag.class);
allBindings.add(syntaxElementsBinding);
allBindings.add(callBinding);
}
}
private EventBinding extends ExecutionEventNodeFactory> createBinding(boolean includeInternalCode, Predicate sFilter, ExecutionEventNodeFactory factory, boolean onInput,
Class>... tags) {
Builder builder = SourceSectionFilter.newBuilder().tagIs(tags);
builder.includeInternal(includeInternalCode);
if (sFilter != null) {
builder.sourceIs(new SourceSectionFilter.SourcePredicate() {
@Override
public boolean test(Source source) {
return sFilter.test(source);
}
});
}
SourceSectionFilter ssf = builder.build();
if (onInput) {
return debugger.getInstrumenter().attachExecutionEventFactory(ssf, ssf, factory);
} else {
return debugger.getInstrumenter().attachExecutionEventFactory(ssf, factory);
}
}
private void removeBindings() {
assert Thread.holdsLock(this);
if (syntaxElementsBinding != null) {
allBindings.remove(callBinding);
allBindings.remove(syntaxElementsBinding);
callBinding.dispose();
syntaxElementsBinding.dispose();
callBinding = null;
rootBinding.dispose();
rootBinding = null;
syntaxElementsBinding = null;
if (Debugger.TRACE) {
trace("disabled stepping");
}
}
}
Set getSourceElements() {
return sourceElements;
}
/**
* Closes the current debugging session and disposes all installed breakpoints.
*
* @since 0.17
*/
public synchronized void close() {
if (Debugger.TRACE) {
trace("close session");
}
if (closed) {
throw new IllegalStateException("session already closed");
}
clearStrategies();
removeBindings();
for (Breakpoint breakpoint : this.breakpoints) {
breakpoint.sessionClosed(this);
}
currentSuspendedEventMap.clear();
allBindings.clear();
debugger.disposedSession(this);
closed = true;
}
/**
* Returns all breakpoints {@link #install(com.oracle.truffle.api.debug.Breakpoint) installed}
* in this session, in the install order. The returned list contains a current snapshot of
* breakpoints, those that were {@link Breakpoint#dispose() disposed}, or
* {@link Debugger#install(com.oracle.truffle.api.debug.Breakpoint) installed on Debugger} are
* not included.
*
* @since 0.17
* @see Debugger#getBreakpoints()
*/
public List getBreakpoints() {
if (closed) {
throw new IllegalStateException("session already closed");
}
List b;
synchronized (this.breakpoints) {
// need to synchronize manually breakpoints are iterated which is not
// synchronized by default.
b = new ArrayList<>(this.breakpoints);
}
return Collections.unmodifiableList(b);
}
/**
* Set whether breakpoints are active in this session. This has no effect on breakpoints
* enabled/disabled state. Breakpoints need to be active to actually break the execution. The
* breakpoints are active by default.
*
* @param active true
to make all breakpoints active, false
to make
* all breakpoints inactive.
* @since 0.24
* @deprecated Use {@link #setBreakpointsActive(Breakpoint.Kind, boolean)} instead.
*/
@Deprecated
public void setBreakpointsActive(boolean active) {
for (Breakpoint.Kind kind : Breakpoint.Kind.VALUES) {
setBreakpointsActive(kind, active);
}
}
/**
* Set whether breakpoints of the given kind are active in this session. This has no effect on
* breakpoints enabled/disabled state. Breakpoints need to be active to actually break the
* execution. The breakpoints are active by default.
*
* @param breakpointKind the kind of breakpoints to activate/deactivate
* @param active true
to make breakpoints active, false
to make
* breakpoints inactive.
* @since 1.0
*/
public void setBreakpointsActive(Breakpoint.Kind breakpointKind, boolean active) {
switch (breakpointKind) {
case SOURCE_LOCATION:
locationBreakpointsActive.set(active);
break;
case EXCEPTION:
exceptionBreakpointsActive.set(active);
break;
case HALT_INSTRUCTION:
alwaysHaltBreakpointsActive.set(active);
break;
default:
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Unhandled breakpoint kind: " + breakpointKind);
}
}
/**
* Test whether breakpoints are active in this session. Breakpoints do not break execution when
* not active.
*
* @since 0.24
* @deprecated Use {@link #isBreakpointsActive(Breakpoint.Kind)} instead.
*/
@Deprecated
public boolean isBreakpointsActive() {
for (Breakpoint.Kind kind : Breakpoint.Kind.VALUES) {
if (isBreakpointsActive(kind)) {
return true;
}
}
return false;
}
/**
* Test whether breakpoints of the given kind are active in this session. Breakpoints do not
* break execution when not active.
*
* @param breakpointKind the kind of breakpoints to test
* @since 1.0
*/
public boolean isBreakpointsActive(Breakpoint.Kind breakpointKind) {
switch (breakpointKind) {
case SOURCE_LOCATION:
return locationBreakpointsActive.get();
case EXCEPTION:
return exceptionBreakpointsActive.get();
case HALT_INSTRUCTION:
return alwaysHaltBreakpointsActive.get();
default:
CompilerDirectives.transferToInterpreter();
throw new IllegalStateException("Unhandled breakpoint kind: " + breakpointKind);
}
}
/**
* Adds a new breakpoint to this session and makes it capable of suspending execution.
*
* The breakpoint suspends execution by making a {@link SuspendedCallback callback} to this
* session, together with an event description that includes
* {@linkplain SuspendedEvent#getBreakpoints() which breakpoint(s)} were hit.
*
* @param breakpoint a new breakpoint
* @return the installed breakpoint
* @throws IllegalStateException if the session has been closed
*
* @since 0.17
*/
public synchronized Breakpoint install(Breakpoint breakpoint) {
install(breakpoint, false);
return breakpoint;
}
synchronized void install(Breakpoint breakpoint, boolean global) {
if (closed) {
if (!global) {
throw new IllegalStateException("Debugger session is already closed. Cannot install new breakpoints.");
} else {
return;
}
}
if (!breakpoint.install(this, !global)) {
return;
}
if (!global) { // Do not keep global breakpoints in the list
this.breakpoints.add(breakpoint);
}
if (Debugger.TRACE) {
trace("installed session breakpoint %s", breakpoint);
}
}
synchronized void disposeBreakpoint(Breakpoint breakpoint) {
breakpoints.remove(breakpoint);
if (Debugger.TRACE) {
trace("disposed session breakpoint %s", breakpoint);
}
}
/**
* Set a {@link DebugContextsListener listener} to be notified about changes in contexts in
* guest language application. One listener can be set at a time, call with null
to
* remove the current listener.
*
* @param listener a listener to receive the context events, or null
to reset it
* @param includeActiveContexts whether or not this listener should be notified for present
* active contexts
* @since 0.30
*/
public void setContextsListener(DebugContextsListener listener, boolean includeActiveContexts) {
executionLifecycle.setContextsListener(listener, includeActiveContexts);
}
/**
* Set a {@link DebugThreadsListener listener} to be notified about changes in threads in guest
* language application. One listener can be set at a time, call with null
to
* remove the current listener.
*
* @param listener a listener to receive the context events
* @param includeInitializedThreads whether or not this listener should be notified for present
* initialized threads
* @since 0.30
*/
public void setThreadsListener(DebugThreadsListener listener, boolean includeInitializedThreads) {
executionLifecycle.setThreadsListener(listener, includeInitializedThreads);
}
@TruffleBoundary
void notifyCallback(DebuggerNode source, MaterializedFrame frame, SuspendAnchor suspendAnchor,
InputValuesProvider inputValuesProvider, Object returnValue, DebugException exception,
BreakpointConditionFailure conditionFailure) {
ThreadSuspension suspensionDisabled = threadSuspensions.get();
if (suspensionDisabled != null && !suspensionDisabled.enabled) {
return;
}
// SuspensionFilter:
if (source.isStepNode()) {
if (ignoreLanguageContextInitialization.get() && !source.getContext().isLanguageContextInitialized()) {
return;
}
}
Thread currentThread = Thread.currentThread();
SuspendedEvent event = currentSuspendedEventMap.get(currentThread);
if (event != null) {
if (Debugger.TRACE) {
trace("ignored suspended reason: recursive from source:%s context:%s location:%s", source, source.getContext(), source.getSuspendAnchors());
}
// avoid recursive suspensions in non legacy mode.
return;
}
if (source.consumeIsDuplicate(this)) {
if (Debugger.TRACE) {
trace("ignored suspended reason: duplicate from source:%s context:%s location:%s", source, source.getContext(), source.getSuspendAnchors());
}
return;
}
// only the first DebuggerNode for a source location and thread will reach here.
// mark all other nodes at this source location as duplicates
List nodes = collectDebuggerNodes(source, suspendAnchor);
for (DebuggerNode node : nodes) {
if (node == source) {
// for the current one we won't call isDuplicate
continue;
}
node.markAsDuplicate(this);
}
SteppingStrategy s = getSteppingStrategy(currentThread);
if (suspendNext) {
synchronized (this) {
// double checked locking to avoid more than one suspension
if (suspendNext) {
s = SteppingStrategy.createAlwaysHalt();
setSteppingStrategy(currentThread, s, true);
suspendNext = false;
}
}
}
if (s == null) {
// a new Thread just appeared
s = notifyNewThread(currentThread);
}
Map breakpointFailures = null;
if (conditionFailure != null) {
breakpointFailures = new HashMap<>();
Breakpoint fb = conditionFailure.getBreakpoint();
if (fb.isGlobal()) {
fb = fb.getROWrapper();
}
breakpointFailures.put(fb, conditionFailure.getConditionFailure());
}
List breaks = null;
for (DebuggerNode node : nodes) {
Breakpoint breakpoint = node.getBreakpoint();
if (breakpoint == null || !isBreakpointsActive(breakpoint.getKind())) {
continue; // not a breakpoint node
}
boolean hit = true;
BreakpointConditionFailure failure = null;
try {
hit = breakpoint.notifyIndirectHit(source, node, frame, exception);
} catch (BreakpointConditionFailure e) {
failure = e;
}
if (hit) {
if (breaks == null) {
breaks = new ArrayList<>();
}
breaks.add(breakpoint.isGlobal() ? breakpoint.getROWrapper() : breakpoint);
}
if (failure != null) {
if (breakpointFailures == null) {
breakpointFailures = new HashMap<>();
}
Breakpoint fb = failure.getBreakpoint();
if (fb.isGlobal()) {
fb = fb.getROWrapper();
}
breakpointFailures.put(fb, failure.getConditionFailure());
}
}
boolean hitStepping = s.step(this, source.getContext(), suspendAnchor);
boolean hitBreakpoint = breaks != null && !breaks.isEmpty();
if (hitStepping || hitBreakpoint) {
s.consume();
doSuspend(SuspendedContext.create(source.getContext(), debugger.getEnv()), suspendAnchor, frame, source, inputValuesProvider, returnValue, exception, breaks, breakpointFailures);
} else {
if (Debugger.TRACE) {
trace("ignored suspended reason: strategy(%s) from source:%s context:%s location:%s", s, source, source.getContext(), source.getSuspendAnchors());
}
}
if (s.isKill()) { // ComposedStrategy can become kill
throw new KillException(source.getContext().getInstrumentedNode());
}
}
private static void clearFrame(RootNode root, MaterializedFrame frame) {
FrameDescriptor descriptor = frame.getFrameDescriptor();
if (root.getFrameDescriptor() == descriptor) {
// Clear only those frames that correspond to the current root
Object value = descriptor.getDefaultValue();
for (FrameSlot slot : descriptor.getSlots()) {
frame.setObject(slot, value);
}
}
}
private void notifyUnwindCallback(MaterializedFrame frame, InsertableNode insertableNode) {
Thread currentThread = Thread.currentThread();
SteppingStrategy s = getSteppingStrategy(currentThread);
// We must have an active stepping strategy on this thread when unwind finished
assert s != null;
assert s.isUnwind();
assert s.step(this, null, null);
s.consume();
// Clear the frame that is to be re-entered
clearFrame(((Node) insertableNode).getRootNode(), frame);
// Fake the caller context
class Caller {
final Node node;
final MaterializedFrame frame;
Caller(FrameInstance frameInstance) {
this.node = frameInstance.getCallNode();
this.frame = frameInstance.getFrame(FrameAccess.MATERIALIZE).materialize();
}
}
Caller[] nearestCaller = new Caller[1];
Caller caller = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() {
private int depth = 0;
@Override
public Caller visitFrame(FrameInstance frameInstance) {
// we stop at eval root stack frames
if (!SuspendedEvent.isEvalRootStackFrame(DebuggerSession.this, frameInstance) && (depth++ == 0)) {
return null;
}
Node callNode = frameInstance.getCallNode();
// Prefer call node with a source section
if (callNode != null && callNode.getEncapsulatingSourceSection() != null) {
return new Caller(frameInstance);
} else {
if (nearestCaller[0] == null) {
nearestCaller[0] = new Caller(frameInstance);
}
return null;
}
}
});
if (caller == null) {
caller = nearestCaller[0];
}
SuspendedContext context = SuspendedContext.create(caller.node, ((SteppingStrategy.Unwind) s).unwind);
doSuspend(context, SuspendAnchor.AFTER, caller.frame, insertableNode, null, null, null, Collections.emptyList(), Collections.emptyMap());
}
private void doSuspend(SuspendedContext context, SuspendAnchor suspendAnchor, MaterializedFrame frame,
InsertableNode insertableNode, InputValuesProvider inputValuesProvider, Object returnValue, DebugException exception,
List breaks, Map conditionFailures) {
CompilerAsserts.neverPartOfCompilation();
Thread currentThread = Thread.currentThread();
SuspendedEvent suspendedEvent;
try {
suspendedEvent = new SuspendedEvent(this, currentThread, context, frame, suspendAnchor, insertableNode, inputValuesProvider, returnValue, exception, breaks, conditionFailures);
if (exception != null) {
exception.setSuspendedEvent(suspendedEvent);
}
currentSuspendedEventMap.put(currentThread, suspendedEvent);
try {
callback.onSuspend(suspendedEvent);
} finally {
currentSuspendedEventMap.remove(currentThread);
/*
* In case the debug client did not behave and did store the suspended event.
*/
suspendedEvent.clearLeakingReferences();
}
} catch (Throwable t) {
// let the instrumentation handle this
throw t;
}
if (closed) {
// session got closed in the meantime
return;
}
SteppingStrategy strategy = suspendedEvent.getNextStrategy();
if (!strategy.isKill()) {
// suspend(...) has been called during SuspendedEvent notification. this is only
// possible in non-legacy mode.
SteppingStrategy currentStrategy = getSteppingStrategy(currentThread);
if (currentStrategy != null && !currentStrategy.isConsumed()) {
strategy = currentStrategy;
}
}
strategy.initialize(context, suspendAnchor);
if (Debugger.TRACE) {
trace("end suspend with strategy %s at %s location %s", strategy, context, suspendAnchor);
}
setSteppingStrategy(currentThread, strategy, true);
if (strategy.isKill()) {
throw new KillException(context.getInstrumentedNode());
} else if (strategy.isUnwind()) {
ThreadDeath unwind = context.createUnwind(null, rootBinding);
((SteppingStrategy.Unwind) strategy).unwind = unwind;
throw unwind;
}
}
private List collectDebuggerNodes(DebuggerNode source, SuspendAnchor suspendAnchor) {
EventContext context = source.getContext();
List nodes = new ArrayList<>();
nodes.add(source);
Iterator nodesIterator = context.lookupExecutionEventNodes(allBindings);
if (SuspendAnchor.BEFORE.equals(suspendAnchor)) {
// We collect nodes following the source (these nodes remain to be executed)
boolean after = false;
while (nodesIterator.hasNext()) {
DebuggerNode node = (DebuggerNode) nodesIterator.next();
if (after) {
if (node.isActiveAt(suspendAnchor)) {
nodes.add(node);
}
} else {
after = node == source;
}
}
} else {
// We collect nodes preceding the source (these nodes remain to be executed)
while (nodesIterator.hasNext()) {
DebuggerNode node = (DebuggerNode) nodesIterator.next();
if (node == source) {
break;
}
if (node.isActiveAt(suspendAnchor)) {
nodes.add(node);
}
}
}
return nodes;
}
private synchronized SteppingStrategy notifyNewThread(Thread currentThread) {
SteppingStrategy s = getSteppingStrategy(currentThread);
// double checked locking
if (s == null) {
if (suspendAll) {
// all suspended
s = SteppingStrategy.createAlwaysHalt();
} else {
// not suspended continue execution for this thread
s = SteppingStrategy.createContinue();
}
setSteppingStrategy(currentThread, s, true);
}
assert s != null;
return s;
}
/**
* Evaluates a snippet of code in a halted execution context. Assumes frame is part of the
* current execution stack, behavior is undefined if not.
*
* @param ev event notification where execution is halted
* @param code text of the code to be executed
* @param frameInstance frame where execution is halted
* @throws DebugException
*/
static Object evalInContext(SuspendedEvent ev, String code, FrameInstance frameInstance) throws DebugException {
Node node;
MaterializedFrame frame;
if (frameInstance == null) {
node = ev.getContext().getInstrumentedNode();
frame = ev.getMaterializedFrame();
} else {
node = frameInstance.getCallNode();
frame = frameInstance.getFrame(FrameAccess.MATERIALIZE).materialize();
}
try {
return evalInContext(ev, node, frame, code);
} catch (KillException kex) {
throw new DebugException(ev.getSession().getDebugger(), "Evaluation was killed.", null, true, null);
} catch (Throwable ex) {
LanguageInfo language = null;
RootNode root = node.getRootNode();
if (root != null) {
language = root.getLanguageInfo();
}
throw new DebugException(ev.getSession().getDebugger(), ex, language, null, true, null);
}
}
private static Object evalInContext(SuspendedEvent ev, Node node, MaterializedFrame frame, String code) {
RootNode rootNode = node.getRootNode();
if (rootNode == null) {
throw new IllegalArgumentException("Cannot evaluate in context using a node that is not yet adopated using a RootNode.");
}
LanguageInfo info = rootNode.getLanguageInfo();
if (info == null) {
throw new IllegalArgumentException("Cannot evaluate in context using a without an associated TruffleLanguage.");
}
if (!info.isInteractive()) {
throw new IllegalStateException("Can not evaluate in a non-interactive language.");
}
final Source source = Source.newBuilder(info.getId(), code, "eval in context").build();
ExecutableNode fragment = ev.getSession().getDebugger().getEnv().parseInline(source, node, frame);
if (fragment != null) {
ev.getInsertableNode().setParentOf(fragment);
return fragment.execute(frame);
} else {
return Debugger.ACCESSOR.evalInContext(source, node, frame);
}
}
static final class ThreadSuspension {
static final ThreadSuspension ENABLED = new ThreadSuspension(true);
static final ThreadSuspension DISABLED = new ThreadSuspension(false);
boolean enabled;
ThreadSuspension(boolean enabled) {
this.enabled = enabled;
}
}
private final class SteppingNode extends DebuggerNode implements InputValuesProvider {
SteppingNode(EventContext context) {
super(context);
}
@Override
EventBinding> getBinding() {
return syntaxElementsBinding;
}
@Override
boolean isStepNode() {
return true;
}
@Override
protected void onEnter(VirtualFrame frame) {
if (stepping.get()) {
doStepBefore(frame.materialize());
}
}
@Override
protected void onReturnValue(VirtualFrame frame, Object result) {
if (stepping.get()) {
doStepAfter(frame.materialize(), result);
}
}
@Override
protected void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (stepping.get()) {
doStepAfter(frame.materialize(), exception);
}
}
@Override
protected void onInputValue(VirtualFrame frame, EventContext inputContext, int inputIndex, Object inputValue) {
if (stepping.get() && hasExpressionElement) {
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null && steppingStrategy.isCollectingInputValues()) {
saveInputValue(frame, inputIndex, inputValue);
}
}
}
@TruffleBoundary
private void doStepBefore(MaterializedFrame frame) {
SuspendAnchor anchor = SuspendAnchor.BEFORE;
SteppingStrategy steppingStrategy;
if (suspendNext || suspendAll || (steppingStrategy = getSteppingStrategy(Thread.currentThread())) != null && steppingStrategy.isActiveOnStepTo(context, anchor)) {
notifyCallback(this, frame, anchor, null, null, null, null);
}
}
@TruffleBoundary
private void doStepAfter(MaterializedFrame frame, Object result) {
SuspendAnchor anchor = SuspendAnchor.AFTER;
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null && steppingStrategy.isActiveOnStepTo(context, anchor)) {
notifyCallback(this, frame, anchor, this, result, null, null);
}
}
@Override
public Object[] getDebugInputValues(MaterializedFrame frame) {
return getSavedInputValues(frame);
}
@Override
Set getSuspendAnchors() {
return DebuggerSession.ANCHOR_SET_ALL;
}
@Override
boolean isActiveAt(SuspendAnchor anchor) {
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null) {
return steppingStrategy.isActive(context, anchor);
} else {
return false;
}
}
}
private final class CallSteppingNode extends DebuggerNode {
CallSteppingNode(EventContext context) {
super(context);
}
@Override
EventBinding> getBinding() {
return callBinding;
}
@Override
boolean isStepNode() {
return true;
}
@Override
public void onReturnValue(VirtualFrame frame, Object result) {
if (stepping.get()) {
doReturn(frame.materialize(), result);
}
}
@Override
public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (stepping.get()) {
doReturn(frame.materialize(), null);
}
}
@TruffleBoundary
private void doReturn(MaterializedFrame frame, Object result) {
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());
if (steppingStrategy != null && steppingStrategy.isStopAfterCall()) {
notifyCallback(this, frame, SuspendAnchor.AFTER, null, result, null, null);
}
}
@Override
Set getSuspendAnchors() {
return DebuggerSession.ANCHOR_SET_AFTER;
}
@Override
boolean isActiveAt(SuspendAnchor anchor) {
return SuspendAnchor.AFTER == anchor;
}
}
private final class RootSteppingDepthNode extends ExecutionEventNode implements InsertableNode {
@Override
protected void onEnter(VirtualFrame frame) {
if (stepping.get()) {
doEnter();
}
}
@Override
public void onReturnValue(VirtualFrame frame, Object result) {
if (stepping.get()) {
doReturn();
}
}
@Override
public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
if (stepping.get()) {
doReturn();
}
}
@Override
protected Object onUnwind(VirtualFrame frame, Object info) {
if (stepping.get()) {
return doUnwind(frame.materialize());
} else {
return null;
}
}
@Override
public void setParentOf(Node child) {
insert(child);
}
@TruffleBoundary
private void doEnter() {
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());
if (steppingStrategy != null) {
steppingStrategy.notifyCallEntry();
}
}
@TruffleBoundary
private void doReturn() {
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());
if (steppingStrategy != null) {
steppingStrategy.notifyCallExit();
}
}
@TruffleBoundary
private Object doUnwind(MaterializedFrame frame) {
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());
if (steppingStrategy != null) {
Object info = steppingStrategy.notifyOnUnwind();
if (info == ProbeNode.UNWIND_ACTION_REENTER) {
notifyUnwindCallback(frame, this);
}
return info;
} else {
return null;
}
}
}
/**
* Helper class that uses an assumption to switch between stepping mode and non-stepping mode
* efficiently.
*/
static final class StableBoolean {
@CompilationFinal private volatile Assumption unchanged;
@CompilationFinal private volatile boolean value;
StableBoolean(boolean initialValue) {
this.value = initialValue;
this.unchanged = Truffle.getRuntime().createAssumption("Unchanged boolean");
}
boolean get() {
if (unchanged.isValid()) {
return value;
} else {
CompilerDirectives.transferToInterpreterAndInvalidate();
return value;
}
}
void set(boolean value) {
if (this.value != value) {
this.value = value;
Assumption old = this.unchanged;
unchanged = Truffle.getRuntime().createAssumption("Unchanged boolean");
old.invalidate();
}
}
}
}
class DebuggerSessionSnippets {
@SuppressFBWarnings("")
public void example() {
// @formatter:off
TruffleInstrument.Env instrumentEnv = null;
// BEGIN: DebuggerSessionSnippets#example
try (DebuggerSession session = Debugger.find(instrumentEnv).
startSession(new SuspendedCallback() {
public void onSuspend(SuspendedEvent event) {
// step into the next event
event.prepareStepInto(1);
}
})) {
Source someCode = Source.newBuilder("...",
"...", "example").build();
// install line breakpoint
session.install(Breakpoint.newBuilder(someCode).lineIs(3).build());
}
// END: DebuggerSessionSnippets#example
// @formatter:on
}
}