package com.oracle.truffle.trufflenode.node.debug;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.Debugger;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.trufflenode.GraalJSAccess;
public class SetBreakPointNode extends JavaScriptRootNode {
public static final String NAME = "setBreakPoint";
private final GraalJSAccess graalJSAccess;
public SetBreakPointNode(GraalJSAccess graalJSAccess) {
this.graalJSAccess = graalJSAccess;
}
@Override
public Object execute(VirtualFrame frame) {
Object[] args = frame.getArguments();
int numArgs = JSArguments.getUserArgumentCount(args);
Object arg0 = Undefined.instance;
if (numArgs >= 1) {
arg0 = JSArguments.getUserArgument(args, 0);
}
if (JSFunction.isJSFunction(arg0)) {
CallTarget callTarget = JSFunction.getFunctionData((DynamicObject) arg0).getCallTarget();
if (callTarget instanceof RootCallTarget) {
return addBreakPoint((RootCallTarget) callTarget, args);
}
}
unsupported();
return 0;
}
@CompilerDirectives.TruffleBoundary
private int addBreakPoint(RootCallTarget callTarget, Object[] args) {
int numArgs = JSArguments.getUserArgumentCount(args);
Node rootNode = callTarget.getRootNode();
SourceSection sourceSection = rootNode.getSourceSection();
Source source = sourceSection.getSource();
int lineNo = sourceSection.getStartLine();
int columnNo = sourceSection.getStartColumn();
int userLine = 0;
if (numArgs >= 2) {
Object arg1 = JSArguments.getUserArgument(args, 1);
if (arg1 instanceof Number) {
userLine = ((Number) arg1).intValue();
lineNo += userLine;
}
}
if (userLine > 0) {
columnNo = 1;
}
if (numArgs >= 3) {
Object arg2 = JSArguments.getUserArgument(args, 2);
if (arg2 instanceof Number) {
columnNo += ((Number) arg2).intValue();
}
}
boolean oneShot = false;
if (numArgs >= 5) {
Object arg4 = JSArguments.getUserArgument(args, 4);
oneShot = JSRuntime.toBoolean(arg4);
}
lineNo = Math.max(1, Math.min(lineNo, source.getLineCount()));
columnNo = Math.max(1, Math.min(columnNo, source.getLineLength(lineNo)));
int offset = source.getLineStartOffset(lineNo) + columnNo - 1;
BreakPointOffsetFinder visitor = new BreakPointOffsetFinder(offset);
rootNode.accept(visitor);
int bestOffset = visitor.getBestOffset();
lineNo = source.getLineNumber(bestOffset);
columnNo = bestOffset - source.getLineStartOffset(lineNo) + 1;
addBreakPoint(source, lineNo, columnNo, oneShot);
return 0;
}
static class BreakPointOffsetFinder implements NodeVisitor {
private final int expectedOffset;
private int bestOffset = Integer.MAX_VALUE;
BreakPointOffsetFinder(int expectedOffset) {
this.expectedOffset = expectedOffset;
}
int getBestOffset() {
return (bestOffset == Integer.MAX_VALUE) ? expectedOffset : bestOffset;
}
@Override
public boolean visit(Node node) {
if (node instanceof InstrumentableNode && ((InstrumentableNode) node).hasTag(StandardTags.StatementTag.class)) {
SourceSection section = node.getSourceSection();
if (section != null && section.isAvailable()) {
int offset = section.getCharIndex();
if (expectedOffset <= offset && offset < bestOffset) {
bestOffset = offset;
}
}
}
return true;
}
}
@CompilerDirectives.TruffleBoundary
private void addBreakPoint(Source source, int lineNo, int columnNo, boolean oneShot) {
Breakpoint.Builder builder = Breakpoint.newBuilder(source).lineIs(lineNo).columnIs(columnNo);
if (oneShot) {
builder.oneShot();
}
Breakpoint breakpoint = builder.build();
Debugger debugger = graalJSAccess.lookupInstrument("debugger", Debugger.class);
debugger.install(breakpoint);
}
@CompilerDirectives.TruffleBoundary
private static void unsupported() {
System.err.println("Unsupported usage of Debug.setBreakpoint!");
}
@CompilerDirectives.TruffleBoundary
private static boolean startsWith(SourceSection sourceSection, String prefix) {
CharSequence characters = sourceSection.getCharacters();
int n = prefix.length();
if (n > characters.length()) {
return false;
}
return characters.subSequence(0, n).toString().equals(prefix);
}
}