Skip to content

Commit

Permalink
Print warnings on local variables to stderr
Browse files Browse the repository at this point in the history
  • Loading branch information
JaroslavTulach committed Aug 18, 2024
1 parent dbe3c45 commit f257235
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.RootNode;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.enso.compiler.context.FramePointer;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointNode;
Expand Down Expand Up @@ -49,8 +52,11 @@
/** The Instrument implementation for the interactive debugger REPL. */
@TruffleInstrument.Registration(id = DebugServerInfo.INSTRUMENT_NAME)
public final class ReplDebuggerInstrument extends TruffleInstrument {
private static final OptionKey<Boolean> ENABLE_OPTION = new OptionKey<Boolean>(false);
private static final OptionKey<String> FN_OPTION = new OptionKey<String>("");
/** Option for {@link DebugServerInfo#ENABLE_OPTION} */
private static final OptionKey<Boolean> ENABLE_OPTION = new OptionKey<>(false);

/** Option for {@link DebugServerInfo#FN_OPTION} */
private static final OptionKey<String> FN_OPTION = new OptionKey<>("");

/**
* Called by Truffle when this instrument is installed.
Expand All @@ -68,7 +74,7 @@ protected void onCreate(Env env) {
ctx ->
ctx.getInstrumentedNode() instanceof DebugBreakpointNode
? new ReplExecutionEventNodeImpl(
false, ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class))
null, ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class))
: null;

filter = SourceSectionFilter.newBuilder().tagIs(DebuggerTags.AlwaysHalt.class).build();
Expand Down Expand Up @@ -126,10 +132,10 @@ private static class ReplExecutionEventNodeImpl extends ExecutionEventNode
private EventContext eventContext;
private DebuggerMessageHandler handler;
private TruffleLogger logger;
private final boolean atExit;
private final OutputStream atExit;

private ReplExecutionEventNodeImpl(
boolean atExit,
OutputStream atExit,
EventContext eventContext,
DebuggerMessageHandler handler,
TruffleLogger logger) {
Expand All @@ -139,7 +145,7 @@ private ReplExecutionEventNodeImpl(
this.atExit = atExit;
}

private Object getValue(MaterializedFrame frame, FramePointer ptr) {
private Object getValue(MaterializedFrame frame, FramePointer ptr, boolean onlyWarnings) {
var raw = getProperFrame(frame, ptr).getValue(ptr.frameSlotIdx());
if (WarningsLibrary.getUncached().hasWarnings(raw)) {
try {
Expand All @@ -162,7 +168,7 @@ private Object getValue(MaterializedFrame frame, FramePointer ptr) {
// go on
}
}
return raw;
return onlyWarnings ? null : raw;
}

private MaterializedFrame getProperFrame(MaterializedFrame frame, FramePointer ptr) {
Expand All @@ -175,11 +181,19 @@ private MaterializedFrame getProperFrame(MaterializedFrame frame, FramePointer p

@Override
public Map<String, Object> listBindings() {
return listBindings(false);
}

public Map<String, Object> listBindings(boolean onlyWarnings) {
Map<String, FramePointer> flatScope =
nodeState.getLastScope().getLocalScope().flattenBindings();
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, FramePointer> entry : flatScope.entrySet()) {
result.put(entry.getKey(), getValue(nodeState.getLastScope().getFrame(), entry.getValue()));
var valueOrNull =
getValue(nodeState.getLastScope().getFrame(), entry.getValue(), onlyWarnings);
if (valueOrNull != null) {
result.put(entry.getKey(), valueOrNull);
}
}
return result;
}
Expand Down Expand Up @@ -232,7 +246,7 @@ public void exit() {
*/
@Override
protected void onEnter(VirtualFrame frame) {
if (!atExit) {
if (atExit == null) {
CallerInfo lastScope = Function.ArgumentsHelper.getCallerInfo(frame.getArguments());
Object lastReturn = EnsoContext.get(this).getNothing();
// Note [Safe Access to State in the Debugger Instrument]
Expand All @@ -244,8 +258,8 @@ protected void onEnter(VirtualFrame frame) {

@Override
public void onReturnValue(VirtualFrame frame, Object result) {
if (atExit) {
startSession(getRootNode(), frame);
if (atExit != null) {
startSession(getRootNode(), frame, result);
}
}

Expand All @@ -270,17 +284,16 @@ protected Object onUnwind(VirtualFrame frame, Object info) {
return nodeState.getLastReturn();
}

private void startSession(RootNode root, VirtualFrame frame) {
private void startSession(RootNode root, VirtualFrame frame, Object toReturn) {
CallerInfo lastScope = Function.ArgumentsHelper.getCallerInfo(frame.getArguments());
if (lastScope == null && root instanceof EnsoRootNode enso) {
lastScope =
new CallerInfo(frame.materialize(), enso.getLocalScope(), enso.getModuleScope());
}
if (lastScope != null) {
var lastReturn = EnsoContext.get(this).getNothing();
// Note [Safe Access to State in the Debugger Instrument]
monadicState = Function.ArgumentsHelper.getState(frame.getArguments());
nodeState = new ReplExecutionEventNodeState(lastReturn, lastScope);
nodeState = new ReplExecutionEventNodeState(toReturn, lastScope);
startSessionImpl();
}
}
Expand All @@ -290,9 +303,20 @@ private void startSessionImpl() {
if (handler.hasClient()) {
handler.startSession(this);
} else {
logger.warning(
"Debugger session starting, "
+ "but no client connected, will terminate the session immediately");
if (atExit == null) {
logger.warning(
"Debugger session starting, "
+ "but no client connected, will terminate the session immediately");
} else {
for (var b : listBindings(true).entrySet()) {
var line = b.getKey() + " = " + b.getValue() + "\n";
try {
atExit.write(line.getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
logger.log(Level.SEVERE, line, ex);
}
}
}
exit();
}
}
Expand Down Expand Up @@ -335,7 +359,7 @@ private final class AtTheEndOfMethod implements ExecutionEventNodeFactory {
@Override
public ExecutionEventNode create(EventContext ctx) {
var log = env.getLogger(ReplExecutionEventNodeImpl.class);
return new ReplExecutionEventNodeImpl(true, ctx, handler, log);
return new ReplExecutionEventNodeImpl(env.err(), ctx, handler, log);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.enso.interpreter.test.instrument;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import org.enso.polyglot.debugger.DebugServerInfo;
import org.enso.test.utils.ContextUtils;
import org.graalvm.polyglot.Context;
import org.hamcrest.core.AllOf;
import org.junit.Test;

public class DebugServerWithScriptTest {
private interface WithContext {
void action(Context ctx, Object out, Object err) throws Exception;
}

private void withContext(String script, WithContext action) throws Exception {
var out = new ByteArrayOutputStream();
var err = new ByteArrayOutputStream();

var b = ContextUtils.defaultContextBuilder().out(out).err(err);

b.option(DebugServerInfo.FN_OPTION, "ScriptTest.inspect");

try (var ctx = b.build()) {
action.action(ctx, out, err);
}
}

@Test
public void propertyListingVariables() throws Exception {
withContext(
":list\n:q",
(ctx, out, err) -> {
var r =
ContextUtils.evalModule(
ctx,
"""
from Standard.Base import all
inspect =
j = 1
d = Warning.attach "doubled value" 2
t = j + d
v = [j, d, t]
v
""",
"ScriptTest",
"inspect");
assertTrue("Got array back: " + r, r.hasArrayElements());
assertEquals("Got three elements", 3, r.getArraySize());
assertEquals("One", 1, r.getArrayElement(0).asInt());
assertEquals("Two", 2, r.getArrayElement(1).asInt());
assertEquals("Three", 3, r.getArrayElement(2).asInt());
assertEquals("No output printed", "", out.toString());
assertThat(
"Error contains some warnings",
err.toString(),
AllOf.allOf(
containsString("d = 2"),
containsString("t = 3"),
containsString("doubled value"),
not(containsString("j = 1"))));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,35 @@ public static Value createValue(Context ctx, String src) {
/**
* Evaluates the given source as if it was in an unnamed module.
*
* @param ctx context to evaluate the module at
* @param src The source code of the module
* @return The value returned from the main method of the unnamed module.
*/
public static Value evalModule(Context ctx, String src) {
Value module = ctx.eval(Source.create("enso", src));
Value assocType = module.invokeMember(Module.GET_ASSOCIATED_TYPE);
Value mainMethod = module.invokeMember(Module.GET_METHOD, assocType, "main");
return mainMethod.execute();
public static Value evalModule(Context ctx, CharSequence src) {
return evalModule(ctx, src, null, "main");
}

/**
* Evaluates the given source as if it was in a module with given name.
*
* @param ctx context to evaluate the module at
* @param src The source code of the module
* @param name name of the module defining the source
* @param methodName name of main method to invoke
* @return The value returned from the main method of the unnamed module.
*/
public static Value evalModule(Context ctx, CharSequence src, String name, String methodName) {
Source s;
if (name == null) {
s = Source.create("enso", src);
} else {
var b = Source.newBuilder("enso", src, name);
s = b.buildLiteral();
}
var module = ctx.eval(s);
var assocType = module.invokeMember(Module.GET_ASSOCIATED_TYPE);
var mainMethod = module.invokeMember(Module.GET_METHOD, assocType, methodName);
return mainMethod.execute(assocType);
}

public static org.enso.compiler.core.ir.Module compileModule(Context ctx, String src) {
Expand Down

0 comments on commit f257235

Please sign in to comment.