From 6db9cdb107917c10a0ce9846a338673e20aeeb9b Mon Sep 17 00:00:00 2001 From: Florian Angerer <florian.angerer@oracle.com> Date: Thu, 20 Jul 2017 17:27:50 +0200 Subject: [PATCH] Refactored debug handling to cleanup bindings if not needed any longer. --- .../nodes/builtin/helpers/DebugHandling.java | 196 ++++++++++++------ .../instrument/InstrumentationState.java | 10 +- .../truffle/r/test/ExpectedTestOutput.test | 72 +++++++ .../library/utils/TestInteractiveDebug.java | 11 + 4 files changed, 227 insertions(+), 62 deletions(-) diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java index e9d18f142d..be71516f86 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/helpers/DebugHandling.java @@ -42,7 +42,6 @@ import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.utilities.CyclicAssumption; -import com.oracle.truffle.r.launcher.ConsoleHandler; import com.oracle.truffle.r.nodes.control.AbstractLoopNode; import com.oracle.truffle.r.nodes.function.FunctionDefinitionNode; import com.oracle.truffle.r.nodes.instrumentation.RInstrumentation; @@ -110,8 +109,6 @@ public class DebugHandling { /** * Attach the DebugHandling instrument to the FunctionStatementsNode and all syntactic nodes. - * - * @param implicit TODO */ public static boolean enableDebug(RFunction func, Object text, Object condition, boolean once, boolean implicit) { FunctionStatementsEventListener fbr = getFunctionStatementsEventListener(func); @@ -120,18 +117,21 @@ public class DebugHandling { } else { fbr.enable(); fbr.setParentListener(null); + + if (!fbr.isAttached()) { + fbr.attach(); + } } return true; } public static boolean undebug(RFunction func) { - FunctionStatementsEventListener fbr = getFunctionStatementsEventListener(func); - if (fbr == null) { - return false; - } else { - fbr.disable(); + FunctionStatementsEventListener fsel = getFunctionStatementsEventListener(func); + if (fsel != null && !fsel.disabled()) { + fsel.dispose(); return true; } + return false; } public static boolean isDebugged(RFunction func) { @@ -139,12 +139,19 @@ public class DebugHandling { return fser != null && (!fser.disabled() || fser.parentListener != null && !fser.parentListener.disabled()); } - private static FunctionStatementsEventListener getFunctionStatementsEventListener(RFunction func) { - return (FunctionStatementsEventListener) RContext.getInstance().stateInstrumentation.getDebugListener(RInstrumentation.getSourceSection(func)); + /** + * + */ + public static void dispose() { + for (ExecutionEventListener l : RContext.getInstance().stateInstrumentation.getDebugListeners()) { + if (l instanceof DebugEventListener) { + ((DebugEventListener) l).dispose(); + } + } } - private static LineBreakpointEventListener getLineBreakpointEventListener(Source source, int lineNr) { - return (LineBreakpointEventListener) RContext.getInstance().stateInstrumentation.getDebugListener(source.createSection(lineNr)); + private static FunctionStatementsEventListener getFunctionStatementsEventListener(RFunction func) { + return (FunctionStatementsEventListener) RContext.getInstance().stateInstrumentation.getDebugListener(RInstrumentation.getSourceSection(func)); } private static FunctionStatementsEventListener getFunctionStatementsEventListener(FunctionDefinitionNode fdn) { @@ -158,48 +165,10 @@ public class DebugHandling { @TruffleBoundary private static FunctionStatementsEventListener attachDebugHandler(FunctionDefinitionNode fdn, Object text, Object condition, boolean once, boolean implicit) { FunctionStatementsEventListener fser = new FunctionStatementsEventListener(fdn, text, condition, once, implicit); - // First attach the main listener on the START_FUNCTION - Instrumenter instrumenter = RInstrumentation.getInstrumenter(); - SourceSectionFilter.Builder functionBuilder = RInstrumentation.createFunctionFilter(fdn, StandardTags.RootTag.class); - instrumenter.attachListener(functionBuilder.build(), fser); - // Next attach statement handler to all STATEMENTs except LOOPs - SourceSectionFilter.Builder statementBuilder = RInstrumentation.createFunctionStatementFilter(fdn); - statementBuilder.tagIsNot(RSyntaxTags.LoopTag.class); - instrumenter.attachListener(statementBuilder.build(), fser.getStatementListener()); - // Finally attach loop listeners to all loop nodes - SourceSectionFilter.Builder loopBuilder = RInstrumentation.createFunctionFilter(fdn, RSyntaxTags.LoopTag.class); - new RSyntaxVisitor<Void>() { - - @Override - protected Void visit(RSyntaxCall element) { - if (element instanceof AbstractLoopNode) { - instrumenter.attachListener(loopBuilder.build(), fser.getLoopStatementReceiver((AbstractLoopNode) element)); - } - accept(element.getSyntaxLHS()); - for (RSyntaxElement arg : element.getSyntaxArguments()) { - if (arg != null) { - accept(arg); - } - } - return null; - } - @Override - protected Void visit(RSyntaxConstant element) { - return null; - } - - @Override - protected Void visit(RSyntaxLookup element) { - return null; - } + // First attach the main listener on the START_FUNCTION + fser.attach(); - @Override - protected Void visit(RSyntaxFunction element) { - accept(element.getSyntaxBody()); - return null; - } - }.accept(fdn); return fser; } @@ -208,14 +177,17 @@ public class DebugHandling { Instrumenter instrumenter = RInstrumentation.getInstrumenter(); SourceSection lineSourceSection = fdn.createSection(line); SourceSectionFilter.Builder functionBuilder = RInstrumentation.createLineFilter(fdn, line, StandardTags.StatementTag.class); - instrumenter.attachListener(functionBuilder.build(), new LineBreakpointEventListener(lineSourceSection)); + LineBreakpointEventListener listener = new LineBreakpointEventListener(lineSourceSection); + listener.setBinding(instrumenter.attachListener(functionBuilder.build(), listener)); + RContext.getInstance().stateInstrumentation.putDebugListener(lineSourceSection, listener); } @TruffleBoundary public static void disableLineDebug(Source fdn, int line) { - LineBreakpointEventListener l = getLineBreakpointEventListener(fdn, line); + LineBreakpointEventListener l = (LineBreakpointEventListener) RContext.getInstance().stateInstrumentation.getDebugListener(fdn.createSection(line)); if (l != null) { - l.disable(); + l.dispose(); + if (l.fser != null) { l.fser.setParentListener(null); l.fser.disable(); @@ -234,6 +206,9 @@ public class DebugHandling { // record initial state was disabled for undo fser.enabledForStepInto = true; } + if (!fser.isAttached()) { + fser.attach(); + } } fser.setParentListener(parentListener); return fser; @@ -241,6 +216,8 @@ public class DebugHandling { private abstract static class DebugEventListener implements ExecutionEventListener { + private EventBinding<? extends DebugEventListener> binding; + @TruffleBoundary protected static void print(String msg, boolean nl) { try { @@ -271,6 +248,23 @@ public class DebugHandling { disabled = newState; } } + + public EventBinding<? extends DebugEventListener> getBinding() { + return binding; + } + + public void setBinding(EventBinding<? extends DebugEventListener> binding) { + assert binding != null; + assert this.binding == null; + this.binding = binding; + } + + public void dispose() { + if (binding != null && !binding.isDisposed()) { + binding.dispose(); + binding = null; + } + } } private abstract static class InteractingDebugEventListener extends DebugEventListener { @@ -348,6 +342,12 @@ public class DebugHandling { stepIntoInstrument = null; } } + + @Override + public void dispose() { + super.dispose(); + clearStepInstrument(); + } } /** @@ -360,7 +360,7 @@ public class DebugHandling { private static class FunctionStatementsEventListener extends InteractingDebugEventListener { private final StatementEventListener statementListener; - ArrayList<LoopStatementEventListener> loopStatementListeners = new ArrayList<>(); + private final ArrayList<LoopStatementEventListener> loopStatementListeners = new ArrayList<>(); /** * Denotes the {@code debugOnce} function, debugging disabled after one execution, or a @@ -403,16 +403,84 @@ public class DebugHandling { parentListener = l; } - StatementEventListener getStatementListener() { - return statementListener; + void attachStatementListener() { + if (statementListener.getBinding() == null) { + SourceSectionFilter.Builder statementBuilder = RInstrumentation.createFunctionStatementFilter(functionDefinitionNode); + statementBuilder.tagIsNot(RSyntaxTags.LoopTag.class); + Instrumenter instrumenter = RInstrumentation.getInstrumenter(); + statementListener.setBinding(instrumenter.attachListener(statementBuilder.build(), statementListener)); + } } - LoopStatementEventListener getLoopStatementReceiver(RSyntaxNode loopNode) { + LoopStatementEventListener getLoopStatementReceiver(SourceSectionFilter ssf, RSyntaxNode loopNode) { LoopStatementEventListener lser = new LoopStatementEventListener(functionDefinitionNode, text, condition, loopNode, this); loopStatementListeners.add(lser); + Instrumenter instrumenter = RInstrumentation.getInstrumenter(); + lser.setBinding(instrumenter.attachListener(ssf, lser)); return lser; } + @Override + public void dispose() { + disable(); + super.dispose(); + statementListener.dispose(); + + for (LoopStatementEventListener lser : loopStatementListeners) { + lser.dispose(); + } + loopStatementListeners.clear(); + } + + public boolean isAttached() { + return getBinding() != null && !getBinding().isDisposed(); + } + + public void attach() { + + Instrumenter instrumenter = RInstrumentation.getInstrumenter(); + SourceSectionFilter.Builder functionBuilder = RInstrumentation.createFunctionFilter(functionDefinitionNode, StandardTags.RootTag.class); + setBinding(instrumenter.attachListener(functionBuilder.build(), this)); + + // Next attach statement handler to all STATEMENTs except LOOPs + attachStatementListener(); + + // Finally attach loop listeners to all loop nodes + SourceSectionFilter.Builder loopBuilder = RInstrumentation.createFunctionFilter(functionDefinitionNode, RSyntaxTags.LoopTag.class); + new RSyntaxVisitor<Void>() { + + @Override + protected Void visit(RSyntaxCall element) { + if (element instanceof AbstractLoopNode) { + getLoopStatementReceiver(loopBuilder.build(), (AbstractLoopNode) element); + } + accept(element.getSyntaxLHS()); + for (RSyntaxElement arg : element.getSyntaxArguments()) { + if (arg != null) { + accept(arg); + } + } + return null; + } + + @Override + protected Void visit(RSyntaxConstant element) { + return null; + } + + @Override + protected Void visit(RSyntaxLookup element) { + return null; + } + + @Override + protected Void visit(RSyntaxFunction element) { + accept(element.getSyntaxBody()); + return null; + } + }.accept(functionDefinitionNode); + } + @Override void disable() { super.disable(); @@ -586,7 +654,6 @@ public class DebugHandling { LineBreakpointEventListener(SourceSection ss) { this.ss = ss; - RContext.getInstance().stateInstrumentation.putDebugListener(ss, this); } @Override @@ -620,6 +687,15 @@ public class DebugHandling { fser.enableChildren(); } } + + @Override + public void dispose() { + super.dispose(); + disable(); + if (fser != null) { + fser.dispose(); + } + } } /** diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java index 8c91575ef9..10873939fb 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/instrument/InstrumentationState.java @@ -24,6 +24,7 @@ package com.oracle.truffle.r.runtime.instrument; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.WeakHashMap; @@ -203,8 +204,8 @@ public final class InstrumentationState implements RContext.ContextState { } @TruffleBoundary - public void putDebugListener(SourceSection ss, ExecutionEventListener listener) { - debugListenerMap.put(ss, listener); + public void putDebugListener(SourceSection sourceSection, ExecutionEventListener listener) { + debugListenerMap.put(sourceSection, listener); } @TruffleBoundary @@ -215,6 +216,11 @@ public final class InstrumentationState implements RContext.ContextState { } + @TruffleBoundary + public Collection<ExecutionEventListener> getDebugListeners() { + return debugListenerMap.values(); + } + @TruffleBoundary public ExecutionEventListener getDebugListener(SourceSection ss) { return debugListenerMap.get(ss); diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test index f6a1d11272..8e0463ffec 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test @@ -147946,6 +147946,45 @@ debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#6: bar("Wo debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#7: print(x) [1] 10 +##com.oracle.truffle.r.test.library.utils.TestInteractiveDebug.testSetBreakpoint#Output.IgnoreDebugCallString#Output.IgnoreDebugPath# +#source('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r'); setBreakpoint('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r', 4, verbose=F); fun(10)<<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>>setBreakpoint('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r', 4, verbose=F, clear=T); fun(10)<<<NEWLINE>>>setBreakpoint('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r', 4, verbose=F); invisible(fun(10))<<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>> +debug.r#4 +Called from: fun(10) +debug: print("Hello") +[1] "Hello" +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: for (i in seq(3)) print(i) +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 1 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 2 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 3 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#6: bar("World") +[1] "World" +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#7: print(x) +[1] 10 +[1] "Hello" +[1] 1 +[1] 2 +[1] 3 +[1] "World" +[1] 10 +debug.r#4 +Called from: fun(10) +debug: print("Hello") +[1] "Hello" +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: for (i in seq(3)) print(i) +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 1 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 2 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#5: print(i) +[1] 3 +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#6: bar("World") +[1] "World" +debug at tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r#7: print(x) +[1] 10 + ##com.oracle.truffle.r.test.library.utils.TestInteractiveDebug.testSetBreakpoint# #source('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r'); setBreakpoint('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r', 4, verbose=F); setBreakpoint('tmptest/com.oracle.truffle.r.test.library.utils.rsrc/debug.r', 4, verbose=F, clear=T); fun(10)<<<NEWLINE>>> [1] "Hello" @@ -147972,6 +148011,39 @@ debug at #4: t exiting from: f(5) [1] 6 +##com.oracle.truffle.r.test.library.utils.TestInteractiveDebug.testSimple# +#f <- function(x) {<<<NEWLINE>>> t <- x + 1<<<NEWLINE>>> print(t)<<<NEWLINE>>> t}<<<NEWLINE>>>debug(f)<<<NEWLINE>>>f(5)<<<NEWLINE>>>x<<<NEWLINE>>>n<<<NEWLINE>>>n<<<NEWLINE>>>t<<<NEWLINE>>>n<<<NEWLINE>>>n<<<NEWLINE>>>undebug(f)<<<NEWLINE>>>f(3)<<<NEWLINE>>>debug(f)<<<NEWLINE>>>f(5)<<<NEWLINE>>>x<<<NEWLINE>>>n<<<NEWLINE>>>n<<<NEWLINE>>>t<<<NEWLINE>>>n<<<NEWLINE>>>n +debugging in: f(5) +debug at #1: { + t <- x + 1 + print(t) + t +} +[1] 5 +debug at #2: t <- x + 1 +debug at #3: print(t) +[1] 6 +[1] 6 +debug at #4: t +exiting from: f(5) +[1] 6 +[1] 4 +[1] 4 +debugging in: f(5) +debug at #1: { + t <- x + 1 + print(t) + t +} +[1] 5 +debug at #2: t <- x + 1 +debug at #3: print(t) +[1] 6 +[1] 6 +debug at #4: t +exiting from: f(5) +[1] 6 + ##com.oracle.truffle.r.test.library.utils.TestInteractiveDebug.testStepInto# #bar <- function(x) { cat(x); cat('\n') }; foo <- function(x) { cat('foo entry\n'); bar(x); cat('foo exit\n') }; debug(foo); foo(3)<<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>>s<<<NEWLINE>>>n<<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>><<<NEWLINE>>> debugging in: foo(3) diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/utils/TestInteractiveDebug.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/utils/TestInteractiveDebug.java index c9b996f590..f497f3ba31 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/utils/TestInteractiveDebug.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/utils/TestInteractiveDebug.java @@ -27,16 +27,24 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; +import com.oracle.truffle.r.nodes.builtin.helpers.DebugHandling; import com.oracle.truffle.r.test.TestBase; // Checkstyle: stop line length check public class TestInteractiveDebug extends TestBase { + @After + public void cleanupDebugListeners() { + DebugHandling.dispose(); + } + @Test public void testSimple() { assertEval("f <- function(x) {\n t <- x + 1\n print(t)\n t}\ndebug(f)\nf(5)\nx\nn\nn\nt\nn\nn"); + assertEval("f <- function(x) {\n t <- x + 1\n print(t)\n t}\ndebug(f)\nf(5)\nx\nn\nn\nt\nn\nn\nundebug(f)\nf(3)\ndebug(f)\nf(5)\nx\nn\nn\nt\nn\nn"); } @Test @@ -117,5 +125,8 @@ public class TestInteractiveDebug extends TestBase { public void testSetBreakpoint() { assertEval(Output.IgnoreDebugCallString, Output.IgnoreDebugPath, String.format("source('%s'); setBreakpoint('%s', 4, verbose=F); fun(10)\n\n\n\n\n\n\n\n", debugFile, debugFile)); assertEval(String.format("source('%s'); setBreakpoint('%s', 4, verbose=F); setBreakpoint('%s', 4, verbose=F, clear=T); fun(10)\n", debugFile, debugFile, debugFile)); + assertEval(Output.IgnoreDebugCallString, Output.IgnoreDebugPath, String.format( + "source('%s'); setBreakpoint('%s', 4, verbose=F); fun(10)\n\n\n\n\n\n\n\n\nsetBreakpoint('%s', 4, verbose=F, clear=T); fun(10)\nsetBreakpoint('%s', 4, verbose=F); invisible(fun(10))\n\n\n\n\n\n\n\n\n", + debugFile, debugFile, debugFile, debugFile)); } } -- GitLab