Skip to content

Commit 0142791

Browse files
committed
[GR-20975] Print stack traces with C/C++ functions in LLVM mode.
PullRequest: fastr/2343
2 parents 50c28ac + 1bda921 commit 0142791

File tree

16 files changed

+526
-199
lines changed

16 files changed

+526
-199
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ New features:
66
* SVG support will be added in the future
77
* Display lists are fully implemented, which, e.g., makes Shiny work better in FastR
88
* Java API for implementing custom graphics devices based on `JavaGD`
9+
* print stacktraces (aka traceback()) for errors coming from c-code when FastR running in llvm mode
910

1011
Bug fixes:
1112

com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/llvm/TruffleLLVM_DLL.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ static LibHandle dlOpen(RContext context, String path) {
7878
if (!isInitialization) {
7979
before = stateRFFI.beforeDowncall(null, Type.LLVM);
8080
}
81-
Source src = Source.newBuilder("llvm", file).internal(true).build();
81+
Source src = Source.newBuilder("llvm", file).internal(isLibR).build();
8282
Object lib = context.getEnv().parseInternal(src).call();
8383
assert lib instanceof TruffleObject;
8484
if (isLibR) {

com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/REPL.java

Lines changed: 23 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@
2828
import java.io.FileOutputStream;
2929
import java.io.FileWriter;
3030
import java.io.IOException;
31-
import java.util.ArrayList;
32-
import java.util.Iterator;
33-
import java.util.List;
34-
import java.util.ListIterator;
3531
import java.util.concurrent.Callable;
3632
import java.util.concurrent.ExecutionException;
3733
import java.util.concurrent.ExecutorService;
@@ -152,8 +148,6 @@ public static int readEvalPrint(Context context, ConsoleHandler consoleHandler,
152148
// we continue the repl even though the system may be broken
153149
lastStatus = 1;
154150
} else if (e.isHostException() || e.isGuestException()) {
155-
// the 'Error in 'caller' : part of the message was already printed
156-
// in ErrorHandling.handleInteropException
157151
handleError(executor, context, e);
158152
// drop through to continue REPL and remember last eval was an error
159153
lastStatus = 1;
@@ -304,71 +298,31 @@ public void run() {
304298
}
305299
}
306300

307-
public static void handleError(ExecutorService executor, Context context, PolyglotException e) {
308-
String errorText = getErrorText(executor, e);
309-
if (!errorText.isEmpty()) {
310-
run(executor, () -> context.eval(PRINT_ERROR).execute(errorText));
311-
}
312-
}
313-
314-
private static String getErrorText(ExecutorService executor, PolyglotException eIn) {
301+
public static void handleError(ExecutorService executor, Context context, PolyglotException eIn) {
315302
PolyglotException e = eIn;
316303
if (eIn.getCause() instanceof PolyglotException) {
317304
e = (PolyglotException) eIn.getCause();
318305
}
319-
List<PolyglotException.StackFrame> stackTrace = new ArrayList<>();
320-
for (PolyglotException.StackFrame s : e.getPolyglotStackTrace()) {
321-
stackTrace.add(s);
322-
}
323306

324-
// remove trailing host frames
325-
for (ListIterator<PolyglotException.StackFrame> iterator = stackTrace.listIterator(stackTrace.size()); iterator.hasPrevious();) {
326-
PolyglotException.StackFrame s = iterator.previous();
327-
if (s.isHostFrame()) {
328-
iterator.remove();
329-
} else {
330-
break;
331-
}
332-
}
333-
334-
// remove trailing <R> frames
335-
for (ListIterator<PolyglotException.StackFrame> iterator = stackTrace.listIterator(stackTrace.size()); iterator.hasPrevious();) {
336-
PolyglotException.StackFrame s = iterator.previous();
337-
if (s.getLanguage().getId().equals("R")) {
338-
iterator.remove();
339-
} else {
340-
break;
341-
}
342-
}
343-
344-
StringBuilder sb = new StringBuilder();
345-
if (!stackTrace.isEmpty()) {
346-
// we just removed all trailing <R> frames and there stil is something left =>
347-
// this has to be a non R exception
348-
sb.append("Error in polyglot evaluation : ");
349-
}
307+
// we just removed all trailing <R> frames and there stil is something left =>
308+
// let's see if it is a R exception or polyglot
350309

351310
if (!wasPrinted(executor, e)) {
311+
boolean isFastR = isFastRRError(executor, e);
312+
StringBuilder sb = new StringBuilder();
313+
if (!isFastR) {
314+
sb.append("Error in polyglot evaluation : ");
315+
}
352316
if (e.isHostException()) {
353317
sb.append(e.asHostException().toString());
354318
} else if (e.getMessage() != null) {
355319
sb.append(e.getMessage());
356320
}
357-
}
358-
359-
if (!stackTrace.isEmpty()) {
360-
sb.append('\n');
361-
Iterator<PolyglotException.StackFrame> it = stackTrace.iterator();
362-
while (it.hasNext()) {
363-
PolyglotException.StackFrame s = it.next();
364-
sb.append("\tat ");
365-
sb.append(s);
366-
if (it.hasNext()) {
367-
sb.append('\n');
368-
}
321+
if (sb.length() > 0) {
322+
run(executor, () -> context.eval(PRINT_ERROR).execute(sb.toString()));
369323
}
370324
}
371-
return sb.toString();
325+
372326
}
373327

374328
private static boolean wasPrinted(ExecutorService executor, PolyglotException e) {
@@ -387,6 +341,18 @@ private static boolean wasPrinted(ExecutorService executor, PolyglotException e)
387341
});
388342
}
389343

344+
private static boolean isFastRRError(ExecutorService executor, PolyglotException e) {
345+
return run(executor, () -> {
346+
Value guestObject = e.getGuestObject();
347+
// TODO ensure we are accessing only the FastR RError object and
348+
// not some another guest object with a wasPrinted member
349+
if (guestObject != null && guestObject.hasMember("wasPrinted")) {
350+
return true;
351+
}
352+
return false;
353+
});
354+
}
355+
390356
/**
391357
* See <code>FastRInitEventLoop</code> for the description of how events occurring in the native
392358
* code are dispatched.

com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Traceback.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,10 +27,15 @@
2727
import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
2828

2929
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
30+
import com.oracle.truffle.api.TruffleLanguage;
31+
import com.oracle.truffle.api.dsl.CachedContext;
3032
import com.oracle.truffle.api.dsl.Specialization;
3133
import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
34+
import com.oracle.truffle.r.runtime.RErrorHandling;
3235
import com.oracle.truffle.r.runtime.Utils;
3336
import com.oracle.truffle.r.runtime.builtins.RBuiltin;
37+
import com.oracle.truffle.r.runtime.context.RContext;
38+
import com.oracle.truffle.r.runtime.context.TruffleRLanguage;
3439

3540
@RBuiltin(name = "traceback", kind = INTERNAL, parameterNames = {"x"}, behavior = COMPLEX)
3641
public abstract class Traceback extends RBuiltinNode.Arg1 {
@@ -42,7 +47,9 @@ public abstract class Traceback extends RBuiltinNode.Arg1 {
4247

4348
@Specialization
4449
@TruffleBoundary
45-
protected Object traceback(int x) {
46-
return Utils.createTraceback(x);
50+
protected Object traceback(int x,
51+
@CachedContext(TruffleRLanguage.class) TruffleLanguage.ContextReference<RContext> ctxRef) {
52+
Object lastInteropTrace = RErrorHandling.getLastInteropTrace(ctxRef.get());
53+
return lastInteropTrace != null ? lastInteropTrace : Utils.createTraceback(x);
4754
}
4855
}

com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRPrintError.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,31 +28,27 @@
2828
import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
2929

3030
import com.oracle.truffle.api.dsl.Specialization;
31-
import com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef;
32-
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue;
33-
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.notLogicalNA;
3431
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement;
3532
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue;
3633
import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
37-
import com.oracle.truffle.r.runtime.RRuntime;
3834
import com.oracle.truffle.r.runtime.Utils;
3935
import com.oracle.truffle.r.runtime.builtins.RBuiltin;
4036
import com.oracle.truffle.r.runtime.data.RNull;
4137

42-
@RBuiltin(name = ".fastr.printError", visibility = OFF, kind = PRIMITIVE, parameterNames = {"txt", "newLine"}, behavior = COMPLEX)
43-
public abstract class FastRPrintError extends RBuiltinNode.Arg2 {
38+
@RBuiltin(name = ".fastr.printError", visibility = OFF, kind = PRIMITIVE, parameterNames = {"txt"}, behavior = COMPLEX)
39+
public abstract class FastRPrintError extends RBuiltinNode.Arg1 {
4440

4541
static {
4642
Casts casts = new Casts(FastRPrintError.class);
4743
casts.arg("txt").mustBe(stringValue()).asStringVector().mustBe(singleElement()).findFirst();
48-
casts.arg("newLine").mapMissing(Predef.constant(RRuntime.LOGICAL_TRUE)).mustBe(logicalValue()).asLogicalVector().mustBe(singleElement()).findFirst().mustBe(notLogicalNA()).map(
49-
Predef.toBoolean());
5044
}
5145

5246
@TruffleBoundary
5347
@Specialization
54-
public Object printError(String txt, boolean nl) {
55-
Utils.writeStderr(txt, nl);
48+
public Object printError(String txt) {
49+
if (!txt.trim().isEmpty()) {
50+
Utils.writeStderr(txt, true);
51+
}
5652
return RNull.instance;
5753
}
5854
}

com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ public ArgumentsSignature getSignature() {
173173
return formalArguments.getSignature();
174174
}
175175

176+
@Override
177+
public boolean isCaptureFramesForTrace() {
178+
return true;
179+
}
180+
176181
@Override
177182
public RootCallTarget duplicateWithNewFrameDescriptor() {
178183
RCodeBuilder<RSyntaxNode> builder = RContext.getASTBuilder();

com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RErrorHandling.java

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Copyright (c) 1995-2015, The R Core Team
33
* Copyright (c) 2003, The R Foundation
4-
* Copyright (c) 2015, 2019, Oracle and/or its affiliates
4+
* Copyright (c) 2015, 2020, Oracle and/or its affiliates
55
*
66
* This program is free software; you can redistribute it and/or modify
77
* it under the terms of the GNU General Public License as published by
@@ -19,12 +19,19 @@
1919
*/
2020
package com.oracle.truffle.r.runtime;
2121

22+
import static com.oracle.truffle.r.runtime.RError.NO_CALLER;
23+
import static com.oracle.truffle.r.runtime.RError.findParentRBase;
24+
2225
import java.util.ArrayList;
26+
import java.util.List;
2327

2428
import com.oracle.truffle.api.CompilerAsserts;
2529
import com.oracle.truffle.api.CompilerDirectives;
2630
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2731
import com.oracle.truffle.api.TruffleException;
32+
import com.oracle.truffle.api.TruffleLanguage;
33+
import com.oracle.truffle.api.TruffleStackTrace;
34+
import com.oracle.truffle.api.TruffleStackTraceElement;
2835
import com.oracle.truffle.api.frame.Frame;
2936
import com.oracle.truffle.api.frame.MaterializedFrame;
3037
import com.oracle.truffle.api.nodes.Node;
@@ -492,18 +499,32 @@ private static Object findCaller(RBaseNode callObj) {
492499
return RContext.getRRuntimeASTAccess().findCaller(callObj);
493500
}
494501

495-
static RError errorcallDflt(boolean showCall, RBaseNode callObj, Message msg, Object... objects) throws RError {
502+
static RError errorcallDflt(boolean showCall, RBaseNode callObj, Message msg, Object... objects) {
496503
return errorcallDfltWithCall(callObj, showCall ? findCaller(callObj) : RNull.instance, msg, objects);
497504
}
498505

506+
static RError errorcallDflt(RuntimeException customException, boolean showCall, RBaseNode callObj, Message msg, Object... objects) {
507+
return errorcallDfltWithCall(customException, callObj, showCall ? findCaller(callObj) : RNull.instance, msg, objects);
508+
}
509+
510+
private static RError errorcallDfltWithCall(Node location, Object call, Message msg, Object... objects) {
511+
return errorcallDfltWithCall(null, location, call, msg, objects);
512+
}
513+
499514
/**
500515
* The default error handler. This is where all the error message formatting is done and the
501516
* output.
502517
*/
503-
private static RError errorcallDfltWithCall(Node location, Object call, Message msg, Object... objects) throws RError {
518+
private static RError errorcallDfltWithCall(RuntimeException customException, Node location, Object call, Message msg, Object... objects) {
504519
String fmsg = formatMessage(msg, objects);
505520

506-
String errorMessage = createErrorMessage(call, fmsg);
521+
RContext context = RContext.getInstance();
522+
String errorMessage;
523+
if (customException == null) {
524+
errorMessage = createErrorMessage(call, fmsg);
525+
} else {
526+
errorMessage = getPolyglotErrorMessage(context, customException);
527+
}
507528

508529
ContextStateImpl errorHandlingState = getRErrorHandlingState();
509530
if (errorHandlingState.inError > 0) {
@@ -519,7 +540,7 @@ private static RError errorcallDfltWithCall(Node location, Object call, Message
519540
throw new RError(null, location);
520541
}
521542

522-
Object errorExpr = RContext.getInstance().stateROptions.getValue("error");
543+
Object errorExpr = context.stateROptions.getValue("error");
523544
boolean printNow = errorExpr != RNull.instance || RContext.isEmbedded();
524545

525546
if (printNow) {
@@ -576,16 +597,44 @@ private static RError errorcallDfltWithCall(Node location, Object call, Message
576597
}
577598
}
578599

579-
if (RContext.getInstance().isInteractive() || errorExpr != RNull.instance) {
580-
Object trace = Utils.createTraceback(0);
600+
if (context.isInteractive() || errorExpr != RNull.instance) {
601+
Object lastInteropTrace = getLastInteropTrace(context);
602+
Object trace = lastInteropTrace != null ? lastInteropTrace : Utils.createTraceback(0);
581603
try {
604+
// TODO: create second traceback with all interop/native frames -> put into
605+
// .FastRTraceback (or it can be something in RContext not env)
606+
// fastr.traceback without argument -> use .FastRTraceback
582607
REnvironment env = RContext.getInstance().stateREnvironment.getBaseEnv();
583608
env.put(".Traceback", trace);
584609
} catch (PutException x) {
585610
throw RInternalError.shouldNotReachHere("cannot write .Traceback");
586611
}
587612
}
588-
throw new RError(printNow ? null : errorMessage, location);
613+
context.lastInteropTrace = null;
614+
throw customException != null ? customException : new RError(printNow ? null : errorMessage, location);
615+
}
616+
617+
public static Object getLastInteropTrace(RContext context) {
618+
if (context.lastInteropTrace != null) {
619+
Object pl = Utils.toPairList(context.lastInteropTrace, 0);
620+
if (pl instanceof RPairList) {
621+
return pl;
622+
}
623+
}
624+
return null;
625+
}
626+
627+
private static String getPolyglotErrorMessage(RContext context, RuntimeException customException) {
628+
String errorMessage;
629+
String str;
630+
TruffleLanguage.Env env = context.getEnv();
631+
if (env.isHostException(customException)) {
632+
str = env.asHostException(customException).toString();
633+
} else {
634+
str = customException.getMessage();
635+
}
636+
errorMessage = "Error in polyglot evaluation : " + str;
637+
return errorMessage;
589638
}
590639

591640
private static MaterializedFrame safeCurrentFrame() {
@@ -608,7 +657,25 @@ public static RError handleInteropException(Node callObj, RuntimeException e) {
608657
}
609658
}
610659
}
611-
throw e;
660+
661+
// TODO: probably some special handling for case e instanceof HostException
662+
663+
// Save the interop trace so that we can use it in traceback(..., interop=T)
664+
List<TruffleStackTraceElement> interopTrace = TruffleStackTrace.getStackTrace(e);
665+
RContext.getInstance().lastInteropTrace = interopTrace;
666+
667+
// Run the R error handling machinery:
668+
RBaseNode parentRBase = findParentRBase(callObj);
669+
String message = getPolyglotErrorMessage(RContext.getInstance(), e);
670+
// Runs signal handlers, e.g., "error" handler in tryCatch(some-expression, error =
671+
// function(...) ...)
672+
// The handler usually throws another exception and so the following step is skipped
673+
// (In the case of tryCatch(...) the handler throws ReturnException that falls through to
674+
// the tryCatch R function
675+
RErrorHandling.signalError(parentRBase, Message.GENERIC, message);
676+
// Default error handling: print the error and run options(error = handler) if set, also
677+
// throw Java exception which will fall-through to the REPL/embedder
678+
return RErrorHandling.errorcallDflt(e, callObj != NO_CALLER, parentRBase, Message.GENERIC, message);
612679
}
613680

614681
/**

0 commit comments

Comments
 (0)