/*
* Copyright (c) 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.instrumentation.test.examples;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
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.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
/**
* This is an example how debugging can be implemented using the instrumentation framework. This
* class itself shall be hidden in an implementation package. The actual API that
* {@link DebuggerExampleTest clients} can use to talk to the debugger is exposed in a separate
* {@link DebuggerController} interface.
*/
// BEGIN: DebuggerExample
@Registration(id = DebuggerExample.ID, services = DebuggerController.class)
public final class DebuggerExample extends TruffleInstrument {
private Controller controller;
@Override
protected void onCreate(Env env) {
assert this.controller == null;
this.controller = new Controller(env.getInstrumenter());
env.registerService(controller);
}
private static final class Controller extends DebuggerController {
private final Instrumenter instrumenter;
private EventBinding<?> stepping;
private Callback currentStatementCallback;
Controller(Instrumenter instrumenter) {
this.instrumenter = instrumenter;
}
// FINISH: DebuggerExample
@Override
public void installBreakpoint(int line, final Callback callback) {
instrumenter.attachListener(SourceSectionFilter.newBuilder().lineIs(line).tagIs(InstrumentationTestLanguage.STATEMENT).build(), new Breakpoint(callback));
}
@Override
public void stepInto(Callback next) {
runToNextStatement(new StepIntoCallback(next));
}
@Override
public void stepOver(Callback next) {
runToNextStatement(new StepOverCallback(next));
}
@Override
public void stepOut(Callback next) {
runToNextStatement(new StepOutCallback(next));
}
@TruffleBoundary
private void ontStatementStep(EventContext context) {
Callback callback = currentStatementCallback;
if (callback != null) {
currentStatementCallback = null;
callback.halted(this, context);
}
}
private void runToNextStatement(Callback callback) {
setStepping(true);
if (currentStatementCallback != null) {
throw new IllegalStateException("Debug command already scheduled.");
}
this.currentStatementCallback = callback;
}
private void setStepping(boolean stepping) {
EventBinding<?> step = this.stepping;
if (stepping) {
if (step == null) {
this.stepping = instrumenter.attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.STATEMENT).build(), new Stepping());
}
} else {
if (step != null && !step.isDisposed()) {
step.dispose();
this.stepping = null;
}
}
}
@TruffleBoundary
private static int currentStackDepth() {
final int[] count = {0};
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Void>() {
@Override
public Void visitFrame(FrameInstance frameInstance) {
count[0] = count[0] + 1;
return null;
}
});
return count[0] == 0 ? 0 : count[0] + 1;
}
private final class Breakpoint implements ExecutionEventListener {
private final Callback delegate;
private Breakpoint(Callback callback) {
this.delegate = callback;
}
public void onEnter(EventContext context, VirtualFrame frame) {
CompilerDirectives.transferToInterpreter();
onBreakpoint(delegate, context);
}
@TruffleBoundary
protected void onBreakpoint(final Callback callback, EventContext context) {
callback.halted(Controller.this, context);
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
}
private class Stepping implements ExecutionEventListener {
public void onEnter(EventContext context, VirtualFrame frame) {
ontStatementStep(context);
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
}
private abstract class StepCallBack implements Callback {
private final Callback delegate;
StepCallBack(Callback delegate) {
this.delegate = delegate;
}
public void halted(DebuggerController debugger, EventContext haltedAt) {
if (shouldHalt()) {
currentStatementCallback = null;
delegate.halted(debugger, haltedAt);
} else {
currentStatementCallback = this;
}
}
protected abstract boolean shouldHalt();
}
private class StepOverCallback extends StepCallBack {
protected final int stackDepth;
StepOverCallback(Callback delegate) {
super(delegate);
this.stackDepth = currentStackDepth();
}
@Override
protected boolean shouldHalt() {
return currentStackDepth() <= stackDepth;
}
}
private class StepOutCallback extends StepCallBack {
protected final int stackDepth;
StepOutCallback(Callback delegate) {
super(delegate);
this.stackDepth = currentStackDepth();
}
@Override
protected boolean shouldHalt() {
return currentStackDepth() < stackDepth;
}
}
private class StepIntoCallback extends StepCallBack {
StepIntoCallback(Callback delegate) {
super(delegate);
}
@Override
protected boolean shouldHalt() {
return true;
}
}
}
@Override
protected void onDispose(Env env) {
}
public static final String ID = "example-debugger";
}