Skip to content

Commit

Permalink
Prevent from NPEs from profiler when there are nodes without source s…
Browse files Browse the repository at this point in the history
…ections. (GR-34094)
  • Loading branch information
entlicher committed Oct 6, 2021
1 parent 9757a3c commit 1be8614
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2021, 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 com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleSafepoint;
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.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 com.oracle.truffle.tools.utils.json.JSONObject;

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import org.graalvm.polyglot.Source;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;

public class ProfilerTest {

@Test
public void testNoSourceProfile() throws InterruptedException, IOException, ExecutionException {
ProxyLanguage.setDelegate(new TestNoSourceLanguage());
InspectorTester tester = InspectorTester.start(false);
Source source = Source.newBuilder(ProxyLanguage.ID, "1", "NoSourceProfile").build();
tester.sendMessage("{\"id\":1,\"method\":\"Runtime.enable\"}");
assertEquals("{\"result\":{},\"id\":1}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":2,\"method\":\"Profiler.enable\"}");
assertEquals("{\"result\":{},\"id\":2}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":3,\"method\":\"Profiler.setSamplingInterval\",\"params\":{\"interval\":1000}}");
assertEquals("{\"result\":{},\"id\":3}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":4,\"method\":\"Profiler.start\"}");
assertEquals("{\"result\":{},\"id\":4}", tester.getMessages(true).trim());
tester.eval(source).get();
tester.sendMessage("{\"id\":5,\"method\":\"Profiler.stop\"}");
JSONObject json = new JSONObject(tester.getMessages(true).trim());
assertNotNull(json);
assertEquals(json.getInt("id"), 5);
JSONObject jsonResult = json.getJSONObject("result");
assertNotNull(jsonResult);
JSONObject jsonProfile = jsonResult.getJSONObject("profile");
assertNotNull(jsonProfile);
tester.sendMessage("{\"id\":6,\"method\":\"Profiler.disable\"}");
assertEquals("{\"result\":{},\"id\":6}", tester.getMessages(true).trim());
ProxyLanguage.setDelegate(new ProxyLanguage());
tester.finish();
}

@Test
public void testNoSourceCoverage() throws InterruptedException, IOException, ExecutionException {
ProxyLanguage.setDelegate(new TestNoSourceLanguage());
InspectorTester tester = InspectorTester.start(false);
Source source = Source.newBuilder(ProxyLanguage.ID, "1", "NoSourceProfile").build();
tester.sendMessage("{\"id\":1,\"method\":\"Runtime.enable\"}");
assertEquals("{\"result\":{},\"id\":1}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":2,\"method\":\"Profiler.enable\"}");
assertEquals("{\"result\":{},\"id\":2}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":3,\"method\":\"Profiler.startPreciseCoverage\"}");
assertEquals("{\"result\":{},\"id\":3}", tester.getMessages(true).trim());
tester.eval(source).get();
tester.sendMessage("{\"id\":5,\"method\":\"Profiler.takePreciseCoverage\"}");
// No results in case of no source section
assertEquals("{\"result\":{\"result\":[]},\"id\":5}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":7,\"method\":\"Profiler.stopPreciseCoverage\"}");
assertEquals("{\"result\":{},\"id\":7}", tester.getMessages(true).trim());
tester.sendMessage("{\"id\":6,\"method\":\"Profiler.disable\"}");
assertEquals("{\"result\":{},\"id\":6}", tester.getMessages(true).trim());
ProxyLanguage.setDelegate(new ProxyLanguage());
tester.finish();
}

private static class TestNoSourceLanguage extends ProxyLanguage {

@Override
protected final CallTarget parse(TruffleLanguage.ParsingRequest request) throws Exception {
return Truffle.getRuntime().createCallTarget(new NoSourceRootNode(languageInstance));
}

private final class NoSourceRootNode extends RootNode {

@Node.Child private NoSourceStatementNode statement;

NoSourceRootNode(TruffleLanguage<?> language) {
super(language);
statement = new NoSourceStatementNode();
insert(statement);
}

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

@Override
public String getName() {
return "1";
}

}

}

@GenerateWrapper
static class NoSourceStatementNode extends Node implements InstrumentableNode {

NoSourceStatementNode() {
}

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

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

public Object execute(VirtualFrame frame) {
assert frame != null;
TruffleSafepoint.poll(this);
return 10;
}

@Override
public SourceSection getSourceSection() {
return null;
}

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

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,7 @@ private CallFrame[] createCallFrames(Iterable<DebugStackFrame> frames, SuspendAn
// should not be, double-check
continue;
}
int scriptId = scriptsHandler.assureLoaded(source);
if (scriptId < 0) {
continue;
}
Script script = scriptsHandler.getScript(scriptId);
Script script = scriptsHandler.assureLoaded(source);
List<Scope> scopes = new ArrayList<>();
DebugScope dscope;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,12 @@ private Params getCoverage(Collection<CPUTracer.Payload> payloads) {
JSONObject json = new JSONObject();
Map<Source, Map<String, Collection<CPUTracer.Payload>>> sourceToRoots = new LinkedHashMap<>();
payloads.forEach(payload -> {
Map<String, Collection<CPUTracer.Payload>> rootsToPayloads = sourceToRoots.computeIfAbsent(payload.getSourceSection().getSource(), s -> new LinkedHashMap<>());
Collection<CPUTracer.Payload> pls = rootsToPayloads.computeIfAbsent(payload.getRootName(), t -> new LinkedList<>());
pls.add(payload);
SourceSection sourceSection = payload.getSourceSection();
if (sourceSection != null) {
Map<String, Collection<CPUTracer.Payload>> rootsToPayloads = sourceToRoots.computeIfAbsent(sourceSection.getSource(), s -> new LinkedHashMap<>());
Collection<CPUTracer.Payload> pls = rootsToPayloads.computeIfAbsent(payload.getRootName(), t -> new LinkedList<>());
pls.add(payload);
}
});
JSONArray result = new JSONArray();
sourceToRoots.entrySet().stream().map(sourceEntry -> {
Expand All @@ -214,9 +217,8 @@ private Params getCoverage(Collection<CPUTracer.Payload> payloads) {
}
functions.add(new FunctionCoverage(rootEntry.getKey(), isBlockCoverage, ranges.toArray(new CoverageRange[ranges.size()])));
});
int scriptId = slh.getScriptId(sourceEntry.getKey());
Script script = scriptId < 0 ? null : slh.getScript(scriptId);
return new ScriptCoverage(script != null ? script.getId() : 0, script != null ? script.getUrl() : "", functions.toArray(new FunctionCoverage[functions.size()]));
Script script = slh.assureLoaded(sourceEntry.getKey());
return new ScriptCoverage(script.getId(), script.getUrl(), functions.toArray(new FunctionCoverage[functions.size()]));
}).forEachOrdered(scriptCoverage -> {
result.put(scriptCoverage.toJSON());
});
Expand Down Expand Up @@ -248,10 +250,9 @@ private void fillChildren(ProfileNode node, Collection<ProfilerNode<CPUSampler.P
int id = node2id.get(childProfilerNode);
if (id < 0) { // not computed yet
id = -id;
int scriptId = slh.getScriptId(childProfilerNode.getSourceSection().getSource());
Script script = scriptId < 0 ? null : slh.getScript(scriptId);
SourceSection sourceSection = childProfilerNode.getSourceSection();
ProfileNode childNode = new ProfileNode(id, new RuntimeCallFrame(childProfilerNode.getRootName(), script != null ? script.getId() : 0, script != null ? script.getUrl() : "",
Script script = slh.assureLoaded(sourceSection.getSource());
ProfileNode childNode = new ProfileNode(id, new RuntimeCallFrame(childProfilerNode.getRootName(), script.getId(), script.getUrl(),
sourceSection.getStartLine(), sourceSection.getStartColumn()), childProfilerNode.getPayload().getSelfHitCount());
nodes.add(childNode);
for (Long timestamp : childProfilerNode.getPayload().getSelfHitTimes()) {
Expand Down Expand Up @@ -297,9 +298,8 @@ private Params getTypeProfile(Collection<TypeHandler.SectionTypeProfile> profile
entries.add(new TypeProfileEntry(sectionProfile.getSourceSection().getCharEndIndex(), types.toArray(new TypeObject[types.size()])));
}
});
int scriptId = slh.getScriptId(entry.getKey());
Script script = scriptId < 0 ? null : slh.getScript(scriptId);
result.put(new ScriptTypeProfile(script != null ? script.getId() : 0, script != null ? script.getUrl() : "", entries.toArray(new TypeProfileEntry[entries.size()])).toJSON());
Script script = slh.assureLoaded(entry.getKey());
result.put(new ScriptTypeProfile(script.getId(), script.getUrl(), entries.toArray(new TypeProfileEntry[entries.size()])).toJSON());
});
json.put("result", result);
return new Params(json);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,8 @@ public Boolean processException(DebugException ex) {
exceptionText[0] = "<Not suspended>";
}
if (parsed && persistScript) {
int id = slh.assureLoaded(source);
if (id != -1) {
ret.put("scriptId", Integer.toString(id));
}
int id = slh.assureLoaded(source).getId();
ret.put("scriptId", Integer.toString(id));
}
if (exceptionText[0] != null) {
fillExceptionDetails(ret, exceptionText[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,23 @@ void removeLoadScriptListener(LoadScriptListener listener) {
}
}

public int assureLoaded(Source sourceLoaded) {
public Script assureLoaded(Source sourceLoaded) {
DebuggerSession ds = debuggerSession;
Source sourceResolved = sourceLoaded;
if (ds != null) {
sourceResolved = ds.resolveSource(sourceLoaded);
}
Source source = (sourceResolved != null) ? sourceResolved : sourceLoaded;
Script scr;
int id;
LoadScriptListener[] listenersToNotify;
synchronized (sourceIDs) {
Integer eid = sourceIDs.get(source);
if (eid != null) {
return eid;
scr = scripts.get(eid);
assert scr != null : sourceLoaded;
return scr;
}
id = scripts.size();
int id = scripts.size();
String sourceUrl = getSourceURL(source);
scr = new Script(id, sourceUrl, source, sourceLoaded);
sourceIDs.put(source, id);
Expand All @@ -128,7 +129,7 @@ public int assureLoaded(Source sourceLoaded) {
for (LoadScriptListener l : listenersToNotify) {
l.loadedScript(scr);
}
return id;
return scr;
}

public String getSourceURL(Source source) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,12 @@ public StackTrace(InspectorExecutionContext context, List<List<DebugStackTraceEl
callFrame.put("functionName", frame.getName());
ScriptsHandler sch = context.acquireScriptsHandler();
try {
int scriptId = sch.assureLoaded(source);
if (scriptId != -1) {
callFrame.put("scriptId", Integer.toString(scriptId));
callFrame.put("url", sch.getScript(scriptId).getUrl());
callFrame.put("lineNumber", sourceSection.getStartLine() - 1);
callFrame.put("columnNumber", sourceSection.getStartColumn() - 1);
callFrames.put(callFrame);
}
int scriptId = sch.assureLoaded(source).getId();
callFrame.put("scriptId", Integer.toString(scriptId));
callFrame.put("url", sch.getScript(scriptId).getUrl());
callFrame.put("lineNumber", sourceSection.getStartLine() - 1);
callFrame.put("columnNumber", sourceSection.getStartColumn() - 1);
callFrames.put(callFrame);
} finally {
context.releaseScriptsHandler();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public void clearData() {

private Payload getCounter(EventContext context) {
SourceSection sourceSection = context.getInstrumentedSourceSection();
assert sourceSection != null : context;
return payloadMap.computeIfAbsent(sourceSection, new Function<SourceSection, Payload>() {
@Override
public Payload apply(SourceSection section) {
Expand Down Expand Up @@ -192,7 +193,11 @@ private synchronized void resetTracer() {
this.activeBinding = env.getInstrumenter().attachExecutionEventFactory(f, new ExecutionEventNodeFactory() {
@Override
public ExecutionEventNode create(EventContext context) {
return new CounterNode(getCounter(context));
if (context.getInstrumentedSourceSection() != null) {
return new CounterNode(getCounter(context));
} else {
return null;
}
}
});
}
Expand Down

0 comments on commit 1be8614

Please sign in to comment.