From 05558c580cd83c98d3917b6e818474362c134428 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Mon, 18 Nov 2019 15:01:59 +0100 Subject: [PATCH 01/10] Making sure T-Trace exceptions are treated as regular language exceptions --- tools/docs/T-Trace-Manual.md | 46 +++++++++++++++ .../agentscript/test/AgentObjectFactory.java | 8 ++- .../agentscript/test/AgentObjectTest.java | 56 +++++++++++++++++++ .../agentscript/test/InstrumentNode.java | 42 ++++++++++++++ .../api/instrumentation/ProbeNode.java | 3 +- .../oracle/truffle/polyglot/PolyglotImpl.java | 3 + 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java diff --git a/tools/docs/T-Trace-Manual.md b/tools/docs/T-Trace-Manual.md index 4dd863a8c2c9..d868cfe1c5ba 100644 --- a/tools/docs/T-Trace-Manual.md +++ b/tools/docs/T-Trace-Manual.md @@ -459,6 +459,52 @@ $ node --experimental-options --js.print --agentscript=agent-require.js yourScri This initialization sequence is known to work on GraalVM's node `v12.10.0` launched with a main `yourScript.js` parameter. +### Handling Exceptions + +The `agentscript` code can throw exceptions and they are propagate to the +surrounding scripts. Imagine you have a program `seq.js` logging various messages: + +```js +function log(msg) { + print(msg); +} + +log('Hello T-Trace!'); +log('How'); +log('are'); +log('You?'); +``` + +You can register an instrument `term.js` and terminate the execution in the middle of +logging: + +```js +agent.on('enter', (ev, frame) => { + if (frame.msg === 'are') { + throw "great you are!"; + } +}, { + roots: true, + rootNameFilter: (n) => n === 'log' +}); +``` + +The instruments waits for `log('are')` and at that moment it breaks the execution. +As a result one gets: + +```bash +$ js --polyglot --experimental-options --agentscript=term.js seq.js +Hello T-Trace! +How +great you are! + at <js> :=>(<eval>:3:75-97) + at <js> log(seq.js:1-3:18-36) + at <js> :program(seq.js:7:74-83) +``` + +The locations may indeed be slightly different, but otherwise exceptions +from T-Trace instruments are treated as regular language exceptions. + <!-- ### TODO: diff --git a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectFactory.java b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectFactory.java index 611225e23ac1..4ebfd0ab6ce2 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectFactory.java +++ b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectFactory.java @@ -37,6 +37,7 @@ import org.graalvm.polyglot.Value; import static org.junit.Assert.assertNotNull; import com.oracle.truffle.tools.agentscript.AgentScript; +import java.io.ByteArrayOutputStream; import java.util.function.Predicate; import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.HostAccess; @@ -55,7 +56,12 @@ static AgentScriptAPI.OnConfig createConfig(boolean expressions, boolean stateme } static Context newContext() { - return Context.newBuilder().allowExperimentalOptions(true).allowHostAccess(HostAccess.ALL).build(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + return newContext(os, os); + } + + static Context newContext(ByteArrayOutputStream out, ByteArrayOutputStream err) { + return Context.newBuilder().out(out).err(err).allowExperimentalOptions(true).allowHostAccess(HostAccess.ALL).build(); } @Override diff --git a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java index 1fa0f1df3f92..a1bcb4bee5f4 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java +++ b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java @@ -24,10 +24,13 @@ */ package com.oracle.truffle.tools.agentscript.test; +import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.test.polyglot.ProxyLanguage; import com.oracle.truffle.tools.agentscript.AgentScript; import static com.oracle.truffle.tools.agentscript.test.AgentObjectFactory.createConfig; +import java.io.ByteArrayOutputStream; import java.net.URI; import java.util.Arrays; import java.util.LinkedList; @@ -35,6 +38,7 @@ import java.util.Set; import java.util.TreeSet; import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; import org.junit.Assert; @@ -43,6 +47,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; public class AgentObjectTest { @@ -280,6 +285,57 @@ public void onExpressionCallback() throws Exception { } } + @Test + public void onError() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try (Context c = AgentObjectFactory.newContext(os, os)) { + Value agent = AgentObjectFactory.createAgentObject(c); + AgentScriptAPI agentAPI = agent.as(AgentScriptAPI.class); + Assert.assertNotNull("Agent API obtained", agentAPI); + + class TE extends RuntimeException implements TruffleException { + static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "TE"; + } + + @Override + public Node getLocation() { + return new InstrumentNode(); + } + } + + agentAPI.on("enter", (ev, frame) -> { + throw new TE(); + }, AgentObjectFactory.createConfig(true, false, false, null)); + + // @formatter:off + Source sampleScript = Source.newBuilder(InstrumentationTestLanguage.ID, + "ROOT(\n" + + " DEFINE(foo,\n" + + " LOOP(10, STATEMENT(EXPRESSION,EXPRESSION))\n" + + " ),\n" + + " CALL(foo)\n" + + ")", + "sample.px" + ).build(); + // @formatter:on + + try { + c.eval(sampleScript); + fail("Instrument throws an exception"); + } catch (PolyglotException ex) { + assertEquals("TE", ex.getMessage()); + assertEquals("INSTR", ex.getSourceLocation().getCharacters().toString()); + } + if (os.toByteArray().length > 0) { + fail("Unexpected output: " + os.toString()); + } + } + } + @Test public void onEnterAndReturn() throws Exception { try (Context c = AgentObjectFactory.newContext()) { diff --git a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java new file mode 100644 index 000000000000..70d72c2ac31d --- /dev/null +++ b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, 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.agentscript.test; + +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; + +final class InstrumentNode extends Node { + + InstrumentNode() { + } + + @Override + public SourceSection getSourceSection() { + Source inst = Source.newBuilder("px", "INSTR", "px.inst").build(); + return inst.createSection(0, inst.getLength()); + } + +} diff --git a/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/ProbeNode.java b/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/ProbeNode.java index c2936b4a095d..cef491c207ae 100644 --- a/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/ProbeNode.java +++ b/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/ProbeNode.java @@ -53,6 +53,7 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; +import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameSlot; import com.oracle.truffle.api.frame.FrameSlotTypeException; @@ -906,7 +907,7 @@ final void onEnter(EventContext context, VirtualFrame frame) { CompilerDirectives.transferToInterpreterAndInvalidate(); setSeenException(); } - if (binding.isLanguageBinding()) { + if (binding.isLanguageBinding() || t instanceof TruffleException) { throw t; } else { CompilerDirectives.transferToInterpreter(); diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java index 351586ce469a..14787a5ede7c 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java @@ -67,6 +67,7 @@ import org.graalvm.polyglot.proxy.Proxy; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.TruffleOptions; import com.oracle.truffle.api.impl.DispatchOutputStream; import com.oracle.truffle.api.interop.InteropException; @@ -361,6 +362,8 @@ static <T extends Throwable> RuntimeException wrapHostException(PolyglotContextI return (HostException) e; } else if (e instanceof InteropException) { throw ((InteropException) e).raise(); + } else if ((e instanceof RuntimeException) && (e instanceof TruffleException)) { + throw (RuntimeException) e; } try { return new HostException(e); From c83b748ba53cfa80804abd8f1567c6c224a1c397 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Tue, 19 Nov 2019 06:48:07 +0100 Subject: [PATCH 02/10] Showing the expected stacktrace in the documentation --- tools/docs/T-Trace-Manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docs/T-Trace-Manual.md b/tools/docs/T-Trace-Manual.md index a480f048c626..f3f7ee368803 100644 --- a/tools/docs/T-Trace-Manual.md +++ b/tools/docs/T-Trace-Manual.md @@ -497,7 +497,7 @@ $ js --polyglot --experimental-options --agentscript=term.js seq.js Hello T-Trace! How great you are! - at <js> :=>(<eval>:3:75-97) + at <js> :=>(term.js:3:75-97) at <js> log(seq.js:1-3:18-36) at <js> :program(seq.js:7:74-83) ``` From d706ae666ebfddad981d79fe16f2071fd4229b32 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Tue, 19 Nov 2019 06:53:07 +0100 Subject: [PATCH 03/10] Removing special treatment of host exception as TruffleException --- .../agentscript/test/AgentObjectTest.java | 56 ------------------- .../agentscript/test/InstrumentNode.java | 42 -------------- .../oracle/truffle/polyglot/PolyglotImpl.java | 3 - 3 files changed, 101 deletions(-) delete mode 100644 tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java diff --git a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java index a1bcb4bee5f4..1fa0f1df3f92 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java +++ b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/AgentObjectTest.java @@ -24,13 +24,10 @@ */ package com.oracle.truffle.tools.agentscript.test; -import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage; -import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.test.polyglot.ProxyLanguage; import com.oracle.truffle.tools.agentscript.AgentScript; import static com.oracle.truffle.tools.agentscript.test.AgentObjectFactory.createConfig; -import java.io.ByteArrayOutputStream; import java.net.URI; import java.util.Arrays; import java.util.LinkedList; @@ -38,7 +35,6 @@ import java.util.Set; import java.util.TreeSet; import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; import org.junit.Assert; @@ -47,7 +43,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import org.junit.Test; public class AgentObjectTest { @@ -285,57 +280,6 @@ public void onExpressionCallback() throws Exception { } } - @Test - public void onError() throws Exception { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try (Context c = AgentObjectFactory.newContext(os, os)) { - Value agent = AgentObjectFactory.createAgentObject(c); - AgentScriptAPI agentAPI = agent.as(AgentScriptAPI.class); - Assert.assertNotNull("Agent API obtained", agentAPI); - - class TE extends RuntimeException implements TruffleException { - static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "TE"; - } - - @Override - public Node getLocation() { - return new InstrumentNode(); - } - } - - agentAPI.on("enter", (ev, frame) -> { - throw new TE(); - }, AgentObjectFactory.createConfig(true, false, false, null)); - - // @formatter:off - Source sampleScript = Source.newBuilder(InstrumentationTestLanguage.ID, - "ROOT(\n" + - " DEFINE(foo,\n" + - " LOOP(10, STATEMENT(EXPRESSION,EXPRESSION))\n" + - " ),\n" + - " CALL(foo)\n" + - ")", - "sample.px" - ).build(); - // @formatter:on - - try { - c.eval(sampleScript); - fail("Instrument throws an exception"); - } catch (PolyglotException ex) { - assertEquals("TE", ex.getMessage()); - assertEquals("INSTR", ex.getSourceLocation().getCharacters().toString()); - } - if (os.toByteArray().length > 0) { - fail("Unexpected output: " + os.toString()); - } - } - } - @Test public void onEnterAndReturn() throws Exception { try (Context c = AgentObjectFactory.newContext()) { diff --git a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java b/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java deleted file mode 100644 index 70d72c2ac31d..000000000000 --- a/tools/src/com.oracle.truffle.tools.agentscript.test/src/com/oracle/truffle/tools/agentscript/test/InstrumentNode.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019, 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.agentscript.test; - -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.api.source.SourceSection; - -final class InstrumentNode extends Node { - - InstrumentNode() { - } - - @Override - public SourceSection getSourceSection() { - Source inst = Source.newBuilder("px", "INSTR", "px.inst").build(); - return inst.createSection(0, inst.getLength()); - } - -} diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java index 14787a5ede7c..351586ce469a 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotImpl.java @@ -67,7 +67,6 @@ import org.graalvm.polyglot.proxy.Proxy; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.TruffleOptions; import com.oracle.truffle.api.impl.DispatchOutputStream; import com.oracle.truffle.api.interop.InteropException; @@ -362,8 +361,6 @@ static <T extends Throwable> RuntimeException wrapHostException(PolyglotContextI return (HostException) e; } else if (e instanceof InteropException) { throw ((InteropException) e).raise(); - } else if ((e instanceof RuntimeException) && (e instanceof TruffleException)) { - throw (RuntimeException) e; } try { return new HostException(e); From ba1ea588a875211921bf6166b238de45f3df84a0 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Tue, 19 Nov 2019 07:13:58 +0100 Subject: [PATCH 04/10] Testing behavior of JavaScript agent throwing an exception --- vm/tests/all/agentscript/agent-exception.js | 8 ++++++++ vm/tests/all/agentscript/agent-exception.test | 14 ++++++++++++++ vm/tests/all/agentscript/log.js | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 vm/tests/all/agentscript/agent-exception.js create mode 100644 vm/tests/all/agentscript/agent-exception.test create mode 100644 vm/tests/all/agentscript/log.js diff --git a/vm/tests/all/agentscript/agent-exception.js b/vm/tests/all/agentscript/agent-exception.js new file mode 100644 index 000000000000..76579eaacfdf --- /dev/null +++ b/vm/tests/all/agentscript/agent-exception.js @@ -0,0 +1,8 @@ +agent.on('enter', function checkLogging(ev, frame) { + if (frame.msg === 'are') { + throw "great you are!"; + } +}, { + roots: true, + rootNameFilter: (n) => n === 'log' +}); diff --git a/vm/tests/all/agentscript/agent-exception.test b/vm/tests/all/agentscript/agent-exception.test new file mode 100644 index 000000000000..2f9b6d706e38 --- /dev/null +++ b/vm/tests/all/agentscript/agent-exception.test @@ -0,0 +1,14 @@ +>[7] js --jvm --experimental-options --agentscript=agent-exception.js log.js +Hello T-Trace. +How +great you are! +.*at <js> checkLogging.* +.*at <js> log.log.js:1-3:18-36. +.*at <js> :program.log.js:7:74-83. +>[7] js --experimental-options --agentscript=agent-exception.js log.js +Hello T-Trace. +How +great you are! +.*at <js> checkLogging.* +.*at <js> log.log.js:1-3:18-36. +.*at <js> :program.log.js:7:74-83. diff --git a/vm/tests/all/agentscript/log.js b/vm/tests/all/agentscript/log.js new file mode 100644 index 000000000000..22fa2b1c3e99 --- /dev/null +++ b/vm/tests/all/agentscript/log.js @@ -0,0 +1,8 @@ +function log(msg) { + print(msg); +} + +log('Hello T-Trace!'); +log('How'); +log('are'); +log('You?'); From e3b15da01bd937002783ca87f4ebf8612dcd65fb Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Thu, 5 Dec 2019 09:54:26 +0100 Subject: [PATCH 05/10] Using EventContext.createError to propagate the exception to the language --- .../agentscript/impl/AgentExecutionNode.java | 15 ++++----- .../agentscript/impl/EventContextObject.java | 8 +++++ vm/tests/all/agentscript/agent-exception.js | 11 +++++++ vm/tests/all/agentscript/agent-exception.test | 31 +++++++++++++++---- vm/tests/all/agentscript/log.js | 29 ++++++++++++++--- 5 files changed, 77 insertions(+), 17 deletions(-) diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java index 681b1ec46833..af97aa81b5b5 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java @@ -24,6 +24,7 @@ */ package com.oracle.truffle.tools.agentscript.impl; +import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; @@ -59,8 +60,8 @@ protected void onEnter(VirtualFrame frame) { if (enter != null) { try { enterDispatch.execute(enter, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException ex) { - throw AgentException.raise(ex); + } catch (InteropException | RuntimeException ex) { + throw ctx.rethrow(ex); } } } @@ -70,8 +71,8 @@ protected void onReturnValue(VirtualFrame frame, Object result) { if (exit != null) { try { exitDispatch.execute(exit, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException ex) { - throw AgentException.raise(ex); + } catch (InteropException | RuntimeException ex) { + throw ctx.rethrow(ex); } } } @@ -80,9 +81,9 @@ protected void onReturnValue(VirtualFrame frame, Object result) { protected void onReturnExceptional(VirtualFrame frame, Throwable exception) { if (exit != null) { try { - enterDispatch.execute(exit, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException ex) { - throw AgentException.raise(ex); + exitDispatch.execute(exit, ctx, new VariablesObject(env, this, frame)); + } catch (InteropException | RuntimeException ex) { + throw ctx.rethrow(ex); } } } diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java index 7cce7b466d2b..1a936302e0a7 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java @@ -25,6 +25,7 @@ package com.oracle.truffle.tools.agentscript.impl; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; @@ -42,6 +43,13 @@ final class EventContextObject implements TruffleObject { this.context = context; } + RuntimeException rethrow(Exception ex) { + if (ex instanceof TruffleException && ex instanceof RuntimeException) { + return context.createError((RuntimeException) ex); + } + throw AgentException.raise(ex); + } + @ExportMessage static boolean hasMembers(EventContextObject obj) { return true; diff --git a/vm/tests/all/agentscript/agent-exception.js b/vm/tests/all/agentscript/agent-exception.js index 76579eaacfdf..a5337f3b2c57 100644 --- a/vm/tests/all/agentscript/agent-exception.js +++ b/vm/tests/all/agentscript/agent-exception.js @@ -6,3 +6,14 @@ agent.on('enter', function checkLogging(ev, frame) { roots: true, rootNameFilter: (n) => n === 'log' }); +agent.on('return', function checkLogging(ev, frame) { + if (frame.msg === 'do') { + throw "you feel?"; + } + if (frame.msg === 'bad') { + throw "good you are?"; + } +}, { + roots: true, + rootNameFilter: (n) => n === 'log' +}); diff --git a/vm/tests/all/agentscript/agent-exception.test b/vm/tests/all/agentscript/agent-exception.test index 2f9b6d706e38..f2c22802bda1 100644 --- a/vm/tests/all/agentscript/agent-exception.test +++ b/vm/tests/all/agentscript/agent-exception.test @@ -1,14 +1,33 @@ ->[7] js --jvm --experimental-options --agentscript=agent-exception.js log.js +>[7] js --jvm --experimental-options --agentscript=agent-exception.js -f log.js -e "howAreYou()" Hello T-Trace. How great you are! .*at <js> checkLogging.* -.*at <js> log.log.js:1-3:18-36. -.*at <js> :program.log.js:7:74-83. ->[7] js --experimental-options --agentscript=agent-exception.js log.js +.*at <js> log.log.js:1-6:18-103. +.*at <js> howAreYou.log.js:11:176-185. + +>[7] js --experimental-options --agentscript=agent-exception.js -f log.js -e "howAreYou()" Hello T-Trace. How great you are! .*at <js> checkLogging.* -.*at <js> log.log.js:1-3:18-36. -.*at <js> :program.log.js:7:74-83. +.*at <js> log.log.js:1-6:18-103. +.*at <js> howAreYou.log.js:11:176-185. + +>[7] js --experimental-options --agentscript=agent-exception.js -f log.js -e "howDoYouDo()" +Hello T-Trace! +How +do +you feel? +.*at <js> checkLogging.* +.*at <js> log.log.js:1-6:18-103. +.*at <js> howDoYouDo.log.js:18:279-287. +.*at <js> :program.<eval_script>:1:0-11. +>[7] js --experimental-options --agentscript=agent-exception.js -f log.js -e "areYouBad()" +Hello T-Trace! +How +good you are? +.*at <js> checkLogging.* +.*at <js> log.log.js:1-6:18-103. +.*at <js> areYouBad.log.js:26:395-404. +.*at <js> :program.<eval_script>:1:0-10. diff --git a/vm/tests/all/agentscript/log.js b/vm/tests/all/agentscript/log.js index 22fa2b1c3e99..14b79b7ff3af 100644 --- a/vm/tests/all/agentscript/log.js +++ b/vm/tests/all/agentscript/log.js @@ -1,8 +1,29 @@ function log(msg) { + if (msg === 'bad') { + throw 'bad is not allowed'; + } print(msg); } -log('Hello T-Trace!'); -log('How'); -log('are'); -log('You?'); +function howAreYou() { + log('Hello T-Trace!'); + log('How'); + log('are'); + log('You?'); +} + +function howDoYouDo() { + log('Hello T-Trace!'); + log('How'); + log('do'); + log('you'); + log('do?'); +} + +function areYouBad() { + log('Hello T-Trace!'); + log('How'); + log('bad'); + log('are'); + log('you?'); +} From 76cd0b04e5d372dc4dd9252627357da407ef268f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Fri, 6 Dec 2019 08:31:30 +0100 Subject: [PATCH 06/10] Rephrasing the prose section about handling exceptions --- tools/docs/T-Trace-Manual.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/docs/T-Trace-Manual.md b/tools/docs/T-Trace-Manual.md index f3f7ee368803..cfa71a29c2d1 100644 --- a/tools/docs/T-Trace-Manual.md +++ b/tools/docs/T-Trace-Manual.md @@ -461,8 +461,9 @@ launched with a main `yourScript.js` parameter. ### Handling Exceptions -The `agentscript` code can throw exceptions and they are propagate to the -surrounding scripts. Imagine you have a program `seq.js` logging various messages: +The T-Trace agents can throw exceptions which are then propagated to the +surrounding user scripts. Imagine you have a program `seq.js` +logging various messages: ```js function log(msg) { @@ -475,13 +476,13 @@ log('are'); log('You?'); ``` -You can register an instrument `term.js` and terminate the execution in the middle of -logging: +You can register an instrument `term.js` and terminate the execution in the middle +of the `seq.js` program execution based on observing the logged message: ```js agent.on('enter', (ev, frame) => { if (frame.msg === 'are') { - throw "great you are!"; + throw 'great you are!'; } }, { roots: true, @@ -489,8 +490,9 @@ agent.on('enter', (ev, frame) => { }); ``` -The instruments waits for `log('are')` and at that moment it breaks the execution. -As a result one gets: +The `term.js` instrument waits for a call to `log` function with message 'are' +and at that moment it emits its own exception effectively interrupting the user +program execution. As a result one gets: ```bash $ js --polyglot --experimental-options --agentscript=term.js seq.js @@ -502,8 +504,10 @@ great you are! at <js> :program(seq.js:7:74-83) ``` -The locations may indeed be slightly different, but otherwise exceptions -from T-Trace instruments are treated as regular language exceptions. +The exceptions emitted by T-Trace instruments are treated as regular language +exceptions. The `seq.js` program could use regular `try { ... } catch (e) { ... }` +block to catch them and deal with them as if they were emitted by the regular +user code. <!-- From 4e0e73c91f72620e81c869054b9e768eda45fb5b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Fri, 6 Dec 2019 08:31:46 +0100 Subject: [PATCH 07/10] Removing unused import --- .../truffle/tools/agentscript/impl/AgentExecutionNode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java index af97aa81b5b5..ff421660520d 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java @@ -24,7 +24,6 @@ */ package com.oracle.truffle.tools.agentscript.impl; -import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; From d90cec223379f60e5fa3eaeb3caf12a4d5ebe448 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Fri, 13 Dec 2019 06:56:25 +0100 Subject: [PATCH 08/10] Handling Interop and Runtime exceptions in a different way --- .../tools/agentscript/impl/AgentExecutionNode.java | 12 +++++++++--- .../tools/agentscript/impl/EventContextObject.java | 13 ++++++++++--- vm/tests/all/agentscript/interop-exception.rb | 5 +++++ vm/tests/all/agentscript/interop-exception.test | 3 +++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 vm/tests/all/agentscript/interop-exception.rb create mode 100644 vm/tests/all/agentscript/interop-exception.test diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java index ff421660520d..edb0e6ad7d85 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/AgentExecutionNode.java @@ -59,7 +59,9 @@ protected void onEnter(VirtualFrame frame) { if (enter != null) { try { enterDispatch.execute(enter, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException | RuntimeException ex) { + } catch (InteropException ex) { + throw ctx.wrap(enter, 2, ex); + } catch (RuntimeException ex) { throw ctx.rethrow(ex); } } @@ -70,7 +72,9 @@ protected void onReturnValue(VirtualFrame frame, Object result) { if (exit != null) { try { exitDispatch.execute(exit, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException | RuntimeException ex) { + } catch (InteropException ex) { + throw ctx.wrap(exit, 2, ex); + } catch (RuntimeException ex) { throw ctx.rethrow(ex); } } @@ -81,7 +85,9 @@ protected void onReturnExceptional(VirtualFrame frame, Throwable exception) { if (exit != null) { try { exitDispatch.execute(exit, ctx, new VariablesObject(env, this, frame)); - } catch (InteropException | RuntimeException ex) { + } catch (InteropException ex) { + throw ctx.wrap(exit, 2, ex); + } catch (RuntimeException ex) { throw ctx.rethrow(ex); } } diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java index 1a936302e0a7..9ee23c0ffbff 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java @@ -27,6 +27,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleException; import com.oracle.truffle.api.instrumentation.EventContext; +import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnknownIdentifierException; @@ -43,9 +44,15 @@ final class EventContextObject implements TruffleObject { this.context = context; } - RuntimeException rethrow(Exception ex) { - if (ex instanceof TruffleException && ex instanceof RuntimeException) { - return context.createError((RuntimeException) ex); + RuntimeException wrap(Object target, int arity, InteropException ex) { + IllegalStateException ill = new IllegalStateException("Cannot invoke " + target + " with " + arity + " arguments: " + ex.getMessage()); + ill.initCause(ex); + return context.createError(ill); + } + + RuntimeException rethrow(RuntimeException ex) { + if (ex instanceof TruffleException) { + return context.createError(ex); } throw AgentException.raise(ex); } diff --git a/vm/tests/all/agentscript/interop-exception.rb b/vm/tests/all/agentscript/interop-exception.rb new file mode 100644 index 000000000000..0f097bc6ca3a --- /dev/null +++ b/vm/tests/all/agentscript/interop-exception.rb @@ -0,0 +1,5 @@ +agent.on("enter", ->(a1) { + puts "What's #{a1}?" +}, { + statements: true +}); diff --git a/vm/tests/all/agentscript/interop-exception.test b/vm/tests/all/agentscript/interop-exception.test new file mode 100644 index 000000000000..f9c9b3c6757f --- /dev/null +++ b/vm/tests/all/agentscript/interop-exception.test @@ -0,0 +1,3 @@ +>[1] ruby --experimental-options --agentscript=interop-exception.rb -e 'def x(t) puts("x=#{t}"); end; x(6 * 7)' +interop-exception.rb:1.*wrong number of arguments.*given 2.*expected 1.* +from.*main.* From 9b3713b6a0ffa6fc0b4174683432cc6f3a25a18d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Fri, 13 Dec 2019 11:23:22 +0100 Subject: [PATCH 09/10] There can be some prefix before "from" --- vm/tests/all/agentscript/interop-exception.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/tests/all/agentscript/interop-exception.test b/vm/tests/all/agentscript/interop-exception.test index f9c9b3c6757f..005f80bc79bc 100644 --- a/vm/tests/all/agentscript/interop-exception.test +++ b/vm/tests/all/agentscript/interop-exception.test @@ -1,3 +1,3 @@ >[1] ruby --experimental-options --agentscript=interop-exception.rb -e 'def x(t) puts("x=#{t}"); end; x(6 * 7)' interop-exception.rb:1.*wrong number of arguments.*given 2.*expected 1.* -from.*main.* +.*from.*main.* From 37832476b42a5d07d52c1facd8ad6b3e55f9fa11 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach <jaroslav.tulach@oracle.com> Date: Fri, 13 Dec 2019 11:39:41 +0100 Subject: [PATCH 10/10] Propagating only non-internal errors --- .../truffle/tools/agentscript/impl/EventContextObject.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java index 9ee23c0ffbff..6913bba4a21b 100644 --- a/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java +++ b/tools/src/com.oracle.truffle.tools.agentscript/src/com/oracle/truffle/tools/agentscript/impl/EventContextObject.java @@ -52,9 +52,11 @@ RuntimeException wrap(Object target, int arity, InteropException ex) { RuntimeException rethrow(RuntimeException ex) { if (ex instanceof TruffleException) { - return context.createError(ex); + if (!((TruffleException) ex).isInternalError()) { + return context.createError(ex); + } } - throw AgentException.raise(ex); + throw ex; } @ExportMessage