com.oracle.truffle.api.debug.SuspendedEvent Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.debug;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.debug.DebuggerSession.SteppingLocation;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.StandardTags.StatementTag;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
/**
* Access for {@link Debugger} clients to the state of a guest language execution thread that has
* been suspended, for example by a {@link Breakpoint} or stepping action.
*
*
Event lifetime
*
* - A {@link DebuggerSession} {@link Instrumenter instruments} guest language code in order to
* implement {@linkplain Breakpoint breakpoints}, stepping actions, or other debugging actions on
* behalf of the session's {@linkplain Debugger debugger} client.
*
* - A session may choose to suspend a guest language execution thread when it receives
* (synchronous) notification on the execution thread that it has reached an AST location
* instrumented by the session.
*
* - The session passes a new {@link SuspendedEvent} to the debugger client (synchronously) in a
* {@linkplain SuspendedCallback#onSuspend(SuspendedEvent) callback} on the guest language execution
* thread.
*
* - Clients may access certain event state only on the execution thread where the event was
* created and notification received; access from other threads can throws
* {@link IllegalStateException}. Please see the javadoc of the individual method for details.
*
* - A suspended thread resumes guest language execution after the client callback returns and the
* thread unwinds back to instrumentation code in the AST.
*
* - All event methods throw {@link IllegalStateException} after the suspended thread resumes
* guest language execution.
*
*
* Access to execution state
*
*
* - Method {@link #getStackFrames()} describes the suspended thread's location in guest language
* code. This information becomes unusable beyond the lifetime of the event and must not be stored.
*
*
* - Method {@link #getReturnValue()} describes a local result when the thread is suspended just
* {@link #isHaltedBefore() after} a frame.
*
*
* Next debugging action
*
* Clients use the following methods to request the debugging action(s) that will take effect when
* the event's thread resumes guest language execution. All prepare requests accumulate until
* resumed.
*
* - {@link #prepareStepInto(int)}
* - {@link #prepareStepOut(int)}
* - {@link #prepareStepOver(int)}
* - {@link #prepareKill()}
* - {@link #prepareContinue()}
*
* If no debugging action is requested then {@link #prepareContinue() continue} is assumed.
*
*
* @since 0.9
*/
public final class SuspendedEvent {
private final SourceSection sourceSection;
private final SteppingLocation location;
private final Thread thread;
private DebuggerSession session;
private EventContext context;
private MaterializedFrame materializedFrame;
private List breakpoints;
private Object returnValue;
private volatile boolean disposed;
private volatile SteppingStrategy nextStrategy;
private final Map conditionFailures;
private DebugStackFrameIterable cachedFrames;
SuspendedEvent(DebuggerSession session, Thread thread, EventContext context, MaterializedFrame frame, SteppingLocation location, Object returnValue,
List breakpoints, Map conditionFailures) {
this.session = session;
this.context = context;
this.location = location;
this.materializedFrame = frame;
this.returnValue = returnValue;
this.conditionFailures = conditionFailures;
this.breakpoints = breakpoints == null ? Collections. emptyList() : Collections. unmodifiableList(breakpoints);
this.thread = thread;
this.sourceSection = context.getInstrumentedSourceSection();
}
boolean isDisposed() {
return disposed;
}
void clearLeakingReferences() {
this.disposed = true;
// cleanup data for potential memory leaks
this.returnValue = null;
this.breakpoints = null;
this.materializedFrame = null;
this.cachedFrames = null;
this.session = null;
this.context = null;
}
void verifyValidState(boolean allowDifferentThread) {
if (disposed) {
throw new IllegalStateException("Not in a suspended state.");
}
if (!allowDifferentThread && Thread.currentThread() != thread) {
throw new IllegalStateException("Illegal thread access.");
}
}
SteppingStrategy getNextStrategy() {
SteppingStrategy strategy = nextStrategy;
if (strategy == null) {
return SteppingStrategy.createContinue();
}
return strategy;
}
private synchronized void setNextStrategy(SteppingStrategy nextStrategy) {
verifyValidState(true);
if (this.nextStrategy == null) {
this.nextStrategy = nextStrategy;
} else if (this.nextStrategy.isKill()) {
throw new IllegalStateException("Calls to prepareKill() cannot be followed by any other preparation call.");
} else if (this.nextStrategy.isDone()) {
throw new IllegalStateException("Calls to prepareContinue() cannot be followed by any other preparation call.");
} else if (this.nextStrategy.isComposable()) {
this.nextStrategy.add(nextStrategy);
} else {
this.nextStrategy = SteppingStrategy.createComposed(this.nextStrategy, nextStrategy);
}
}
/**
* Returns the debugger session this suspended event was created for.
*
* This method is thread-safe.
*
* @since 0.17
*/
public DebuggerSession getSession() {
verifyValidState(true);
return session;
}
Thread getThread() {
return thread;
}
EventContext getContext() {
return context;
}
SteppingLocation getLocation() {
return location;
}
/**
* Returns the guest language source section of the AST node before/after the execution is
* suspended. Returns null
if no source section information is available.
*
* This method is thread-safe.
*
* @since 0.17
*/
public SourceSection getSourceSection() {
verifyValidState(true);
return sourceSection;
}
/**
* Returns true
if the execution is suspended before executing a guest language
* source location. Returns false
if it was suspended after.
*
* This method is thread-safe..
*
* @since 0.14
*/
public boolean isHaltedBefore() {
verifyValidState(true);
return location == SteppingLocation.BEFORE_STATEMENT;
}
/**
* Test if the language context of the source of the event is initialized.
*
* @since 0.26
*/
public boolean isLanguageContextInitialized() {
verifyValidState(true);
return context.isLanguageContextInitialized();
}
/**
* Returns the return value of the currently executed source location. Returns null
* if the execution is suspended {@link #isHaltedBefore() before} a guest language location. The
* returned value is null
if an exception occurred during execution of the
* instrumented statement. The debug value remains valid event if the current execution was
* suspend.
*
* This method is not thread-safe and will throw an {@link IllegalStateException} if called on
* another thread than it was created with.
*
* @since 0.17
*/
public DebugValue getReturnValue() {
return getTopStackFrame().wrapHeapValue(returnValue);
}
// TODO CHumer: we also want to provide access to guest language errors. The API for that is not
// yet ready.
MaterializedFrame getMaterializedFrame() {
return materializedFrame;
}
/**
* Returns the cause of failure, if any, during evaluation of a breakpoint's
* {@linkplain Breakpoint#setCondition(String) condition}.
*
*
* This method is thread-safe.
*
* @param breakpoint a breakpoint associated with this event
* @return the cause of condition failure
*
* @since 0.17
*/
public Throwable getBreakpointConditionException(Breakpoint breakpoint) {
verifyValidState(true);
if (conditionFailures == null) {
return null;
}
return conditionFailures.get(breakpoint);
}
/**
* Returns the {@link Breakpoint breakpoints} that individually would cause the "hit" where
* execution is suspended.
*
* This method is thread-safe.
*
* @return an unmodifiable list of breakpoints
*
* @since 0.17
*/
public List getBreakpoints() {
verifyValidState(true);
return breakpoints;
}
/**
* Returns the topmost stack frame returned by {@link #getStackFrames()}.
*
* This method is not thread-safe and will throw an {@link IllegalStateException} if called on
* another thread than it was created with.
*
* @see #getStackFrames()
* @since 0.17
*/
public DebugStackFrame getTopStackFrame() {
// there must be always a top stack frame.
return getStackFrames().iterator().next();
}
/**
* Returns a list of guest language stack frame objects that indicate the current guest language
* location. There is always at least one, the topmost, stack frame available. The returned
* stack frames are usable only during {@link SuspendedCallback#onSuspend(SuspendedEvent)
* suspend} and should not be stored permanently.
*
*
* This method is not thread-safe and will throw an {@link IllegalStateException} if called on
* another thread than it was created with.
*
* @since 0.17
*/
public Iterable getStackFrames() {
verifyValidState(false);
if (cachedFrames == null) {
cachedFrames = new DebugStackFrameIterable();
}
return cachedFrames;
}
private static boolean isEvalRootStackFrame(SuspendedEvent event, FrameInstance instance) {
CallTarget target = instance.getCallTarget();
RootNode root = null;
if (target instanceof RootCallTarget) {
root = ((RootCallTarget) target).getRootNode();
}
if (root != null && event.getSession().getDebugger().getEnv().isEngineRoot(root)) {
return true;
}
return false;
}
/**
* Prepare to execute in Continue mode when guest language program execution resumes. In this
* mode execution will continue until either:
*
* - execution arrives at a node to which an enabled breakpoint is attached,
* or:
* - execution completes.
*
*
* This method is thread-safe and the prepared Continue mode is appended to any other previously
* prepared modes. No further modes can be prepared after continue.
*
* @throws IllegalStateException when {@link #prepareContinue() continue} or
* {@link #prepareKill() kill} is prepared already.
* @since 0.9
*/
public void prepareContinue() {
setNextStrategy(SteppingStrategy.createContinue());
}
/**
* Prepare to execute in StepInto mode when guest language program execution
* resumes. In this mode:
*
* - Execution, when resumed, continues until either:
*
* - execution arrives at at the nth node (specified by {@code stepCount}) with the
* tag {@link StatementTag}, or
* - execution arrives at a {@link Breakpoint}, or
* - execution completes.
*
*
* - The mode persists only until either:
*
* - program execution resumes and then halts, at which time the mode reverts to
* {@linkplain #prepareContinue() Continue}, or
* - execution completes, at which time the mode reverts to {@linkplain #prepareContinue()
* Continue}.
*
*
* - A breakpoint set at a location where execution would halt is treated specially to avoid a
* "double halt" during stepping:
*
* - execution halts only once at the location;
* - the halt counts as a breakpoint {@link Breakpoint#getHitCount() hit};
* - the mode reverts to {@linkplain #prepareContinue() Continue}, as if there were no
* breakpoint; and
* - this special treatment applies only for breakpoints created before the
* mode is set.
*
*
*
* This method is thread-safe and the prepared StepInto mode is appended to any other previously
* prepared modes.
*
* @param stepCount the number of times to perform StepInto before halting
* @return this event instance for an easy concatenation of method calls
* @throws IllegalArgumentException if {@code stepCount <= 0}
* @throws IllegalStateException when {@link #prepareContinue() continue} or
* {@link #prepareKill() kill} is prepared already.
* @since 0.9
*/
public SuspendedEvent prepareStepInto(int stepCount) {
if (stepCount <= 0) {
throw new IllegalArgumentException("stepCount must be > 0");
}
setNextStrategy(SteppingStrategy.createStepInto(stepCount));
return this;
}
/**
* Prepare to execute in StepOut mode when guest language program execution
* resumes. In this mode:
*
* - Execution, when resumed, continues until either:
*
* - execution arrives at the nearest enclosing call site on the stack, or
*
* - execution arrives at a {@link Breakpoint}, or
* - execution completes.
*
*
* - The mode persists only until either:
*
* - program execution resumes and then halts, at which time the mode reverts to
* {@linkplain #prepareContinue() Continue}, or
* - execution completes, at which time the mode reverts to {@linkplain #prepareContinue()
* Continue}.
*
*
*
*
* This method is thread-safe.
*
* @since 0.9
* @deprecated Use {@link #prepareStepOut(int)} instead.
*/
@Deprecated
public void prepareStepOut() {
prepareStepOut(1);
}
/**
* Prepare to execute in StepOut mode when guest language program execution
* resumes. In this mode:
*
* - Execution, when resumed, continues until either:
*
* - execution arrives at the nth enclosing call site on the stack (specified by
* {@code stepCount}), or
* - execution arrives at a {@link Breakpoint}, or
* - execution completes.
*
*
* - The mode persists only until either:
*
* - program execution resumes and then halts, at which time the mode reverts to
* {@linkplain #prepareContinue() Continue}, or
* - execution completes, at which time the mode reverts to {@linkplain #prepareContinue()
* Continue}.
*
*
*
*
* This method is thread-safe and the prepared StepOut mode is appended to any other previously
* prepared modes.
*
* @param stepCount the number of times to perform StepOver before halting
* @return this event instance for an easy concatenation of method calls
* @throws IllegalArgumentException if {@code stepCount <= 0}
* @throws IllegalStateException when {@link #prepareContinue() continue} or
* {@link #prepareKill() kill} is prepared already.
* @since 0.26
*/
public SuspendedEvent prepareStepOut(int stepCount) {
if (stepCount <= 0) {
throw new IllegalArgumentException("stepCount must be > 0");
}
setNextStrategy(SteppingStrategy.createStepOut(stepCount));
return this;
}
/**
* Prepare to execute in StepOver mode when guest language program execution resumes. In this
* mode:
*
* - Execution, when resumed, continues until either:
*
* - execution arrives at at the nth node (specified by {@code stepCount}) with the
* tag {@link StatementTag}, ignoring nodes nested in function/method calls, or
*
* - execution arrives at a {@link Breakpoint}, or
* - execution completes.
*
*
* - The mode persists only until either:
*
* - program execution resumes and then halts, at which time the mode reverts to
* {@linkplain #prepareContinue() Continue}, or
* - execution completes, at which time the mode reverts to {@linkplain #prepareContinue()
* Continue}.
*
*
* - A breakpoint set at a location where execution would halt is treated specially to avoid a
* "double halt" during stepping:
*
* - execution halts only once at the location;
* - the halt counts as a breakpoint {@link Breakpoint#getHitCount() hit};
* - the mode reverts to {@linkplain #prepareContinue() Continue}, as if there were no
* breakpoint; and
* - this special treatment applies only for breakpoints created before the
* mode is set.
*
*
* This method is thread-safe and the prepared StepOver mode is appended to any other previously
* prepared modes.
*
* @param stepCount the number of times to perform StepOver before halting
* @return this event instance for an easy concatenation of method calls
* @throws IllegalArgumentException if {@code stepCount <= 0}
* @throws IllegalStateException when {@link #prepareContinue() continue} or
* {@link #prepareKill() kill} is prepared already.
* @since 0.9
*/
public SuspendedEvent prepareStepOver(int stepCount) {
if (stepCount <= 0) {
throw new IllegalArgumentException("stepCount must be > 0");
}
setNextStrategy(SteppingStrategy.createStepOver(stepCount));
return this;
}
/**
* Prepare to terminate the suspended execution represented by this event. One use-case for this
* method is to shield an execution of an unknown code with a timeout:
*
* {@link com.oracle.truffle.tck.ExecWithTimeOut#tckSnippets}
*
*
* This method is thread-safe and the prepared termination is appended to any other previously
* prepared modes. No further modes can be prepared after kill.
*
* @throws IllegalStateException when {@link #prepareContinue() continue} or
* {@link #prepareKill() kill} is prepared already.
* @since 0.12
*/
public void prepareKill() {
setNextStrategy(SteppingStrategy.createKill());
}
/**
* @since 0.17
*/
@Override
public String toString() {
return "Suspended at " + getSourceSection() + " for thread " + getThread();
}
private final class DebugStackFrameIterable implements Iterable {
private DebugStackFrame topStackFrame;
private List otherFrames;
private DebugStackFrame getTopStackFrame() {
if (topStackFrame == null) {
topStackFrame = new DebugStackFrame(SuspendedEvent.this, null);
}
return topStackFrame;
}
private List getOtherFrames() {
if (otherFrames == null) {
final List frameInstances = new ArrayList<>();
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() {
private boolean first = true;
@Override
public FrameInstance visitFrame(FrameInstance frameInstance) {
if (isEvalRootStackFrame(SuspendedEvent.this, frameInstance)) {
// we stop at eval root stack frames
return frameInstance;
}
if (first) {
first = false;
return null;
}
frameInstances.add(new DebugStackFrame(SuspendedEvent.this, frameInstance));
return null;
}
});
otherFrames = frameInstances;
}
return otherFrames;
}
public Iterator iterator() {
return new Iterator() {
private int index;
private Iterator otherIterator;
public boolean hasNext() {
verifyValidState(false);
if (index == 0) {
return true;
} else {
return getOtherStackFrames().hasNext();
}
}
public DebugStackFrame next() {
verifyValidState(false);
if (index == 0) {
index++;
return getTopStackFrame();
} else {
return getOtherStackFrames().next();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
private Iterator getOtherStackFrames() {
if (otherIterator == null) {
otherIterator = getOtherFrames().iterator();
}
return otherIterator;
}
};
}
}
}