Properly expose stacktraces and related data to user code (#3271)

This commit is contained in:
Marcin Kostrzewa 2022-02-16 08:36:19 +01:00 committed by GitHub
parent 19b2064070
commit 67b4e59506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 482 additions and 49 deletions

View File

@ -38,6 +38,8 @@
- [Implemented `Range.find`, `Table.rename_columns` and
`Table.use_first_row_as_names` operations][3249]
- [Implemented `Text.at` and `Text.is_digit` methods][3269]
- [Implemented `Runtime.get_stack_trace` together with some utilities to process
stack traces and code locations][3271]
- [Implemented `Vector.flatten`][3259]
[debug-shortcuts]:
@ -59,6 +61,7 @@
[3249]: https://github.com/enso-org/enso/pull/3249
[3264]: https://github.com/enso-org/enso/pull/3264
[3269]: https://github.com/enso-org/enso/pull/3269
[3271]: https://github.com/enso-org/enso/pull/3271
[3259]: https://github.com/enso-org/enso/pull/3259
#### Enso Compiler

View File

@ -1,55 +1,57 @@
import Standard.Base.Data.Any.Extensions
import Standard.Base.Data.Array.Extensions
import Standard.Base.Data.Interval
import Standard.Base.Data.Json
import Standard.Base.Data.List
import Standard.Base.Data.Locale
import Standard.Base.Data.Map
import Standard.Base.Data.Maybe
import Standard.Base.Data.Noise
import Standard.Base.Data.Number.Extensions
import Standard.Base.Data.Ordering
import Standard.Base.Data.Ordering.Sort_Order
import Standard.Base.Data.Pair
import Standard.Base.Data.Range
import Standard.Base.Data.Text.Extensions
import Standard.Base.Data.Vector
import Standard.Base.Error.Common
import Standard.Base.Error.Extensions
import Standard.Base.Math
import Standard.Base.Meta
import Standard.Base.Meta.Enso_Project
import Standard.Base.Polyglot.Java
import Standard.Base.System.File
import Standard.Base.Data.Text.Regex.Mode as Regex_Mode
import project.Data.Any.Extensions
import project.Data.Array.Extensions
import project.Data.Interval
import project.Data.Json
import project.Data.List
import project.Data.Locale
import project.Data.Map
import project.Data.Maybe
import project.Data.Noise
import project.Data.Number.Extensions
import project.Data.Ordering
import project.Data.Ordering.Sort_Order
import project.Data.Pair
import project.Data.Range
import project.Data.Text.Extensions
import project.Data.Vector
import project.Error.Common
import project.Error.Extensions
import project.Math
import project.Meta
import project.Meta.Enso_Project
import project.Polyglot.Java
import project.Runtime.Extensions
import project.System.File
import project.Data.Text.Regex.Mode as Regex_Mode
from Standard.Builtins import Nothing, Number, Integer, Any, True, False, Cons, Boolean, Arithmetic_Error
export Standard.Base.Data.Interval
export Standard.Base.Data.Json
export Standard.Base.Data.Locale
export Standard.Base.Data.Map
export Standard.Base.Data.Maybe
export Standard.Base.Data.Ordering
export Standard.Base.Data.Ordering.Sort_Order
export Standard.Base.Data.Vector
export Standard.Base.Math
export Standard.Base.Meta
export Standard.Base.System.File
export Standard.Base.Data.Text.Regex.Mode as Regex_Mode
export project.Data.Interval
export project.Data.Json
export project.Data.Locale
export project.Data.Map
export project.Data.Maybe
export project.Data.Ordering
export project.Data.Ordering.Sort_Order
export project.Data.Vector
export project.Math
export project.Meta
export project.System.File
export project.Data.Text.Regex.Mode as Regex_Mode
from Standard.Base.Data.Any.Extensions export all
from Standard.Base.Data.Array.Extensions export all
from Standard.Base.Data.List export Nil, Cons
from Standard.Base.Data.Number.Extensions export all hiding Math, String, Double
from Standard.Base.Data.Noise export all hiding Noise
from Standard.Base.Data.Pair export Pair
from Standard.Base.Data.Range export Range
from Standard.Base.Data.Text.Extensions export Text, Split_Kind, Line_Ending_Style
from Standard.Base.Error.Common export all
from Standard.Base.Error.Extensions export all
from Standard.Base.Meta.Enso_Project export all
from Standard.Base.Polyglot.Java export all
from project.Data.Any.Extensions export all
from project.Data.Array.Extensions export all
from project.Data.List export Nil, Cons
from project.Data.Number.Extensions export all hiding Math, String, Double
from project.Data.Noise export all hiding Noise
from project.Data.Pair export Pair
from project.Data.Range export Range
from project.Data.Text.Extensions export Text, Split_Kind, Line_Ending_Style
from project.Error.Common export all
from project.Error.Extensions export all
from project.Meta.Enso_Project export all
from project.Polyglot.Java export all
from project.Runtime.Extensions export all
from Standard.Builtins export all hiding Meta, Less, Equal, Greater, Ordering

View File

@ -9,7 +9,7 @@ import Standard.Builtins
Enso_Project.root
Builtins.Project_Description.root : File.File
Builtins.Project_Description.root = File.File this.prim_root_file
Builtins.Project_Description.root = File.new this.prim_root_file.getPath
## Returns the root data directory of the project.

View File

@ -0,0 +1,93 @@
from Standard.Base import all
## ADVANCED
UNSTABLE
Represents a source location in Enso code. Contains information about the
source file and code position within it.
type Source_Location
## PRIVATE
type Source_Location prim_location
## UNSTABLE
Pretty prints the location.
to_text : Text
to_text =
'(Source_Location ' + this.formatted_coordinates + ')'
## UNSTABLE
Returns the 1-based line index of the start of this code range.
start_line : Integer
start_line = this.prim_location.getStartLine
## UNSTABLE
Returns the 1-based line index of the end of this code range.
end_line : Integer
end_line = this.prim_location.getEndLine
## UNSTABLE
Returns the 1-based column index of the start of this code range.
start_column : Integer
start_column = this.prim_location.getStartColumn
## UNSTABLE
Returns the 1-based column index of the end of this code range.
end_column : Integer
end_column = this.prim_location.getEndColumn
## UNSTABLE
Returns a pretty-printed location (file and line info).
formatted_coordinates : Text
formatted_coordinates =
start_line = this.start_line
end_line = this.end_line
indices = case start_line == end_line of
True ->
row = start_line.to_text
start = this.start_column.to_text
end = this.end_column.to_text
row + ":" + start + "-" + end
False ->
start_line + '-' + end_line
cwd = File.current_directory
file = this.file.absolute
formatted_file = case file.is_child_of cwd of
True -> cwd.relativize file . path
_ -> file.path
formatted_file + ":" + indices
## UNSTABLE
Return the source file corresponding to this location.
file : File.File
file = File.new this.prim_location.getSource.getPath
## ADVANCED
UNSTABLE
Represents a single stack frame in an Enso stack trace.
type Stack_Trace_Element
## PRIVATE
type Stack_Trace_Element name source_location
## ADVANCED
UNSTABLE
Returns the execution stack trace of its call site. The ordering of the
resulting vector is such that the top stack frame is the first element.
Runtime.get_stack_trace : Vector.Vector Stack_Trace_Element
Runtime.get_stack_trace =
prim_stack = this.primitive_get_stack_trace
stack_with_prims = Vector.Vector prim_stack
stack = stack_with_prims.map el->
loc = case Polyglot.has_source_location el of
True -> Source_Location (Polyglot.get_source_location el)
False -> Nothing
name = Polyglot.get_executable_name el
Stack_Trace_Element name loc
# drop this frame and the one from `Runtime.primitive_get_stack_trace`
stack.drop_start 2

View File

@ -598,6 +598,16 @@ type File
matcher.matches (Path.of pathStr)
filtered
## UNSTABLE
Checks if `this` is a child path of `other`.
is_child_of other = this.prim_file.startsWith other.prim_file
## UNSTABLE
Transforms `child` to a relative path with respect to `this`.
relativize child = File (this.prim_file.relativize child.prim_file)
## An output stream, allowing for interactive writing of contents into an
open file.
type Output_Stream

View File

@ -14,6 +14,7 @@ code from modules.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Qualified Names](#qualified-names)
- [Import Syntax](#import-syntax)
- [Qualified Imports](#qualified-imports)
- [Unqualified Imports](#unqualified-imports)
@ -24,6 +25,21 @@ code from modules.
<!-- /MarkdownTOC -->
## Qualified Names
Both imports and exports require the use of qualified module names. A qualified
name consists of the library namespace (usually organization under which its
published) and the library name, followed by module names mirroring the source
tree of the library. For example the file `src/Stuff/Things/Util.enso` inside
the library `My_Lib` published by the user `wdanilo` would have the following
qualified name: `wdanilo.My_Lib.Stuff.Things.Util`. To facilitate library
renaming (or deciding on the publishing organization later in the development
cycle, or working on a project that won't be published) it is possible to use
the keyword `project` instead of namespace and project name, to import a file in
the same project. Therefore, the file `src/Varia/Tools/Manager.enso` in `My_Lib`
published (or not) by `wdanilo` may use `project.Stuff.Things.Util` to refer to
the previously mentioned file.
## Import Syntax
There are two main ways of importing a module into the current scope.

View File

@ -0,0 +1,36 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Polyglot",
name = "get_executable_name",
description = "Returns the executable name of a polyglot object.")
public class GetExecutableNameNode extends Node {
private @Child InteropLibrary functionsLibrary =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private @Child InteropLibrary stringsLibrary =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
Text execute(Object _this, Object function) {
try {
return Text.create(stringsLibrary.asString(functionsLibrary.getExecutableName(function)));
} catch (UnsupportedMessageException e) {
err.enter();
Builtins builtins = Context.get(this).getBuiltins();
throw new PanicException(
builtins.error().makeTypeError(builtins.function(), function, "function"), this);
}
}
}

View File

@ -0,0 +1,36 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.EnsoSourceSection;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Polyglot",
name = "get_source_location",
description = "Returns the source location of a polyglot object.")
public class GetSourceLocationNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
Object execute(Object _this, Object value) {
try {
return Context.get(this)
.getEnvironment()
.asGuestValue(new EnsoSourceSection(library.getSourceLocation(value)));
} catch (UnsupportedMessageException e) {
err.enter();
Builtins builtins = Context.get(this).getBuiltins();
throw new PanicException(
builtins.error().makeTypeError(builtins.function(), value, "function"), this);
}
}
}

View File

@ -0,0 +1,21 @@
package org.enso.interpreter.node.expression.builtin.interop.generic;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.dsl.BuiltinMethod;
@BuiltinMethod(
type = "Polyglot",
name = "has_source_location",
description = "Checks if an object has a source location.")
public class HasSourceLocationNode extends Node {
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
boolean execute(Object _this, Object value) {
return library.hasSourceLocation(value);
}
}

View File

@ -0,0 +1,25 @@
package org.enso.interpreter.node.expression.builtin.runtime;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Runtime",
name = "primitive_get_stack_trace",
description = "Gets the current execution stacktrace.")
public class GetStackTraceNode extends Node {
Array execute(Object _this) {
var exception = new PanicException(null, this);
TruffleStackTrace.fillIn(exception);
var elements = TruffleStackTrace.getStackTrace(exception);
var ret = new Array(elements.size());
for (int i = 0; i < elements.size(); i++) {
var element = elements.get(i);
ret.getItems()[i] = element.getGuestObject();
}
return ret;
}
}

View File

@ -5,6 +5,7 @@ import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.nodes.Node;
import org.enso.compiler.Compiler;
import org.enso.compiler.PackageRepository;
import org.enso.compiler.data.CompilerConfig;
@ -37,6 +38,9 @@ import java.util.UUID;
*/
public class Context {
private static final TruffleLanguage.ContextReference<Context> REFERENCE =
TruffleLanguage.ContextReference.create(Language.class);
private final Language language;
private final Env environment;
private @CompilationFinal Compiler compiler;
@ -138,6 +142,15 @@ public class Context {
pkg -> packageRepository.registerMainProjectPackage(pkg.libraryName(), pkg));
}
/**
* @param node the location of context access. Pass {@code null} if not in a node.
* @return the proper context instance for the current {@link
* com.oracle.truffle.api.TruffleContext}.
*/
public static Context get(Node node) {
return REFERENCE.get(node);
}
/** Performs eventual cleanup before the context is disposed of. */
public void shutdown() {
threadManager.shutdown();

View File

@ -24,6 +24,7 @@ import org.enso.interpreter.node.expression.builtin.io.PrintErrMethodGen;
import org.enso.interpreter.node.expression.builtin.io.PrintlnMethodGen;
import org.enso.interpreter.node.expression.builtin.io.ReadlnMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.GCMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.NoInlineMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.NoInlineWithArgMethodGen;
import org.enso.interpreter.node.expression.builtin.state.GetStateMethodGen;
@ -157,6 +158,7 @@ public class Builtins {
scope.registerMethod(
runtime, "no_inline_with_arg", NoInlineWithArgMethodGen.makeFunction(language));
scope.registerMethod(runtime, "gc", GCMethodGen.makeFunction(language));
scope.registerMethod(runtime, "primitive_get_stack_trace", GetStackTraceMethodGen.makeFunction(language));
scope.registerMethod(panic, "throw", ThrowPanicMethodGen.makeFunction(language));
scope.registerMethod(panic, "recover", RecoverPanicMethodGen.makeFunction(language));

View File

@ -31,6 +31,12 @@ public class Polyglot {
scope.registerMethod(polyglot, "get_array_size", GetArraySizeMethodGen.makeFunction(language));
scope.registerMethod(
polyglot, "is_language_installed", IsLanguageInstalledMethodGen.makeFunction(language));
scope.registerMethod(
polyglot, "get_executable_name", GetExecutableNameMethodGen.makeFunction(language));
scope.registerMethod(
polyglot, "get_source_location", GetSourceLocationMethodGen.makeFunction(language));
scope.registerMethod(
polyglot, "has_source_location", HasSourceLocationMethodGen.makeFunction(language));
}
/** @return the atom constructor for polyglot */

View File

@ -89,6 +89,10 @@ public class EnsoFile {
truffleFile.delete();
}
public boolean startsWith(EnsoFile parent) {
return truffleFile.startsWith(parent.truffleFile);
}
@Override
public String toString() {
return "(File " + truffleFile.getPath() + ")";

View File

@ -0,0 +1,45 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/** Wrapper for exposing sources to Enso. Delegates to original methods with no behavior changes. */
public class EnsoSource {
private final Source source;
public EnsoSource(Source source) {
this.source = source;
}
public String getLanguage() {
return source.getLanguage();
}
public String getName() {
return source.getName();
}
public String getPath() {
return source.getPath();
}
public boolean isInternal() {
return source.isInternal();
}
public CharSequence getCharacters() {
return source.getCharacters();
}
public int getLength() {
return source.getLength();
}
public CharSequence getCharacters(int lineNumber) {
return source.getCharacters(lineNumber);
}
public int getLineCount() {
return source.getLineCount();
}
}

View File

@ -0,0 +1,67 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.source.SourceSection;
/**
* Wrapper for exposing source sections in Enso. Delegates to the original methods with no behaviour
* changes.
*/
public class EnsoSourceSection {
private final SourceSection sourceSection;
public EnsoSourceSection(SourceSection sourceSection) {
this.sourceSection = sourceSection;
}
public int getStartLine() {
return sourceSection.getStartLine();
}
public int getEndLine() {
return sourceSection.getEndLine();
}
public int getEndColumn() {
return sourceSection.getEndColumn();
}
public int getCharIndex() {
return sourceSection.getCharIndex();
}
public int getCharLength() {
return sourceSection.getCharLength();
}
public int getCharEndIndex() {
return sourceSection.getCharEndIndex();
}
public CharSequence getCharacters() {
return sourceSection.getCharacters();
}
public int getStartColumn() {
return sourceSection.getStartColumn();
}
public boolean isAvailable() {
return sourceSection.isAvailable();
}
public boolean hasLines() {
return sourceSection.hasLines();
}
public boolean hasColumns() {
return sourceSection.hasColumns();
}
public boolean hasCharIndex() {
return sourceSection.hasCharIndex();
}
public EnsoSource getSource() {
return new EnsoSource(sourceSection.getSource());
}
}

View File

@ -493,6 +493,28 @@ type Polyglot
invoke : Any -> Text -> Vector -> Any
invoke target name arguments = @Builtin_Method "Polyglot.invoke"
## ADVANCED
UNSTABLE
Checks if `value` defines a source location.
Source locations are typically exposed by functions, classes, sometimes
also other objects to specify their allocation sites.
has_source_location : Any -> Boolean
has_source_location value = @Builtin_Method "Polyglot.has_source_location"
## ADVANCED
UNSTABLE
Gets the source location of `value`.
Source locations are typically exposed by functions, classes, sometimes
also other objects to specify their allocation sites.
This method will throw a polyglot exception if
`Polyglot.has_source_location value` returns `False`.
get_source_location : Any -> Source_Location
get_source_location value = @Builtin_Method "Polyglot.get_source_location"
## Utilities for working with Java polyglot objects.
type Java
@ -1794,6 +1816,15 @@ type Runtime
no_inline_with_arg : Any -> Any
no_inline_with_arg function arg = @Builtin_Method "Runtime.no_inline_with_arg"
## PRIVATE
Returns a raw representation of the current execution stack trace.
You probably want `Runtime.get_stack_trace` instead.
primitive_get_stack_trace : Array
primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace"
## The runtime's integrated monadic state management.
type State

View File

@ -41,6 +41,8 @@ import project.Network.Http.Request_Spec as Http_Request_Spec
import project.Network.Http_Spec
import project.Network.Uri_Spec
import project.Runtime.Stack_Traces_Spec
import project.System.File_Spec
import project.System.Process_Spec
@ -81,6 +83,7 @@ main = Test.Suite.run_main <|
Regex_Spec.spec
Runtime_Spec.spec
Span_Spec.spec
Stack_Traces_Spec.spec
Text_Spec.spec
Time_Spec.spec
Uri_Spec.spec

View File

@ -0,0 +1,20 @@
from Standard.Base import all
import Standard.Test
type My_Type
bar = Runtime.get_stack_trace
baz = here.bar
Number.foo = here.baz
foo x = x.foo
My_Type.foo = here.foo 123
spec = Test.group "Stack traces" <|
Test.specify "should capture traces correctly" <|
modname = Meta.Constructor (Meta.meta here . constructor) . name
stack = My_Type.foo
names = [modname + ".bar", modname + ".baz", "Number.foo", modname + ".foo", "My_Type.foo"]
stack.take_start 5 . map .name . should_equal names
file = Enso_Project.root / 'src' / 'Runtime' / 'Stack_Traces_Spec.enso'
stack.take_start 5 . map (.source_location >> .file) . each (_.should_equal file)