# HG changeset patch # User Michael Van De Vanter # Date 1432683493 25200 # Node ID 3b8bbf51d320bc3df1e4612a7911d92dc528cb93 # Parent 1bbef57f9a38aca210358f6feaa9efc7aa722a4f Truffle/Debugging: add the Truffle DebugEngine and supporting code, as well as add a crude command-line debugging tool used mainly to test the DebugEngine. Migrate the small tols out of project com.oracle.truffle.api into the new project com.oracle.truffle.tools. diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/CoverageTrackerTest.java --- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/CoverageTrackerTest.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2015, 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. - * - * 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.test.tools; - -import static com.oracle.truffle.api.test.tools.TestNodes.*; -import static org.junit.Assert.*; - -import org.junit.*; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.tools.*; - -public class CoverageTrackerTest { - - @Test - public void testNoExecution() { - final CoverageTracker tool = new CoverageTracker(); - assertEquals(tool.getCounts().entrySet().size(), 0); - tool.install(); - assertEquals(tool.getCounts().entrySet().size(), 0); - tool.setEnabled(false); - assertEquals(tool.getCounts().entrySet().size(), 0); - tool.setEnabled(true); - assertEquals(tool.getCounts().entrySet().size(), 0); - tool.reset(); - assertEquals(tool.getCounts().entrySet().size(), 0); - tool.dispose(); - assertEquals(tool.getCounts().entrySet().size(), 0); - } - - @Test - public void testToolCreatedTooLate() { - final RootNode expr13rootNode = createExpr13TestRootNode(); - final CoverageTracker tool = new CoverageTracker(); - tool.install(); - assertEquals(13, expr13rootNode.execute(null)); - assertTrue(tool.getCounts().isEmpty()); - tool.dispose(); - } - - @Test - public void testToolInstalledcTooLate() { - final CoverageTracker tool = new CoverageTracker(); - final RootNode expr13rootNode = createExpr13TestRootNode(); - tool.install(); - assertEquals(13, expr13rootNode.execute(null)); - assertTrue(tool.getCounts().isEmpty()); - tool.dispose(); - } - - @Test - public void testCountingCoverage() { - final CoverageTracker tool = new CoverageTracker(); - tool.install(); - final RootNode expr13rootNode = createExpr13TestRootNode(); - - // Not probed yet. - assertEquals(13, expr13rootNode.execute(null)); - assertTrue(tool.getCounts().isEmpty()); - - final Node addNode = expr13rootNode.getChildren().iterator().next(); - final Probe probe = addNode.probe(); - - // Probed but not tagged yet. - assertEquals(13, expr13rootNode.execute(null)); - assertTrue(tool.getCounts().isEmpty()); - - probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing"); - - // Counting now; execute once - assertEquals(13, expr13rootNode.execute(null)); - - final Long[] longs1 = tool.getCounts().get(addNode.getSourceSection().getSource()); - assertNotNull(longs1); - assertEquals(longs1.length, 2); - assertNull(longs1[0]); // Line 1 is empty (text lines are 1-based) - assertEquals(1L, longs1[1].longValue()); // Expression is on line 2 - - // Execute 99 more times - for (int i = 0; i < 99; i++) { - assertEquals(13, expr13rootNode.execute(null)); - } - - final Long[] longs100 = tool.getCounts().get(addNode.getSourceSection().getSource()); - assertNotNull(longs100); - assertEquals(longs100.length, 2); - assertNull(longs100[0]); - assertEquals(100L, longs100[1].longValue()); - - tool.dispose(); - } - -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/LineToProbesMapTest.java --- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/LineToProbesMapTest.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2015, 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. - * - * 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.test.tools; - -import static com.oracle.truffle.api.test.tools.TestNodes.*; -import static org.junit.Assert.*; - -import org.junit.*; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.source.*; -import com.oracle.truffle.api.tools.*; - -public class LineToProbesMapTest { - - @Test - public void testToolCreatedTooLate() { - final RootNode expr13rootNode = createExpr13TestRootNode(); - final Node addNode = expr13rootNode.getChildren().iterator().next(); - final Probe probe = addNode.probe(); - final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation(); - assertEquals(lineLocation, expr13Line2); - - final LineToProbesMap tool = new LineToProbesMap(); - tool.install(); - - assertNull(tool.findFirstProbe(expr13Line1)); - assertNull(tool.findFirstProbe(expr13Line2)); - tool.dispose(); - } - - @Test - public void testToolInstalledTooLate() { - final LineToProbesMap tool = new LineToProbesMap(); - - final RootNode expr13rootNode = createExpr13TestRootNode(); - final Node addNode = expr13rootNode.getChildren().iterator().next(); - final Probe probe = addNode.probe(); - final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation(); - assertEquals(lineLocation, expr13Line2); - - tool.install(); - - assertNull(tool.findFirstProbe(expr13Line1)); - assertNull(tool.findFirstProbe(expr13Line2)); - tool.dispose(); - } - - @Test - public void testMapping() { - final LineToProbesMap tool = new LineToProbesMap(); - tool.install(); - - final RootNode expr13rootNode = createExpr13TestRootNode(); - final Node addNode = expr13rootNode.getChildren().iterator().next(); - final Probe probe = addNode.probe(); - final LineLocation lineLocation = probe.getProbedSourceSection().getLineLocation(); - assertEquals(lineLocation, expr13Line2); - - assertNull(tool.findFirstProbe(expr13Line1)); - assertEquals(tool.findFirstProbe(expr13Line2), probe); - tool.dispose(); - } - -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/NodeExecCounterTest.java --- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/NodeExecCounterTest.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2015, 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. - * - * 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.test.tools; - -import static com.oracle.truffle.api.test.tools.TestNodes.*; -import static org.junit.Assert.*; - -import org.junit.*; - -import com.oracle.truffle.api.*; -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.test.tools.TestNodes.TestAddNode; -import com.oracle.truffle.api.test.tools.TestNodes.TestValueNode; -import com.oracle.truffle.api.tools.*; -import com.oracle.truffle.api.tools.NodeExecCounter.NodeExecutionCount; - -public class NodeExecCounterTest { - - @Test - public void testNoExecution() { - final NodeExecCounter tool = new NodeExecCounter(); - assertEquals(tool.getCounts().length, 0); - tool.install(); - assertEquals(tool.getCounts().length, 0); - tool.setEnabled(false); - assertEquals(tool.getCounts().length, 0); - tool.setEnabled(true); - assertEquals(tool.getCounts().length, 0); - tool.reset(); - assertEquals(tool.getCounts().length, 0); - tool.dispose(); - assertEquals(tool.getCounts().length, 0); - } - - @Test - public void testToolCreatedTooLate() { - final CallTarget expr13callTarget = createExpr13TestCallTarget(); - final NodeExecCounter tool = new NodeExecCounter(); - tool.install(); - assertEquals(13, expr13callTarget.call()); - assertEquals(tool.getCounts().length, 0); - tool.dispose(); - } - - @Test - public void testToolInstalledcTooLate() { - final NodeExecCounter tool = new NodeExecCounter(); - final CallTarget expr13callTarget = createExpr13TestCallTarget(); - tool.install(); - assertEquals(13, expr13callTarget.call()); - assertEquals(tool.getCounts().length, 0); - tool.dispose(); - } - - @Test - public void testCountingAll() { - final NodeExecCounter tool = new NodeExecCounter(); - tool.install(); - final CallTarget expr13callTarget = createExpr13TestCallTarget(); - - // execute once - assertEquals(13, expr13callTarget.call()); - final NodeExecutionCount[] count1 = tool.getCounts(); - assertNotNull(count1); - assertEquals(count1.length, 2); - for (NodeExecutionCount count : count1) { - final Class class1 = count.nodeClass(); - final long executionCount = count.executionCount(); - if (class1 == TestAddNode.class) { - assertEquals(executionCount, 1); - } else if (class1 == TestValueNode.class) { - assertEquals(executionCount, 2); - } else { - fail(); - } - } - - // Execute 99 more times - for (int i = 0; i < 99; i++) { - assertEquals(13, expr13callTarget.call()); - } - final NodeExecutionCount[] counts100 = tool.getCounts(); - assertNotNull(counts100); - assertEquals(counts100.length, 2); - for (NodeExecutionCount count : counts100) { - final Class class1 = count.nodeClass(); - final long executionCount = count.executionCount(); - if (class1 == TestAddNode.class) { - assertEquals(executionCount, 100); - } else if (class1 == TestValueNode.class) { - assertEquals(executionCount, 200); - } else { - fail(); - } - } - - tool.dispose(); - } - - @Test - public void testCountingTagged() { - final NodeExecCounter tool = new NodeExecCounter(StandardSyntaxTag.STATEMENT); - tool.install(); - final RootNode expr13rootNode = createExpr13TestRootNode(); - - // Not probed yet. - assertEquals(13, expr13rootNode.execute(null)); - assertEquals(tool.getCounts().length, 0); - - final Node addNode = expr13rootNode.getChildren().iterator().next(); - final Probe probe = addNode.probe(); - - // Probed but not tagged yet. - assertEquals(13, expr13rootNode.execute(null)); - assertEquals(tool.getCounts().length, 0); - - probe.tagAs(StandardSyntaxTag.STATEMENT, "fake statement for testing"); - - // Counting now; execute once - assertEquals(13, expr13rootNode.execute(null)); - final NodeExecutionCount[] counts1 = tool.getCounts(); - assertNotNull(counts1); - assertEquals(counts1.length, 1); - final NodeExecutionCount count1 = counts1[0]; - assertNotNull(count1); - assertEquals(count1.nodeClass(), addNode.getClass()); - assertEquals(count1.executionCount(), 1); - - // Execute 99 more times - for (int i = 0; i < 99; i++) { - assertEquals(13, expr13rootNode.execute(null)); - } - - final NodeExecutionCount[] counts100 = tool.getCounts(); - assertNotNull(counts100); - assertEquals(counts100.length, 1); - final NodeExecutionCount count100 = counts100[0]; - assertNotNull(count100); - assertEquals(count100.nodeClass(), addNode.getClass()); - assertEquals(count100.executionCount(), 100); - - tool.dispose(); - } -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java --- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TestNodes.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2015, 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. - * - * 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.test.tools; - -import com.oracle.truffle.api.*; -import com.oracle.truffle.api.frame.*; -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.ProbeNode.WrapperNode; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.source.*; - -/** - * Nodes and an {@linkplain CallTarget executable ASTs} for testing. - */ -class TestNodes { - - /** - * A fake source used for testing: empty line 1, expression on line 2. - */ - public static final Source expr13Source = Source.fromText("\n6+7\n", "Test Source: expression on line 2 that evaluates to 13"); - public static final LineLocation expr13Line1 = expr13Source.createLineLocation(1); - public static final LineLocation expr13Line2 = expr13Source.createLineLocation(2); - - /** - * An executable addition expression that evaluates to 13. - */ - static CallTarget createExpr13TestCallTarget() { - final RootNode rootNode = createExpr13TestRootNode(); - return Truffle.getRuntime().createCallTarget(rootNode); - } - - /** - * Root holding an addition expression that evaluates to 13. - */ - static RootNode createExpr13TestRootNode() { - final TestLanguageNode ast = createExpr13AST(); - final TestRootNode rootNode = new TestRootNode(ast); - rootNode.adoptChildren(); - return rootNode; - } - - /** - * Addition expression that evaluates to 13, with faked source attribution. - */ - static TestLanguageNode createExpr13AST() { - final SourceSection leftSourceSection = expr13Source.createSection("left", 1, 1); - final TestValueNode leftValueNode = new TestValueNode(6, leftSourceSection); - final SourceSection rightSourceSection = expr13Source.createSection("right", 3, 1); - final TestValueNode rightValueNode = new TestValueNode(7, rightSourceSection); - final SourceSection exprSourceSection = expr13Source.createSection("expr", 1, 3); - return new TestAddNode(leftValueNode, rightValueNode, exprSourceSection); - } - - abstract static class TestLanguageNode extends Node { - public abstract Object execute(VirtualFrame frame); - - public TestLanguageNode() { - } - - public TestLanguageNode(SourceSection srcSection) { - super(srcSection); - } - - @Override - public boolean isInstrumentable() { - return true; - } - - @Override - public WrapperNode createWrapperNode() { - return new TestWrapperNode(this); - } - } - - @NodeInfo(cost = NodeCost.NONE) - static class TestWrapperNode extends TestLanguageNode implements WrapperNode { - @Child private TestLanguageNode child; - @Child private ProbeNode probeNode; - - public TestWrapperNode(TestLanguageNode child) { - assert !(child instanceof TestWrapperNode); - this.child = child; - } - - @Override - public String instrumentationInfo() { - return "Wrapper node for testing"; - } - - @Override - public boolean isInstrumentable() { - return false; - } - - @Override - public void insertProbe(ProbeNode newProbeNode) { - this.probeNode = newProbeNode; - } - - @Override - public Probe getProbe() { - return probeNode.getProbe(); - } - - @Override - public Node getChild() { - return child; - } - - @Override - public Object execute(VirtualFrame frame) { - probeNode.enter(child, frame); - Object result; - - try { - result = child.execute(frame); - probeNode.returnValue(child, frame, result); - } catch (KillException e) { - throw (e); - } catch (Exception e) { - probeNode.returnExceptional(child, frame, e); - throw (e); - } - - return result; - } - } - - /** - * Truffle requires that all guest languages to have a {@link RootNode} which sits atop any AST - * of the guest language. This is necessary since creating a {@link CallTarget} is how Truffle - * completes an AST. The root nodes serves as our entry point into a program. - */ - static class TestRootNode extends RootNode { - @Child private TestLanguageNode body; - - /** - * This constructor emulates the global machinery that applies registered probers to every - * newly created AST. Global registry is not used, since that would interfere with other - * tests run in the same environment. - */ - public TestRootNode(TestLanguageNode body) { - super(null); - this.body = body; - } - - @Override - public Object execute(VirtualFrame frame) { - return body.execute(frame); - } - - @Override - public boolean isCloningAllowed() { - return true; - } - - @Override - public void applyInstrumentation() { - Probe.applyASTProbers(body); - } - } - - static class TestValueNode extends TestLanguageNode { - private final int value; - - public TestValueNode(int value) { - this.value = value; - } - - public TestValueNode(int value, SourceSection srcSection) { - super(srcSection); - this.value = value; - } - - @Override - public Object execute(VirtualFrame frame) { - return new Integer(this.value); - } - } - - static class TestAddNode extends TestLanguageNode { - @Child private TestLanguageNode leftChild; - @Child private TestLanguageNode rightChild; - - public TestAddNode(TestValueNode leftChild, TestValueNode rightChild, SourceSection sourceSection) { - super(sourceSection); - this.leftChild = insert(leftChild); - this.rightChild = insert(rightChild); - } - - @Override - public Object execute(VirtualFrame frame) { - return new Integer(((Integer) leftChild.execute(frame)).intValue() + ((Integer) rightChild.execute(frame)).intValue()); - } - } - -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TruffleToolTest.java --- a/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/tools/TruffleToolTest.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2015, 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. - * - * 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.test.tools; - -import static org.junit.Assert.*; - -import org.junit.*; - -import com.oracle.truffle.api.instrument.*; - -/** - * Test the basic life cycle properties shared by all instances of {@link InstrumentationTool}. - */ -public class TruffleToolTest { - - @Test - public void testEmptyLifeCycle() { - final DummyTruffleTool tool = new DummyTruffleTool(); - assertFalse(tool.isEnabled()); - tool.install(); - assertTrue(tool.isEnabled()); - tool.reset(); - assertTrue(tool.isEnabled()); - tool.setEnabled(false); - assertFalse(tool.isEnabled()); - tool.reset(); - assertFalse(tool.isEnabled()); - tool.setEnabled(true); - assertTrue(tool.isEnabled()); - tool.reset(); - assertTrue(tool.isEnabled()); - tool.dispose(); - assertFalse(tool.isEnabled()); - } - - @Test(expected = IllegalStateException.class) - public void testNotYetInstalled1() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.reset(); - } - - @Test(expected = IllegalStateException.class) - public void testNotYetInstalled2() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.setEnabled(true); - } - - @Test(expected = IllegalStateException.class) - public void testNotYetInstalled3() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.dispose(); - } - - @Test(expected = IllegalStateException.class) - public void testAlreadyInstalled() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.install(); - tool.install(); - } - - @Test(expected = IllegalStateException.class) - public void testAlreadyDisposed1() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.install(); - tool.dispose(); - tool.install(); - } - - @Test(expected = IllegalStateException.class) - public void testAlreadyDisposed2() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.install(); - tool.dispose(); - tool.reset(); - } - - @Test(expected = IllegalStateException.class) - public void testAlreadyDisposed3() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.install(); - tool.dispose(); - tool.setEnabled(true); - } - - @Test(expected = IllegalStateException.class) - public void testAlreadyDisposed4() { - final DummyTruffleTool tool = new DummyTruffleTool(); - tool.install(); - tool.dispose(); - tool.dispose(); - } - - private static final class DummyTruffleTool extends InstrumentationTool { - - @Override - protected boolean internalInstall() { - return true; - } - - @Override - protected void internalReset() { - } - - @Override - protected void internalDispose() { - } - - } -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java --- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,320 +0,0 @@ -/* - * Copyright (c) 2015, 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.tools; - -import java.io.*; -import java.util.*; -import java.util.Map.Entry; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.impl.*; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.source.*; - -/** - * An {@link InstrumentationTool} that counts interpreter execution calls to AST nodes that - * hold a specified {@linkplain SyntaxTag syntax tag}, tabulated by source and line number - * associated with each node. Syntax tags are presumed to be applied external to the tool. If no tag - * is specified, {@linkplain StandardSyntaxTag#STATEMENT STATEMENT} is used, corresponding to - * conventional behavior for code coverage tools. - *

- * No counts will be kept for execution in sources that hold the {@link SourceTag} - * {@link Tags#NO_COVERAGE}. - *

- * Tool Life Cycle - *

- * See {@link InstrumentationTool} for the life cycle common to all such tools. - *

- * Execution Counts - *

- *

- *

- * Results - *

- * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time, - * without effect on the state of the tool. - *

- *

- * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at - * any time in a simple textual format, with no other effect on the state of the tool. - *

- * - * @see Instrument - * @see SyntaxTag - */ -public final class CoverageTracker extends InstrumentationTool { - - public enum Tags implements SourceTag { - - /** - * Report no counts for sources holding this tag. - */ - NO_COVERAGE("No Coverage", "Coverage Tracker will igore"); - - private final String name; - private final String description; - - private Tags(String name, String description) { - this.name = name; - this.description = description; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - } - - /** Counting data. */ - private final Map coverageMap = new HashMap<>(); - - /** Needed for disposal. */ - private final List instruments = new ArrayList<>(); - - /** - * Coverage counting is restricted to nodes holding this tag. - */ - private final SyntaxTag countingTag; - - private final ProbeListener probeListener; - - /** - * Create a per-line coverage tool for nodes tagged as {@linkplain StandardSyntaxTag#STATEMENT - * statements} in subsequently created ASTs. - */ - public CoverageTracker() { - this(StandardSyntaxTag.STATEMENT); - } - - /** - * Create a per-line coverage tool for nodes tagged as specified, presuming that tags applied - * outside this tool. - */ - public CoverageTracker(SyntaxTag tag) { - this.probeListener = new CoverageProbeListener(); - this.countingTag = tag; - } - - @Override - protected boolean internalInstall() { - Probe.addProbeListener(probeListener); - return true; - } - - @Override - protected void internalReset() { - coverageMap.clear(); - } - - @Override - protected void internalDispose() { - Probe.removeProbeListener(probeListener); - for (Instrument instrument : instruments) { - instrument.dispose(); - } - } - - /** - * Gets a modification-safe summary of the current per-type node execution counts; does not - * affect the counts. - *

- * The map holds an array for each source, and elements of the a array corresponding to the - * textual lines of that source. An array entry contains null if the corresponding line of - * source is associated with no nodes of the relevant type, i.e. where no count was made. A - * numeric entry represents the execution count at the corresponding line of source. - *

- * Note: source line numbers are 1-based, so array index {@code i} corresponds to source - * line number {@code i + 1} - */ - public Map getCounts() { - - /** - * Counters for every {Source, line number} for which a counter was installed, i.e. for - * every line associated with an appropriately tagged AST node; iterable in order of source - * name, then line number. - */ - final TreeSet> entries = new TreeSet<>(new LineLocationEntryComparator()); - - for (Entry entry : coverageMap.entrySet()) { - entries.add(entry); - } - final Map result = new HashMap<>(); - Source curSource = null; - Long[] curLineTable = null; - for (Entry entry : entries) { - final LineLocation key = entry.getKey(); - final Source source = key.getSource(); - final int lineNo = key.getLineNumber(); - if (source != curSource) { - if (curSource != null) { - result.put(curSource, curLineTable); - } - curSource = source; - curLineTable = new Long[source.getLineCount()]; - } - curLineTable[lineNo - 1] = entry.getValue().count; - } - if (curSource != null) { - result.put(curSource, curLineTable); - } - return result; - } - - /** - * A default printer for the current line counts, producing lines of the form " () : ", grouped by source. - */ - public void print(PrintStream out) { - out.println(); - out.println(countingTag.name() + " coverage:"); - - /** - * Counters for every {Source, line number} for which a counter was installed, i.e. for - * every line associated with an appropriately tagged AST node; iterable in order of source - * name, then line number. - */ - final TreeSet> entries = new TreeSet<>(new LineLocationEntryComparator()); - - for (Entry entry : coverageMap.entrySet()) { - entries.add(entry); - } - Source curSource = null; - int curLineNo = 1; - for (Entry entry : entries) { - final LineLocation key = entry.getKey(); - final Source source = key.getSource(); - final int lineNo = key.getLineNumber(); - if (source != curSource) { - if (curSource != null) { - while (curLineNo <= curSource.getLineCount()) { - displayLine(out, null, curSource, curLineNo++); - } - } - curSource = source; - curLineNo = 1; - out.println(); - out.println(source.getPath()); - } - while (curLineNo < lineNo) { - displayLine(out, null, curSource, curLineNo++); - } - displayLine(out, entry.getValue(), curSource, curLineNo++); - } - if (curSource != null) { - while (curLineNo <= curSource.getLineCount()) { - displayLine(out, null, curSource, curLineNo++); - } - } - } - - private static void displayLine(PrintStream out, CoverageRecord record, Source source, int lineNo) { - if (record == null) { - out.format("%14s", " "); - } else { - out.format("(%12d)", record.count); - } - out.format(" %3d: ", lineNo); - out.println(source.getCode(lineNo)); - } - - /** - * A listener for events at each instrumented AST location. This listener counts - * "execution calls" to the instrumented node. - */ - private final class CoverageRecord extends DefaultSimpleInstrumentListener { - - private final SourceSection srcSection; // The text of the code being counted - private Instrument instrument; // The attached Instrument, in case need to remove. - private long count = 0; - - CoverageRecord(SourceSection srcSection) { - this.srcSection = srcSection; - } - - @Override - public void enter(Probe probe) { - if (isEnabled()) { - count++; - } - } - - } - - private static final class LineLocationEntryComparator implements Comparator> { - - public int compare(Entry e1, Entry e2) { - return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey()); - } - } - - /** - * Attach a counting instrument to each node that is assigned a specified tag. - */ - private class CoverageProbeListener extends DefaultProbeListener { - - @Override - public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { - if (countingTag == tag) { - - final SourceSection srcSection = probe.getProbedSourceSection(); - if (srcSection == null) { - // TODO (mlvdv) report this? - } else if (!srcSection.getSource().isTaggedAs(Tags.NO_COVERAGE)) { - // Get the source line where the - final LineLocation lineLocation = srcSection.getLineLocation(); - CoverageRecord record = coverageMap.get(lineLocation); - if (record != null) { - // Another node starts on same line; count only the first (textually) - if (srcSection.getCharIndex() > record.srcSection.getCharIndex()) { - // Existing record, corresponds to code earlier on line - return; - } else { - // Existing record, corresponds to code at a later position; replace it - record.instrument.dispose(); - } - } - - final CoverageRecord coverage = new CoverageRecord(srcSection); - final Instrument instrument = Instrument.create(coverage, CoverageTracker.class.getSimpleName()); - coverage.instrument = instrument; - instruments.add(instrument); - probe.attach(instrument); - coverageMap.put(lineLocation, coverage); - } - } - } - } - -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java --- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2014, 2015, 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.tools; - -import java.io.*; -import java.util.*; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.impl.*; -import com.oracle.truffle.api.source.*; - -/** - * An {@link InstrumentationTool} that builds a map of every {@link Probe} attached to some AST, - * indexed by {@link Source} and line number. - */ -public final class LineToProbesMap extends InstrumentationTool { - - private static final boolean TRACE = false; - private static final PrintStream OUT = System.out; - - private static void trace(String msg) { - OUT.println("LineToProbesMap: " + msg); - } - - /** - * Map: Source line ==> probes associated with source sections starting on the line. - */ - private final Map> lineToProbesMap = new HashMap<>(); - - private final ProbeListener probeListener; - - /** - * Create a map of {@link Probe}s that collects information on all probes added to subsequently - * created ASTs (once installed). - */ - public LineToProbesMap() { - this.probeListener = new LineToProbesListener(); - } - - @Override - protected boolean internalInstall() { - Probe.addProbeListener(probeListener); - return true; - } - - @Override - protected void internalReset() { - lineToProbesMap.clear(); - } - - @Override - protected void internalDispose() { - Probe.removeProbeListener(probeListener); - } - - /** - * Returns the {@link Probe}, if any, associated with a specific line of guest language code; if - * more than one, return the one with the first starting character location. - */ - public Probe findFirstProbe(LineLocation lineLocation) { - Probe probe = null; - final Collection probes = findProbes(lineLocation); - for (Probe probesOnLine : probes) { - if (probe == null) { - probe = probesOnLine; - } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) { - probe = probesOnLine; - } - } - return probe; - } - - /** - * Returns all {@link Probe}s whose associated source begins at the given {@link LineLocation}, - * an empty list if none. - */ - public Collection findProbes(LineLocation line) { - final Collection probes = lineToProbesMap.get(line); - if (probes == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableCollection(probes); - } - - private class LineToProbesListener extends DefaultProbeListener { - - @Override - public void newProbeInserted(Probe probe) { - final SourceSection sourceSection = probe.getProbedSourceSection(); - if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) { - final LineLocation lineLocation = sourceSection.getLineLocation(); - if (TRACE) { - trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription()); - } - Collection probes = lineToProbesMap.get(lineLocation); - if (probes == null) { - probes = new ArrayList<>(2); - lineToProbesMap.put(lineLocation, probes); - } else { - assert !probes.contains(probe); - } - probes.add(probe); - } - } - } -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java --- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java Fri May 22 10:20:38 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,332 +0,0 @@ -/* - * Copyright (c) 2015, 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.tools; - -import java.io.*; -import java.util.*; -import java.util.concurrent.atomic.*; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.frame.*; -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.impl.*; -import com.oracle.truffle.api.nodes.*; -import com.oracle.truffle.api.nodes.Node.Child; - -/** - * An {@link InstrumentationTool} that counts interpreter execution calls to AST nodes, - * tabulated by the type of called nodes; counting can be enabled all nodes or restricted - * to nodes with a specified {@linkplain SyntaxTag tag} that is presumed to be applied external to - * the tool. - *

- * Tool Life Cycle - *

- * See {@link InstrumentationTool} for the life cycle common to all such tools. - *

- * Execution Counts - *

- *

    - *
  • "Execution call" on a node is is defined as invocation of a node method that is instrumented - * to produce the event {@link StandardInstrumentListener#enter(Probe, Node, VirtualFrame)};
  • - *
  • Execution calls are tabulated only at instrumented nodes, i.e. those for which - * {@linkplain Node#isInstrumentable() isInstrumentable() == true};
  • - *
  • Execution calls are tabulated only at nodes present in the AST when originally created; - * dynamically added nodes will not be instrumented.
  • - *
- *

- * Failure Log - *

- * For the benefit of language implementors, the tool maintains a log describing failed attempts to - * probe AST nodes. Most failures occur when the type of the wrapper created by - * {@link Node#createWrapperNode()} is not assignable to the relevant {@link Child} field in the - * node's parent. - *

- *

- * {@linkplain #reset() Resetting} the counts has no effect on the failure log. - *

- * Results - *

- * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time, - * without effect on the state of the tool. - *

- *

- * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at - * any time in a simple textual format, without effect on the state of the tool. - *

- * - * @see Instrument - * @see SyntaxTag - * @see ProbeFailure - */ -public final class NodeExecCounter extends InstrumentationTool { - - /** - * Execution count for AST nodes of a particular type. - */ - public interface NodeExecutionCount { - Class nodeClass(); - - long executionCount(); - } - - /** - * Listener for events at instrumented nodes. Counts are maintained in a shared table, so the - * listener is stateless and can be shared by every {@link Instrument}. - */ - private final StandardInstrumentListener instrumentListener = new DefaultStandardInstrumentListener() { - @Override - public void enter(Probe probe, Node node, VirtualFrame vFrame) { - if (isEnabled()) { - final Class nodeClass = node.getClass(); - /* - * Everything up to here is inlined by Truffle compilation. Delegate the next part - * to a method behind an inlining boundary. - * - * Note that it is not permitted to pass a {@link VirtualFrame} across an inlining - * boundary; they are truly virtual in inlined code. - */ - AtomicLong nodeCounter = getCounter(nodeClass); - nodeCounter.getAndIncrement(); - } - } - - /** - * Mark this method as a boundary that will stop Truffle inlining, which should not be - * allowed to inline the hash table method or any other complex library code. - */ - @TruffleBoundary - private AtomicLong getCounter(Class nodeClass) { - AtomicLong nodeCounter = counters.get(nodeClass); - if (nodeCounter == null) { - nodeCounter = new AtomicLong(); - counters.put(nodeClass, nodeCounter); - } - return nodeCounter; - } - }; - - /** Counting data. */ - private final Map, AtomicLong> counters = new HashMap<>(); - - /** Failure log. */ - private final List failures = new ArrayList<>(); - - /** For disposal. */ - private final List instruments = new ArrayList<>(); - - /** - * If non-null, counting is restricted to nodes holding this tag. - */ - private final SyntaxTag countingTag; - - /** - * Prober used only when instrumenting every node. - */ - private ASTProber astProber; - - /** - * Listener used only when restricting counting to a specific tag. - */ - private ProbeListener probeListener; - - /** - * Create a per node-type execution counting tool for all nodes in subsequently created ASTs. - */ - public NodeExecCounter() { - this.countingTag = null; - } - - /** - * Creates a per-type execution counting for nodes tagged as specified in subsequently created - * ASTs. - */ - public NodeExecCounter(SyntaxTag tag) { - this.countingTag = tag; - } - - @Override - protected boolean internalInstall() { - if (countingTag == null) { - astProber = new ExecCounterASTProber(); - Probe.registerASTProber(astProber); - } else { - probeListener = new NodeExecCounterProbeListener(); - Probe.addProbeListener(probeListener); - } - return true; - } - - @Override - protected void internalReset() { - counters.clear(); - failures.clear(); - } - - @Override - protected void internalDispose() { - if (astProber != null) { - Probe.unregisterASTProber(astProber); - } - if (probeListener != null) { - Probe.removeProbeListener(probeListener); - } - for (Instrument instrument : instruments) { - instrument.dispose(); - } - } - - /** - * Gets a modification-safe summary of the current per-type node execution counts; does not - * affect the counts. - */ - public NodeExecutionCount[] getCounts() { - final Collection, AtomicLong>> entrySet = counters.entrySet(); - final NodeExecutionCount[] result = new NodeExecCountImpl[entrySet.size()]; - int i = 0; - for (Map.Entry, AtomicLong> entry : entrySet) { - result[i++] = new NodeExecCountImpl(entry.getKey(), entry.getValue().longValue()); - } - return result; - } - - /** - * Gets a log containing a report of every failed attempt to instrument a node. - */ - public ProbeFailure[] getFailures() { - return failures.toArray(new ProbeFailure[failures.size()]); - } - - /** - * A default printer for the current counts, producing lines of the form - * " : " in descending order of count. - */ - public void print(PrintStream out) { - print(out, false); - } - - /** - * A default printer for the current counts, producing lines of the form - * " : " in descending order of count. - * - * @param out - * @param verbose whether to describe nodes on which instrumentation failed - */ - public void print(PrintStream out, boolean verbose) { - - final long missedNodes = failures.size(); - out.println(); - if (countingTag == null) { - out.println("Execution counts by node type:"); - } else { - out.println("\"" + countingTag.name() + "\"-tagged execution counts by node type:"); - } - final StringBuilder disclaim = new StringBuilder("("); - if (missedNodes > 0) { - disclaim.append(Long.toString(missedNodes) + " original AST nodes not instrumented, "); - } - disclaim.append("dynamically added nodes not instrumented)"); - out.println(disclaim.toString()); - NodeExecutionCount[] execCounts = getCounts(); - // Sort in descending order - Arrays.sort(execCounts, new Comparator() { - - public int compare(NodeExecutionCount o1, NodeExecutionCount o2) { - return Long.compare(o2.executionCount(), o1.executionCount()); - } - - }); - for (NodeExecutionCount nodeCount : execCounts) { - out.format("%12d", nodeCount.executionCount()); - out.println(" : " + nodeCount.nodeClass().getName()); - } - - if (verbose && missedNodes > 0) { - out.println("Instrumentation failures for execution counts:"); - - for (ProbeFailure failure : failures) { - out.println("\t" + failure.getMessage()); - } - } - } - - /** - * A prober that attempts to probe and instrument every node. - */ - private class ExecCounterASTProber implements ASTProber, NodeVisitor { - - public boolean visit(Node node) { - - if (node.isInstrumentable()) { - try { - final Instrument instrument = Instrument.create(instrumentListener, "NodeExecCounter"); - instruments.add(instrument); - node.probe().attach(instrument); - } catch (ProbeException ex) { - failures.add(ex.getFailure()); - } - } - return true; - } - - public void probeAST(Node node) { - node.accept(this); - } - } - - /** - * A listener that assumes ASTs have been tagged external to this tool, and which instruments - * nodes holding a specified tag. - */ - private class NodeExecCounterProbeListener extends DefaultProbeListener { - - @Override - public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { - if (countingTag == tag) { - final Instrument instrument = Instrument.create(instrumentListener, NodeExecCounter.class.getSimpleName()); - instruments.add(instrument); - probe.attach(instrument); - } - } - } - - private static class NodeExecCountImpl implements NodeExecutionCount { - - private final Class nodeClass; - private final long count; - - public NodeExecCountImpl(Class nodeClass, long count) { - this.nodeClass = nodeClass; - this.count = count; - } - - public Class nodeClass() { - return nodeClass; - } - - public long executionCount() { - return count; - } - } -} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2014, 2015, 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.sl.tools.debug; + +import java.util.*; + +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.shell.*; +import com.oracle.truffle.tools.debug.shell.client.*; +import com.oracle.truffle.tools.debug.shell.server.*; + +/** + * Instantiation of the "server handler" part of the "REPL*" debugger for the simple language. + *

+ * These handlers implement debugging commands that require language-specific support. + * + * @see SimpleREPLClient + */ +public abstract class SLREPLHandler extends REPLHandler { + + protected SLREPLHandler(String op) { + super(op); + } + + public static final SLREPLHandler INFO_HANDLER = new SLREPLHandler(REPLMessage.INFO) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + final String topic = request.get(REPLMessage.TOPIC); + + if (topic == null || topic.isEmpty()) { + final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + return finishReplyFailed(message, "No info topic specified"); + } + + switch (topic) { + + case REPLMessage.LANGUAGE: + return createLanguageInfoReply(); + + default: + final REPLMessage message = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + return finishReplyFailed(message, "No info about topic \"" + topic + "\""); + } + } + }; + + private static REPLMessage[] createLanguageInfoReply() { + final ArrayList langMessages = new ArrayList<>(); + + final REPLMessage msg1 = new REPLMessage(REPLMessage.OP, REPLMessage.INFO); + msg1.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE); + msg1.put(REPLMessage.INFO_KEY, "Language"); + msg1.put(REPLMessage.INFO_VALUE, "Simple"); + msg1.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + langMessages.add(msg1); + + return langMessages.toArray(new REPLMessage[0]); + } + + public static final SLREPLHandler LOAD_RUN_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_RUN) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + return loadHandler(request, serverContext, false); + } + }; + + public static final SLREPLHandler LOAD_STEP_SOURCE_HANDLER = new SLREPLHandler(REPLMessage.LOAD_STEP) { + + @Override + public REPLMessage[] receive(REPLMessage request, REPLServerContext serverContext) { + return loadHandler(request, serverContext, true); + } + }; + + /** + * Runs a source, optionally stepping into a specified tag. + */ + private static REPLMessage[] loadHandler(REPLMessage request, REPLServerContext serverContext, boolean stepInto) { + final REPLMessage reply = new REPLMessage(REPLMessage.OP, REPLMessage.LOAD_RUN); + final String fileName = request.get(REPLMessage.SOURCE_NAME); + try { + final Source source = Source.fromFileName(fileName, true); + if (source == null) { + return finishReplyFailed(reply, "can't find file \"" + fileName + "\""); + } + serverContext.getDebugEngine().run(source, stepInto); + reply.put(REPLMessage.FILE_PATH, source.getPath()); + return finishReplySucceeded(reply, source.getName() + " exited"); + } catch (QuitException ex) { + throw ex; + } catch (KillException ex) { + return finishReplySucceeded(reply, fileName + " killed"); + } catch (Exception ex) { + return finishReplyFailed(reply, "error loading file \"" + fileName + "\": " + ex.getMessage()); + } + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLServer.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2014, 2015, 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.sl.tools.debug; + +import java.io.*; +import java.util.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.engine.*; +import com.oracle.truffle.tools.debug.shell.*; +import com.oracle.truffle.tools.debug.shell.client.*; +import com.oracle.truffle.tools.debug.shell.server.*; +import com.oracle.truffle.sl.factory.*; +import com.oracle.truffle.sl.runtime.*; + +/** + * Instantiation of the "server" side of the "REPL*" debugger for the Simple language. + *

+ * The SL parser is not equipped to parse program fragments, so any debugging functions that depend + * on this are not supported, for example {@link DebugEngine#eval(Source, Node, MaterializedFrame)} + * and {@link Breakpoint#setCondition(String)}. + * + * @see SimpleREPLClient + */ +public final class SLREPLServer implements REPLServer { + public static void main(String[] args) { + // Cheating for the prototype: start from SL, rather than from the client. + final SLREPLServer server = new SLREPLServer(); + final SimpleREPLClient client = new SimpleREPLClient(server.slContext, server); + + // Cheating for the prototype: allow server access to client for recursive debugging + server.setClient(client); + + try { + client.start(); + } catch (QuitException ex) { + } + } + + private final SLContext slContext; + private final DebugEngine slDebugEngine; + private final String statusPrefix; + private final Map handlerMap = new HashMap<>(); + private SLServerContext currentServerContext; + private SimpleREPLClient replClient = null; + + private void add(REPLHandler fileHandler) { + handlerMap.put(fileHandler.getOp(), fileHandler); + } + + public SLREPLServer() { + add(REPLHandler.BACKTRACE_HANDLER); + add(REPLHandler.BREAK_AT_LINE_HANDLER); + add(REPLHandler.BREAK_AT_LINE_ONCE_HANDLER); + add(REPLHandler.BREAK_AT_THROW_HANDLER); + add(REPLHandler.BREAK_AT_THROW_ONCE_HANDLER); + add(REPLHandler.BREAKPOINT_INFO_HANDLER); + add(REPLHandler.CLEAR_BREAK_HANDLER); + add(REPLHandler.CONTINUE_HANDLER); + add(REPLHandler.DELETE_HANDLER); + add(REPLHandler.DISABLE_BREAK_HANDLER); + add(REPLHandler.ENABLE_BREAK_HANDLER); + add(REPLHandler.FILE_HANDLER); + add(REPLHandler.FRAME_HANDLER); + add(SLREPLHandler.INFO_HANDLER); + add(REPLHandler.KILL_HANDLER); + add(SLREPLHandler.LOAD_RUN_SOURCE_HANDLER); + add(SLREPLHandler.LOAD_STEP_SOURCE_HANDLER); + add(REPLHandler.QUIT_HANDLER); + add(REPLHandler.STEP_INTO_HANDLER); + add(REPLHandler.STEP_OUT_HANDLER); + add(REPLHandler.STEP_OVER_HANDLER); + add(REPLHandler.TRUFFLE_HANDLER); + add(REPLHandler.TRUFFLE_NODE_HANDLER); + + // Set up an SL context + this.slContext = SLContextFactory.create(null, new PrintWriter(System.out)); + + final SLSourceExecutionProvider slSourceExecution = new SLSourceExecutionProvider(slContext); + final SLREPLDebugClient slDebugClient = new SLREPLDebugClient(this.slContext); + this.slDebugEngine = DebugEngine.create(slDebugClient, slSourceExecution); + this.statusPrefix = slContext.getLanguageShortName() + " REPL:"; + } + + private void setClient(SimpleREPLClient replClient) { + this.replClient = replClient; + } + + public REPLMessage start() { + + this.currentServerContext = new SLServerContext(null, null, null); + + // SL doesn't load modules (like other languages), so we just return a success + final REPLMessage reply = new REPLMessage(); + reply.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + reply.put(REPLMessage.DISPLAY_MSG, slContext.getLanguageShortName() + " started"); + return reply; + } + + public REPLMessage[] receive(REPLMessage request) { + if (currentServerContext == null) { + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.STATUS, REPLMessage.FAILED); + message.put(REPLMessage.DISPLAY_MSG, "server not started"); + final REPLMessage[] reply = new REPLMessage[]{message}; + return reply; + } + return currentServerContext.receive(request); + } + + /** + * Execution context of a halted SL program. + */ + public final class SLServerContext extends REPLServerContext { + + private final SLServerContext predecessor; + + public SLServerContext(SLServerContext predecessor, Node astNode, MaterializedFrame mFrame) { + super(predecessor == null ? 0 : predecessor.getLevel() + 1, astNode, mFrame); + this.predecessor = predecessor; + } + + @Override + public REPLMessage[] receive(REPLMessage request) { + final String command = request.get(REPLMessage.OP); + final REPLHandler handler = handlerMap.get(command); + + if (handler == null) { + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.OP, command); + message.put(REPLMessage.STATUS, REPLMessage.FAILED); + message.put(REPLMessage.DISPLAY_MSG, statusPrefix + " op \"" + command + "\" not supported"); + final REPLMessage[] reply = new REPLMessage[]{message}; + return reply; + } + return handler.receive(request, currentServerContext); + } + + @Override + public SLContext getLanguageContext() { + return slContext; + } + + @Override + public DebugEngine getDebugEngine() { + return slDebugEngine; + } + + } + + /** + * Specialize the standard SL debug context by notifying the REPL client when execution is + * halted, e.g. at a breakpoint. + *

+ * Before notification, the server creates a new context at the halted location, in which + * subsequent evaluations take place until such time as the client says to "continue". + *

+ * This implementation "cheats" the intended asynchronous architecture by calling back directly + * to the client with the notification. + */ + private final class SLREPLDebugClient implements DebugClient { + + private final SLContext slContext; + + SLREPLDebugClient(SLContext slContext) { + this.slContext = slContext; + } + + public void haltedAt(Node node, MaterializedFrame mFrame, List warnings) { + // Create and push a new debug context where execution is halted + currentServerContext = new SLServerContext(currentServerContext, node, mFrame); + + // Message the client that execution is halted and is in a new debugging context + final REPLMessage message = new REPLMessage(); + message.put(REPLMessage.OP, REPLMessage.STOPPED); + final SourceSection src = node.getSourceSection(); + final Source source = src.getSource(); + message.put(REPLMessage.SOURCE_NAME, source.getName()); + message.put(REPLMessage.FILE_PATH, source.getPath()); + message.put(REPLMessage.LINE_NUMBER, Integer.toString(src.getStartLine())); + message.put(REPLMessage.STATUS, REPLMessage.SUCCEEDED); + message.put(REPLMessage.DEBUG_LEVEL, Integer.toString(currentServerContext.getLevel())); + if (!warnings.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (String warning : warnings) { + sb.append(warning + "\n"); + } + message.put(REPLMessage.WARNINGS, sb.toString()); + } + try { + // Cheat with synchrony: call client directly about entering a nested debugging + // context. + replClient.halted(message); + } finally { + // Returns when "continue" is called in the new debugging context + + // Pop the debug context, and return so that the old context will continue + currentServerContext = currentServerContext.predecessor; + } + } + + public ExecutionContext getExecutionContext() { + return slContext; + } + } + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLSourceExecutionProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLSourceExecutionProvider.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014, 2015, 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.sl.tools.debug; + +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.sl.nodes.instrument.*; +import com.oracle.truffle.sl.runtime.*; +import com.oracle.truffle.tools.debug.engine.*; + +/** + * Specialization of the Truffle debugging engine for the Simple language. The engine implements + * basic debugging operations during Truffle-based execution. + */ +public final class SLSourceExecutionProvider extends SourceExecutionProvider { + + private final SLContext slContext; + + public SLSourceExecutionProvider(SLContext context) { + this.slContext = context; + Probe.registerASTProber(new SLStandardASTProber()); + } + + @Override + public void languageRun(Source source) { + slContext.executeMain(source); + } + + /** + * {@inheritDoc} + *

+ * The implementation of Simple is too, well, simple to support eval. Just for starters, the + * parser can only produce a whole program. + */ + @Override + public Object languageEval(Source source, Node node, MaterializedFrame mFrame) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + *

+ * The implementation of Simple is too, well, simple to support this. Just for starters, the + * parser can only produce a whole program. + */ + @Override + public AdvancedInstrumentRootFactory languageAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException { + throw new UnsupportedOperationException(); + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java --- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java Fri May 22 10:20:38 2015 -0700 +++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java Tue May 26 16:38:13 2015 -0700 @@ -31,8 +31,7 @@ import com.oracle.truffle.api.instrument.*; import com.oracle.truffle.api.nodes.*; import com.oracle.truffle.api.source.*; -import com.oracle.truffle.api.tools.*; -import com.oracle.truffle.api.vm.TruffleVM; +import com.oracle.truffle.api.vm.*; import com.oracle.truffle.sl.builtins.*; import com.oracle.truffle.sl.factory.*; import com.oracle.truffle.sl.nodes.*; @@ -43,6 +42,7 @@ import com.oracle.truffle.sl.nodes.local.*; import com.oracle.truffle.sl.parser.*; import com.oracle.truffle.sl.runtime.*; +import com.oracle.truffle.tools.*; /** * SL is a simple language to demonstrate and showcase features of Truffle. The implementation is as diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/Breakpoint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/Breakpoint.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.source.*; + +public abstract class Breakpoint { + + /** + * A general model of the states occupied by a breakpoint during its lifetime. + */ + public enum BreakpointState { + + /** + * Not attached, enabled. + *

+ * Created for a source location but not yet attached: perhaps just created and the source + * hasn't been loaded yet; perhaps source has been loaded, but the line location isn't + * probed so a breakpoint cannot be attached. Can be either enabled or disabled. + */ + ENABLED_UNRESOLVED("Enabled/Unresolved"), + + /** + * Not attached, disabled. + *

+ * Created for a source location but not yet attached: perhaps just created and the source + * hasn't been loaded yet; perhaps source has been loaded, but the line location isn't + * probed so a breakpoint cannot be attached. + */ + DISABLED_UNRESOLVED("Disabled/Unresolved"), + + /** + * Attached, instrument enabled. + *

+ * Is currently implemented by some {@link Instrument}, which is attached to a {@link Probe} + * at a specific node in the AST, and the breakpoint is enabled. + */ + ENABLED("Enabled"), + + /** + * Attached, instrument disabled. + *

+ * Is currently implemented by some {@link Instrument}, which is attached to a {@link Probe} + * at a specific node in the AST, and the breakpoint is disabled. + */ + DISABLED("Disabled"), + + /** + * Not attached, instrument is permanently disabled. + */ + DISPOSED("Disposed"); + + private final String name; + + BreakpointState(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + + } + + private static int nextBreakpointId = 0; + + private final int id; + private final int groupId; + private final boolean isOneShot; + + private int ignoreCount; + + private int hitCount = 0; + + private BreakpointState state; + + Breakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) { + this.state = state; + this.id = nextBreakpointId++; + this.groupId = groupId; + this.isOneShot = isOneShot; + this.ignoreCount = ignoreCount; + } + + /** + * Unique ID. + */ + public final int getId() { + return id; + } + + /** + * Group ID, set when created. + */ + public final int getGroupId() { + return groupId; + } + + /** + * Enables or disables this breakpoint's AST instrumentation. The breakpoint is enabled by + * default. + * + * @param enabled true to activate the instrumentation, false to + * deactivate the instrumentation so that it has no effect. + */ + public abstract void setEnabled(boolean enabled); + + /** + * Is this breakpoint active? + */ + public abstract boolean isEnabled(); + + /** + * Sets the condition on this breakpoint, {@code null} to make it unconditional. + * + * @param expr if non{@code -null}, a boolean expression, expressed in the guest language, to be + * evaluated in the lexical context at the breakpoint location. + * @throws DebugException if condition is invalid + * @throws UnsupportedOperationException if the breakpoint does not support conditions + */ + public abstract void setCondition(String expr) throws DebugException; + + /** + * Gets the string, expressed in the Guest Language, that defines the current condition on this + * breakpoint; {@code null} if this breakpoint is currently unconditional. + */ + public String getCondition() { + return null; + } + + /** + * Does this breakpoint remove itself after first activation? + */ + public final boolean isOneShot() { + return isOneShot; + } + + /** + * Gets the number of hits left to be ignored before halting. + */ + public final int getIgnoreCount() { + return ignoreCount; + } + + /** + * Change the threshold for when this breakpoint should start causing a break. When both an + * ignore count and a {@linkplain #setCondition(String) condition} are specified, the condition + * is evaluated first: if {@code false} it is not considered to be a hit. In other words, the + * ignore count is for successful conditions only. + */ + public final void setIgnoreCount(int ignoreCount) { + this.ignoreCount = ignoreCount; + } + + /** + * Number of times this breakpoint has reached, with one exception; if the breakpoint has a + * condition that evaluates to {@code false}, it does not count as a hit. + */ + public final int getHitCount() { + return hitCount; + } + + /** + * Disables this breakpoint and removes any associated instrumentation; it becomes permanently + * inert. + */ + public abstract void dispose(); + + /** + * Gets a human-sensible description of this breakpoint's location in a {@link Source}. + */ + public abstract String getLocationDescription(); + + public final BreakpointState getState() { + return state; + } + + final void assertState(BreakpointState s) { + assert state == s; + } + + final void setState(BreakpointState state) { + this.state = state; + } + + /** + * Assumes that all conditions for causing the break have been satisfied, so increments the + * hit count. Then checks if the ignore count has been exceeded, and if so + * returns {@code true}. If not, it still counts as a hit but should be ignored. + * + * @return whether to proceed + */ + final boolean incrHitCountCheckIgnore() { + return ++hitCount > ignoreCount; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()); + sb.append(" state="); + sb.append(getState() == null ? "" : getState().getName()); + if (isOneShot()) { + sb.append(", " + "One-Shot"); + } + if (getCondition() != null) { + sb.append(", condition=\"" + getCondition() + "\""); + } + return sb.toString(); + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugClient.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import java.util.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.nodes.Node; + +/** + * A client of the debugger where certain events should be posted. + * + * @see DebugEngine + */ +public interface DebugClient { + + /** + * Notifies client that program execution has been halted at some location; execution will + * resume when this method returns. + * + * @param astNode AST node that is just about to be executed + * @param mFrame frame that will be passed to the node when executed + * @param warnings any warnings generated since thie most recent halt. + */ + void haltedAt(Node astNode, MaterializedFrame mFrame, List warnings); + + // TODO (mlvdv) temporary; will eventually be accessible by a new Truffle language API + /** + * Gets the context for the language being debugged. + */ + ExecutionContext getExecutionContext(); +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugEngine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugEngine.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,881 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import java.io.*; +import java.util.*; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.engine.SourceExecutionProvider.ExecutionListener; + +/** + * Language-agnostic engine for running Truffle languages under debugging control. + */ +public final class DebugEngine { + + private static final boolean TRACE = false; + private static final boolean TRACE_PROBES = false; + private static final String TRACE_PREFIX = "DEBUG ENGINE: "; + + private static final PrintStream OUT = System.out; + + private static final SyntaxTag STEPPING_TAG = StandardSyntaxTag.STATEMENT; + private static final SyntaxTag CALL_TAG = StandardSyntaxTag.CALL; + + @SuppressWarnings("unused") + private static void trace(String format, Object... args) { + if (TRACE || TRACE_PROBES) { + OUT.println(TRACE_PREFIX + String.format(format, args)); + } + } + + interface BreakpointCallback { + + /** + * Passes control to the debugger with execution suspended. + */ + void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason); + } + + interface WarningLog { + + /** + * Logs a warning that is kept until the start of the next execution. + */ + void addWarning(String warning); + } + + /** + * The client of this engine. + */ + private final DebugClient debugClient; + + private final SourceExecutionProvider sourceExecutionProvider; + + /** + * Implementation of line-oriented breakpoints. + */ + private final LineBreakpointFactory lineBreaks; + + /** + * Implementation of tag-oriented breakpoints. + */ + private final TagBreakpointFactory tagBreaks; + + /** + * Head of the stack of executions. + */ + private DebugExecutionContext debugContext; + + /** + * @param debugClient + * @param sourceExecutionProvider + */ + private DebugEngine(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) { + this.debugClient = debugClient; + this.sourceExecutionProvider = sourceExecutionProvider; + + Source.setFileCaching(true); + + // Initialize execution context stack + debugContext = new DebugExecutionContext(null, null); + prepareContinue(); + debugContext.contextTrace("START EXEC DEFAULT"); + + sourceExecutionProvider.addExecutionListener(new ExecutionListener() { + + public void executionStarted(Source source, boolean stepInto) { + // Push a new execution context onto stack + DebugEngine.this.debugContext = new DebugExecutionContext(source, DebugEngine.this.debugContext); + if (stepInto) { + DebugEngine.this.prepareStepInto(1); + } else { + DebugEngine.this.prepareContinue(); + } + DebugEngine.this.debugContext.contextTrace("START EXEC "); + } + + public void executionEnded() { + DebugEngine.this.lineBreaks.disposeOneShots(); + DebugEngine.this.tagBreaks.disposeOneShots(); + DebugEngine.this.debugContext.clearStrategy(); + DebugEngine.this.debugContext.contextTrace("END EXEC "); + // Pop the stack of execution contexts. + DebugEngine.this.debugContext = DebugEngine.this.debugContext.predecessor; + } + }); + + final BreakpointCallback breakpointCallback = new BreakpointCallback() { + + @TruffleBoundary + public void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason) { + debugContext.halt(astNode, mFrame, true, haltReason); + } + }; + + final WarningLog warningLog = new WarningLog() { + + public void addWarning(String warning) { + assert debugContext != null; + debugContext.logWarning(warning); + } + }; + + this.lineBreaks = new LineBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog); + + this.tagBreaks = new TagBreakpointFactory(sourceExecutionProvider, breakpointCallback, warningLog); + + if (TRACE_PROBES) { + Probe.addProbeListener(new ProbeListener() { + + private Source beingProbed = null; + + @Override + public void startASTProbing(Source source) { + final String sourceName = source == null ? "" : source.getShortName(); + trace("START PROBING %s", sourceName); + beingProbed = source; + } + + @Override + public void newProbeInserted(Probe probe) { + trace("PROBE ADDED %s", probe.getShortDescription()); + } + + @Override + public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { + trace("PROBE TAGGED as %s: %s", tag, probe.getShortDescription()); + } + + @Override + public void endASTProbing(Source source) { + final String sourceName = source == null ? "" : source.getShortName(); + trace("FINISHED PROBING %s", sourceName); + assert source == beingProbed; + beingProbed = null; + } + }); + } + } + + public static DebugEngine create(DebugClient debugClient, SourceExecutionProvider sourceExecutionProvider) { + return new DebugEngine(debugClient, sourceExecutionProvider); + } + + /** + * Runs a script. If "StepInto" is requested, halts at the first location tagged as a + * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}. + * + * @throws DebugException if an unexpected failure occurs + */ + public void run(Source source, boolean stepInto) throws DebugException { + sourceExecutionProvider.run(source, stepInto); + } + + /** + * Sets a breakpoint to halt at a source line. + * + * @param groupId + * @param ignoreCount number of hits to ignore before halting + * @param lineLocation where to set the breakpoint (source, line number) + * @param oneShot breakpoint disposes itself after fist hit, if {@code true} + * @return a new breakpoint, initially enabled + * @throws DebugException if the breakpoint can not be set. + */ + @TruffleBoundary + public LineBreakpoint setLineBreakpoint(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) throws DebugException { + return lineBreaks.create(groupId, ignoreCount, lineLocation, oneShot); + } + + /** + * Sets a breakpoint to halt at any node holding a specified {@link SyntaxTag}. + * + * @param groupId + * @param ignoreCount number of hits to ignore before halting + * @param oneShot if {@code true} breakpoint removes it self after a hit + * @return a new breakpoint, initially enabled + * @throws DebugException if the breakpoint already set + */ + @TruffleBoundary + public Breakpoint setTagBreakpoint(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) throws DebugException { + return tagBreaks.create(groupId, ignoreCount, tag, oneShot); + } + + /** + * Finds a breakpoint created by this engine, but not yet disposed, by id. + */ + @TruffleBoundary + public Breakpoint findBreakpoint(long id) { + final Breakpoint breakpoint = lineBreaks.find(id); + return breakpoint == null ? tagBreaks.find(id) : breakpoint; + } + + /** + * Gets all existing breakpoints, whatever their status, in natural sorted order. Modification + * save. + */ + @TruffleBoundary + public Collection getBreakpoints() { + final Collection result = new ArrayList<>(); + result.addAll(lineBreaks.getAll()); + result.addAll(tagBreaks.getAll()); + return result; + } + + /** + * Prepare to execute in Continue mode when guest language program execution resumes. In this + * mode: + *

    + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a node to which an enabled breakpoint is attached, + * or:
    2. + *
    3. execution completes.
    4. + *
    + *
+ */ + @TruffleBoundary + public void prepareContinue() { + debugContext.setStrategy(new Continue()); + } + + /** + * Prepare to execute in StepInto mode when guest language program execution resumes. In this + * mode: + *
    + *
  • User breakpoints are disabled.
  • + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT + * STATMENT}, or:
    2. + *
    3. execution completes.
    4. + *
    + *
  • + * StepInto mode persists only through one resumption (i.e. {@code stepIntoCount} steps), and + * reverts by default to Continue mode.
  • + *
+ * + * @param stepCount the number of times to perform StepInto before halting + * @throws IllegalArgumentException if the specified number is {@code <= 0} + */ + @TruffleBoundary + public void prepareStepInto(int stepCount) { + if (stepCount <= 0) { + throw new IllegalArgumentException(); + } + debugContext.setStrategy(new StepInto(stepCount)); + } + + /** + * Prepare to execute in StepOut mode when guest language program execution resumes. In this + * mode: + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at the nearest enclosing call site on the stack, or
    2. + *
    3. execution completes.
    4. + *
    + *
  • StepOut mode persists only through one resumption, and reverts by default to Continue + * mode.
  • + *
+ */ + @TruffleBoundary + public void prepareStepOut() { + debugContext.setStrategy(new StepOut()); + } + + /** + * Prepare to execute in StepOver mode when guest language program execution resumes. In this + * mode: + *
    + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT + * STATEMENT} when not nested in one or more function/method calls, or:
    2. + *
    3. execution arrives at a node to which a breakpoint is attached and when nested in one or + * more function/method calls, or:
    4. + *
    5. execution completes.
    6. + *
    + *
  • StepOver mode persists only through one resumption (i.e. {@code stepOverCount} steps), + * and reverts by default to Continue mode.
  • + *
+ * + * @param stepCount the number of times to perform StepInto before halting + * @throws IllegalArgumentException if the specified number is {@code <= 0} + */ + @TruffleBoundary + public void prepareStepOver(int stepCount) { + if (stepCount <= 0) { + throw new IllegalArgumentException(); + } + debugContext.setStrategy(new StepOver(stepCount)); + } + + /** + * Gets the stack frames from the (topmost) halted Truffle execution; {@code null} null if no + * execution. + */ + @TruffleBoundary + public List getStack() { + return debugContext == null ? null : debugContext.getFrames(); + } + + /** + * Evaluates code in a halted execution context, at top-level if mFrame==null. + */ + public Object eval(Source source, Node node, MaterializedFrame mFrame) { + return sourceExecutionProvider.eval(source, node, mFrame); + } + + /** + * A mode of user navigation from a current code location to another, e.g "step in" vs. + * "step over". + */ + private abstract class StepStrategy { + + private DebugExecutionContext context; + protected final String strategyName; + + protected StepStrategy() { + this.strategyName = getClass().getSimpleName(); + } + + final String getName() { + return strategyName; + } + + /** + * Reconfigure the debugger so that when execution continues the program will halt at the + * location specified by this strategy. + */ + final void enable(DebugExecutionContext c, int stackDepth) { + this.context = c; + setStrategy(stackDepth); + } + + /** + * Return the debugger to the default navigation mode. + */ + final void disable() { + unsetStrategy(); + } + + @TruffleBoundary + final void halt(Node astNode, MaterializedFrame mFrame, boolean before) { + context.halt(astNode, mFrame, before, this.getClass().getSimpleName()); + } + + @TruffleBoundary + final void replaceStrategy(StepStrategy newStrategy) { + context.setStrategy(newStrategy); + } + + @TruffleBoundary + protected final void strategyTrace(String action, String format, Object... args) { + if (TRACE) { + context.contextTrace("%s (%s) %s", action, strategyName, String.format(format, args)); + } + } + + @TruffleBoundary + protected final void suspendUserBreakpoints() { + lineBreaks.setActive(false); + tagBreaks.setActive(false); + } + + @SuppressWarnings("unused") + protected final void restoreUserBreakpoints() { + lineBreaks.setActive(true); + tagBreaks.setActive(true); + } + + /** + * Reconfigure the debugger so that when execution continues, it will do so using this mode + * of navigation. + */ + protected abstract void setStrategy(int stackDepth); + + /** + * Return to the debugger to the default mode of navigation. + */ + protected abstract void unsetStrategy(); + } + + /** + * Strategy: the null stepping strategy. + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution continues until either: + *
      + *
    1. execution arrives at a node with attached user breakpoint, or:
    2. + *
    3. execution completes.
    4. + *
    + *
+ */ + private final class Continue extends StepStrategy { + + @Override + protected void setStrategy(int stackDepth) { + } + + @Override + protected void unsetStrategy() { + } + } + + /** + * Strategy: per-statement stepping. + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution continues until either: + *
      + *
    1. execution arrives at a STATEMENT node, or:
    2. + *
    3. execution returns to a CALL node and the call stack is smaller then when + * execution started, or:
    4. + *
    5. execution completes.
    6. + *
    + *
+ * + * @see DebugEngine#prepareStepInto(int) + */ + private final class StepInto extends StepStrategy { + private int unfinishedStepCount; + + StepInto(int stepCount) { + super(); + this.unfinishedStepCount = stepCount; + } + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + // HALT: just before statement + --unfinishedStepCount; + strategyTrace("TRAP BEFORE", "unfinished steps=%d", unfinishedStepCount); + // Should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, true); + } + strategyTrace("RESUME BEFORE", ""); + } + }); + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + --unfinishedStepCount; + strategyTrace(null, "TRAP AFTER unfinished steps=%d", unfinishedStepCount); + if (currentStackDepth() < stackDepth) { + // HALT: just "stepped out" + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + } + strategyTrace("RESUME AFTER", ""); + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: execution to nearest enclosing call site. + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution continues until either: + *
      + *
    1. execution arrives at a node with attached user breakpoint, or:
    2. + *
    3. execution returns to a CALL node and the call stack is smaller than when + * execution started, or:
    4. + *
    5. execution completes.
    6. + *
    + *
+ * + * @see DebugEngine#prepareStepOut() + */ + private final class StepOut extends StepStrategy { + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @TruffleBoundary + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + // HALT: + final int currentStackDepth = currentStackDepth(); + strategyTrace("TRAP AFTER", "stackDepth: start=%d current=%d", stackDepth, currentStackDepth); + if (currentStackDepth < stackDepth) { + halt(node, mFrame, false); + } + strategyTrace("RESUME AFTER", ""); + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: per-statement stepping, so long as not nested in method calls (i.e. at original + * stack depth). + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution continues until either: + *
      + *
    1. execution arrives at a STATEMENT node with stack depth no more than when started + * or:
    2. + *
    3. the program completes.
    4. + *
    + *
+ */ + private final class StepOver extends StepStrategy { + private int unfinishedStepCount; + + StepOver(int stepCount) { + this.unfinishedStepCount = stepCount; + } + + @Override + protected void setStrategy(int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth <= stackDepth) { + // HALT: stack depth unchanged or smaller; treat like StepInto + --unfinishedStepCount; + if (TRACE) { + strategyTrace("TRAP BEFORE", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + } + // Test should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, true); + } + } else { + // CONTINUE: Stack depth increased; don't count as a step + strategyTrace("STEP INTO", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + // Stop treating like StepInto, start treating like StepOut + replaceStrategy(new StepOverNested(unfinishedStepCount, stackDepth)); + } + strategyTrace("RESUME BEFORE", ""); + } + }); + + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth < stackDepth) { + // HALT: just "stepped out" + --unfinishedStepCount; + strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth: start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + // Should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + strategyTrace("RESUME AFTER", ""); + } + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: per-statement stepping, not into method calls, in effect while at increased stack + * depth + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execution continues until either: + *
      + *
    1. execution arrives at a STATEMENT node with stack depth no more than when started + * or:
    2. + *
    3. the program completes or:
    4. + *
    + *
+ */ + private final class StepOverNested extends StepStrategy { + private int unfinishedStepCount; + private final int startStackDepth; + + StepOverNested(int stepCount, int startStackDepth) { + this.unfinishedStepCount = stepCount; + this.startStackDepth = startStackDepth; + } + + @Override + protected void setStrategy(int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth <= startStackDepth) { + // At original step depth (or smaller) after being nested + --unfinishedStepCount; + strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + // TODO (mlvdv) fixme for multiple steps + strategyTrace("RESUME BEFORE", ""); + } + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + } + } + + /** + * Information and debugging state for a single Truffle execution (which make take place over + * one or more suspended executions). This holds interaction state, for example what is + * executing (e.g. some {@link Source}), what the execution mode is ("stepping" or + * "continuing"). When not running, this holds a cache of the Truffle stack for this particular + * execution, effectively hiding the Truffle stack for any currently suspended executions (down + * the stack). + */ + private final class DebugExecutionContext { + + // Previous halted context in stack + private final DebugExecutionContext predecessor; + + // The current execution level; first is 0. + private final int level; // Number of contexts suspended below + private final Source source; + private final int contextStackBase; // Where the stack for this execution starts + private final List warnings = new ArrayList<>(); + + private boolean running; + + /** + * The stepping strategy currently configured in the debugger. + */ + private StepStrategy strategy; + + /** + * Where halted; null if running. + */ + private Node haltedNode; + + /** + * Where halted; null if running. + */ + private MaterializedFrame haltedFrame; + + /** + * Cached list of stack frames when halted; null if running. + */ + private List frames = new ArrayList<>(); + + private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) { + this.source = executionSource; + this.predecessor = previousContext; + this.level = previousContext == null ? 0 : previousContext.level + 1; + + // "Base" is the number of stack frames for all nested (halted) executions. + this.contextStackBase = currentStackDepth(); + this.running = true; + contextTrace("NEW CONTEXT"); + } + + /** + * Sets up a strategy for the next resumption of execution. + * + * @param stepStrategy + */ + void setStrategy(StepStrategy stepStrategy) { + if (this.strategy == null) { + this.strategy = stepStrategy; + this.strategy.enable(this, currentStackDepth()); + if (TRACE) { + contextTrace("SET MODE -->" + stepStrategy.getName()); + } + } else { + strategy.disable(); + strategy = stepStrategy; + strategy.enable(this, currentStackDepth()); + contextTrace("SWITCH MODE %s-->%s", strategy.getName(), stepStrategy.getName()); + } + } + + void clearStrategy() { + if (strategy != null) { + final StepStrategy oldStrategy = strategy; + strategy.disable(); + strategy = null; + contextTrace("CLEAR MODE %s-->", oldStrategy.getName()); + } + } + + /** + * Handle a program halt, caused by a breakpoint, stepping strategy, or other cause. + * + * @param astNode the guest language node at which execution is halted + * @param mFrame the current execution frame where execution is halted + * @param before {@code true} if halted before the node, else after. + */ + @TruffleBoundary + void halt(Node astNode, MaterializedFrame mFrame, boolean before, String haltReason) { + assert running; + assert frames.isEmpty(); + assert haltedNode == null; + assert haltedFrame == null; + + haltedNode = astNode; + haltedFrame = mFrame; + running = false; + + clearStrategy(); + + // Clean up, just in cased the one-shot breakpoints got confused + lineBreaks.disposeOneShots(); + + // Map the Truffle stack for this execution, ignore nested executions + // The top (current) frame is not produced by the iterator. + frames.add(new FrameDebugDescription(0, haltedNode, Truffle.getRuntime().getCurrentFrame())); + final int contextStackDepth = currentStackDepth() - contextStackBase; + final int[] frameCount = {1}; + Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { + @Override + public FrameInstance visitFrame(FrameInstance frameInstance) { + if (frameCount[0] < contextStackDepth) { + frames.add(new FrameDebugDescription(frameCount[0], frameInstance.getCallNode(), frameInstance)); + frameCount[0] = frameCount[0] + 1; + return null; + } + return frameInstance; + } + }); + + if (TRACE) { + final String reason = haltReason == null ? "" : haltReason + ""; + final String where = before ? "BEFORE" : "AFTER"; + contextTrace("HALT %s : (%s) stack base=%d", where, reason, contextStackBase); + contextTrace("CURRENT STACK:"); + printStack(OUT); + } + + final List recentWarnings = new ArrayList<>(warnings); + warnings.clear(); + + try { + // Pass control to the debug client with current execution suspended + debugClient.haltedAt(astNode, mFrame, recentWarnings); + // Debug client finished normally, execution resumes + // Presume that the client has set a new strategy (or default to Continue) + running = true; + } catch (KillException e) { + contextTrace("KILL"); + throw e; + } finally { + haltedNode = null; + haltedFrame = null; + frames.clear(); + } + + } + + List getFrames() { + return Collections.unmodifiableList(frames); + } + + void logWarning(String warning) { + warnings.add(warning); + } + + // For tracing + private void printStack(PrintStream stream) { + getFrames(); + if (frames == null) { + stream.println(""); + } else { + // TODO (mlvdv) get visualizer via the (to be developed) Truffle langauge API + final Visualizer visualizer = debugClient.getExecutionContext().getVisualizer(); + + for (FrameDebugDescription frameDesc : frames) { + final StringBuilder sb = new StringBuilder(" frame " + Integer.toString(frameDesc.index())); + sb.append(":at " + visualizer.displaySourceLocation(frameDesc.node())); + sb.append(":in '" + visualizer.displayMethodName(frameDesc.node()) + "'"); + stream.println(sb.toString()); + } + } + } + + void contextTrace(String format, Object... args) { + if (TRACE) { + final String srcName = (source != null) ? source.getName() : "no source"; + DebugEngine.trace("<%d> %s (%s)", level, String.format(format, args), srcName); + } + } + } + + // TODO (mlvdv) wish there were fast-path access to stack depth + /** + * Depth of current Truffle stack, including nested executions. Includes the top/current frame, + * which the standard iterator does not count: {@code 0} if no executions. + */ + @TruffleBoundary + private static int currentStackDepth() { + final int[] count = {0}; + Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { + @Override + public Void visitFrame(FrameInstance frameInstance) { + count[0] = count[0] + 1; + return null; + } + }); + return count[0] == 0 ? 0 : count[0] + 1; + + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/DebugException.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +/** + * An unexpected failure in the operation of the {@link DebugEngine}. + */ +public class DebugException extends Exception { + + public DebugException(String string) { + super(string); + } + + private static final long serialVersionUID = 3307454453821997224L; + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/FrameDebugDescription.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/FrameDebugDescription.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; + +/** + * A summary description of a Truffle {@link Frame} in a particular stack context. + * + * @see DebugEngine + */ +public final class FrameDebugDescription { + + private final int index; + private final Node node; + private final FrameInstance frameInstance; + + FrameDebugDescription(int index, Node node, FrameInstance frameInstance) { + this.index = index; + this.node = node; + this.frameInstance = frameInstance; + } + + /** + * Position in the current stack: {@code 0} at the top. + */ + public int index() { + return index; + } + + /** + * AST location. + */ + public Node node() { + return node; + } + + /** + * Access to the Truffle {@link Frame}. + */ + public FrameInstance frameInstance() { + return frameInstance; + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpoint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpoint.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import com.oracle.truffle.api.source.*; + +// TODO (mlvdv) generic? +/** + * A breakpoint associated with a {@linkplain LineLocation source line location}. + * + * @see DebugEngine + */ +public abstract class LineBreakpoint extends Breakpoint { + + LineBreakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) { + super(state, groupId, ignoreCount, isOneShot); + } + + /** + * Gets the {@linkplain LineLocation source line location} that specifies where this breakpoint + * will trigger. + */ + public abstract LineLocation getLineLocation(); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpointFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/LineBreakpointFactory.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import static com.oracle.truffle.tools.debug.engine.Breakpoint.BreakpointState.*; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.instrument.impl.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.api.utilities.*; +import com.oracle.truffle.tools.*; +import com.oracle.truffle.tools.debug.engine.DebugEngine.BreakpointCallback; +import com.oracle.truffle.tools.debug.engine.DebugEngine.WarningLog; + +//TODO (mlvdv) some common functionality could be factored out of this and TagBreakpointSupport + +/** + * Support class for creating and managing all existing ordinary (user visible) line breakpoints. + *

+ * Notes: + *

    + *
  1. Line breakpoints can only be set at nodes tagged as {@link StandardSyntaxTag#STATEMENT}.
  2. + *
  3. A newly created breakpoint looks for probes matching the location, attaches to them if found + * by installing an {@link Instrument} that calls back to the breakpoint.
  4. + *
  5. When Truffle "splits" or otherwise copies an AST, any attached {@link Instrument} will be + * copied along with the rest of the AST and will call back to the same breakpoint.
  6. + *
  7. When notification is received of a new Node being tagged as a statement, and if a + * breakpoint's line location matches the Probe's line location, then the breakpoint will attach a + * new Instrument at the probe to activate the breakpoint at that location.
  8. + *
  9. A breakpoint may have multiple Instruments deployed, one attached to each Probe that matches + * the breakpoint's line location; this might happen when a source is reloaded.
  10. + *
+ * + */ +final class LineBreakpointFactory { + + private static final boolean TRACE = false; + private static final PrintStream OUT = System.out; + + private static final String BREAKPOINT_NAME = "LINE BREAKPOINT"; + + private static void trace(String format, Object... args) { + if (TRACE) { + OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args))); + } + } + + private static final Comparator> BREAKPOINT_COMPARATOR = new Comparator>() { + + @Override + public int compare(Entry entry1, Entry entry2) { + final LineLocation line1 = entry1.getKey(); + final LineLocation line2 = entry2.getKey(); + final int nameOrder = line1.getSource().getShortName().compareTo(line2.getSource().getShortName()); + if (nameOrder != 0) { + return nameOrder; + } + return Integer.compare(line1.getLineNumber(), line2.getLineNumber()); + } + }; + + private final SourceExecutionProvider sourceExecutionProvider; + private final BreakpointCallback breakpointCallback; + private final WarningLog warningLog; + + /** + * Map: Source lines ==> attached breakpoints. There may be no more than one line breakpoint + * associated with a line. + */ + private final Map lineToBreakpoint = new HashMap<>(); + + /** + * A map of {@link LineLocation} to a collection of {@link Probe}s. This list must be + * initialized and filled prior to being used by this class. + */ + private final LineToProbesMap lineToProbesMap; + + /** + * Globally suspends all line breakpoint activity when {@code false}, ignoring whether + * individual breakpoints are enabled. + */ + @CompilationFinal private boolean breakpointsActive = true; + private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active"); + + LineBreakpointFactory(SourceExecutionProvider sourceExecutionProvider, BreakpointCallback breakpointCallback, WarningLog warningLog) { + this.sourceExecutionProvider = sourceExecutionProvider; + this.breakpointCallback = breakpointCallback; + this.warningLog = warningLog; + + lineToProbesMap = new LineToProbesMap(); + lineToProbesMap.install(); + + Probe.addProbeListener(new DefaultProbeListener() { + + @Override + public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { + if (tag == StandardSyntaxTag.STATEMENT) { + final SourceSection sourceSection = probe.getProbedSourceSection(); + if (sourceSection != null) { + final LineLocation lineLocation = sourceSection.getLineLocation(); + if (lineLocation != null) { + // A Probe with line location tagged STATEMENT we haven't seen before. + final LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation); + if (breakpoint != null) { + try { + breakpoint.attach(probe); + } catch (DebugException e) { + warningLog.addWarning(BREAKPOINT_NAME + " failure attaching to newly tagged Probe: " + e.getMessage()); + if (TRACE) { + OUT.println(BREAKPOINT_NAME + " failure: " + e.getMessage()); + } + } + } + } + } + } + } + }); + } + + /** + * Globally enables line breakpoint activity; all breakpoints are ignored when set to + * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint + * determines whether it will trigger when flow of execution reaches it. + * + * @param breakpointsActive + */ + void setActive(boolean breakpointsActive) { + if (this.breakpointsActive != breakpointsActive) { + breakpointsActiveUnchanged.invalidate(); + this.breakpointsActive = breakpointsActive; + } + } + + /** + * Returns the (not yet disposed) breakpoint by id; null if none. + */ + LineBreakpoint find(long id) { + for (LineBreakpoint breakpoint : lineToBreakpoint.values()) { + if (breakpoint.getId() == id) { + return breakpoint; + } + } + return null; + } + + /** + * Gets all current line breakpoints,regardless of status; sorted and modification safe. + */ + List getAll() { + ArrayList> entries = new ArrayList<>(lineToBreakpoint.entrySet()); + Collections.sort(entries, BREAKPOINT_COMPARATOR); + + final ArrayList breakpoints = new ArrayList<>(entries.size()); + for (Entry entry : entries) { + breakpoints.add(entry.getValue()); + } + return breakpoints; + } + + /** + * Creates a new line breakpoint if one doesn't already exist. If one does exist, then resets + * the ignore count. + * + * @param lineLocation where to set the breakpoint + * @param ignoreCount number of initial hits before the breakpoint starts causing breaks. + * @param oneShot whether the breakpoint should dispose itself after one hit + * @return a possibly new breakpoint + * @throws DebugException if a breakpoint already exists at the location and the ignore count is + * the same + */ + LineBreakpoint create(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) throws DebugException { + + LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation); + + if (breakpoint == null) { + breakpoint = new LineBreakpointImpl(groupId, ignoreCount, lineLocation, oneShot); + + if (TRACE) { + trace("NEW " + breakpoint.getShortDescription()); + } + + lineToBreakpoint.put(lineLocation, breakpoint); + + for (Probe probe : lineToProbesMap.findProbes(lineLocation)) { + if (probe.isTaggedAs(StandardSyntaxTag.STATEMENT)) { + breakpoint.attach(probe); + break; + } + } + } else { + if (ignoreCount == breakpoint.getIgnoreCount()) { + throw new DebugException(BREAKPOINT_NAME + " already set at line " + lineLocation); + } + breakpoint.setIgnoreCount(ignoreCount); + if (TRACE) { + trace("CHANGED ignoreCount %s", breakpoint.getShortDescription()); + } + } + return breakpoint; + } + + /** + * Returns the {@link LineBreakpoint} for a given line. There should only ever be one breakpoint + * per line. + * + * @param lineLocation The {@link LineLocation} to get the breakpoint for. + * @return The breakpoint for the given line. + */ + LineBreakpoint get(LineLocation lineLocation) { + return lineToBreakpoint.get(lineLocation); + } + + /** + * Removes the associated instrumentation for all one-shot breakpoints only. + */ + void disposeOneShots() { + List breakpoints = new ArrayList<>(lineToBreakpoint.values()); + for (LineBreakpointImpl breakpoint : breakpoints) { + if (breakpoint.isOneShot()) { + breakpoint.dispose(); + } + } + } + + /** + * Removes all knowledge of a breakpoint, presumed disposed. + */ + private void forget(LineBreakpointImpl breakpoint) { + lineToBreakpoint.remove(breakpoint.getLineLocation()); + } + + /** + * Concrete representation of a line breakpoint, implemented by attaching an instrument to a + * probe at the designated source location. + */ + private final class LineBreakpointImpl extends LineBreakpoint implements AdvancedInstrumentResultListener { + + private static final String SHOULD_NOT_HAPPEN = "LineBreakpointImpl: should not happen"; + + private final LineLocation lineLocation; + + // Cached assumption that the global status of line breakpoint activity has not changed. + private Assumption breakpointsActiveAssumption; + + // Whether this breakpoint is enable/disabled + @CompilationFinal private boolean isEnabled; + private Assumption enabledUnchangedAssumption; + + private String conditionExpr; + + /** + * The instrument(s) that this breakpoint currently has attached to a {@link Probe}: + * {@code null} if not attached. + */ + private List instruments = new ArrayList<>(); + + public LineBreakpointImpl(int groupId, int ignoreCount, LineLocation lineLocation, boolean oneShot) { + super(ENABLED_UNRESOLVED, groupId, ignoreCount, oneShot); + this.lineLocation = lineLocation; + + this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption(); + this.isEnabled = true; + this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged"); + } + + @Override + public boolean isEnabled() { + return isEnabled; + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled != isEnabled) { + switch (getState()) { + case ENABLED: + assert !enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(false); + changeState(DISABLED); + break; + case ENABLED_UNRESOLVED: + assert !enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(false); + changeState(DISABLED_UNRESOLVED); + break; + case DISABLED: + assert enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(true); + changeState(ENABLED); + break; + case DISABLED_UNRESOLVED: + assert enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(true); + changeState(DISABLED_UNRESOLVED); + break; + case DISPOSED: + assert false : "breakpoint disposed"; + break; + default: + assert false : SHOULD_NOT_HAPPEN; + break; + } + } + } + + @Override + public void setCondition(String expr) throws DebugException { + if (this.conditionExpr != null || expr != null) { + // De-instrument the Probes instrumented by this breakpoint + final ArrayList probes = new ArrayList<>(); + for (Instrument instrument : instruments) { + probes.add(instrument.getProbe()); + instrument.dispose(); + } + instruments.clear(); + this.conditionExpr = expr; + // Re-instrument the probes previously instrumented + for (Probe probe : probes) { + attach(probe); + } + } + } + + @Override + public String getCondition() { + return conditionExpr; + } + + @Override + public void dispose() { + if (getState() != DISPOSED) { + for (Instrument instrument : instruments) { + instrument.dispose(); + } + changeState(DISPOSED); + LineBreakpointFactory.this.forget(this); + } + } + + private void attach(Probe newProbe) throws DebugException { + if (getState() == DISPOSED) { + throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME); + } + Instrument newInstrument = null; + if (conditionExpr == null) { + newInstrument = Instrument.create(new UnconditionalLineBreakInstrumentListener(), BREAKPOINT_NAME); + } else { + newInstrument = Instrument.create(this, sourceExecutionProvider.createAdvancedInstrumentRootFactory(conditionExpr, this), Boolean.class, BREAKPOINT_NAME); + } + newProbe.attach(newInstrument); + instruments.add(newInstrument); + changeState(isEnabled ? ENABLED : DISABLED); + } + + private void doSetEnabled(boolean enabled) { + if (this.isEnabled != enabled) { + enabledUnchangedAssumption.invalidate(); + this.isEnabled = enabled; + } + } + + private String getShortDescription() { + return BREAKPOINT_NAME + "@" + getLineLocation().getShortDescription(); + } + + private void changeState(BreakpointState after) { + if (TRACE) { + trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription()); + } + setState(after); + } + + private void doBreak(Node node, VirtualFrame vFrame) { + if (incrHitCountCheckIgnore()) { + breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME); + } + } + + /** + * Receives notification from the attached instrument that execution is about to enter node + * where the breakpoint is set. Designed so that when in the fast path, there is either an + * unconditional "halt" call to the debugger or nothing. + */ + private void nodeEnter(Node astNode, VirtualFrame vFrame) { + + // Deopt if the global active/inactive flag has changed + try { + this.breakpointsActiveAssumption.check(); + } catch (InvalidAssumptionException ex) { + this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption(); + } + + // Deopt if the enabled/disabled state of this breakpoint has changed + try { + this.enabledUnchangedAssumption.check(); + } catch (InvalidAssumptionException ex) { + this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged"); + } + + if (LineBreakpointFactory.this.breakpointsActive && this.isEnabled) { + if (isOneShot()) { + dispose(); + } + LineBreakpointImpl.this.doBreak(astNode, vFrame); + } + } + + public void notifyResult(Node node, VirtualFrame vFrame, Object result) { + final boolean condition = (Boolean) result; + if (TRACE) { + trace("breakpoint condition = %b %s", condition, getShortDescription()); + } + if (condition) { + nodeEnter(node, vFrame); + } + } + + @TruffleBoundary + public void notifyFailure(Node node, VirtualFrame vFrame, RuntimeException ex) { + warningLog.addWarning(String.format("Exception in %s: %s", getShortDescription(), ex.getMessage())); + if (TRACE) { + trace("breakpoint failure = %s %s", ex.toString(), getShortDescription()); + } + // Take the breakpoint if evaluation fails. + nodeEnter(node, vFrame); + } + + @Override + public String getLocationDescription() { + return "Line: " + lineLocation.getShortDescription(); + } + + @Override + public LineLocation getLineLocation() { + return lineLocation; + } + + private final class UnconditionalLineBreakInstrumentListener extends DefaultStandardInstrumentListener { + + @Override + public void enter(Probe probe, Node node, VirtualFrame vFrame) { + LineBreakpointImpl.this.nodeEnter(node, vFrame); + } + } + } + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/SourceExecutionProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/SourceExecutionProvider.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import java.util.*; + +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; + +/** + * Base for language-specific support required by the {@link DebugEngine} and any other tools that + * run sources. + */ +public abstract class SourceExecutionProvider { + + interface ExecutionListener { + + /** + * Notifies that execution is about to start and requests initial execution mode. + */ + void executionStarted(Source source, boolean stepInto); + + /** + * Notification that the current execution has just ended. + */ + void executionEnded(); + } + + private final List listeners = new ArrayList<>(); + + final void addExecutionListener(ExecutionListener listener) { + assert listener != null; + listeners.add(listener); + } + + /** + * Runs a script. If "StepInto" is specified, halts at the first location tagged as a + * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT}. + */ + final void run(Source source, boolean stepInto) throws DebugException { + for (ExecutionListener listener : listeners) { + listener.executionStarted(source, stepInto); + } + try { + languageRun(source); + } finally { + for (ExecutionListener listener : listeners) { + listener.executionEnded(); + } + } + } + + /** + * Evaluates string of language code in a halted execution context, at top level if + * mFrame==null. + */ + final Object eval(Source source, Node node, MaterializedFrame mFrame) { + for (ExecutionListener listener : listeners) { + listener.executionStarted(source, false); + } + try { + return languageEval(source, node, mFrame); + } finally { + for (ExecutionListener listener : listeners) { + listener.executionEnded(); + } + } + } + + /** + * Creates a language-specific factory to produce instances of {@link AdvancedInstrumentRoot} + * that, when executed, computes the result of a textual expression in the language; used to + * create an + * {@linkplain Instrument#create(AdvancedInstrumentResultListener, AdvancedInstrumentRootFactory, Class, String) + * Advanced Instrument}. + * + * @param expr a guest language expression + * @param resultListener optional listener for the result of each evaluation. + * @return a new factory + * @throws DebugException if the factory cannot be created, for example if the expression is + * badly formed. + */ + final AdvancedInstrumentRootFactory createAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException { + return languageAdvancedInstrumentRootFactory(expr, resultListener); + } + + /** + * Runs source code. + * + * @param source code + * @throws DebugException if unable to run successfully + */ + public abstract void languageRun(Source source) throws DebugException; + + /** + * Runs source code in a halted execution context, or at top level. + * + * @param source the code to run + * @param node node where execution halted, {@code null} if no execution context + * @param mFrame frame where execution halted, {@code null} if no execution context + * @return result of running the code in the context, or at top level if no execution context. + */ + public abstract Object languageEval(Source source, Node node, MaterializedFrame mFrame); + + /** + * Creates a {@linkplain AdvancedInstrumentRootFactory factory} that produces AST fragments from + * a textual expression, suitable for execution in context by the Instrumentation Framework. + * + * @param expr + * @param resultListener + * @return a factory that returns AST fragments that compute the expression + * @throws DebugException if the expression cannot be processed + * + * @see Instrument#create(AdvancedInstrumentResultListener, AdvancedInstrumentRootFactory, + * Class, String) + */ + public abstract AdvancedInstrumentRootFactory languageAdvancedInstrumentRootFactory(String expr, AdvancedInstrumentResultListener resultListener) throws DebugException; +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpoint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpoint.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import com.oracle.truffle.api.instrument.*; + +// TODO (mlvdv) generic? +/** + * A breakpoint associated with a {@link SyntaxTag}. + * + * @see DebugEngine + */ +public abstract class TagBreakpoint extends Breakpoint { + + TagBreakpoint(BreakpointState state, int groupId, int ignoreCount, boolean isOneShot) { + super(state, groupId, ignoreCount, isOneShot); + } + + /** + * Gets the tag that specifies where this breakpoint will trigger. + */ + public abstract SyntaxTag getTag(); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpointFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/TagBreakpointFactory.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2015, 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.tools.debug.engine; + +import static com.oracle.truffle.tools.debug.engine.Breakpoint.BreakpointState.*; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.instrument.impl.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.utilities.*; +import com.oracle.truffle.tools.debug.engine.DebugEngine.BreakpointCallback; +import com.oracle.truffle.tools.debug.engine.DebugEngine.WarningLog; + +// TODO (mlvdv) some common functionality could be factored out of this and LineBreakpointSupport + +/** + * Support class for creating and managing "Tag Breakpoints". A Tag Breakpoint halts execution just + * before reaching any node whose Probe carries a specified {@linkplain SyntaxTag Tag}. + *

+ * The {@linkplain Probe#setBeforeTagTrap(SyntaxTagTrap) Tag Trap}, which is built directly into the + * Instrumentation Framework, does the same thing more efficiently, but there may only be one Tag + * Trap active at a time. Any number of tag breakpoints may coexist with the Tag Trap, but it would + * be confusing to have a Tag Breakpoint set for the same Tag as the current Tag Trap. + *

+ * Notes: + *

    + *
  1. Only one Tag Breakpoint can be active for a specific {@linkplain SyntaxTag Tag}.
  2. + *
  3. A newly created breakpoint looks for probes matching the tag, attaches to them if found by + * installing an {@link Instrument}.
  4. + *
  5. When Truffle "splits" or otherwise copies an AST, any attached {@link Instrument} will be + * copied along with the rest of the AST and will call back to the same breakpoint.
  6. + *
  7. When notification is received that the breakpoint's Tag has been newly added to a Node, then + * the breakpoint will attach a new Instrument at the probe to activate the breakpoint at that + * location.
  8. + *
  9. A breakpoint may have multiple Instruments deployed, one attached to each Probe that holds + * the breakpoint's tag; this might happen when a source is reloaded.
  10. + *
+ */ +final class TagBreakpointFactory { + + private static final boolean TRACE = false; + private static final PrintStream OUT = System.out; + + private static final String BREAKPOINT_NAME = "TAG BREAKPOINT"; + + private static void trace(String format, Object... args) { + if (TRACE) { + OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args))); + } + } + + private static final Comparator> BREAKPOINT_COMPARATOR = new Comparator>() { + + @Override + public int compare(Entry entry1, Entry entry2) { + return entry1.getKey().name().compareTo(entry2.getKey().name()); + } + }; + + private final SourceExecutionProvider sourceExecutionProvider; + private final BreakpointCallback breakpointCallback; + private final WarningLog warningLog; + + /** + * Map: Tags ==> Tag Breakpoints. There may be no more than one breakpoint per Tag. + */ + private final Map tagToBreakpoint = new HashMap<>(); + + /** + * Globally suspends all line breakpoint activity when {@code false}, ignoring whether + * individual breakpoints are enabled. + */ + @CompilationFinal private boolean breakpointsActive = true; + private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active"); + + TagBreakpointFactory(SourceExecutionProvider sourceExecutionProvider, BreakpointCallback breakpointCallback, WarningLog warningLog) { + this.sourceExecutionProvider = sourceExecutionProvider; + this.breakpointCallback = breakpointCallback; + this.warningLog = warningLog; + + Probe.addProbeListener(new DefaultProbeListener() { + + @Override + public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { + final TagBreakpointImpl breakpoint = tagToBreakpoint.get(tag); + if (breakpoint != null) { + try { + breakpoint.attach(probe); + } catch (DebugException e) { + warningLog.addWarning(BREAKPOINT_NAME + " failure attaching to newly tagged Probe: " + e.getMessage()); + if (TRACE) { + OUT.println(BREAKPOINT_NAME + " failure: " + e.getMessage()); + } + } + } + } + }); + } + + /** + * Globally enables tag breakpoint activity; all breakpoints are ignored when set to + * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint + * determines whether it will trigger when flow of execution reaches it. + * + * @param breakpointsActive + */ + void setActive(boolean breakpointsActive) { + if (this.breakpointsActive != breakpointsActive) { + breakpointsActiveUnchanged.invalidate(); + this.breakpointsActive = breakpointsActive; + } + } + + /** + * Returns the (not yet disposed) breakpoint by id, if any; null if none. + */ + TagBreakpoint find(long id) { + for (TagBreakpointImpl breakpoint : tagToBreakpoint.values()) { + if (breakpoint.getId() == id) { + return breakpoint; + } + } + return null; + } + + /** + * Gets all current tag breakpoints,regardless of status; sorted and modification safe. + */ + List getAll() { + ArrayList> entries = new ArrayList<>(tagToBreakpoint.entrySet()); + Collections.sort(entries, BREAKPOINT_COMPARATOR); + + final ArrayList breakpoints = new ArrayList<>(entries.size()); + for (Entry entry : entries) { + breakpoints.add(entry.getValue()); + } + return breakpoints; + } + + /** + * Creates a new tag breakpoint if one doesn't already exist. If one does exist, then resets the + * ignore count. + * + * @param tag where to set the breakpoint + * @param ignoreCount number of initial hits before the breakpoint starts causing breaks. + * @param oneShot whether the breakpoint should dispose itself after one hit + * @return a possibly new breakpoint + * @throws DebugException if a breakpoint already exists for the tag and the ignore count is the + * same + */ + TagBreakpoint create(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) throws DebugException { + + TagBreakpointImpl breakpoint = tagToBreakpoint.get(tag); + + if (breakpoint == null) { + breakpoint = new TagBreakpointImpl(groupId, ignoreCount, tag, oneShot); + + if (TRACE) { + trace("NEW " + breakpoint.getShortDescription()); + } + + tagToBreakpoint.put(tag, breakpoint); + + for (Probe probe : Probe.findProbesTaggedAs(tag)) { + breakpoint.attach(probe); + } + } else { + if (ignoreCount == breakpoint.getIgnoreCount()) { + throw new DebugException(BREAKPOINT_NAME + " already set for tag " + tag.name()); + } + breakpoint.setIgnoreCount(ignoreCount); + if (TRACE) { + trace("CHANGED ignoreCount %s", breakpoint.getShortDescription()); + } + } + return breakpoint; + } + + /** + * Returns the {@link TagBreakpoint} for a given tag, {@code null} if none. + */ + TagBreakpoint get(SyntaxTag tag) { + return tagToBreakpoint.get(tag); + } + + /** + * Removes the associated instrumentation for all one-shot breakpoints only. + */ + void disposeOneShots() { + List breakpoints = new ArrayList<>(tagToBreakpoint.values()); + for (TagBreakpointImpl breakpoint : breakpoints) { + if (breakpoint.isOneShot()) { + breakpoint.dispose(); + } + } + } + + /** + * Removes all knowledge of a breakpoint, presumed disposed. + */ + private void forget(TagBreakpointImpl breakpoint) { + tagToBreakpoint.remove(breakpoint.getTag()); + } + + /** + * Concrete representation of a line breakpoint, implemented by attaching an instrument to a + * probe at the designated source location. + */ + private final class TagBreakpointImpl extends TagBreakpoint implements AdvancedInstrumentResultListener { + + private static final String SHOULD_NOT_HAPPEN = "TagBreakpointImpl: should not happen"; + + private final SyntaxTag tag; + + // Cached assumption that the global status of tag breakpoint activity has not changed. + private Assumption breakpointsActiveAssumption; + + // Whether this breakpoint is enable/disabled + @CompilationFinal private boolean isEnabled; + private Assumption enabledUnchangedAssumption; + + private String conditionExpr; + + /** + * The instrument(s) that this breakpoint currently has attached to a {@link Probe}: + * {@code null} if not attached. + */ + private List instruments = new ArrayList<>(); + + private TagBreakpointImpl(int groupId, int ignoreCount, SyntaxTag tag, boolean oneShot) { + super(ENABLED, groupId, ignoreCount, oneShot); + this.tag = tag; + this.breakpointsActiveAssumption = TagBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption(); + this.isEnabled = true; + this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged"); + } + + @Override + public boolean isEnabled() { + return isEnabled; + } + + @Override + public void setEnabled(boolean enabled) { + // Tag Breakpoints are never unresolved + if (enabled != isEnabled) { + switch (getState()) { + case ENABLED: + assert !enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(false); + changeState(DISABLED); + break; + case DISABLED: + assert enabled : SHOULD_NOT_HAPPEN; + doSetEnabled(true); + changeState(ENABLED); + break; + case DISPOSED: + assert false : "breakpoint disposed"; + break; + case ENABLED_UNRESOLVED: + case DISABLED_UNRESOLVED: + default: + assert false : SHOULD_NOT_HAPPEN; + break; + } + } + } + + @Override + public void setCondition(String expr) throws DebugException { + if (this.conditionExpr != null || expr != null) { + // De-instrument the Probes instrumented by this breakpoint + final ArrayList probes = new ArrayList<>(); + for (Instrument instrument : instruments) { + probes.add(instrument.getProbe()); + instrument.dispose(); + } + instruments.clear(); + this.conditionExpr = expr; + // Re-instrument the probes previously instrumented + for (Probe probe : probes) { + attach(probe); + } + } + } + + @Override + public String getCondition() { + return conditionExpr; + } + + @Override + public void dispose() { + if (getState() != DISPOSED) { + for (Instrument instrument : instruments) { + instrument.dispose(); + } + changeState(DISPOSED); + TagBreakpointFactory.this.forget(this); + } + } + + private void attach(Probe newProbe) throws DebugException { + if (getState() == DISPOSED) { + throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME); + } + Instrument newInstrument = null; + if (conditionExpr == null) { + newInstrument = Instrument.create(new UnconditionalTagBreakInstrumentListener(), BREAKPOINT_NAME); + } else { + newInstrument = Instrument.create(this, sourceExecutionProvider.createAdvancedInstrumentRootFactory(conditionExpr, this), Boolean.class, BREAKPOINT_NAME); + } + newProbe.attach(newInstrument); + instruments.add(newInstrument); + changeState(isEnabled ? ENABLED : DISABLED); + } + + private void doSetEnabled(boolean enabled) { + if (this.isEnabled != enabled) { + enabledUnchangedAssumption.invalidate(); + this.isEnabled = enabled; + } + } + + private String getShortDescription() { + return BREAKPOINT_NAME + "@" + tag.name(); + } + + private void changeState(BreakpointState after) { + if (TRACE) { + trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription()); + } + setState(after); + } + + private void doBreak(Node node, VirtualFrame vFrame) { + if (incrHitCountCheckIgnore()) { + breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME); + } + } + + /** + * Receives notification from the attached instrument that execution is about to enter node + * where the breakpoint is set. Designed so that when in the fast path, there is either an + * unconditional "halt" call to the debugger or nothing. + */ + private void nodeEnter(Node astNode, VirtualFrame vFrame) { + + // Deopt if the global active/inactive flag has changed + try { + this.breakpointsActiveAssumption.check(); + } catch (InvalidAssumptionException ex) { + this.breakpointsActiveAssumption = TagBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption(); + } + + // Deopt if the enabled/disabled state of this breakpoint has changed + try { + this.enabledUnchangedAssumption.check(); + } catch (InvalidAssumptionException ex) { + this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged"); + } + + if (TagBreakpointFactory.this.breakpointsActive && this.isEnabled) { + if (isOneShot()) { + dispose(); + } + TagBreakpointImpl.this.doBreak(astNode, vFrame); + } + + } + + public void notifyResult(Node node, VirtualFrame vFrame, Object result) { + final boolean condition = (Boolean) result; + if (TRACE) { + trace("breakpoint condition = %b %s", condition, getShortDescription()); + } + if (condition) { + nodeEnter(node, vFrame); + } + } + + public void notifyFailure(Node node, VirtualFrame vFrame, RuntimeException ex) { + warningLog.addWarning(String.format("Exception in %s: %s", getShortDescription(), ex.getMessage())); + if (TRACE) { + trace("breakpoint failure = %s %s", ex.toString(), getShortDescription()); + } + // Take the breakpoint if evaluation fails. + nodeEnter(node, vFrame); + } + + @Override + public String getLocationDescription() { + return "Tag " + tag.name(); + } + + @Override + public SyntaxTag getTag() { + return tag; + } + + private final class UnconditionalTagBreakInstrumentListener extends DefaultStandardInstrumentListener { + + @Override + public void enter(Probe probe, Node node, VirtualFrame vFrame) { + TagBreakpointImpl.this.nodeEnter(node, vFrame); + } + } + + } + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.engine/src/com/oracle/truffle/tools/debug/engine/package-info.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, 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. + */ + +/** + * This package contains the shared (language-agnostic) support for implementing debuggers + * that work with Truffle-implemented languages. + *

+ * This implementation is made possible by the general purpose Instrumentation Framework built + * into the Truffle platform. Some online documentation for the Instrumentation Framework is available + * online: + * + * https://wiki.openjdk.java.net/display/Graal/Instrumentation+API + * + *

+ * Debugging services for a Truffle-implemented language are provided by creating an instance + * of {@link com.oracle.truffle.tools.debug.engine.DebugEngine} specialized for a specific language. The DebugEngine can: + *

    + *
  • Load and run sources in the language
  • + *
  • Set breakpoints possibly with conditions and other attributes, on source lines
  • + *
  • Navigate by Continue, StepIn, StepOver, or StepOut
  • + *
  • Examine the execution stack
  • + *
  • Examine the contents of a stack frame
  • + *
  • Evaluate a code fragment in the context of a stack frame
  • + *
+ *

+ * Specialization of the DebugEngine for a Truffle-implemented language takes several forms: + *

    + *
  1. A specification from the language implementor that adds Instrumentation "tags" to the nodes + * that a debugger should know about, for example Statements, Calls, and Throws
  2. + *
  3. Methods to run programs/scripts generally, and more specifically run text fragments in the context of + * a particular frame/Node in a halted Truffle execution
  4. + *
  5. Utility methods, such as providing textual displays of Objects that represent values in the language
  6. + *
+ *

+ * Note: Both the functionality and API for this package are under active development. + *

+ * @see com.oracle.truffle.api.instrument + */ +package com.oracle.truffle.tools.debug.engine; + diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLClient.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell; + +/** + * The client side of a simple message-based protocol for a possibly remote language + * Read-Eval-Print-Loop. + */ +public interface REPLClient { + + /** + * Accepts a reply from the server; there may be more than one reply in response to a request. + */ + REPLMessage receive(REPLMessage reply); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLMessage.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +/** + * A message for communication between a Read-Eval-Print-Loop server associated with a language + * implementation and a possibly remote client. + * + * @see REPLClient + * @see REPLServer + */ +public final class REPLMessage { + + // Some standard keys and values + public static final String AST = "ast"; + public static final String AST_DEPTH = "show-max-depth"; + public static final String BACKTRACE = "backtrace"; + public static final String BREAK_AT_LINE = "break-at-line"; + public static final String BREAK_AT_LINE_ONCE = "break-at-line-once"; + public static final String BREAK_AT_THROW = "break-at-throw"; + public static final String BREAK_AT_THROW_ONCE = "break-at-throw-once"; + public static final String BREAKPOINT_CONDITION = "breakpoint-condition"; + public static final String BREAKPOINT_GROUP_ID = "breakpoint-group-id"; + public static final String BREAKPOINT_HIT_COUNT = "breakpoint-hit-count"; + public static final String BREAKPOINT_ID = "breakpoint-id"; + public static final String BREAKPOINT_IGNORE_COUNT = "breakpoint-ignore-count"; + public static final String BREAKPOINT_INFO = "breakpoint-info"; + public static final String BREAKPOINT_STATE = "breakpoint-state"; + public static final String CLEAR_BREAK = "clear-breakpoint"; + public static final String CODE = "code"; + public static final String CONTINUE = "continue"; + public static final String DEBUG_LEVEL = "debug-level"; + public static final String DELETE_BREAK = "delete-breakpoint"; + public static final String DISABLE_BREAK = "disable-breakpoint"; + public static final String DISPLAY_MSG = "displayable-message"; + public static final String DOWN = "down"; + public static final String ENABLE_BREAK = "enable-breakpoint"; + public static final String EVAL = "eval"; + public static final String FAILED = "failed"; + public static final String FILE = "file"; + public static final String FILE_PATH = "path"; + public static final String FRAME = "frame"; + public static final String FRAME_INFO = "frame-info"; + public static final String FRAME_NUMBER = "frame-number"; + public static final String INFO = "info"; + public static final String INFO_KEY = "info-key"; + public static final String INFO_VALUE = "info-value"; + public static final String KILL = "kill"; + public static final String LANGUAGE = "language"; + public static final String LINE_NUMBER = "line-number"; + public static final String LIST = "list"; + public static final String LOAD_RUN = "load-run-source"; + public static final String LOAD_STEP = "load-step-into-source"; + public static final String METHOD_NAME = "method-name"; + public static final String OP = "op"; + public static final String OPTION = "option"; + public static final String QUIT = "quit"; + public static final String REPEAT = "repeat"; + public static final String SET = "set"; + public static final String SET_BREAK_CONDITION = "set-breakpoint-condition"; + public static final String SOURCE_LINE_TEXT = "source-line-text"; + public static final String SOURCE_LOCATION = "source-location"; + public static final String SOURCE_NAME = "source-name"; + public static final String SOURCE_TEXT = "source-text"; + public static final String STACK_SIZE = "stack-size"; + public static final String STATUS = "status"; + public static final String STEP_INTO = "step-into"; + public static final String STEP_OUT = "step-out"; + public static final String STEP_OVER = "step-over"; + public static final String STOPPED = "stopped"; + public static final String SUB = "sub"; + public static final String SUBTREE = "subtree"; + public static final String SUCCEEDED = "succeeded"; + public static final String TOPIC = "topic"; + public static final String TRUFFLE = "truffle"; + public static final String TRUFFLE_AST = "truffle-ast"; + public static final String TRUFFLE_NODE = "truffle-node"; + public static final String TRUFFLE_SUBTREE = "truffle-subtree"; + public static final String UNSET_BREAK_CONDITION = "unset-breakpoint-condition"; + public static final String UP = "up"; + public static final String VALUE = "value"; + public static final String WARNINGS = "warnings"; + private final Map map; + + /** + * Creates an empty REPL message. + */ + public REPLMessage() { + this.map = new TreeMap<>(); + } + + /** + * Creates a REPL message with an initial entry. + */ + public REPLMessage(String key, String value) { + this(); + map.put(key, value); + } + + public REPLMessage(REPLMessage message) { + this.map = new TreeMap<>(message.map); + } + + public String get(String key) { + return map.get(key); + } + + /** + * Returns the specified key value as an integer; {@code null} if missing or non-numeric. + */ + public Integer getIntValue(String key) { + final String value = map.get(key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + + } + } + return null; + } + + public String put(String key, String value) { + return map.put(key, value); + } + + public String remove(String key) { + return map.remove(key); + } + + public Set keys() { + return map.keySet(); + } + + public void print(PrintStream out, String linePrefix) { + map.keySet(); + + for (Entry entry : map.entrySet()) { + String value = entry.getValue(); + if (value != null && value.length() > 50) { + value = value.substring(0, 50) + " ..."; + } + out.println(linePrefix + entry.getKey() + " = \"" + value + "\""); + } + } +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/REPLServer.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell; + +/** + * The server side of a simple message-based protocol for a possibly remote language + * Read-Eval-Print-Loop. + */ +public interface REPLServer { + + /** + * Starts up a server; status returned in a message. + */ + REPLMessage start(); + + /** + * Ask the server to handle a request. Return a non-empty array of messages to simulate remote + * operation where the protocol has possibly multiple messages being returned asynchronously in + * response to each request. + */ + REPLMessage[] receive(REPLMessage request); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLClientContext.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +import java.util.*; + +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.shell.*; + +/** + * Client context for interaction with a program halted by the {@link REPLServer}. + */ +public interface REPLClientContext { + + /** + * The source code halted in this context. + */ + Source source(); + + /** + * The 1-based line at which execution is halted in this context; 0 means unknown. + */ + int lineNumber(); + + /** + * The Truffle stack where execution is halted in this context. + */ + List frames(); + + /** + * The nesting level of the execution context: 0 means evaluating shell commands outside any + * executing program. + */ + int level(); + + /** + * The source currently selected by the user; defaults to where halted. + */ + Source getSelectedSource(); + + /** + * The frame number in this execution context currently selected by the user; defaults to 0. + */ + int getSelectedFrameNumber(); + + /** + * Issue a command to the REPLServer that + *

    + *
  • can be specified by a single "op",
  • + *
  • produces information in the form of a single string, and
  • + *
  • has no effect on the execution state.
  • + *
+ */ + String stringQuery(String op); + + /** + * Sets a new "default" frame number for frame-related commands. + */ + void selectFrameNumber(int frameNumber); + + /** + * Sends an information message. + */ + void displayInfo(String message); + + void displayStack(); + + /** + * Send a message related to handling a command in this context. + */ + void displayReply(String message); + + /** + * Send a message related to failure while handling a command in this context. + */ + void displayFailReply(String message); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLCommand.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +// TODO (mlvdv) write a real command line parser +abstract class REPLCommand { + + private final String command; + private final String abbreviation; + private final String description; + + public REPLCommand(String command, String abbreviation, String description) { + this.command = command; + this.abbreviation = abbreviation; + this.description = description; + } + + public final String getCommand() { + return command; + } + + public final String getAbbreviation() { + return abbreviation; + } + + public final String getDescription() { + return description; + } + + public String[] getHelp() { + return new String[]{getCommand() + ": " + getDescription()}; + } + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLContinueException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLContinueException.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +class REPLContinueException extends RuntimeException { + + private static final long serialVersionUID = 4763527450877123326L; + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLFrame.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLFrame.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +/** + * Information about a stack frame in the current {@link REPLClientContext}. + */ +interface REPLFrame { + + /** + * Index of the frame. + */ + int index(); + + /** + * File path; null if unknown. + */ + String locationFilePath(); + + /** + * Line number of frame location; null if unknown. + */ + Integer locationLineNumber(); + + /** + * Short description of the code location. + */ + String locationDescription(); + + /** + * Name of the method body containing the location. + */ + String name(); + + /** + * The line of text at the code location. + */ + String sourceLineText(); + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLRemoteCommand.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,696 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +import java.io.*; +import java.util.*; + +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.shell.*; + +// TODO (mlvdv) write a real command line parser +public abstract class REPLRemoteCommand extends REPLCommand { + + public REPLRemoteCommand(String command, String abbreviation, String description) { + super(command, abbreviation, description); + } + + protected abstract REPLMessage createRequest(REPLClientContext context, String[] args); + + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + context.displayFailReply(result != null ? result : firstReply.toString()); + } else { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + context.displayReply(result != null ? result : firstReply.toString()); + } + + for (int i = 1; i < replies.length; i++) { + REPLMessage reply = replies[i]; + final String result = reply.get(REPLMessage.DISPLAY_MSG); + context.displayInfo(result != null ? result : reply.toString()); + } + } + + public static final REPLRemoteCommand BREAK_AT_LINE_CMD = new REPLRemoteCommand("break-at-line", "break", "Set a breakpoint") { + + private final String[] help = {"break [ignore=] : set breakpoint at line in current file", "break : [ignore=] : set breakpoint at line in ", + " optionally ignore first hits (default 0)"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + try { + final REPLineLocation lineLocation = REPLineLocation.parse(context, args); + final REPLMessage requestMessage = lineLocation.createMessage(REPLMessage.BREAK_AT_LINE); + int ignoreCount = 0; + if (args.length > 2) { + final String ignoreText = args[2]; + if (ignoreText.equals("ignore")) { + throw new IllegalArgumentException("No ignore count specified"); + } + final String[] split = ignoreText.split("="); + if (split.length == 2 && split[0].equals("ignore")) { + try { + ignoreCount = Integer.parseInt(split[1]); + if (ignoreCount < 0) { + throw new IllegalArgumentException("Illegal ignore count: " + split[1]); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("No ignore count specified"); + } + } else { + throw new IllegalArgumentException("Unrecognized argument \"" + ignoreText + "\""); + } + } + requestMessage.put(REPLMessage.BREAKPOINT_IGNORE_COUNT, Integer.toString(ignoreCount)); + return requestMessage; + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final String number = firstReply.get(REPLMessage.BREAKPOINT_ID); + final String fileName = firstReply.get(REPLMessage.SOURCE_NAME); + final String lineNumber = firstReply.get(REPLMessage.LINE_NUMBER); + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + number + " set at " + fileName + ":" + lineNumber); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand BREAK_AT_LINE_ONCE_CMD = new REPLRemoteCommand("break-at-line-once", "break1", "Set a one-shot breakpoint") { + + private final String[] help = {"break : set one-shot breakpoint at line in current file", "break :: set one-shot breakpoint at line in current file"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + try { + return REPLineLocation.parse(context, args).createMessage(REPLMessage.BREAK_AT_LINE_ONCE); + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final String fileName = firstReply.get(REPLMessage.SOURCE_NAME); + final String lineNumber = firstReply.get(REPLMessage.LINE_NUMBER); + firstReply.put(REPLMessage.DISPLAY_MSG, "one-shot breakpoint set at " + fileName + ":" + lineNumber); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand BREAK_AT_THROW_CMD = new REPLRemoteCommand("break-at-throw", "breakthrow", "Break at any throw") { + + private final String[] help = {"break-at-throw: set breakpoint on any throw"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.BREAK_AT_THROW); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint at any throw set"); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand BREAK_AT_THROW_ONCE_CMD = new REPLRemoteCommand("break-at-throw-once", "break1throw", "Break once at any throw") { + + private final String[] help = {"break-at-throw: set one-short breakpoint on any throw"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.BREAK_AT_THROW_ONCE); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + firstReply.put(REPLMessage.DISPLAY_MSG, "one-shot breakpoint at any throw set"); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand CLEAR_BREAK_CMD = new REPLRemoteCommand("clear", null, "Clear a breakpoint") { + + private final String[] help = {"clear : clear breakpoint number "}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + context.displayFailReply("breakpoint number not speciified: \"break \""); + } else if (args.length > 2) { + context.displayFailReply("breakpoint number not understood: \"break \""); + } else { + try { + final int breakpointNumber = Integer.parseInt(args[1]); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.CLEAR_BREAK); + request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); + return request; + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID); + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " cleared"); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand CONDITION_BREAK_CMD = new REPLRemoteCommand("cond", null, "Set new condition on a breakpoint") { + + private final String[] help = {"cond [expr]: sets new condition on breakpoint number ; make unconditional if no [expr]"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + context.displayFailReply("breakpoint number not speciified: \"cond \""); + } else { + try { + final int breakpointNumber = Integer.parseInt(args[1]); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); + if (args.length == 2) { + request.put(REPLMessage.OP, REPLMessage.UNSET_BREAK_CONDITION); + } else { + final StringBuilder exprBuilder = new StringBuilder(); + for (int i = 2; i < args.length; i++) { + exprBuilder.append(args[i]).append(" "); + } + request.put(REPLMessage.BREAKPOINT_CONDITION, exprBuilder.toString().trim()); + request.put(REPLMessage.OP, REPLMessage.SET_BREAK_CONDITION); + } + return request; + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID); + final String condition = firstReply.get(REPLMessage.BREAKPOINT_CONDITION); + if (condition == null) { + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " condition cleared"); + } else { + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " condition=\"" + condition + "\""); + } + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand CONTINUE_CMD = new REPLRemoteCommand("continue", "c", "Continue execution") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.CONTINUE); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + + throw new REPLContinueException(); + + } + }; + + public static final REPLRemoteCommand DELETE_CMD = new REPLRemoteCommand("delete", "d", "Delete all breakpoints") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.DELETE_BREAK); + return request; + } + }; + + public static final REPLRemoteCommand DISABLE_CMD = new REPLRemoteCommand("disable", null, "Disable a breakpoint") { + + private final String[] help = {"disable : disable breakpoint number "}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + context.displayFailReply("breakpoint number not speciified: \"disable \""); + } else if (args.length > 2) { + context.displayFailReply("breakpoint number not understood: \"disable \""); + } else { + try { + final int breakpointNumber = Integer.parseInt(args[1]); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.DISABLE_BREAK); + request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); + return request; + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID); + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " disabled"); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand DOWN_CMD = new REPLRemoteCommand("down", null, "Move down a stack frame") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final List frames = context.frames(); + final int newFrameSelection = context.getSelectedFrameNumber() + 1; + if (newFrameSelection > frames.size() - 1) { + context.displayFailReply("at bottom of stack"); + return null; + } + context.selectFrameNumber(newFrameSelection); + return FRAME_CMD.createRequest(context, Arrays.copyOfRange(args, 0, 0)); + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + context.displayFailReply(result != null ? result : firstReply.toString()); + } else { + context.displayStack(); + } + } + }; + + public static final REPLRemoteCommand ENABLE_CMD = new REPLRemoteCommand("enable", null, "Enable a breakpoint") { + + private final String[] help = {"enable : enable breakpoint number "}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + context.displayFailReply("breakpoint number not speciified: \"enable \""); + } else if (args.length > 2) { + context.displayFailReply("breakpoint number not understood: \"enable \""); + } else { + try { + final int breakpointNumber = Integer.parseInt(args[1]); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.ENABLE_BREAK); + request.put(REPLMessage.BREAKPOINT_ID, Integer.toString(breakpointNumber)); + return request; + } catch (IllegalArgumentException ex) { + context.displayFailReply(ex.getMessage()); + } + } + return null; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + final int breakpointNumber = firstReply.getIntValue(REPLMessage.BREAKPOINT_ID); + firstReply.put(REPLMessage.DISPLAY_MSG, "breakpoint " + breakpointNumber + " enabled"); + } + super.processReply(context, replies); + } + }; + + public static final REPLRemoteCommand FRAME_CMD = new REPLRemoteCommand("frame", null, "Display a stack frame") { + + private final String[] help = {"frame : display currently selected frame", "frame : display frame "}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.FRAME); + + int frameNumber = context.getSelectedFrameNumber(); + if (args.length > 1) { + if (args.length == 2) { + try { + frameNumber = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Unrecognized argument \"" + args[1] + "\""); + } + } else { + throw new IllegalArgumentException("Unrecognized argument \"" + args[2] + "\""); + } + } + request.put(REPLMessage.FRAME_NUMBER, Integer.toString(frameNumber)); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + context.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); + } else { + Integer frameNumber = replies[0].getIntValue(REPLMessage.FRAME_NUMBER); + context.selectFrameNumber(frameNumber); + context.displayReply("Frame " + frameNumber + ":"); + for (REPLMessage message : replies) { + for (String line : message.get(REPLMessage.DISPLAY_MSG).split("\n")) { + context.displayInfo(line); + } + } + } + } + }; + + public static final REPLRemoteCommand KILL_CMD = new REPLRemoteCommand("kill", null, "Stop program execution") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, "kill"); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.SUCCEEDED)) { + context.displayReply(replies[0].get(REPLMessage.DISPLAY_MSG)); + } else { + context.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); + } + } + }; + + public static final REPLRemoteCommand LOAD_RUN_CMD = new REPLRemoteCommand("load-run", "loadr", "Load and run a source") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + Source runSource = null; + if (args.length == 1) { + runSource = context.getSelectedSource(); + if (runSource == null) { + context.displayFailReply("No file selected"); + return null; + } + } else { + try { + runSource = Source.fromFileName(args[1]); + } catch (IOException e) { + context.displayFailReply("Can't find file: " + args[1]); + return null; + } + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.LOAD_RUN); + request.put(REPLMessage.SOURCE_NAME, runSource.getPath()); + return request; + } + }; + + public static final REPLRemoteCommand LOAD_STEP_CMD = new REPLRemoteCommand("load-step", "loads", "Load and step into a source") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + Source runSource = null; + if (args.length == 1) { + runSource = context.getSelectedSource(); + if (runSource == null) { + context.displayFailReply("No file selected"); + return null; + } + } else { + try { + runSource = Source.fromFileName(args[1]); + } catch (IOException e) { + context.displayFailReply("Can't find file: " + args[1]); + return null; + } + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.LOAD_STEP); + request.put(REPLMessage.SOURCE_NAME, runSource.getPath()); + return request; + } + }; + + public static final REPLRemoteCommand STEP_INTO_CMD = new REPLRemoteCommand("step", "s", "(StepInto) next statement, going into functions.") { + + @Override + public String[] getHelp() { + return new String[]{"step into: step to next statement (into calls)", "step : step to nth next statement (into calls)"}; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.STEP_INTO); + + if (args.length >= 2) { + final String nText = args[1]; + try { + final int nSteps = Integer.parseInt(nText); + if (nSteps > 0) { + request.put(REPLMessage.REPEAT, Integer.toString(nSteps)); + } else { + return null; + } + } catch (NumberFormatException e) { + context.displayFailReply("Step into count \"" + nText + "\" not recognized"); + return null; + } + } + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + + throw new REPLContinueException(); + } + }; + + public static final REPLRemoteCommand STEP_OUT_CMD = new REPLRemoteCommand("finish", null, "(StepOut) continue to end of function") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.STEP_OUT); + + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + + throw new REPLContinueException(); + } + }; + + public static final REPLRemoteCommand STEP_OVER_CMD = new REPLRemoteCommand("next", "n", "(StepOver) execute next line of code, not into functions.") { + + @Override + public String[] getHelp() { + return new String[]{"next: (StepOver) execute next line of code, not into functions.", "next : (StepOver) execute to nth next statement (not counting into functions)"}; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.STEP_OVER); + + if (args.length >= 2) { + final String nText = args[1]; + try { + final int nSteps = Integer.parseInt(nText); + if (nSteps > 0) { + request.put(REPLMessage.REPEAT, Integer.toString(nSteps)); + } else { + return null; + } + } catch (NumberFormatException e) { + context.displayFailReply("Next count \"" + nText + "\" not recognized"); + return null; + } + } + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + + throw new REPLContinueException(); + } + }; + + public static final REPLRemoteCommand UP_CMD = new REPLRemoteCommand("up", null, "Move up a stack frame") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (context.level() == 0) { + context.displayFailReply("no active execution"); + return null; + } + final int newFrameSelection = context.getSelectedFrameNumber() - 1; + if (newFrameSelection < 0) { + context.displayFailReply("at top of stack"); + return null; + } + context.selectFrameNumber(newFrameSelection); + return FRAME_CMD.createRequest(context, Arrays.copyOfRange(args, 0, 0)); + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + context.displayFailReply(result != null ? result : firstReply.toString()); + } else { + context.displayStack(); + } + } + }; + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLineLocation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/REPLineLocation.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +import java.io.*; + +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.shell.*; + +final class REPLineLocation { + + private final Source source; + private final int lineNumber; + + /** + * Attempts to extract description of a source line from {@code arg[1]}, either + * ":" or just "". + */ + static REPLineLocation parse(REPLClientContext context, String[] args) throws IllegalArgumentException { + if (args.length == 1) { + throw new IllegalArgumentException("no location specified"); + } + + Source source = null; + int lineNumber = -1; + String lineNumberText = null; + + final String[] split = args[1].split(":"); + if (split.length == 1) { + // Specification only has one part; it should be a line number + lineNumberText = split[0]; + try { + lineNumber = Integer.parseInt(lineNumberText); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("no line number specified"); + } + // If only line number specified, then there must be a selected file + final Source selectedSource = context.getSelectedSource(); + if (selectedSource == null) { + throw new IllegalArgumentException("no selected file set"); + } + source = selectedSource; + + } else { + final String fileName = split[0]; + lineNumberText = split[1]; + try { + source = Source.fromFileName(fileName); + } catch (IOException e1) { + throw new IllegalArgumentException("Can't find file \"" + fileName + "\""); + } + try { + lineNumber = Integer.parseInt(lineNumberText); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid line number \"" + lineNumberText + "\""); + } + if (lineNumber <= 0) { + throw new IllegalArgumentException("Invalid line number \"" + lineNumberText + "\""); + } + } + + return new REPLineLocation(source, lineNumber); + } + + REPLineLocation(Source source, int lineNumber) { + this.source = source; + this.lineNumber = lineNumber; + } + + public Source getSource() { + return source; + } + + public int getLineNumber() { + return lineNumber; + } + + /** + * Creates a message containing an "op" and a line location. + * + * @param op the operation to be performed on this location + */ + public REPLMessage createMessage(String op) { + final REPLMessage msg = new REPLMessage(REPLMessage.OP, op); + msg.put(REPLMessage.SOURCE_NAME, source.getShortName()); + msg.put(REPLMessage.FILE_PATH, source.getPath()); + msg.put(REPLMessage.LINE_NUMBER, Integer.toString(lineNumber)); + return msg; + } + +} diff -r 1bbef57f9a38 -r 3b8bbf51d320 graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.tools.debug.shell/src/com/oracle/truffle/tools/debug/shell/client/SimpleREPLClient.java Tue May 26 16:38:13 2015 -0700 @@ -0,0 +1,1399 @@ +/* + * Copyright (c) 2015, 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.tools.debug.shell.client; + +import java.io.*; +import java.util.*; + +import jline.console.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.tools.debug.shell.*; + +/** + * A very simple line-oriented, language-agnostic debugging client shell: the first step toward a + * general, extensible debugging framework designed to be adapted for remote debugging. + *

+ * The architecture of this debugging framework is modeled loosely on nREPL, a network REPL developed by the Clojure + * community with a focus on generality: + *

    + *
  • Client and (possibly remote) server communicate via messages carried over some + * transport;
  • + *
  • A message is a map of key/value pairs;
  • + *
  • Keys and values are strings;
  • + *
  • The client sends messages as requests to a server;
  • + *
  • A server dispatches each incoming request to an appropriate handler that takes + * appropriate action and responds to the client with one or more messages; and
  • + *
  • Many implementations of the transport are possible.
  • + *
+ *

+ * Compromises: + *

+ * In order to get + *

    + *
  1. A debugging session should start from this shell, but there is no machinery in place for + * doing that; instead, an entry into the language implementation creates both the server and this + * shell;
  2. + *
  3. The current startup sequence is based on method calls, not messages;
  4. + *
  5. Only a very few request types and keys are implemented, omitting for example request and + * session ids;
  6. + *
  7. Message passing is synchronous and "transported" via method calls;
  8. + *
  9. Asynchrony is emulated by having each call to the server pass only a message, and by having + * the server return only a list of messages.
  10. + *
+ * + * @see REPLServer + * @see REPLMessage + */ +public class SimpleREPLClient implements REPLClient { + + private static final String REPLY_PREFIX = "==> "; + private static final String FAIL_PREFIX = "**> "; + private static final String WARNING_PREFIX = "!!> "; + private static final String TRACE_PREFIX = ">>> "; + private static final String[] NULL_ARGS = new String[0]; + + static final String INFO_LINE_FORMAT = " %s\n"; + static final String CODE_LINE_FORMAT = " %3d %s\n"; + static final String CODE_LINE_BREAK_FORMAT = "--> %3d %s\n"; + + private static final String STACK_FRAME_FORMAT = " %3d: at %s in %s line =\"%s\"\n"; + private static final String STACK_FRAME_SELECTED_FORMAT = "==> %3d: at %s in %s line =\"%s\"\n"; + + private final ExecutionContext executionContext; // Language context + + // Top level commands + private final Map commandMap = new HashMap<>(); + private final Collection commandNames = new TreeSet<>(); + + // Local options + private final Map localOptions = new HashMap<>(); + private final Collection optionNames = new TreeSet<>(); + + // Current local context + ClientContextImpl clientContext; + + // Cheating for the prototype; prototype startup now happens from the language server. + // So this isn't used. + public static void main(String[] args) { + final SimpleREPLClient repl = new SimpleREPLClient(null, null); + repl.start(); + } + + private final ConsoleReader reader; + + private final PrintStream writer; + + private final REPLServer replServer; + + private final LocalOption astDepthOption = new IntegerOption(9, "astdepth", "default depth for AST display"); + + private final LocalOption autoWhereOption = new BooleanOption(true, "autowhere", "run the \"where\" command after each navigation"); + + private final LocalOption autoNodeOption = new BooleanOption(false, "autonode", "run the \"truffle node\" command after each navigation"); + + private final LocalOption autoSubtreeOption = new BooleanOption(false, "autosubtree", "run the \"truffle subtree\" command after each navigation"); + + private final LocalOption autoASTOption = new BooleanOption(false, "autoast", "run the \"truffle ast\" command after each navigation"); + + private final LocalOption listSizeOption = new IntegerOption(25, "listsize", "default number of lines to list"); + + private final LocalOption traceMessagesOption = new BooleanOption(false, "tracemessages", "trace REPL messages between client and server"); + + private final LocalOption verboseBreakpointInfoOption = new BooleanOption(true, "verbosebreakpointinfo", "\"info breakpoint\" displays more info"); + + private void addOption(LocalOption localOption) { + final String optionName = localOption.getName(); + localOptions.put(optionName, localOption); + optionNames.add(optionName); + } + + /** + * Non-null when the user has named a file other than where halted, providing context for + * commands such as "break"; if no explicit selection, then defaults to where halted. This is + * session state, so it persists across halting contexts. + */ + private Source selectedSource = null; + + public SimpleREPLClient(ExecutionContext context, REPLServer replServer) { + this.executionContext = context; + this.replServer = replServer; + this.writer = System.out; + try { + this.reader = new ConsoleReader(); + } catch (IOException e) { + throw new RuntimeException("Unable to create console " + e); + } + + addCommand(backtraceCommand); + addCommand(REPLRemoteCommand.BREAK_AT_LINE_CMD); + addCommand(REPLRemoteCommand.BREAK_AT_LINE_ONCE_CMD); + addCommand(REPLRemoteCommand.BREAK_AT_THROW_CMD); + addCommand(REPLRemoteCommand.BREAK_AT_THROW_ONCE_CMD); + addCommand(REPLRemoteCommand.CLEAR_BREAK_CMD); + addCommand(REPLRemoteCommand.CONDITION_BREAK_CMD); + addCommand(REPLRemoteCommand.CONTINUE_CMD); + addCommand(REPLRemoteCommand.DELETE_CMD); + addCommand(REPLRemoteCommand.DISABLE_CMD); + addCommand(REPLRemoteCommand.DOWN_CMD); + addCommand(REPLRemoteCommand.ENABLE_CMD); + addCommand(evalCommand); + addCommand(fileCommand); + addCommand(REPLRemoteCommand.FRAME_CMD); + addCommand(helpCommand); + addCommand(infoCommand); + addCommand(REPLRemoteCommand.KILL_CMD); + addCommand(listCommand); + addCommand(REPLRemoteCommand.LOAD_RUN_CMD); + addCommand(REPLRemoteCommand.LOAD_STEP_CMD); + addCommand(quitCommand); + addCommand(setCommand); + addCommand(REPLRemoteCommand.STEP_INTO_CMD); + addCommand(REPLRemoteCommand.STEP_OUT_CMD); + addCommand(REPLRemoteCommand.STEP_OVER_CMD); + addCommand(truffleCommand); + addCommand(REPLRemoteCommand.UP_CMD); + addCommand(whereCommand); + + infoCommand.addCommand(infoBreakCommand); + infoCommand.addCommand(infoLanguageCommand); + infoCommand.addCommand(infoSetCommand); + + truffleCommand.addCommand(truffleASTCommand); + truffleCommand.addCommand(truffleNodeCommand); + truffleCommand.addCommand(truffleSubtreeCommand); + + addOption(astDepthOption); + addOption(autoASTOption); + addOption(autoNodeOption); + addOption(autoSubtreeOption); + addOption(autoWhereOption); + addOption(listSizeOption); + addOption(traceMessagesOption); + addOption(verboseBreakpointInfoOption); + } + + public void start() { + + REPLMessage startReply = replServer.start(); + + if (startReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + clientContext.displayFailReply(startReply.get(REPLMessage.DISPLAY_MSG)); + throw new RuntimeException("Can't start REPL server"); + } + + this.clientContext = new ClientContextImpl(null, null); + + try { + clientContext.startSession(); + } finally { + clientContext.displayReply("Goodbye from " + executionContext.getLanguageShortName() + "/REPL"); + } + + } + + public void addCommand(REPLCommand replCommand) { + final String commandName = replCommand.getCommand(); + final String abbreviation = replCommand.getAbbreviation(); + + commandNames.add(commandName); + commandMap.put(commandName, replCommand); + if (abbreviation != null) { + commandMap.put(abbreviation, replCommand); + } + } + + private class ClientContextImpl implements REPLClientContext { + + private final ClientContextImpl predecessor; + private final int level; + + // Information about where the execution is halted + /** The source where execution, if any, is halted; null if none. */ + private Source haltedSource = null; + /** The line number where execution, if any, is halted; 0 if none. */ + private int haltedLineNumber = 0; + /** The stack where execution, if any, is halted; null if none. Evaluated lazily. */ + private List frames = null; + + /** The frame number currently selected by user. */ + private int selectedFrameNumber = 0; + + private String currentPrompt; + + /** + * Create a new context on the occasion of an execution halting. + */ + public ClientContextImpl(ClientContextImpl predecessor, REPLMessage message) { + this.predecessor = predecessor; + this.level = predecessor == null ? 0 : predecessor.level + 1; + + if (message != null) { + try { + this.haltedSource = Source.fromFileName(message.get(REPLMessage.SOURCE_NAME)); + selectedSource = this.haltedSource; + try { + haltedLineNumber = Integer.parseInt(message.get(REPLMessage.LINE_NUMBER)); + } catch (NumberFormatException e) { + haltedLineNumber = 0; + } + } catch (IOException e1) { + this.haltedSource = null; + this.haltedLineNumber = 0; + } + } + updatePrompt(); + } + + private void selectSource(String fileName) { + try { + selectedSource = Source.fromFileName(fileName); + } catch (IOException e1) { + selectedSource = null; + } + updatePrompt(); + } + + private void updatePrompt() { + if (level == 0) { + // 0-level context; no executions halted. + if (selectedSource == null) { + final String languageName = executionContext.getLanguageShortName(); + currentPrompt = languageName == null ? "() " : "(" + languageName + ") "; + } else { + currentPrompt = "(" + selectedSource.getShortName() + ") "; + } + } else if (selectedSource != null && selectedSource != haltedSource) { + // User is focusing somewhere else than the current locn; show no line number. + final StringBuilder sb = new StringBuilder(); + sb.append("(<" + Integer.toString(level) + "> "); + sb.append(selectedSource.getShortName()); + sb.append(") "); + currentPrompt = sb.toString(); + } else { + // Prompt reveals where currently halted. + final StringBuilder sb = new StringBuilder(); + sb.append("(<" + Integer.toString(level) + "> "); + sb.append(haltedSource == null ? "??" : haltedSource.getShortName()); + if (haltedLineNumber > 0) { + sb.append(":" + Integer.toString(haltedLineNumber)); + } + sb.append(") "); + currentPrompt = sb.toString(); + } + + } + + public Source source() { + return haltedSource; + } + + public int lineNumber() { + return haltedLineNumber; + } + + public List frames() { + if (frames == null) { + final REPLMessage request = new REPLMessage(REPLMessage.OP, REPLMessage.BACKTRACE); + final REPLMessage[] replies = sendToServer(request); + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + return null; + } + frames = new ArrayList<>(); + for (REPLMessage reply : replies) { + final int index = reply.getIntValue(REPLMessage.FRAME_NUMBER); + final String locationFilePath = reply.get(REPLMessage.FILE_PATH); + final Integer locationLineNumber = reply.getIntValue(REPLMessage.LINE_NUMBER); + final String locationDescription = reply.get(REPLMessage.SOURCE_LOCATION); + final String name = reply.get(REPLMessage.METHOD_NAME); + final String sourceLineText = reply.get(REPLMessage.SOURCE_LINE_TEXT); + frames.add(new REPLFrameImpl(index, locationFilePath, locationLineNumber, locationDescription, name, sourceLineText)); + } + frames = Collections.unmodifiableList(frames); + } + return frames; + } + + public int level() { + return this.level; + } + + public Source getSelectedSource() { + return selectedSource == null ? haltedSource : selectedSource; + } + + public int getSelectedFrameNumber() { + return selectedFrameNumber; + } + + public String stringQuery(String op) { + assert op != null; + REPLMessage request = null; + switch (op) { + case REPLMessage.TRUFFLE_AST: + request = truffleASTCommand.createRequest(clientContext, NULL_ARGS); + break; + case REPLMessage.TRUFFLE_SUBTREE: + request = truffleSubtreeCommand.createRequest(clientContext, NULL_ARGS); + break; + default: + request = new REPLMessage(); + request.put(REPLMessage.OP, op); + } + if (request == null) { + return null; + } + final REPLMessage[] replies = sendToServer(request); + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + return null; + } + return replies[0].get(REPLMessage.DISPLAY_MSG); + } + + public void selectFrameNumber(int frameNumber) { + this.selectedFrameNumber = frameNumber; + } + + void displayWhere() { + if (level == 0) { + displayFailReply("no active execution"); + return; + } + + Source whereSource = null; + int whereLineNumber = 0; + + if (selectedFrameNumber == 0) { + whereSource = haltedSource; + whereLineNumber = haltedLineNumber; + } else { + final REPLFrame frame = frames().get(selectedFrameNumber); + final String locationFileName = frame.locationFilePath(); + if (locationFileName != null) { + try { + whereSource = Source.fromFileName(locationFileName); + } catch (IOException e) { + } + } + whereLineNumber = frame.locationLineNumber(); + } + if (whereSource == null) { + displayFailReply("Frame " + selectedFrameNumber + ": source unavailable"); + return; + } + final int listSize = listSizeOption.getInt(); + + final int fileLineCount = whereSource.getLineCount(); + final String code = whereSource.getCode(); + + writer.println("Frame " + selectedFrameNumber + ": " + whereSource.getShortName() + "\n"); + final int halfListSize = listSize / 2; + final int startLineNumber = Math.max(1, whereLineNumber - halfListSize); + final int lastLineNumber = Math.min(startLineNumber + listSize - 1, fileLineCount); + for (int line = startLineNumber; line <= lastLineNumber; line++) { + final int offset = whereSource.getLineStartOffset(line); + final String lineText = code.substring(offset, offset + whereSource.getLineLength(line)); + if (line == whereLineNumber) { + writer.format(CODE_LINE_BREAK_FORMAT, line, lineText); + } else { + writer.format(CODE_LINE_FORMAT, line, lineText); + } + } + } + + public void displayStack() { + final List frameList = frames(); + if (frameList == null) { + writer.println(""); + } else { + for (REPLFrame frame : frameList) { + String sourceLineText = frame.sourceLineText(); + if (sourceLineText == null) { + sourceLineText = ""; + } + if (frame.index() == selectedFrameNumber) { + writer.format(STACK_FRAME_SELECTED_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText); + } else { + writer.format(STACK_FRAME_FORMAT, frame.index(), frame.locationDescription(), frame.name(), sourceLineText); + } + } + } + } + + public void displayInfo(String message) { + writer.format(INFO_LINE_FORMAT, message); + } + + public void displayReply(String message) { + writer.println(REPLY_PREFIX + message); + } + + public void displayFailReply(String message) { + writer.println(FAIL_PREFIX + message); + } + + public void displayWarnings(String warnings) { + for (String warning : warnings.split("\\n")) { + writer.println(WARNING_PREFIX + warning); + } + } + + public void traceMessage(String message) { + writer.println(TRACE_PREFIX + message); + } + + public void startSession() { + + while (true) { + try { + String[] args; + String line = reader.readLine(currentPrompt).trim(); + if (line.startsWith("eval ")) { + args = new String[]{"eval", line.substring(5)}; + } else { + args = line.split("[ \t]+"); + } + if (args.length == 0) { + break; + } + final String cmd = args[0]; + + if (cmd.isEmpty()) { + continue; + } + + REPLCommand command = commandMap.get(cmd); + while (command instanceof REPLIndirectCommand) { + if (traceMessagesOption.getBool()) { + traceMessage("Executing indirect: " + command.getCommand()); + } + command = ((REPLIndirectCommand) command).getCommand(args); + } + if (command == null) { + clientContext.displayFailReply("Unrecognized command \"" + cmd + "\""); + continue; + } + if (command instanceof REPLLocalCommand) { + if (traceMessagesOption.getBool()) { + traceMessage("Executing local: " + command.getCommand()); + } + ((REPLLocalCommand) command).execute(args); + + } else if (command instanceof REPLRemoteCommand) { + final REPLRemoteCommand remoteCommand = (REPLRemoteCommand) command; + + final REPLMessage request = remoteCommand.createRequest(clientContext, args); + if (request == null) { + continue; + } + + REPLMessage[] replies = sendToServer(request); + + remoteCommand.processReply(clientContext, replies); + } else { + assert false; // Should not happen. + } + + } catch (REPLContinueException ex) { + break; + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + private REPLMessage[] sendToServer(REPLMessage request) { + if (traceMessagesOption.getBool()) { + clientContext.traceMessage("Sever request:"); + request.print(writer, " "); + } + + REPLMessage[] replies = replServer.receive(request); + + assert replies != null && replies.length > 0; + if (traceMessagesOption.getBool()) { + if (replies.length > 1) { + clientContext.traceMessage("Received " + replies.length + " server replies"); + int replyCount = 0; + for (REPLMessage reply : replies) { + clientContext.traceMessage("Server Reply " + replyCount++ + ":"); + reply.print(writer, " "); + } + } else { + clientContext.traceMessage("Received reply:"); + replies[0].print(writer, " "); + } + } + return replies; + } + + private final class REPLFrameImpl implements REPLFrame { + + private final int index; + private final String locationFilePath; + private final Integer locationLineNumber; + private final String locationDescription; + private final String name; + private final String sourceLineText; + + REPLFrameImpl(int index, String locationFilePath, Integer locationLineNumber, String locationDescription, String name, String sourceLineText) { + this.index = index; + this.locationFilePath = locationFilePath; + this.locationLineNumber = locationLineNumber; + this.locationDescription = locationDescription; + this.name = name; + this.sourceLineText = sourceLineText; + } + + public int index() { + return index; + } + + public String locationFilePath() { + return locationFilePath; + } + + public Integer locationLineNumber() { + return locationLineNumber; + } + + public String locationDescription() { + return locationDescription; + } + + public String name() { + return name; + } + + public String sourceLineText() { + return sourceLineText; + } + + } + + } + + // Cheating with synchrony: asynchronous replies should arrive here, but don't. + @Override + public REPLMessage receive(REPLMessage request) { + final String result = request.get("result"); + clientContext.displayReply(result != null ? result : request.toString()); + return null; + } + + /** + * Cheating with synchrony: take a direct call from the server that execution has halted and + * we've entered a nested debugging context. + */ + public void halted(REPLMessage message) { + + // Push a new context for where we've stopped. + clientContext = new ClientContextImpl(clientContext, message); + final String warnings = message.get(REPLMessage.WARNINGS); + if (warnings != null) { + clientContext.displayWarnings(warnings); + } + if (autoWhereOption.getBool()) { + clientContext.displayWhere(); + } + if (autoNodeOption.getBool()) { + final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_NODE); + if (result != null) { + displayTruffleNode(result); + } + } + if (autoASTOption.getBool()) { + final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_AST); + if (result != null) { + displayTruffleAST(result); + } + } + if (autoSubtreeOption.getBool()) { + final String result = clientContext.stringQuery(REPLMessage.TRUFFLE_SUBTREE); + if (result != null) { + displayTruffleSubtree(result); + } + } + + try { + clientContext.startSession(); + } finally { + + // To continue execution, pop the context and return + this.clientContext = clientContext.predecessor; + } + } + + /** + * A command that can be executed without (direct) communication with the server; it may rely on + * some other method that goes to the server for information. + */ + private abstract class REPLLocalCommand extends REPLCommand { + + public REPLLocalCommand(String command, String abbreviation, String description) { + super(command, abbreviation, description); + } + + abstract void execute(String[] args); + } + + /** + * A command that redirects to other commands, based on arguments. + */ + private abstract class REPLIndirectCommand extends REPLCommand { + + public REPLIndirectCommand(String command, String abbreviation, String description) { + super(command, abbreviation, description); + } + + abstract void addCommand(REPLCommand command); + + abstract REPLCommand getCommand(String[] args); + } + + private final REPLCommand backtraceCommand = new REPLLocalCommand("backtrace", "bt", "Display current stack") { + + @Override + void execute(String[] args) { + if (clientContext.level == 0) { + clientContext.displayFailReply("no active execution"); + } else { + clientContext.displayStack(); + } + } + }; + + private final REPLCommand evalCommand = new REPLRemoteCommand("eval", null, "Evaluate a string, in context of the current frame if any") { + + private int evalCounter = 0; + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length > 1) { + final String code = args[1]; + if (!code.isEmpty()) { + // Create a fake entry in the file maps and cache, based on this unique name + final String fakeFileName = ""; + Source.fromNamedText(fakeFileName, code); + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.EVAL); + request.put(REPLMessage.CODE, code); + request.put(REPLMessage.SOURCE_NAME, fakeFileName); + if (clientContext.level > 0) { + // Specify a requested execution context, if one exists; otherwise top level + request.put(REPLMessage.FRAME_NUMBER, Integer.toString(context.getSelectedFrameNumber())); + } + return request; + } + } + return null; + } + }; + + private final REPLCommand fileCommand = new REPLRemoteCommand("file", null, "Set/display current file for viewing") { + + final String[] help = {"file: display current file path", "file : Set file to be current file for viewing"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + if (args.length == 1) { + final Source source = clientContext.getSelectedSource(); + if (source == null) { + clientContext.displayFailReply("no file currently selected"); + } else { + clientContext.displayReply(source.getPath()); + } + return null; + } + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.FILE); + request.put(REPLMessage.SOURCE_NAME, args[1]); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + REPLMessage firstReply = replies[0]; + + if (firstReply.get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + final String result = firstReply.get(REPLMessage.DISPLAY_MSG); + clientContext.displayFailReply(result != null ? result : firstReply.toString()); + return; + } + final String fileName = firstReply.get(REPLMessage.SOURCE_NAME); + final String path = firstReply.get(REPLMessage.FILE_PATH); + clientContext.selectSource(path == null ? fileName : path); + clientContext.displayReply(clientContext.getSelectedSource().getPath()); + + for (int i = 1; i < replies.length; i++) { + REPLMessage reply = replies[i]; + final String result = reply.get(REPLMessage.DISPLAY_MSG); + clientContext.displayInfo(result != null ? result : reply.toString()); + } + } + + }; + + private final REPLCommand helpCommand = new REPLLocalCommand("help", null, "Describe commands") { + + final String[] help = {"help: list available commands", "help : additional information about "}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public void execute(String[] args) { + + if (args.length == 1) { + clientContext.displayReply("Available commands:"); + for (String commandName : commandNames) { + final REPLCommand command = commandMap.get(commandName); + if (command == null) { + clientContext.displayInfo(commandName + ": Error, no implementation for command"); + } else { + final String abbrev = command.getAbbreviation(); + if (abbrev == null) { + clientContext.displayInfo(commandName + ": " + command.getDescription()); + } else { + clientContext.displayInfo(commandName + "(" + abbrev + "): " + command.getDescription()); + } + } + } + } else { + final String cmdName = args[1]; + final REPLCommand cmd = commandMap.get(cmdName); + if (cmd == null) { + clientContext.displayReply("command \"" + cmdName + "\" not recognized"); + } else { + final String[] helpLines = cmd.getHelp(); + if (helpLines == null) { + clientContext.displayReply("\"" + cmdName + "\":"); + } else if (helpLines.length == 1) { + clientContext.displayInfo(helpLines[0]); + } else { + clientContext.displayReply("\"" + cmdName + "\":"); + for (String line : helpLines) { + clientContext.displayInfo(line); + } + } + } + } + } + }; + + private final REPLIndirectCommand infoCommand = new REPLIndirectCommand(REPLMessage.INFO, null, "Additional information on topics") { + + // "Info" commands + private final Map infoCommandMap = new HashMap<>(); + private final Collection infoCommandNames = new TreeSet<>(); + + @Override + public String[] getHelp() { + final ArrayList lines = new ArrayList<>(); + for (String infoCommandName : infoCommandNames) { + final REPLCommand cmd = infoCommandMap.get(infoCommandName); + if (cmd == null) { + lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\" not implemented"); + } else { + lines.add("\"" + REPLMessage.INFO + " " + infoCommandName + "\": " + cmd.getDescription()); + } + } + return lines.toArray(new String[0]); + } + + @Override + void addCommand(REPLCommand replCommand) { + final String commandName = replCommand.getCommand(); + final String abbreviation = replCommand.getAbbreviation(); + + infoCommandNames.add(commandName); + infoCommandMap.put(commandName, replCommand); + if (abbreviation != null) { + infoCommandMap.put(abbreviation, replCommand); + } + } + + @Override + REPLCommand getCommand(String[] args) { + if (args.length == 1) { + clientContext.displayFailReply("info topic not specified; try \"help info\""); + return null; + } + final String topic = args[1]; + REPLCommand command = infoCommandMap.get(topic); + if (command == null) { + clientContext.displayFailReply("topic \"" + topic + "\" not recognized"); + return null; + } + + return command; + } + + }; + + private final REPLCommand infoBreakCommand = new REPLRemoteCommand("breakpoint", "break", "info about breakpoints") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.BREAKPOINT_INFO); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); + } else { + Arrays.sort(replies, new Comparator() { + + public int compare(REPLMessage o1, REPLMessage o2) { + try { + final int n1 = Integer.parseInt(o1.get(REPLMessage.BREAKPOINT_ID)); + final int n2 = Integer.parseInt(o2.get(REPLMessage.BREAKPOINT_ID)); + return Integer.compare(n1, n2); + } catch (Exception ex) { + } + return 0; + } + + }); + clientContext.displayReply("Breakpoints set:"); + for (REPLMessage message : replies) { + final StringBuilder sb = new StringBuilder(); + + sb.append(Integer.parseInt(message.get(REPLMessage.BREAKPOINT_ID)) + ": "); + sb.append("@" + message.get(REPLMessage.INFO_VALUE)); + sb.append(" (state=" + message.get(REPLMessage.BREAKPOINT_STATE)); + if (verboseBreakpointInfoOption.getBool()) { + sb.append(", group=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_GROUP_ID))); + sb.append(", hits=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_HIT_COUNT))); + sb.append(", ignore=" + Integer.parseInt(message.get(REPLMessage.BREAKPOINT_IGNORE_COUNT))); + } + final String condition = message.get(REPLMessage.BREAKPOINT_CONDITION); + if (condition != null) { + sb.append(", condition=\"" + condition + "\""); + } + sb.append(")"); + clientContext.displayInfo(sb.toString()); + } + } + } + }; + + private final REPLCommand infoLanguageCommand = new REPLRemoteCommand("language", "lang", "language and implementation details") { + + final String[] help = {"info language: list details about the language implementation"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.INFO); + request.put(REPLMessage.TOPIC, REPLMessage.LANGUAGE); + return request; + } + + @Override + void processReply(REPLClientContext context, REPLMessage[] replies) { + if (replies[0].get(REPLMessage.STATUS).equals(REPLMessage.FAILED)) { + clientContext.displayFailReply(replies[0].get(REPLMessage.DISPLAY_MSG)); + } else { + clientContext.displayReply("Language info:"); + for (REPLMessage message : replies) { + final StringBuilder sb = new StringBuilder(); + sb.append(message.get(REPLMessage.INFO_KEY)); + sb.append(": "); + sb.append(message.get(REPLMessage.INFO_VALUE)); + clientContext.displayInfo(sb.toString()); + } + } + } + }; + + private final REPLCommand infoSetCommand = new REPLLocalCommand("set", null, "info about settings") { + + final String[] help = {"info sets: list local options that can be set"}; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public void execute(String[] args) { + + clientContext.displayReply("Settable options:"); + + for (String optionName : optionNames) { + final LocalOption localOption = localOptions.get(optionName); + if (localOption == null) { + clientContext.displayInfo(localOption + ": Error, no implementation for option"); + } else { + clientContext.displayInfo(optionName + "=" + localOption.getValue() + ": " + localOption.getDescription()); + } + } + } + }; + + private final REPLCommand listCommand = new REPLLocalCommand("list", null, "Display selected source file") { + + final String[] help = {"list: list lines of selected file (see option \"listsize\")", "list all: list all lines", "list : list lines centered around line "}; + + private Source lastListedSource = null; + + private int nextLineToList = 1; + + @Override + public String[] getHelp() { + return help; + } + + @Override + public void execute(String[] args) { + final Source source = clientContext.getSelectedSource(); + if (source == null) { + clientContext.displayFailReply("No selected file"); + reset(); + return; + } + final int listSize = listSizeOption.getInt(); + + if (args.length == 1) { + if (!source.equals(lastListedSource)) { + reset(); + } else if (nextLineToList > source.getLineCount()) { + reset(); + } + final int lastListedLine = printLines(source, nextLineToList, listSize); + lastListedSource = source; + nextLineToList = lastListedLine > source.getLineCount() ? 1 : lastListedLine + 1; + } else if (args.length == 2) { + reset(); + if (args[1].equals("all")) { + printLines(source, 1, source.getLineCount()); + } else { + try { + final int line = Integer.parseInt(args[1]); + final int halfListSize = listSize / 2; + final int start = Math.max(1, line - halfListSize); + final int count = Math.min(source.getLineCount() + 1 - start, listSize); + printLines(source, start, count); + } catch (NumberFormatException e) { + clientContext.displayFailReply("\"" + args[1] + "\" not recognized"); + } + + } + } + } + + private int printLines(Source printSource, int start, int listSize) { + + clientContext.displayReply(printSource.getShortName() + ":"); + final int lastLineNumber = Math.min(start + listSize - 1, printSource.getLineCount()); + for (int line = start; line <= lastLineNumber; line++) { + writer.format(CODE_LINE_FORMAT, line, printSource.getCode(line)); + } + return lastLineNumber; + } + + /** + * Forget where we were in a sequence of list commands with no arguments + */ + private void reset() { + lastListedSource = clientContext.getSelectedSource(); + nextLineToList = 1; + } + }; + + private final REPLCommand quitCommand = new REPLRemoteCommand("quit", "q", "Quit execution and REPL") { + + @Override + public REPLMessage createRequest(REPLClientContext context, String[] args) { + final REPLMessage request = new REPLMessage(); + request.put(REPLMessage.OP, REPLMessage.QUIT); + return request; + } + + }; + + private final REPLCommand setCommand = new REPLLocalCommand("set", null, "set