Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow arbitrary expression evaluation in chromeinspector #3941

Merged
merged 28 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
38eb3b5
Wrap all host object in HostWrapper
Akirathan Nov 28, 2022
bc5fa41
Implement setting variable values in debugger
Akirathan Nov 29, 2022
e66eab2
Implement Language.parse(InlineParsingRequest)
Akirathan Dec 1, 2022
e246830
Add --inspect-path option
Akirathan Dec 1, 2022
90dbe2a
Inline parsing fails fast for undesirable expressions
Akirathan Dec 1, 2022
85aeea7
Migrate Compiler.runInline to the new parser
Akirathan Dec 2, 2022
c3879b6
Add redirectOutput and isStrictErrors to CompilerConfig
Akirathan Dec 2, 2022
a977486
Propagate errors from compiler and parser to DebugException
Akirathan Dec 2, 2022
99517a6
Cosmetics
Akirathan Dec 2, 2022
2385f88
Merge branch 'develop' into wip/akirathan/expr-eval-183855919
Akirathan Dec 2, 2022
b12b429
Update CHANGELOG
Akirathan Dec 2, 2022
38120c7
Add stepping tests
Akirathan Dec 5, 2022
d254637
Wrap all HostObjects transitively
Akirathan Dec 5, 2022
c17478d
Bindings in DebugLocalScope can be empty
Akirathan Dec 6, 2022
253d78b
Cosmetics
Akirathan Dec 6, 2022
b600bb9
Fix NPE in inline parsing (Line::geExpression can return null)
Akirathan Dec 6, 2022
33d94c4
Wrap all the host objects in AST fragment returned by inline parsing.
Akirathan Dec 6, 2022
2d99076
Implement host object wrapping as method in ExpressionNode annotated …
Akirathan Dec 6, 2022
7cd2bfb
Refactor HostWrapper to public class in org.enso.interpreter.instrume…
Akirathan Dec 6, 2022
166da64
Revert "Add --inspect-path option"
Akirathan Dec 6, 2022
a04dcd6
Merge branch 'develop' into wip/akirathan/expr-eval-183855919
Akirathan Dec 6, 2022
dc6f8e8
Add some docs
Akirathan Dec 6, 2022
67de267
Add TruffleBoundary at appropriate places.
Akirathan Dec 6, 2022
2badef7
Merge branch 'develop' into wip/akirathan/expr-eval-183855919
Akirathan Dec 7, 2022
936e78f
DebuggingEnsoTest: Split newlines with \n even on Windows
Akirathan Dec 7, 2022
dc810a7
Improve exception message from inline parsing
Akirathan Dec 7, 2022
99f47a2
Merge branch 'develop' into wip/akirathan/expr-eval-183855919
mergify[bot] Dec 7, 2022
a0c5ec6
Merge branch 'develop' into wip/akirathan/expr-eval-183855919
mergify[bot] Dec 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@
- [Support VCS for projects in Language Server][3851]
- [Support multiple exports of the same module][3897]
- [Import modules' extension methods only with unqualified imports][3906]
- [Support expression evaluation in chromeinspector console][3941]
- [Don't export polyglot symbols][3915]
- [From/all import must not include module in name resolution][3931]
- [Vector returns warnings of individual elements][3938]
Expand Down Expand Up @@ -537,6 +538,7 @@
[3851]: https://github.com/enso-org/enso/pull/3851
[3897]: https://github.com/enso-org/enso/pull/3897
[3906]: https://github.com/enso-org/enso/pull/3906
[3941]: https://github.com/enso-org/enso/pull/3941
[3915]: https://github.com/enso-org/enso/pull/3915
[3931]: https://github.com/enso-org/enso/pull/3931
[3938]: https://github.com/enso-org/enso/pull/3938
Expand Down
16 changes: 14 additions & 2 deletions docs/debugger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,20 @@ copy the printed URL into chrome browser and you should see:

![Chrome Debugger](chrome-debugger.png)

Step in, step over, set breakpoints, watch values of your variables as execution
of your Enso program progresses.
Step in, step over, set breakpoints, watch values of the variables as well as
evaluate arbitrary expressions in the console. Note that as of December 2022,
with GraalVM 22.3.0, there is a well-known
[bug in Truffle](https://github.com/oracle/graal/issues/5513) that causes
`NullPointerException` when a host object gets into the chrome inspector. There
is a workaround for that, but it may not work in certain situations. Therefore,
if you encounter `NullPointerException` thrown from

```
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotContextImpl.getContext(PolyglotContextImpl.java:685)
```

simply ignore it. It will be handled within the debugger and should not affect
the rest of the environment.

# Debugging Enso and Java Code at Once

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ Tree parse(Source src) {
public IR.Module generateIR(Tree t) {
return TreeToIr.MODULE.translate(t);
}

public scala.Option<IR.Expression> generateIRInline(Tree t) {
return TreeToIr.MODULE.translateInline(t);
}
}
71 changes: 71 additions & 0 deletions engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.enso.compiler;

import java.util.ArrayList;
import java.util.UUID;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.IR$Application$Literal$Sequence;
Expand Down Expand Up @@ -89,6 +90,76 @@ IR.Module translate(Tree ast) {
return translateModule(ast);
}

/**
* Translates an inline program expression represented in the parser {@link Tree}
* to the compiler's {@link IR} representation.
*
* Inline expressions must <b>only</b> be expressions, and may not contain any
* type of definition.
*
* @param ast The tree representing the expression to translate.
* @return The {@link IR} representation of the given ast if it is valid, otherwise
* {@link Option#empty()}.
*/
Option<IR.Expression> translateInline(Tree ast) {
Akirathan marked this conversation as resolved.
Show resolved Hide resolved
return switch(ast) {
case Tree.BodyBlock b -> {
List<IR.Expression> expressions = nil();
java.util.List<IR.IdentifiedLocation> locations = new ArrayList<>();
for (Line statement : b.getStatements()) {
Tree exprTree = statement.getExpression();
IR.Expression expr = switch (exprTree) {
case Tree.Export x -> null;
case Tree.Import x -> null;
case Tree.Invalid x -> null;
case null -> null;
default -> translateExpression(exprTree);
};
if (expr != null) {
expressions = cons(expr, expressions);
if (expr.location().isDefined()) {
locations.add(expr.location().get());
}
}
}
if (expressions.size() == 0) {
yield Option.empty();
} else if (expressions.size() == 1) {
yield Option.apply(expressions.apply(0));
} else {
Option<IdentifiedLocation> combinedLocation;
if (locations.isEmpty()) {
combinedLocation = Option.empty();
} else {
combinedLocation = Option.apply(
new IdentifiedLocation(
new Location(
locations.get(1).start(),
locations.get(locations.size() - 1).end()
),
Option.empty()
)
);
}

yield Option.apply(
new IR$Expression$Block(
expressions,
expressions.last(),
combinedLocation,
false,
null,
null
)
);
}
}
default -> {
throw new IllegalStateException();
}
};
}

/** Translate a top-level Enso module into [[IR]].
*
* @param module the [[AST]] representation of the module to translate
Expand Down
138 changes: 134 additions & 4 deletions engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
package org.enso.interpreter;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.debug.DebuggerTags;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.enso.compiler.Compiler;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.data.CompilerConfig;
import org.enso.compiler.exception.CompilationAbortedException;
import org.enso.compiler.exception.UnhandledEntity;
import org.enso.distribution.DistributionManager;
import org.enso.distribution.Environment;
import org.enso.distribution.locking.LockManager;
Expand All @@ -17,9 +30,12 @@
import org.enso.interpreter.instrument.IdExecutionService;
import org.enso.interpreter.instrument.NotificationHandler.Forwarder;
import org.enso.interpreter.instrument.NotificationHandler.TextMode$;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.state.IOPermissions;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.runtime.tag.Patchable;
import org.enso.interpreter.service.ExecutionService;
Expand All @@ -28,11 +44,10 @@
import org.enso.logger.masking.MaskingFactory;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.RuntimeOptions;
import org.enso.syntax2.Line;
import org.enso.syntax2.Tree;
import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionDescriptors;

import java.util.Optional;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.graalvm.options.OptionKey;
import org.graalvm.options.OptionType;

Expand Down Expand Up @@ -174,6 +189,121 @@ protected CallTarget parse(ParsingRequest request) {
return root.getCallTarget();
}

/**
* Parses the given Enso source code snippet in {@code request}.
*
* Inline parsing does not handle the following expressions:
* <ul>
* <li>Assignments</li>
* <li>Imports and exports</li>
* </ul>
* When given the aforementioned expressions in the request, {@code null}
* will be returned.
*
* @param request request for inline parsing
* @throws Exception if the compiler failed to parse
* @return An {@link ExecutableNode} representing an AST fragment if the request contains
* syntactically correct Enso source, {@code null} otherwise.
*/
@Override
protected ExecutableNode parse(InlineParsingRequest request) throws Exception {
if (request.getLocation().getRootNode() instanceof EnsoRootNode ensoRootNode) {
var context = EnsoContext.get(request.getLocation());
Tree inlineExpr = context.getCompiler().parseInline(request.getSource());
var undesirableExprTypes = List.of(
Tree.Assignment.class,
Tree.Import.class,
Tree.Export.class
);
if (astContainsExprTypes(inlineExpr, undesirableExprTypes)) {
throw new InlineParsingException(
"Inline parsing request contains some of undesirable expression types: "
+ undesirableExprTypes
+ "\n"
+ "Parsed expression: \n"
+ inlineExpr.codeRepr(),
null
);
}

var module = ensoRootNode.getModuleScope().getModule();
var localScope = ensoRootNode.getLocalScope();
var outputRedirect = new ByteArrayOutputStream();
var redirectConfigWithStrictErrors = new CompilerConfig(
false,
false,
true,
scala.Option.apply(new PrintStream(outputRedirect))
);
var inlineContext = new InlineContext(
module,
scala.Some.apply(localScope),
scala.Some.apply(false),
scala.Option.empty(),
scala.Option.empty(),
redirectConfigWithStrictErrors
);
Compiler silentCompiler = context.getCompiler().duplicateWithConfig(redirectConfigWithStrictErrors);
scala.Option<ExpressionNode> exprNode;
try {
exprNode = silentCompiler
.runInline(
request.getSource().getCharacters().toString(),
inlineContext
);
} catch (UnhandledEntity e) {
throw new InlineParsingException("Unhandled entity: " + e.entity(), e);
} catch (CompilationAbortedException e) {
assert outputRedirect.toString().lines().count() > 1 : "Expected a header line from the compiler";
String compilerErrOutput = outputRedirect.toString()
.lines()
.skip(1)
.collect(Collectors.joining(";"));
throw new InlineParsingException(compilerErrOutput, e);
} finally {
silentCompiler.shutdown(false);
}

if (exprNode.isDefined()) {
var language = EnsoLanguage.get(exprNode.get());
return new ExecutableNode(language) {
@Override
public Object execute(VirtualFrame frame) {
return exprNode.get().executeGeneric(frame);
}
};
}
}
return null;
}

private static final class InlineParsingException extends Exception {
InlineParsingException(String message, Throwable cause) {
super(message, cause);
}
}

/**
* Returns true if the given ast transitively contains any of {@code exprTypes}.
*/
private boolean astContainsExprTypes(Tree ast, List<Class<? extends Tree>> exprTypes) {
boolean astMatchesExprType = exprTypes
.stream()
.anyMatch(exprType -> exprType.equals(ast.getClass()));
if (astMatchesExprType) {
return true;
} else if (ast instanceof Tree.BodyBlock block) {
return block
.getStatements()
.stream()
.map(Line::getExpression)
.filter(Objects::nonNull)
.anyMatch((Tree expr) -> astContainsExprTypes(expr, exprTypes));
} else {
return false;
}
}

@Option(
name = "IOEnvironment",
category = OptionCategory.USER,
Expand Down
Loading