Skip to content

Commit 2ea4d8d

Browse files
committed
[GR-19220] Add Thread.each_caller_location
PullRequest: truffleruby/3922
2 parents 9ff255a + 881f560 commit 2ea4d8d

File tree

7 files changed

+76
-6
lines changed

7 files changed

+76
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Compatibility:
3232
* Add `rb_hash_new_capa` function (#3039, @itarato).
3333
* Fix `Encoding::Converter#primitive_convert` and raise `FrozenError` when a destination buffer argument is frozen (@andrykonchin).
3434
* Add `Module#undefined_instance_methods` (#3039, @itarato).
35+
* Add `Thread.each_caller_location` (#3039, @itarato).
3536

3637
Performance:
3738

doc/user/compatibility.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ When TruffleRuby is run as part of a polyglot application, any signals that are
145145

146146
TruffleRuby provides similar `GC.stat` statistics to MRI, but not all statistics are available, and some statistics may be approximations. Use `GC.stat.keys` to see which are provided with real or approximate values. Missing values will return `0`.
147147

148+
### Caller locations
149+
150+
Using `Kernel#caller_locations` or `Thread.each_caller_location` might contain engine specific location objects and/or
151+
paths. This is expected and should be filtered in application code where necessary.
152+
153+
The enumerator returned by `Thread.to_enum(:each_caller_location)` is not supporting iteration with `.next`. In CRuby
154+
this raises a `StopIteration`, while in TruffleRuby it iterates on an undetermined (related to where and how `.next` is
155+
called) call stack. It is not recommended to use this in any circumstance (neither CRuby nor TruffleRuby).
156+
148157
## Features with Very Low Performance
149158

150159
### `ObjectSpace`

spec/truffleruby.next-specs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ spec/ruby/core/module/undefined_instance_methods_spec.rb
3535
spec/ruby/core/refinement/refined_class_spec.rb
3636
spec/ruby/core/module/used_refinements_spec.rb
3737
spec/ruby/optional/capi/hash_spec.rb
38+
39+
spec/ruby/core/thread/each_caller_location_spec.rb

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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@
4444
import java.util.List;
4545
import java.util.concurrent.TimeUnit;
4646

47+
import com.oracle.truffle.api.RootCallTarget;
4748
import com.oracle.truffle.api.TruffleContext;
4849
import com.oracle.truffle.api.TruffleSafepoint;
4950
import com.oracle.truffle.api.TruffleSafepoint.Interrupter;
5051
import com.oracle.truffle.api.dsl.Bind;
52+
import com.oracle.truffle.api.TruffleStackTraceElement;
5153
import com.oracle.truffle.api.frame.VirtualFrame;
5254
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
5355
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
@@ -95,6 +97,7 @@
9597
import org.truffleruby.language.arguments.ArgumentsDescriptor;
9698
import org.truffleruby.language.arguments.RubyArguments;
9799
import org.truffleruby.language.backtrace.Backtrace;
100+
import org.truffleruby.language.backtrace.BacktraceFormatter;
98101
import org.truffleruby.language.control.KillException;
99102
import org.truffleruby.language.control.RaiseException;
100103
import org.truffleruby.language.objects.AllocationTracing;
@@ -1040,4 +1043,53 @@ protected Object runBlockingSystemCall(Object executable, RubyArray argsArray,
10401043
return foreignToRubyNode.execute(this, result);
10411044
}
10421045
}
1046+
1047+
@CoreMethod(names = "each_caller_location", needsBlock = true, onSingleton = true)
1048+
public abstract static class EachCallerLocationNode extends CoreMethodArrayArgumentsNode {
1049+
1050+
// Skip the block of `Thread#each_caller_location` + its internal iteration.
1051+
private static final int SKIP = 2;
1052+
1053+
@Child private CallBlockNode yieldNode = CallBlockNode.create();
1054+
1055+
@Specialization
1056+
protected Object eachCallerLocation(VirtualFrame frame, RubyProc block) {
1057+
final List<TruffleStackTraceElement> elements = new ArrayList<>();
1058+
1059+
getContext().getCallStack().iterateFrameBindings(SKIP, frameInstance -> {
1060+
final Node location = frameInstance.getCallNode();
1061+
1062+
final var rootCallTarget = (RootCallTarget) frameInstance.getCallTarget();
1063+
final var element = TruffleStackTraceElement.create(location, rootCallTarget, null);
1064+
elements.add(element);
1065+
1066+
final var elementsArray = elements.toArray(Backtrace.EMPTY_STACK_TRACE_ELEMENTS_ARRAY);
1067+
final boolean readyToYield = BacktraceFormatter.nextAvailableSourceSection(elementsArray,
1068+
0) != null;
1069+
1070+
if (readyToYield) {
1071+
for (int i = 0; i < elementsArray.length; i++) {
1072+
final var backtrace = new Backtrace(location, 0, elementsArray);
1073+
final var rubyBacktraceLocation = new RubyBacktraceLocation(
1074+
getContext().getCoreLibrary().threadBacktraceLocationClass,
1075+
getLanguage().threadBacktraceLocationShape,
1076+
backtrace,
1077+
i);
1078+
1079+
yieldNode.yield(block, rubyBacktraceLocation);
1080+
}
1081+
elements.clear();
1082+
}
1083+
1084+
return null;
1085+
});
1086+
1087+
return nil;
1088+
}
1089+
1090+
@Specialization
1091+
protected Object eachCallerLocation(VirtualFrame frame, Nil block) {
1092+
throw new RaiseException(getContext(), coreExceptions().localJumpError("no block given", this));
1093+
}
1094+
}
10431095
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public final class Backtrace {
7777
private TruffleStackTraceElement[] stackTrace;
7878
private int totalUnderlyingElements;
7979

80+
public static final TruffleStackTraceElement[] EMPTY_STACK_TRACE_ELEMENTS_ARRAY = new TruffleStackTraceElement[0];
81+
8082
// endregion
8183
// region Constructors
8284

@@ -87,6 +89,14 @@ public Backtrace(Node location, int omitted, Throwable javaThrowable) {
8789
this.javaThrowable = javaThrowable;
8890
}
8991

92+
/** For manually crafted backtraces. */
93+
public Backtrace(Node location, int omitted, TruffleStackTraceElement[] stackTraceElements) {
94+
this.location = location;
95+
this.omitted = omitted;
96+
this.javaThrowable = null;
97+
this.stackTrace = stackTraceElements;
98+
}
99+
90100
/** Creates a backtrace for the given foreign exception, setting the {@link #getLocation() location} accordingly,
91101
* and computing the activations eagerly (since the exception itself is not retained). */
92102
public Backtrace(AbstractTruffleException exception) {
@@ -241,8 +251,6 @@ public TruffleStackTraceElement[] getStackTrace() {
241251
return getStackTrace(this.raiseException);
242252
}
243253

244-
private static final TruffleStackTraceElement[] EMPTY_STACK_TRACE_ELEMENTS_ARRAY = new TruffleStackTraceElement[0];
245-
246254
/** Returns a ruby array of {@code Thread::Backtrace::Locations} with maximum length {@code length}, and omitting
247255
* locations as requested ({@link #getOmitted()}). If more locations are omitted than are available, return a Ruby
248256
* {@code nil}.

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)