Skip to content

Commit c13cfb9

Browse files
itaratoandrykonchin
authored andcommitted
Add Thread.each_caller_location.
1 parent b6c229a commit c13cfb9

File tree

7 files changed

+81
-4
lines changed

7 files changed

+81
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Compatibility:
2929
* Add `Module#refinements` (#3039, @itarato).
3030
* Add `Refinement#refined_class` (#3039, @itarato).
3131
* Add `rb_hash_new_capa` function (#3039, @itarato).
32+
* Add `Thread.each_caller_location` (#3039, @itarato).
3233

3334
Performance:
3435

doc/user/compatibility.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ It is not recommended to use exceptions for control flow on any implementation o
167167

168168
To help alleviate this problem, backtraces are automatically disabled in cases where we can detect that they will not be used.
169169

170+
### Caller locations
171+
172+
Using `Kernel#caller_locations` or `Thread.each_caller_location` might contain engine specific location objects and/or
173+
paths. This is expected and should be filtered in application code where necessary.
174+
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+
170179
## C Extension Compatibility
171180

172181
### Identifiers may be macros or functions

spec/truffleruby.next-specs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ spec/ruby/core/module/refinements_spec.rb
3434
spec/ruby/core/refinement/refined_class_spec.rb
3535
spec/ruby/core/module/used_refinements_spec.rb
3636
spec/ruby/optional/capi/hash_spec.rb
37+
38+
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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@
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;
53+
import com.oracle.truffle.api.frame.FrameInstance;
5154
import com.oracle.truffle.api.frame.VirtualFrame;
5255
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
5356
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
@@ -95,6 +98,7 @@
9598
import org.truffleruby.language.arguments.ArgumentsDescriptor;
9699
import org.truffleruby.language.arguments.RubyArguments;
97100
import org.truffleruby.language.backtrace.Backtrace;
101+
import org.truffleruby.language.backtrace.BacktraceFormatter;
98102
import org.truffleruby.language.control.KillException;
99103
import org.truffleruby.language.control.RaiseException;
100104
import org.truffleruby.language.objects.AllocationTracing;
@@ -1040,4 +1044,59 @@ protected Object runBlockingSystemCall(Object executable, RubyArray argsArray,
10401044
return foreignToRubyNode.execute(this, result);
10411045
}
10421046
}
1047+
1048+
@CoreMethod(names = "each_caller_location", needsBlock = true, onSingleton = true)
1049+
public abstract static class EachCallerLocationNode extends CoreMethodArrayArgumentsNode {
1050+
1051+
private static final Object STOP_ITERATE = new Object();
1052+
1053+
// Skip the block of `Thread#each_caller_location` + its internal iteration.
1054+
private static final int SKIP = 2;
1055+
1056+
@Child private CallBlockNode yieldNode = CallBlockNode.create();
1057+
1058+
@Specialization
1059+
protected Object eachCallerLocation(VirtualFrame frame, RubyProc block) {
1060+
final List<TruffleStackTraceElement> stackTraceElements = new ArrayList<>();
1061+
1062+
getContext().getCallStack().iterateFrameBindings(SKIP, frameInstance -> {
1063+
final Node location = frameInstance.getCallNode();
1064+
1065+
final RootCallTarget rootCallTarget = (RootCallTarget) frameInstance.getCallTarget();
1066+
final TruffleStackTraceElement stackTraceElement = TruffleStackTraceElement.create(
1067+
location,
1068+
rootCallTarget,
1069+
frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY));
1070+
stackTraceElements.add(stackTraceElement);
1071+
1072+
final TruffleStackTraceElement[] finalStackTraceElements = stackTraceElements
1073+
.toArray(TruffleStackTraceElement[]::new);
1074+
final boolean readyToYield = BacktraceFormatter.nextAvailableSourceSection(finalStackTraceElements,
1075+
0) != null;
1076+
1077+
if (readyToYield) {
1078+
for (int i = 0; i < finalStackTraceElements.length; i++) {
1079+
final Backtrace backtrace = new Backtrace(location, 0, finalStackTraceElements);
1080+
RubyBacktraceLocation rubyBacktraceLocation = new RubyBacktraceLocation(
1081+
getContext().getCoreLibrary().threadBacktraceLocationClass,
1082+
getLanguage().threadBacktraceLocationShape,
1083+
backtrace,
1084+
i);
1085+
1086+
yieldNode.yield(block, rubyBacktraceLocation);
1087+
}
1088+
stackTraceElements.clear();
1089+
}
1090+
1091+
return null;
1092+
});
1093+
1094+
return nil;
1095+
}
1096+
1097+
@Specialization
1098+
protected Object eachCallerLocation(VirtualFrame frame, Nil block) {
1099+
throw new RaiseException(getContext(), coreExceptions().localJumpError("no block given", this));
1100+
}
1101+
}
10431102
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ public Backtrace(Node location, int omitted, Throwable javaThrowable) {
8787
this.javaThrowable = javaThrowable;
8888
}
8989

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+
9098
/** Creates a backtrace for the given foreign exception, setting the {@link #getLocation() location} accordingly,
9199
* and computing the activations eagerly (since the exception itself is not retained). */
92100
public Backtrace(AbstractTruffleException exception) {

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)