Skip to content

Commit ef9d57a

Browse files
committed
Merge branch 'topic/lkql_stack_trace' into 'master'
Add a call-stack to LKQL error message Closes #10 See merge request eng/libadalang/langkit-query-language!244
2 parents 75ed0ac + bda551f commit ef9d57a

File tree

9 files changed

+120
-4
lines changed

9 files changed

+120
-4
lines changed

lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.adacore.lkql_jit.exception.LKQLRuntimeException;
1414
import com.adacore.lkql_jit.options.JsonUtils;
1515
import com.adacore.lkql_jit.options.RuleInstance;
16+
import com.adacore.lkql_jit.runtime.CallStack;
1617
import com.adacore.lkql_jit.runtime.GlobalScope;
1718
import com.adacore.lkql_jit.utils.Constants;
1819
import com.adacore.lkql_jit.utils.functions.ArrayUtils;
@@ -43,6 +44,9 @@ public final class LKQLContext {
4344

4445
public final CheckerUtils.SourceLinesCache linesCache = new CheckerUtils.SourceLinesCache();
4546

47+
/** The call stack of the current language thread. */
48+
public final CallStack callStack = new CallStack();
49+
4650
// ----- Ada project attributes -----
4751

4852
/** The analysis context for the ada files. */

lkql_jit/language/src/main/java/com/adacore/lkql_jit/checker/utils/CheckerUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.adacore.lkql_jit.LKQLContext;
1010
import com.adacore.lkql_jit.LKQLLanguage;
1111
import com.adacore.lkql_jit.checker.BaseChecker;
12+
import com.adacore.lkql_jit.runtime.CallStack;
1213
import com.adacore.lkql_jit.utils.functions.FileUtils;
1314
import com.adacore.lkql_jit.utils.functions.StringUtils;
1415
import com.adacore.lkql_jit.utils.source_location.SourceLocation;
@@ -87,6 +88,23 @@ String diagnostic(
8788
SourceLocation lkqlErrorLocation,
8889
String ruleName);
8990

91+
/** Given a call stack, returns a formatted representation of this stack. */
92+
default String callStack(CallStack callStack) {
93+
StringBuilder res = new StringBuilder();
94+
for (int i = 0; i < callStack.calls.size(); i++) {
95+
var call = callStack.calls.get(i);
96+
97+
// Add a newline of we're not on the first call
98+
if (i > 0) {
99+
res.append('\n');
100+
}
101+
102+
// Format the current call and add it to the result
103+
res.append(" in ").append(call.display());
104+
}
105+
return res.toString();
106+
}
107+
90108
/**
91109
* Emit a diagnostic. Location parameters can be null. If both are null, then a non located
92110
* diagnostic will be emitted. The ruleName can also be "", in which case the diagnostic

lkql_jit/language/src/main/java/com/adacore/lkql_jit/exception/LKQLRuntimeException.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.adacore.lkql_jit.LKQLLanguage;
99
import com.adacore.lkql_jit.checker.utils.CheckerUtils;
10+
import com.adacore.lkql_jit.runtime.CallStack;
1011
import com.adacore.lkql_jit.utils.source_location.SourceLocation;
1112
import com.adacore.lkql_jit.utils.source_location.SourceSectionWrapper;
1213
import com.oracle.truffle.api.CompilerDirectives;
@@ -25,10 +26,14 @@ public final class LKQLRuntimeException extends AbstractTruffleException {
2526

2627
@Serial private static final long serialVersionUID = 8401390548003855662L;
2728

29+
/** The state of the call stack when the exception has been raised. */
30+
public final CallStack callStack;
31+
2832
// ----- Constructors -----
2933

3034
private LKQLRuntimeException(String message, Node location) {
3135
super(message, location);
36+
this.callStack = LKQLLanguage.getContext(null).callStack.clone();
3237
}
3338

3439
// ----- Exception creation methods -----
@@ -382,6 +387,8 @@ public static LKQLRuntimeException positionAfterNamedArgument(Node location) {
382387
"positional argument after named argument", location);
383388
}
384389

390+
// ----- Instance methods -----
391+
385392
public SourceLocation getSourceLoc() {
386393
var loc = getLocation();
387394
return loc != null ? new SourceSectionWrapper(getLocation().getSourceSection()) : null;
@@ -407,9 +414,9 @@ public String getErrorMessage() {
407414
@Override
408415
public String getMessage() {
409416
var loc = getSourceLoc();
410-
return LKQLLanguage.getContext(null)
411-
.getDiagnosticEmitter()
412-
.diagnostic(
413-
CheckerUtils.MessageKind.ERROR, this.getErrorMessage(), null, loc, null);
417+
var diagEmitter = LKQLLanguage.getContext(null).getDiagnosticEmitter();
418+
return diagEmitter.diagnostic(
419+
CheckerUtils.MessageKind.ERROR, this.getErrorMessage(), null, loc, null)
420+
+ (this.callStack.isEmpty() ? "" : "\n" + diagEmitter.callStack(this.callStack));
414421
}
415422
}

lkql_jit/language/src/main/java/com/adacore/lkql_jit/nodes/expressions/FunCall.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package com.adacore.lkql_jit.nodes.expressions;
77

8+
import com.adacore.lkql_jit.LKQLLanguage;
89
import com.adacore.lkql_jit.built_ins.BuiltInFunctionValue;
910
import com.adacore.lkql_jit.built_ins.BuiltInMethodValue;
1011
import com.adacore.lkql_jit.built_ins.values.*;
@@ -229,14 +230,27 @@ private Object executeLKQLFunction(
229230
// We don't place the closure in the arguments because built-ins don't have any.
230231
// Just execute the function.
231232
try {
233+
pushCallStack(function);
232234
return functionLibrary.execute(function, args);
233235
} catch (ArityException | UnsupportedTypeException | UnsupportedMessageException e) {
234236
// TODO: Implement runtime checks in the LKQLFunction class and base computing on them
235237
// (#138)
236238
throw LKQLRuntimeException.fromJavaException(e, this.getCallee());
239+
} finally {
240+
popCallStack();
237241
}
238242
}
239243

244+
/** Push this call node to the call stack. */
245+
private void pushCallStack(LKQLFunction function) {
246+
LKQLLanguage.getContext(this).callStack.pushCall(function, this);
247+
}
248+
249+
/** Remove this call node from the call stack. */
250+
private void popCallStack() {
251+
LKQLLanguage.getContext(this).callStack.popCall();
252+
}
253+
240254
// ----- Override methods -----
241255

242256
/**
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Copyright (C) 2005-2024, AdaCore
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
//
5+
6+
package com.adacore.lkql_jit.runtime;
7+
8+
import com.adacore.lkql_jit.built_ins.values.LKQLFunction;
9+
import com.adacore.lkql_jit.nodes.expressions.FunCall;
10+
import com.oracle.truffle.api.CompilerDirectives;
11+
import java.util.LinkedList;
12+
13+
/** This class represents a call stack for the LKQL language. */
14+
public final class CallStack implements Cloneable {
15+
16+
// ----- Attributes -----
17+
18+
/** This is where all calls are stored. */
19+
public final LinkedList<Element> calls = new LinkedList<>();
20+
21+
// ----- Instance methods -----
22+
23+
public boolean isEmpty() {
24+
return this.calls.isEmpty();
25+
}
26+
27+
@CompilerDirectives.TruffleBoundary
28+
public void pushCall(LKQLFunction function, FunCall call) {
29+
this.calls.addFirst(new Element(function, call));
30+
}
31+
32+
@CompilerDirectives.TruffleBoundary
33+
public void popCall() {
34+
this.calls.removeFirst();
35+
}
36+
37+
// ----- Override methods -----
38+
39+
@Override
40+
public CallStack clone() {
41+
var res = new CallStack();
42+
res.calls.addAll(this.calls);
43+
return res;
44+
}
45+
46+
// ----- Inner classes -----
47+
48+
/**
49+
* An element of a call stack is an association between an LKQL functional value and a call
50+
* node.
51+
*/
52+
public record Element(LKQLFunction function, FunCall call) {
53+
public String display() {
54+
return this.function.getName()
55+
+ " (called at "
56+
+ this.call.getLocation().display()
57+
+ ")";
58+
}
59+
}
60+
}

testsuite/tests/interpreter/sublist_builtin/test.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
script.lkql:6:7: error: Invalid index: 0
66
6 | print(l.sublist(0, 5))
77
| ^^^^^^^^^^^^^^^
8+
in sublist (called at script.lkql:6:7)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fun error() = null.not_a_field()
2+
fun call_error() = error()
3+
fun call_call_error() = call_error()
4+
call_call_error()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
script.lkql:1:15: error: Null receiver in dot access
2+
1 | fun error() = null.not_a_field()
3+
| ^^^^^^^^^^^^^^^^
4+
in error (called at script.lkql:2:20)
5+
in call_error (called at script.lkql:3:25)
6+
in call_call_error (called at script.lkql:4:1)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
driver: 'interpreter'
2+
project: 'default_project/default.gpr'

0 commit comments

Comments
 (0)