package com.oracle.truffle.api.debug;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.debug.DebugException.CatchLocation;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.StandardTags.TryBlockTag;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.Node;
final class BreakpointExceptionFilter {
private Debugger debugger;
final boolean caught;
final boolean uncaught;
private final DebuggerSession.StableBoolean haveReportedExceptions = new DebuggerSession.StableBoolean(false);
private final Set<Throwable> reportedExceptions = Collections.newSetFromMap(new WeakHashMap<>());
private final ThreadLocal<Throwable> exceptionsOnThreads = new ThreadLocal<>();
BreakpointExceptionFilter(boolean caught, boolean uncaught) {
this.caught = caught;
this.uncaught = uncaught;
}
void setDebugger(Debugger debugger) {
assert this.debugger == null;
this.debugger = debugger;
}
Match matchException(Node throwNode, Throwable exception) {
if (wasReported(exception)) {
return Match.UNMATCHED;
}
if (caught && uncaught) {
return Match.MATCHED;
} else {
return testExceptionCaught(throwNode, exception);
}
}
@TruffleBoundary
private Match testExceptionCaught(Node throwNode, Throwable exception) {
if (!InteropLibrary.getUncached().isException(exception)) {
return uncaught ? Match.MATCHED : Match.UNMATCHED;
}
CatchLocation catchLocation = getCatchNode(throwNode, exception);
boolean exceptionCaught = catchLocation != null;
return new Match(caught && exceptionCaught || uncaught && !exceptionCaught, catchLocation);
}
static CatchLocation getCatchNode(Node throwNode, Throwable exception) {
CatchLocation[] catchLocationPtr = new CatchLocation[]{null};
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<FrameInstance>() {
private int depth = 0;
@Override
public FrameInstance visitFrame(FrameInstance frameInstance) {
Node node;
if (depth == 0) {
node = throwNode;
} else {
node = frameInstance.getCallNode();
}
if (node != null) {
Node catchNode = getCatchNodeImpl(node, exception);
if (catchNode != null) {
catchLocationPtr[0] = new CatchLocation(catchNode.getSourceSection(), frameInstance, depth);
return frameInstance;
}
}
depth++;
return null;
}
});
return catchLocationPtr[0];
}
@SuppressWarnings("deprecation")
private static Node getCatchNodeImpl(Node node, Throwable exception) {
if (node instanceof InstrumentableNode) {
InstrumentableNode inode = (InstrumentableNode) node;
if (inode.isInstrumentable() && inode.hasTag(TryBlockTag.class)) {
Object exceptionObject = ((com.oracle.truffle.api.TruffleException) exception).getExceptionObject();
Object nodeObject = inode.getNodeObject();
if (nodeObject != null && exceptionObject != null) {
InteropLibrary library = InteropLibrary.getFactory().getUncached(nodeObject);
TruffleObject object = (TruffleObject) nodeObject;
if (library.isMemberInvocable(nodeObject, "catches")) {
Object catches;
try {
catches = library.invokeMember(nodeObject, "catches", exceptionObject);
} catch (UnsupportedTypeException | ArityException | UnknownIdentifierException | UnsupportedMessageException ex) {
throw new IllegalStateException("Unexpected exception from 'catches' on '" + object, exception);
}
if (!(catches instanceof Boolean)) {
throw new IllegalStateException("Unexpected return value from 'catches' on '" + object + "' : " + catches);
}
if (Boolean.TRUE.equals(catches)) {
return node;
}
} else {
return node;
}
} else {
return node;
}
}
}
Node parent = node.getParent();
if (parent != null) {
return getCatchNodeImpl(parent, exception);
}
return null;
}
@TruffleBoundary
private boolean wasReported(Throwable exception) {
synchronized (this) {
boolean reported = reportedExceptions.contains(exception);
if (!reported) {
reportedExceptions.add(exception);
}
return reported;
}
}
void resetReportedException() {
if (haveReportedExceptions.get()) {
doResetReportedException();
}
}
@TruffleBoundary
private void doResetReportedException() {
Throwable exception = exceptionsOnThreads.get();
synchronized (this) {
if (exception != null) {
exceptionsOnThreads.remove();
reportedExceptions.remove(exception);
}
if (reportedExceptions.isEmpty()) {
haveReportedExceptions.set(false);
}
}
}
static final class Match {
static final Match MATCHED = new Match(true);
static final Match UNMATCHED = new Match(false);
final boolean isMatched;
final boolean isCatchNodeComputed;
final CatchLocation catchLocation;
private Match(boolean isMatched) {
this.isMatched = isMatched;
this.isCatchNodeComputed = false;
this.catchLocation = null;
}
private Match(boolean isMatched, CatchLocation catchLocation) {
this.isMatched = isMatched;
this.isCatchNodeComputed = true;
this.catchLocation = catchLocation;
}
}
}