Skip to content

Commit

Permalink
[GR-51385] Debugger API to ignore unavailable source sections.
Browse files Browse the repository at this point in the history
PullRequest: graal/16612
  • Loading branch information
entlicher committed Jan 26, 2024
2 parents 3892951 + 928c6bd commit 7e314cf
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024, 2024, 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.chromeinspector.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

import org.graalvm.polyglot.Source;

import com.oracle.truffle.api.debug.test.SuspensionFilterTest;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;

/**
* Test that we do not suspend on unavailable source sections.
*/
public class UnavailableSectionInspectDebugTest {

@Test
public void testNoStopInUnavailable() throws InterruptedException {
ProxyLanguage.setDelegate(SuspensionFilterTest.createSectionAvailabilityTestLanguage());
InspectorTester tester = InspectorTester.start(true, false, false);

tester.sendMessage("{\"id\":1,\"method\":\"Runtime.enable\"}");
assertEquals("{\"result\":{},\"id\":1}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":2,\"method\":\"Debugger.enable\"}");
tester.receiveMessages(
"{\"result\":{\"debuggerId\":\"UniqueDebuggerId.", "},\"id\":2}\n");
tester.sendMessage("{\"id\":3,\"method\":\"Runtime.runIfWaitingForDebugger\"}");
// @formatter:off The default formatting makes unnecessarily big indents and illogical line breaks
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":3}\n" +
"{\"method\":\"Runtime.executionContextCreated\",\"params\":{\"context\":{\"origin\":\"\",\"name\":\"test\",\"id\":1}}}\n"));
// @formatter:on

long cid = tester.getContextId();
int cmdId = 1;
String[] hashes = new String[]{"fc4f434affffffffffffffffffffffffffffffff", "daaab888ffffffffffffffffffffffffffffffff"};
for (int i = 0; i < 3; i++) {
String sourceName = i + ".test";
// Create RootWithUnavailableSection.sectionAvailability = i:
Source source = Source.newBuilder(ProxyLanguage.ID, Integer.toString(i), sourceName).buildLiteral();
tester.eval(source);
// @formatter:off
if (i < 2) { // No Source when SourceSection is null
assertTrue(tester.compareReceivedMessages(
"{\"method\":\"Debugger.scriptParsed\",\"params\":{\"endLine\":0,\"scriptId\":\"" + i + "\",\"endColumn\":1,\"startColumn\":0,\"startLine\":0,\"length\":1," +
"\"executionContextId\":" + cid + ",\"url\":\"" + sourceName + "\",\"hash\":\"" + hashes[i] + "\"}}\n"));
}
if (i == 0) {
assertTrue(tester.compareReceivedMessages(
"{\"method\":\"Debugger.paused\",\"params\":{\"reason\":\"other\",\"hitBreakpoints\":[]," +
"\"callFrames\":[{\"callFrameId\":\"0\"," +
"\"returnValue\":{\"description\":\"42\",\"type\":\"number\",\"value\":42}," +
"\"functionName\":\"\"," +
"\"scopeChain\":[]," +
"\"this\":null," +
"\"location\":{\"scriptId\":\"" + i + "\",\"columnNumber\":1,\"lineNumber\":0}," +
"\"url\":\"" + sourceName + "\"}]}}\n"));
cmdId++;
tester.sendMessage("{\"id\":" + cmdId + ",\"method\":\"Debugger.stepOver\"}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":" + cmdId + "}\n" +
"{\"method\":\"Debugger.resumed\"}\n"));
}
// @formatter:on
}
// Wait for evals to finish, since there may be no protocol communication from some of them.
tester.finishNoGC();
// Reset the delegate so that we can GC the tested Engine
ProxyLanguage.setDelegate(new ProxyLanguage());
tester.finish();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ private void startSession() {
suspendedCallback = new SuspendedCallbackImpl();
debuggerSession = tdbg.startSession(suspendedCallback, SourceElement.ROOT, SourceElement.STATEMENT);
debuggerSession.setSourcePath(context.getSourcePath());
debuggerSession.setSteppingFilter(SuspensionFilter.newBuilder().ignoreLanguageContextInitialization(!context.isInspectInitialization()).includeInternal(context.isInspectInternal()).build());
debuggerSession.setSteppingFilter(SuspensionFilter.newBuilder() //
.ignoreLanguageContextInitialization(!context.isInspectInitialization()) //
.includeInternal(context.isInspectInternal()) //
.sourceSectionAvailableOnly(true).build());
scriptsHandler = context.acquireScriptsHandler(debuggerSession);
breakpointsHandler = new BreakpointsHandler(debuggerSession, scriptsHandler, () -> eventHandler);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2024, 2024, 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.dap.test;

import org.junit.Test;

import org.graalvm.polyglot.Source;

import com.oracle.truffle.api.debug.test.SuspensionFilterTest;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;

/**
* Test that we do not suspend on unavailable source sections.
*/
public class UnavailableSectionDAPTest {

@Test
public void testNoStopInUnavailable() throws Exception {
ProxyLanguage.setDelegate(SuspensionFilterTest.createSectionAvailabilityTestLanguage());
DAPTester tester = DAPTester.start(true, null);

tester.sendMessage(
"{\"command\":\"initialize\",\"arguments\":{\"clientID\":\"DAPTester\",\"clientName\":\"DAP Tester\",\"adapterID\":\"graalvm\",\"pathFormat\":\"path\",\"linesStartAt1\":true,\"columnsStartAt1\":true," +
"\"supportsVariableType\":true,\"supportsVariablePaging\":true,\"supportsRunInTerminalRequest\":true,\"locale\":\"en-us\",\"supportsProgressReporting\":true},\"type\":\"request\",\"seq\":1}");
tester.compareReceivedMessages(
"{\"event\":\"initialized\",\"type\":\"event\"}",
"{\"success\":true,\"type\":\"response\",\"body\":{\"supportsConditionalBreakpoints\":true,\"supportsLoadedSourcesRequest\":true,\"supportsFunctionBreakpoints\":true,\"supportsExceptionInfoRequest\":true," +
"\"supportsBreakpointLocationsRequest\":true,\"supportsHitConditionalBreakpoints\":true,\"supportsLogPoints\":true,\"supportsSetVariable\":true,\"supportsConfigurationDoneRequest\":true," +
"\"exceptionBreakpointFilters\":[{\"filter\":\"all\",\"label\":\"All Exceptions\"},{\"filter\":\"uncaught\",\"label\":\"Uncaught Exceptions\"}]},\"request_seq\":1,\"command\":\"initialize\"}");
tester.sendMessage(
"{\"command\":\"attach\",\"arguments\":{\"type\":\"graalvm\",\"request\":\"attach\",\"name\":\"Attach\",\"port\":9229,\"protocol\":\"debugAdapter\"},\"type\":\"request\",\"seq\":2}");
tester.compareReceivedMessages("{\"event\":\"output\",\"body\":{\"output\":\"Debugger attached.\",\"category\":\"stderr\"},\"type\":\"event\"}",
"{\"success\":true,\"type\":\"response\",\"request_seq\":2,\"command\":\"attach\"}");
tester.sendMessage("{\"command\":\"configurationDone\",\"type\":\"request\",\"seq\":5}");
tester.compareReceivedMessages("{\"success\":true,\"type\":\"response\",\"request_seq\":5,\"command\":\"configurationDone\",\"seq\":5}");

int seq = 6;
for (int i = 0; i < 3; i++) {
String sourceName = i + ".test";
// Create RootWithUnavailableSection.sectionAvailability = i:
Source source = Source.newBuilder(ProxyLanguage.ID, Integer.toString(i), sourceName).buildLiteral();
tester.eval(source);
if (i == 0) {
tester.compareReceivedMessages("{\"event\":\"thread\",\"body\":{\"threadId\":1,\"reason\":\"started\"},\"type\":\"event\",\"seq\":" + (seq++) + "}");
}
if (i < 2) { // No Source when SourceSection is null
String sourceJson = "{\"sourceReference\":" + (i + 1) + ",\"name\":\"" + sourceName + "\"}";
tester.compareReceivedMessages(
"{\"event\":\"loadedSource\",\"body\":{\"reason\":\"new\",\"source\":" + sourceJson + "},\"type\":\"event\",\"seq\":" + (seq++) + "}");
}
if (i == 0) {
tester.compareReceivedMessages(
"{\"event\":\"stopped\",\"body\":{\"threadId\":1,\"reason\":\"debugger_statement\",\"description\":\"Paused on debugger statement\"},\"type\":\"event\",\"seq\":" +
(seq++) + "}");
tester.sendMessage("{\"command\":\"next\",\"arguments\":{\"threadId\":1},\"type\":\"request\",\"seq\":7}");
tester.compareReceivedMessages(
"{\"success\":true,\"type\":\"response\",\"request_seq\":7,\"command\":\"next\",\"seq\":" + (seq++) + "}");
tester.compareReceivedMessages(
"{\"event\":\"continued\",\"body\":{\"threadId\":1,\"allThreadsContinued\":false},\"type\":\"event\",\"seq\":" + (seq++) + "}");
}
}
tester.finish();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,10 @@ private DebuggerSession startDebuggerSession() {
Debugger tdbg = context.getEnv().lookup(context.getEnv().getInstruments().get("debugger"), Debugger.class);
DebuggerSession session = tdbg.startSession(new SuspendedCallbackImpl(), SourceElement.ROOT, SourceElement.STATEMENT);
session.setSourcePath(sourcePath);
session.setSteppingFilter(SuspensionFilter.newBuilder().ignoreLanguageContextInitialization(!context.isInspectInitialization()).includeInternal(context.isInspectInternal()).build());
session.setSteppingFilter(SuspensionFilter.newBuilder() //
.ignoreLanguageContextInitialization(!context.isInspectInitialization()) //
.includeInternal(context.isInspectInternal()) //
.sourceSectionAvailableOnly(true).build());
return session;
}

Expand Down
3 changes: 3 additions & 0 deletions truffle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ This changelog summarizes major changes between Truffle versions relevant to lan
## Version 24.1.0
* GR-51253 Extend allowed DynamicObject shape flags from 8 to 16 bits.

* GR-51385 Added an instrumentation filter to include available source sections only: `SourceSectionFilter.Builder#sourceSectionAvailableOnly(boolean)`
* GR-51385 Added a debugger filter to suspend in available source sections only: `SuspensionFilter.Builder#sourceSectionAvailableOnly(boolean)`.

## Version 24.0.0

* GR-45863 Yield and resume events added to the instrumentation:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -48,11 +48,26 @@
import org.junit.Assert;
import org.junit.Test;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.debug.Breakpoint;
import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.debug.SourceElement;
import com.oracle.truffle.api.debug.SuspendAnchor;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.debug.SuspensionFilter;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
import java.util.List;

import org.graalvm.polyglot.Source;

Expand Down Expand Up @@ -531,4 +546,117 @@ public void testSourceFilterBreakpoints() {
}
}
}

@Test
public void testUnavailableSourceSection() {
ProxyLanguage.setDelegate(createSectionAvailabilityTestLanguage());
try (DebuggerSession session = tester.startSession(SourceElement.ROOT)) {
session.suspendNextExecution();
for (Boolean availableOnly : List.of(Boolean.FALSE, Boolean.TRUE)) {
session.setSteppingFilter(SuspensionFilter.newBuilder().sourceSectionAvailableOnly(availableOnly).build());
for (int i = 0; i < 3; i++) {
// Create RootWithUnavailableSection.sectionAvailability = i:
Source source = Source.create(ProxyLanguage.ID, Integer.toString(i));
tester.startEval(source);
boolean suspends = !availableOnly || i == 0;
if (suspends) {
for (SuspendAnchor anchor : List.of(SuspendAnchor.BEFORE, SuspendAnchor.AFTER)) {
final SuspendAnchor suspendAnchor = anchor;
final int sectionAvailability = i;
tester.expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals(suspendAnchor, event.getSuspendAnchor());
SourceSection section = event.getSourceSection();
switch (sectionAvailability) {
case 0:
Assert.assertTrue(section.isAvailable());
break;
case 1:
Assert.assertFalse(section.isAvailable());
break;
case 2:
Assert.assertNull(section);
break;
}
event.prepareStepOver(1);
}, error -> error.contains("has null SourceSection"));
}
}
tester.expectDone();
}
}
}
}

public static ProxyLanguage createSectionAvailabilityTestLanguage() {
return new ProxyLanguage() {
@Override
protected CallTarget parse(TruffleLanguage.ParsingRequest request) throws Exception {
return new TestRootNode(languageInstance, request.getSource()).getCallTarget();
}

final class TestRootNode extends RootNode {
@Node.Child RootWithUnavailableSection node;

TestRootNode(TruffleLanguage<?> language, com.oracle.truffle.api.source.Source source) {
super(language);
node = new RootWithUnavailableSection(source);
}

@Override
public Object execute(VirtualFrame frame) {
return node.execute(frame);
}
}
};
}

@GenerateWrapper
static class RootWithUnavailableSection extends Node implements InstrumentableNode {

private final int sectionAvailability;
private final com.oracle.truffle.api.source.Source source;

RootWithUnavailableSection(com.oracle.truffle.api.source.Source source) {
this.sectionAvailability = Character.digit(source.getCharacters().charAt(0), 10);
this.source = source;
}

RootWithUnavailableSection(RootWithUnavailableSection root) {
this.sectionAvailability = root.sectionAvailability;
this.source = root.source;
}

@Override
public boolean isInstrumentable() {
return true;
}

@Override
public boolean hasTag(Class<? extends Tag> tag) {
return StandardTags.RootTag.class.equals(tag);
}

@Override
public InstrumentableNode.WrapperNode createWrapper(ProbeNode probe) {
return new RootWithUnavailableSectionWrapper(this, this, probe);
}

@Override
public SourceSection getSourceSection() {
switch (sectionAvailability) {
case 0: // Available
return source.createSection(1);
case 1: // Unavailable
return source.createUnavailableSection();
case 2: // null
return null;
default:
throw new IllegalStateException(String.format("Unknown sectionAvailability %d", sectionAvailability));
}
}

public int execute(@SuppressWarnings("unused") VirtualFrame frame) {
return 42;
}
}
}
Loading

0 comments on commit 7e314cf

Please sign in to comment.