Skip to content

Commit c477b04

Browse files
committed
Try to re-implement backtrace with Truffle.getRuntime().iterateFrames
1 parent 7db725d commit c477b04

File tree

6 files changed

+82
-37
lines changed

6 files changed

+82
-37
lines changed

doc/user/compatibility.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ To help alleviate this problem, backtraces are automatically disabled in cases w
172172
Using `Kernel#caller_locations` or `Thread.each_caller_location` might contain engine specific location objects and/or
173173
paths. This is expected and should be filtered in application code where necessary.
174174

175+
The enumerator returned by `Thread.to_enum(:each_caller_location)` is not supporting iteration with `.next`. In CRuby
176+
this raises a `StopIteration`, while in TruffleRuby it iterates on an undetermined (related to where and how `.next` is
177+
called) call stack. It is not recommended to use this in any circumstance (neither CRuby nor TruffleRuby).
178+
175179
## C Extension Compatibility
176180

177181
### Identifiers may be macros or functions

spec/ruby/core/thread/each_caller_location_spec.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@
4343
cllr.map(&:to_s).should == ary.map(&:to_s)
4444
end
4545

46-
it "raises StopIteration" do
46+
# In CRuby this raises StopIteration, however this is a compiler specific edge case and should not be used in any case.
47+
it "does not raise when the enumerator from `.to_enum` is used with `.next`" do
4748
ecl = Thread.to_enum(:each_caller_location)
48-
-> { ecl.next }.should raise_error(StopIteration)
49+
-> { ecl.next }.should_not raise_error
4950
end
5051
end
5152
end

src/main/java/org/truffleruby/core/thread/ThreadBacktraceLocationNodes.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ private static SourceSection getAvailableSourceSection(RubyContext context,
3838
final Backtrace backtrace = threadBacktraceLocation.backtrace;
3939
final int activationIndex = threadBacktraceLocation.activationIndex;
4040

41-
return context
42-
.getUserBacktraceFormatter()
43-
.nextAvailableSourceSection(backtrace.getStackTrace(), activationIndex);
41+
return BacktraceFormatter.nextAvailableSourceSection(backtrace.getStackTrace(), activationIndex);
4442
}
4543

4644
@CoreMethod(names = "absolute_path")

src/main/java/org/truffleruby/core/thread/ThreadNodes.java

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,19 @@
4343
import java.util.ArrayList;
4444
import java.util.List;
4545
import java.util.concurrent.TimeUnit;
46+
import java.util.concurrent.atomic.AtomicInteger;
4647

48+
import com.oracle.truffle.api.RootCallTarget;
49+
import com.oracle.truffle.api.Truffle;
4750
import com.oracle.truffle.api.TruffleContext;
4851
import com.oracle.truffle.api.TruffleSafepoint;
4952
import com.oracle.truffle.api.TruffleSafepoint.Interrupter;
53+
import com.oracle.truffle.api.TruffleStackTraceElement;
54+
import com.oracle.truffle.api.frame.FrameInstance;
5055
import com.oracle.truffle.api.frame.VirtualFrame;
5156
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
5257
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
58+
import com.oracle.truffle.api.nodes.RootNode;
5359
import com.oracle.truffle.api.strings.TruffleString;
5460
import org.graalvm.collections.Pair;
5561
import org.truffleruby.RubyContext;
@@ -87,14 +93,17 @@
8793
import org.truffleruby.interop.TranslateInteropExceptionNode;
8894
import org.truffleruby.language.Nil;
8995
import org.truffleruby.language.NotProvided;
96+
import org.truffleruby.language.RubyRootNode;
9097
import org.truffleruby.language.SafepointAction;
9198
import org.truffleruby.language.SafepointManager;
9299
import org.truffleruby.annotations.Visibility;
93100
import org.truffleruby.language.arguments.ArgumentsDescriptor;
94101
import org.truffleruby.language.arguments.RubyArguments;
95102
import org.truffleruby.language.backtrace.Backtrace;
103+
import org.truffleruby.language.backtrace.BacktraceFormatter;
96104
import org.truffleruby.language.control.KillException;
97105
import org.truffleruby.language.control.RaiseException;
106+
import org.truffleruby.language.methods.SharedMethodInfo;
98107
import org.truffleruby.language.objects.AllocationTracing;
99108
import org.truffleruby.language.objects.shared.SharedObjects;
100109
import org.truffleruby.language.yield.CallBlockNode;
@@ -1044,10 +1053,59 @@ public abstract static class EachCallerLocationNode extends PrimitiveArrayArgume
10441053
@Specialization
10451054
protected Object eachCallerLocation(VirtualFrame frame) {
10461055
final RubyProc block = (RubyProc) RubyArguments.getBlock(frame);
1047-
final Backtrace backtrace = getContext().getCallStack().getBacktrace(this, 2);
1056+
// Skip the block of `Thread#each_caller_location` + its internal iteration.
1057+
final int skip = 2;
1058+
final List<TruffleStackTraceElement> stackTraceElements = new ArrayList<>();
1059+
final AtomicInteger index = new AtomicInteger(0);
1060+
1061+
Truffle.getRuntime().iterateFrames((frameInstance) -> {
1062+
final RootCallTarget rootCallTarget = (RootCallTarget) frameInstance.getCallTarget();
1063+
Node location = frameInstance.getCallNode();
1064+
1065+
if (location != null) {
1066+
final RootNode rootNode = location.getRootNode();
1067+
1068+
if (rootNode.isInternal()) {
1069+
return frameInstance;
1070+
}
1071+
1072+
if (rootNode instanceof RubyRootNode) {
1073+
final SharedMethodInfo sharedMethodInfo = ((RubyRootNode) rootNode).getSharedMethodInfo();
1074+
if (getContext().getCoreLibrary().isTruffleBootMainMethod(sharedMethodInfo)) {
1075+
return frameInstance;
1076+
}
1077+
}
1078+
}
1079+
1080+
if (index.getAndIncrement() >= skip) {
1081+
final TruffleStackTraceElement stackTraceElement = TruffleStackTraceElement.create(
1082+
location,
1083+
rootCallTarget,
1084+
frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY));
1085+
stackTraceElements.add(stackTraceElement);
1086+
1087+
final TruffleStackTraceElement[] finalStackTraceElements = stackTraceElements
1088+
.toArray(TruffleStackTraceElement[]::new);
1089+
final boolean readyToYield = BacktraceFormatter.nextAvailableSourceSection(finalStackTraceElements,
1090+
0) != null;
1091+
1092+
if (readyToYield) {
1093+
for (int i = 0; i < finalStackTraceElements.length; i++) {
1094+
Backtrace backtrace = new Backtrace(location, 0, finalStackTraceElements);
1095+
RubyBacktraceLocation rubyBacktraceLocation = new RubyBacktraceLocation(
1096+
getContext().getCoreLibrary().threadBacktraceLocationClass,
1097+
getLanguage().threadBacktraceLocationShape,
1098+
backtrace,
1099+
i);
1100+
1101+
yieldNode.yield(block, rubyBacktraceLocation);
1102+
}
1103+
stackTraceElements.clear();
1104+
}
1105+
}
1106+
1107+
return null;
10481108

1049-
backtrace.getBacktraceRawLocations(getContext(), getLanguage(), -1, this, (location) -> {
1050-
yieldNode.yield(block, location);
10511109
});
10521110

10531111
return nil;

src/main/java/org/truffleruby/language/backtrace/Backtrace.java

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ public class Backtrace {
7070
// region Fields
7171
// See accessors for info on most undocumented fields.
7272

73-
/** Used for providing business logic to do something with the location when walking the backtrace. */
74-
public interface LocationHandler {
75-
void call(RubyBacktraceLocation loc);
76-
}
77-
7873
private final Node location;
7974
private final int omitted;
8075
private RaiseException raiseException;
@@ -92,6 +87,14 @@ public Backtrace(Node location, int omitted, Throwable javaThrowable) {
9287
this.javaThrowable = javaThrowable;
9388
}
9489

90+
/** For manually crafted backtraces. */
91+
public Backtrace(Node location, int omitted, TruffleStackTraceElement[] stackTraceElements) {
92+
this.location = location;
93+
this.omitted = omitted;
94+
this.javaThrowable = null;
95+
this.stackTrace = stackTraceElements;
96+
}
97+
9598
/** Creates a backtrace for the given foreign exception, setting the {@link #getLocation() location} accordingly,
9699
* and computing the activations eagerly (since the exception itself is not retained). */
97100
public Backtrace(AbstractTruffleException exception) {
@@ -264,20 +267,6 @@ public TruffleStackTraceElement[] getStackTrace() {
264267
* @param node the node at which we're requiring the backtrace. Can be null if the backtrace is associated with a */
265268
@TruffleBoundary
266269
public Object getBacktraceLocations(RubyContext context, RubyLanguage language, int length, Node node) {
267-
final Object[] locations = getBacktraceRawLocations(context, language, length, node, null);
268-
269-
if (locations == null) {
270-
return omitted > totalUnderlyingElements
271-
? Nil.INSTANCE
272-
: ArrayHelpers.createEmptyArray(context, language);
273-
}
274-
275-
return ArrayHelpers.createArray(context, language, locations);
276-
}
277-
278-
@TruffleBoundary
279-
public Object[] getBacktraceRawLocations(RubyContext context, RubyLanguage language, int length,
280-
Node node, LocationHandler locationHandler) {
281270
final int stackTraceLength;
282271
if (this.raiseException != null) {
283272
// When dealing with the backtrace of a Ruby exception, we use the wrapping
@@ -294,7 +283,9 @@ public Object[] getBacktraceRawLocations(RubyContext context, RubyLanguage langu
294283

295284
// Omitting more locations than available should return nil.
296285
if (stackTraceLength == 0) {
297-
return null;
286+
return omitted > totalUnderlyingElements
287+
? Nil.INSTANCE
288+
: ArrayHelpers.createEmptyArray(context, language);
298289
}
299290

300291
final int locationsLength = length < 0
@@ -304,23 +295,16 @@ public Object[] getBacktraceRawLocations(RubyContext context, RubyLanguage langu
304295
: Math.min(stackTraceLength, length);
305296

306297
final Object[] locations = new Object[locationsLength];
307-
308298
for (int i = 0; i < locationsLength; i++) {
309299
final RubyBacktraceLocation instance = new RubyBacktraceLocation(
310300
context.getCoreLibrary().threadBacktraceLocationClass,
311301
language.threadBacktraceLocationShape,
312302
this,
313303
i);
314304
AllocationTracing.trace(instance, node);
315-
316-
if (locationHandler != null) {
317-
locationHandler.call(instance);
318-
}
319-
320305
locations[i] = instance;
321306
}
322-
323-
return locations;
307+
return ArrayHelpers.createArray(context, language, locations);
324308
}
325309

326310
// endregion

src/main/java/org/truffleruby/language/backtrace/BacktraceFormatter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ private String formatException(RubyException exception) {
337337

338338
/** This logic should be kept in sync with
339339
* {@link org.truffleruby.debug.TruffleDebugNodes.IterateFrameBindingsNode} */
340-
public SourceSection nextAvailableSourceSection(TruffleStackTraceElement[] stackTrace, int n) {
340+
public static SourceSection nextAvailableSourceSection(TruffleStackTraceElement[] stackTrace, int n) {
341341
while (n < stackTrace.length) {
342342
final Node callNode = stackTrace[n].getLocation();
343343

0 commit comments

Comments
 (0)