Skip to content

Commit

Permalink
DataflowError.withoutTrace shall not store a trace (#8608)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaroslavTulach authored Dec 24, 2023
1 parent 7443683 commit 07d58f2
Show file tree
Hide file tree
Showing 23 changed files with 360 additions and 87 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,7 @@
- [Export of non-existing symbols results in error][7960]
- [Upgrade GraalVM to 23.1.0 JDK21][7991]
- [Added opt-in type checks of return type][8502]
- [DataflowError.withoutTrace doesn't store stacktrace][8608]
- [Added text_length to Column][8606]

[3227]: https://github.com/enso-org/enso/pull/3227
Expand Down Expand Up @@ -1162,6 +1163,7 @@
[7960]: https://github.com/enso-org/enso/pull/7960
[7991]: https://github.com/enso-org/enso/pull/7991
[8502]: https://github.com/enso-org/enso/pull/8502
[8608]: https://github.com/enso-org/enso/pull/8608
[8606]: https://github.com/enso-org/enso/pull/8606

# Enso 2.0.0-alpha.18 (2021-10-12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ type Time_Error

Provides a human-readable representation of the time error.
to_display_text : Text
to_display_text self = "Time_Error: " + self.error_message
to_display_text self = "Time_Error: " + self.error_message.to_text
5 changes: 2 additions & 3 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace"
get_stack_trace : Vector Stack_Trace_Element
get_stack_trace =
prim_stack = primitive_get_stack_trace
stack_with_prims = Vector.from_polyglot_array prim_stack
# (First 2) drops the `Runtime.primitive_get_stack_trace` frame and this one
stack = stack_with_prims.drop (First 2)
stack_with_own_frame = Vector.from_polyglot_array prim_stack
stack = stack_with_own_frame.drop (First 1)
stack.map wrap_primitive_stack_trace_element

## PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion distribution/lib/Standard/Table/0.0.0-dev/src/Errors.enso
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ type Invalid_Location
Pretty print the invalid location error.
to_display_text : Text
to_display_text self =
"The location '"+self.location+"' is not valid."
"The location '"+self.location.to_text+"' is not valid."

## Indicates that some values did not match the expected datatype format.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ error_preprocessor x =
message = err.to_display_text
stack_trace = x.get_stack_trace_text.if_nothing "" . split '\n'
truncated_message = Helpers.truncate message
full_message = truncated_message + if stack_trace.length > 1 then " (" + stack_trace.at 1 . trim +")" else ""
full_message = truncated_message + if stack_trace.length > 0 then " (" + stack_trace.at 0 . trim +")" else ""
JS_Object.from_pairs [['kind', 'Dataflow'], ['message', full_message]] . to_json

if result.is_error then result.catch else ok
12 changes: 12 additions & 0 deletions docs/types/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,15 @@ with an automatic propagation mechanism:
> - Ensure that we are okay with initially designing everything around async
> exceptions as broken values are very hard to support without a type checker.
> - Initially not supported for APIs.
Broken values (implemented as `DataflowError` class in the interpreter) are fast
to allocate and pass around the program. They record line of their own
creation - e.g. where `Error.throw` has happened. Shall that not be enough, one
can run with `-ea` flag, like:

```bash
enso$ JAVA_OPTS=-ea ./built-distribution/enso-engine-*/enso-*/bin/enso --run x.enso
```

to get full stack where the _broken value_ has been created. Collecting such
full stack trace however prevents the execution to run at _full speed_.
7 changes: 6 additions & 1 deletion engine/runner/src/main/scala/org/enso/runner/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,12 @@ object Main {
)
val res = main.execute(parsedArgs: _*)
if (!res.isNull) {
out.println(res);
val textRes = if (res.isString) {
res.asString
} else {
res.toString
}
out.println(textRes);
}
case None =>
err.println(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.enso.interpreter.instrument.execution

import com.oracle.truffle.api.{TruffleStackTrace, TruffleStackTraceElement}
import com.oracle.truffle.api.interop.InteropLibrary
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceNode

import java.io.File
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters.RichOptional

/** Methods for handling exceptions in the interpreter. */
Expand All @@ -19,42 +19,50 @@ object ErrorResolver {
def getStackTrace(
throwable: Throwable
)(implicit ctx: RuntimeContext): Vector[Api.StackTraceElement] = {
TruffleStackTrace
.getStackTrace(throwable)
.asScala
.flatMap(toStackElement)
.toVector
val iop = InteropLibrary.getUncached
val arr = GetStackTraceNode.stackTraceToArray(iop, throwable)
val len = iop.getArraySize(arr)
val stackWithOptions = for (i <- 0L until len) yield {
val elem = iop.readArrayElement(arr, i)
toStackElement(iop, elem)
}
val stack = stackWithOptions.map(op => op.get)
stack.toVector
}

/** Convert from the truffle stack element to the runtime API representation.
*
* @param iop interop library to use
* @param element the trufle stack trace element
* @param ctx the runtime context
* @return the runtime API representation of the stack trace element
*/
private def toStackElement(
element: TruffleStackTraceElement
iop: InteropLibrary,
element: Any
)(implicit ctx: RuntimeContext): Option[Api.StackTraceElement] = {
val node = Option(element.getLocation)
node.flatMap(x => {
x.getEncapsulatingSourceSection match {
case null if x.getRootNode == null =>
if (!iop.hasExecutableName(element)) {
None
} else {
val name = iop.asString(iop.getExecutableName(element))
if (!iop.hasSourceLocation(element)) {
Some(Api.StackTraceElement(name, None, None, None))
} else {
val section = iop.getSourceLocation(element)
if (section.getSource.isInternal) {
None
case null if x.getRootNode.isInternal =>
None
case null =>
Some(Api.StackTraceElement(x.getRootNode.getName, None, None, None))
case section =>
} else {
Some(
Api.StackTraceElement(
element.getTarget.getRootNode.getName,
name,
findFileByModuleName(section.getSource.getName),
Some(LocationResolver.sectionToRange(section)),
LocationResolver.getExpressionId(section).map(_.externalId)
)
)
}
}
})
}
}

/** Find source file path by the module name.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
package org.enso.interpreter.node.expression.builtin.error;

import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.EnsoObject;
import org.enso.interpreter.runtime.error.PanicException;

@BuiltinMethod(
type = "Panic",
name = "primitive_get_attached_stack_trace",
description = "Gets the stack trace attached to the throwable.")
public class GetAttachedStackTraceNode extends Node {
EnsoObject execute(@AcceptsError Object error) {
if (error instanceof Throwable) {
return GetStackTraceNode.stackTraceToArray((Throwable) error);
} else {
Builtins builtins = EnsoContext.get(this).getBuiltins();
throw new PanicException(
builtins.error().makeTypeError("Throwable", error, "throwable"), this);
}
@Child private InteropLibrary iop = InteropLibrary.getFactory().createDispatched(3);

Object execute(@AcceptsError Object error) {
return GetStackTraceNode.stackTraceToArray(iop, error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@BuiltinMethod(
type = "Error",
name = "throw",
description = "Returns a new value error with given payload.")
description = "Returns a new value error with given payload.",
inlineable = true)
public class ThrowErrorNode extends Node {
public Object execute(VirtualFrame giveMeAStackFrame, Object payload) {
return DataflowError.withoutTrace(payload, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.enso.interpreter.runtime.data.vector.ArrayLikeAtNode;
import org.enso.interpreter.runtime.data.vector.ArrayLikeLengthNode;
import org.enso.interpreter.runtime.error.DataflowError;
import org.enso.interpreter.runtime.error.PanicException;

@BuiltinMethod(
type = "Array_Like_Helpers",
Expand All @@ -25,7 +26,7 @@ Object execute(Object arrayLike, long index) {
var len = len(arrayLike);
var ctx = EnsoContext.get(this);
var payload = ctx.getBuiltins().error().makeIndexOutOfBounds(index, len);
return DataflowError.withoutTrace(payload, this);
return DataflowError.withTrace(payload, new PanicException(payload, this));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import java.util.List;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.data.EnsoObject;
import org.enso.interpreter.runtime.data.text.Text;
Expand All @@ -23,34 +28,96 @@ public static GetStackTraceNode create() {
return new GetStackTraceNode();
}

@CompilerDirectives.TruffleBoundary
private static EnsoObject wrapStackTraceElements(List<TruffleStackTraceElement> elements) {
var arr = new Object[elements.size()];
for (var i = 0; i < arr.length; i++) {
var e = elements.get(i);
arr[i] = e.getGuestObject();
}
var vector = ArrayLikeHelpers.asVectorWithCheckAt(arr);
try {
return filterStackTraceVector(InteropLibrary.getUncached(), vector);
} catch (UnsupportedMessageException ex) {
assert raise(RuntimeException.class, ex);
return ArrayLikeHelpers.empty();
}
}

private static boolean includeFrame(InteropLibrary iop, Object elem)
throws UnsupportedMessageException {
if (!iop.hasSourceLocation(elem)) {
return false;
}
var ss = iop.getSourceLocation(elem);
if (ss == null) {
return false;
}
var src = ss.getSource();
return src != null && src.hasCharacters() && !src.isInternal();
}

private static EnsoObject filterStackTraceVector(InteropLibrary iop, Object elements)
throws UnsupportedMessageException {
var size = iop.getArraySize(elements);
var count = 0;
for (long i = 0; i < size; i++) {
try {
var element = iop.readArrayElement(elements, i);
if (includeFrame(iop, element)) {
count++;
}
} catch (InvalidArrayIndexException ex) {
assert raise(RuntimeException.class, ex);
}
}
var arr = new Object[count];
var at = 0;
for (long i = 0; i < size; i++) {
try {
var element = iop.readArrayElement(elements, i);
if (includeFrame(iop, element)) {
arr[at++] = element;
}
} catch (InvalidArrayIndexException ex) {
assert raise(RuntimeException.class, ex);
}
}
return ArrayLikeHelpers.wrapObjectsWithCheckAt(arr);
}

EnsoObject execute(VirtualFrame requestOwnStackFrame) {
var exception = new PanicException(Text.create("Stacktrace"), this);
TruffleStackTrace.fillIn(exception);
return stackTraceToArray(exception);
}

public static EnsoObject stackTraceToArray(InteropLibrary iop, Object exception) {
if (iop.hasExceptionStackTrace(exception)) {
try {
var elements = iop.getExceptionStackTrace(exception);
return filterStackTraceVector(iop, elements);
} catch (UnsupportedMessageException ex) {
assert raise(RuntimeException.class, ex);
// return empty
}
} else if (exception instanceof Throwable t) {
return stackTraceToArray(t);
}
return ArrayLikeHelpers.empty();
}

@CompilerDirectives.TruffleBoundary
public static EnsoObject stackTraceToArray(Throwable exception) {
private static EnsoObject stackTraceToArray(Throwable exception) {
var elements = TruffleStackTrace.getStackTrace(exception);
if (elements == null) {
return ArrayLikeHelpers.empty();
}
int count = 0;
for (int i = 0; i < elements.size(); i++) {
var element = elements.get(i);
if (element.getTarget().getRootNode().isInternal()) {
continue;
}
count++;
}
var arr = new Object[count];
for (int i = 0, at = 0; i < elements.size(); i++) {
var element = elements.get(i);
if (element.getTarget().getRootNode().isInternal()) {
continue;
}
arr[at++] = element.getGuestObject();
}
return ArrayLikeHelpers.wrapObjectsWithCheckAt(arr);
return wrapStackTraceElements(elements);
}

@SuppressWarnings("unchecked")
private static <E extends Exception> boolean raise(Class<E> type, Throwable t) throws E {
throw (E) t;
}
}
Loading

0 comments on commit 07d58f2

Please sign in to comment.