Skip to content

Commit 2466fbe

Browse files
committed
Add Thread.each_caller_location.
1 parent 6f0ce72 commit 2466fbe

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require_relative '../../spec_helper'
2+
3+
describe "Thread.each_caller_location" do
4+
ruby_version_is "3.2" do
5+
it "matches caller_locations content and type" do
6+
i = 0
7+
ecl = Thread.each_caller_location { |x| i += 1; break x if i == 1 }
8+
9+
ecl.to_s.should == caller_locations(1, 1)[0].to_s
10+
ecl.should be_kind_of(Thread::Backtrace::Location)
11+
end
12+
13+
it "`break` stops the backtrace iteration" do
14+
i = 0
15+
ary = []
16+
last = Thread.each_caller_location { |x| ary << x; i += 1; break x if i == 2 }
17+
18+
ary.map(&:to_s).should == caller_locations(1, 2).map(&:to_s)
19+
last.should be_kind_of(Thread::Backtrace::Location)
20+
end
21+
22+
it "show multiple block frames" do
23+
i = 0
24+
ary = []
25+
cllr = nil
26+
last = nil
27+
28+
->{
29+
->{
30+
cllr = caller_locations(1, 2)
31+
last = Thread.each_caller_location { |x| ary << x; i += 1; break x if i == 2 }
32+
}.()
33+
}.()
34+
35+
cllr.map(&:to_s).should == ary.map(&:to_s)
36+
last.should be_kind_of(Thread::Backtrace::Location)
37+
end
38+
39+
it "works with #to_enum" do
40+
cllr = caller_locations(1, 2)
41+
ary = Thread.to_enum(:each_caller_location).to_a[2..3]
42+
43+
cllr.map(&:to_s).should == ary.map(&:to_s)
44+
end
45+
46+
it "raises StopIteration" do
47+
ecl = Thread.to_enum(:each_caller_location)
48+
-> { ecl.next }.should raise_error(StopIteration)
49+
end
50+
end
51+
end

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,4 +1034,22 @@ protected Object runBlockingSystemCall(Object executable, RubyArray argsArray,
10341034
return foreignToRubyNode.executeConvert(result);
10351035
}
10361036
}
1037+
1038+
@Primitive(name = "thread_each_caller_location")
1039+
public abstract static class CallerLocationsNode extends PrimitiveArrayArgumentsNode {
1040+
1041+
@Child private CallBlockNode yieldNode = CallBlockNode.create();
1042+
1043+
@Specialization
1044+
protected Object eachCallerLocation(VirtualFrame frame) {
1045+
final RubyProc block = (RubyProc) RubyArguments.getBlock(frame);
1046+
final Backtrace backtrace = getContext().getCallStack().getBacktrace(this, 2);
1047+
1048+
backtrace.getBacktraceRawLocations(getContext(), getLanguage(), -1, this, (location) -> {
1049+
yieldNode.yield(block, location);
1050+
});
1051+
1052+
return nil;
1053+
}
1054+
}
10371055
}

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ 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+
7378
private final Node location;
7479
private final int omitted;
7580
private RaiseException raiseException;
@@ -259,6 +264,20 @@ public TruffleStackTraceElement[] getStackTrace() {
259264
* @param node the node at which we're requiring the backtrace. Can be null if the backtrace is associated with a */
260265
@TruffleBoundary
261266
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) {
262281
final int stackTraceLength;
263282
if (this.raiseException != null) {
264283
// When dealing with the backtrace of a Ruby exception, we use the wrapping
@@ -275,9 +294,7 @@ public Object getBacktraceLocations(RubyContext context, RubyLanguage language,
275294

276295
// Omitting more locations than available should return nil.
277296
if (stackTraceLength == 0) {
278-
return omitted > totalUnderlyingElements
279-
? Nil.INSTANCE
280-
: ArrayHelpers.createEmptyArray(context, language);
297+
return null;
281298
}
282299

283300
final int locationsLength = length < 0
@@ -287,16 +304,23 @@ public Object getBacktraceLocations(RubyContext context, RubyLanguage language,
287304
: Math.min(stackTraceLength, length);
288305

289306
final Object[] locations = new Object[locationsLength];
307+
290308
for (int i = 0; i < locationsLength; i++) {
291309
final RubyBacktraceLocation instance = new RubyBacktraceLocation(
292310
context.getCoreLibrary().threadBacktraceLocationClass,
293311
language.threadBacktraceLocationShape,
294312
this,
295313
i);
296314
AllocationTracing.trace(instance, node);
315+
316+
if (locationHandler != null) {
317+
locationHandler.call(instance);
318+
}
319+
297320
locations[i] = instance;
298321
}
299-
return ArrayHelpers.createArray(context, language, locations);
322+
323+
return locations;
300324
}
301325

302326
// endregion

src/main/ruby/truffleruby/core/thread.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ def start(...)
123123
thread
124124
end
125125
alias_method :fork, :start
126+
127+
def each_caller_location
128+
raise LocalJumpError, 'no block given' unless block_given?
129+
130+
Primitive.thread_each_caller_location
131+
end
126132
end
127133

128134
# Instance methods

0 commit comments

Comments
 (0)