# HG changeset patch
# User Michael Van De Vanter
# Date 1432872688 25200
# Node ID a880844225e45a6bd241f71d90e250c5bcd17d96
# Parent 8a01110bfbaf5878eadb63d69dc99e42b2bb96e0# Parent f41409c6ff26923655747d1a36ac51892b1153ac
Merge with f41409c6ff26923655747d1a36ac51892b1153ac
diff -r f41409c6ff26 -r a880844225e4 env
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/env Thu May 28 21:11:28 2015 -0700
@@ -0,0 +1,15 @@
+DEFAULT_VM=server
+
+LANG=en_US.UTF-8
+
+USE_PRECOMPILED_HEADER=0
+
+# Use clang instead of gcc.
+COMPILER_WARNINGS_FATAL=false
+USE_CLANG=true
+LFLAGS=-Xlinker -lstdc++
+
+# Which JDK to use for javac (and others).
+JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home
+
+EXTRA_JAVA_HOMES=/Library/Java/JavaVirtualMachines/jdk1.7.0_67.jdk/Contents/Home
diff -r f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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 f41409c6ff26 -r a880844225e4 graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java Thu May 28 17:00:59 2015 -0700
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/DefaultVisualizer.java Thu May 28 21:11:28 2015 -0700
@@ -52,6 +52,9 @@
section = node.getEncapsulatingSourceSection();
estimated = true;
}
+ if (section == null) {
+ return "";
+ }
return section.getShortDescription() + (estimated ? "~" : "");
}
diff -r f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 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
- *
- *
- *
"Execution call" on a node is is defined as invocation of a node method that is instrumented
- * to produce the event {@link SimpleInstrumentListener#enter(Probe)};
- *
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.
- *
- *
- * 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
- * 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 f41409c6ff26 -r a880844225e4 graal/com.oracle.truffle.interop/src/com/oracle/truffle/interop/SymbolInvokerImpl.java
--- a/graal/com.oracle.truffle.interop/src/com/oracle/truffle/interop/SymbolInvokerImpl.java Thu May 28 17:00:59 2015 -0700
+++ b/graal/com.oracle.truffle.interop/src/com/oracle/truffle/interop/SymbolInvokerImpl.java Thu May 28 21:11:28 2015 -0700
@@ -29,6 +29,7 @@
import com.oracle.truffle.api.*;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.impl.*;
+import com.oracle.truffle.api.instrument.*;
import com.oracle.truffle.api.interop.*;
import com.oracle.truffle.api.nodes.*;
import com.oracle.truffle.interop.messages.*;
@@ -57,6 +58,11 @@
}
@Override
+ public void applyInstrumentation() {
+ Probe.applyASTProbers(foreignAccess);
+ }
+
+ @Override
public Object execute(VirtualFrame frame) {
return foreignAccess.executeForeign(frame, function, args);
}
diff -r f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 2015 -0700
@@ -0,0 +1,81 @@
+/*
+ * 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 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.*;
+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 {
+
+ @SuppressWarnings("unused") private final SLContext slContext;
+
+ public SLSourceExecutionProvider(SLContext context) {
+ this.slContext = context;
+ Probe.registerASTProber(new SLStandardASTProber());
+ }
+
+ @Override
+ public void languageRun(Source source) {
+ try {
+ SLMain.run(source);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * {@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 f41409c6ff26 -r a880844225e4 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 Thu May 28 17:00:59 2015 -0700
+++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLMain.java Thu May 28 21:11:28 2015 -0700
@@ -33,7 +33,6 @@
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.*;
import com.oracle.truffle.api.vm.TruffleVM.Symbol;
import com.oracle.truffle.sl.builtins.*;
@@ -46,6 +45,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
@@ -181,6 +181,17 @@
}
}
+ public static void run(Source source) throws IOException {
+ TruffleVM vm = TruffleVM.newVM().build();
+ assert vm.getLanguages().containsKey("application/x-sl");
+ vm.eval(new File(source.getPath()).toURI());
+ Symbol main = vm.findGlobalSymbol("main");
+ if (main == null) {
+ throw new SLException("No function main() defined in SL source file.");
+ }
+ main.invoke(null);
+ }
+
/**
* Parse and run the specified SL source. Factored out in a separate method so that it can also
* be used by the unit test harness.
diff -r f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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:
+ *
+ *
execution arrives at a node to which an enabled breakpoint is attached,
+ * or:
+ *
execution completes.
+ *
+ *
+ */
+ @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:
+ *
+ *
execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
+ * STATMENT}, or:
+ *
execution completes.
+ *
+ *
+ * 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:
+ *
+ *
execution arrives at the nearest enclosing call site on the stack, or
+ *
execution completes.
+ *
+ *
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:
+ *
+ *
execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT
+ * STATEMENT} when not nested in one or more function/method calls, or:
+ *
execution arrives at a node to which a breakpoint is attached and when nested in one or
+ * more function/method calls, or:
+ *
execution completes.
+ *
+ *
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:
+ *
+ *
execution arrives at a node with attached user breakpoint, or:
execution arrives at a node with attached user breakpoint, or:
+ *
execution returns to a CALL node and the call stack is smaller than when
+ * execution started, or:
+ *
execution completes.
+ *
+ *
+ *
+ * @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:
+ *
+ *
execution arrives at a STATEMENT node with stack depth no more than when started
+ * or:
+ *
the program completes.
+ *
+ *
+ */
+ 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:
+ *
+ *
execution arrives at a STATEMENT node with stack depth no more than when started
+ * or:
+ *
the program completes or:
+ *
+ *
+ */
+ 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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:
+ *
+ *
Line breakpoints can only be set at nodes tagged as {@link StandardSyntaxTag#STATEMENT}.
+ *
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.
+ *
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.
+ *
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.
+ *
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.
+ *
+ *
+ */
+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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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:
+ *
+ *
Only one Tag Breakpoint can be active for a specific {@linkplain SyntaxTag Tag}.
+ *
A newly created breakpoint looks for probes matching the tag, attaches to them if found by
+ * installing an {@link Instrument}.
+ *
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.
+ *
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.
+ *
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.
+ *
+ */
+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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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:
+ *
+ *
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
+ *
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
+ *
Utility methods, such as providing textual displays of Objects that represent values in the language
+ *
+ *
+ * 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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 f41409c6ff26 -r a880844225e4 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 Thu May 28 21:11:28 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
+ *
+ *
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;
+ *
The current startup sequence is based on method calls, not messages;
+ *
Only a very few request types and keys are implemented, omitting for example request and
+ * session ids;
+ *
Message passing is synchronous and "transported" via method calls;
+ *
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.
+ *
+ *
+ * @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