Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Compatibility:
* Add `rb_hash_new_capa` function (#3039, @itarato).
* Fix `Encoding::Converter#primitive_convert` and raise `FrozenError` when a destination buffer argument is frozen (@andrykonchin).
* Add `Module#undefined_instance_methods` (#3039, @itarato).
* Add `Thread.each_caller_location` (#3039, @itarato).

Performance:

Expand Down
9 changes: 9 additions & 0 deletions doc/user/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ It is not recommended to use exceptions for control flow on any implementation o

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

### Caller locations

Using `Kernel#caller_locations` or `Thread.each_caller_location` might contain engine specific location objects and/or
paths. This is expected and should be filtered in application code where necessary.

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

## C Extension Compatibility

### Identifiers may be macros or functions
Expand Down
2 changes: 2 additions & 0 deletions spec/truffleruby.next-specs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ spec/ruby/core/module/undefined_instance_methods_spec.rb
spec/ruby/core/refinement/refined_class_spec.rb
spec/ruby/core/module/used_refinements_spec.rb
spec/ruby/optional/capi/hash_spec.rb

spec/ruby/core/thread/each_caller_location_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ private static SourceSection getAvailableSourceSection(RubyContext context,
final Backtrace backtrace = threadBacktraceLocation.backtrace;
final int activationIndex = threadBacktraceLocation.activationIndex;

return context
.getUserBacktraceFormatter()
.nextAvailableSourceSection(backtrace.getStackTrace(), activationIndex);
return BacktraceFormatter.nextAvailableSourceSection(backtrace.getStackTrace(), activationIndex);
}

@CoreMethod(names = "absolute_path")
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/org/truffleruby/core/thread/ThreadNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.TruffleSafepoint.Interrupter;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
Expand Down Expand Up @@ -95,6 +98,7 @@
import org.truffleruby.language.arguments.ArgumentsDescriptor;
import org.truffleruby.language.arguments.RubyArguments;
import org.truffleruby.language.backtrace.Backtrace;
import org.truffleruby.language.backtrace.BacktraceFormatter;
import org.truffleruby.language.control.KillException;
import org.truffleruby.language.control.RaiseException;
import org.truffleruby.language.objects.AllocationTracing;
Expand Down Expand Up @@ -1040,4 +1044,59 @@ protected Object runBlockingSystemCall(Object executable, RubyArray argsArray,
return foreignToRubyNode.execute(this, result);
}
}

@CoreMethod(names = "each_caller_location", needsBlock = true, onSingleton = true)
public abstract static class EachCallerLocationNode extends CoreMethodArrayArgumentsNode {

private static final Object STOP_ITERATE = new Object();

// Skip the block of `Thread#each_caller_location` + its internal iteration.
private static final int SKIP = 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


@Child private CallBlockNode yieldNode = CallBlockNode.create();

@Specialization
protected Object eachCallerLocation(VirtualFrame frame, RubyProc block) {
final List<TruffleStackTraceElement> stackTraceElements = new ArrayList<>();

getContext().getCallStack().iterateFrameBindings(SKIP, frameInstance -> {
final Node location = frameInstance.getCallNode();

final RootCallTarget rootCallTarget = (RootCallTarget) frameInstance.getCallTarget();
final TruffleStackTraceElement stackTraceElement = TruffleStackTraceElement.create(
location,
rootCallTarget,
frameInstance.getFrame(FrameInstance.FrameAccess.READ_ONLY));
stackTraceElements.add(stackTraceElement);

final TruffleStackTraceElement[] finalStackTraceElements = stackTraceElements
.toArray(TruffleStackTraceElement[]::new);
final boolean readyToYield = BacktraceFormatter.nextAvailableSourceSection(finalStackTraceElements,
0) != null;

if (readyToYield) {
for (int i = 0; i < finalStackTraceElements.length; i++) {
final Backtrace backtrace = new Backtrace(location, 0, finalStackTraceElements);
RubyBacktraceLocation rubyBacktraceLocation = new RubyBacktraceLocation(
getContext().getCoreLibrary().threadBacktraceLocationClass,
getLanguage().threadBacktraceLocationShape,
backtrace,
i);

yieldNode.yield(block, rubyBacktraceLocation);
}
stackTraceElements.clear();
}

return null;
});

return nil;
}

@Specialization
protected Object eachCallerLocation(VirtualFrame frame, Nil block) {
throw new RaiseException(getContext(), coreExceptions().localJumpError("no block given", this));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ public Backtrace(Node location, int omitted, Throwable javaThrowable) {
this.javaThrowable = javaThrowable;
}

/** For manually crafted backtraces. */
public Backtrace(Node location, int omitted, TruffleStackTraceElement[] stackTraceElements) {
this.location = location;
this.omitted = omitted;
this.javaThrowable = null;
this.stackTrace = stackTraceElements;
}

/** Creates a backtrace for the given foreign exception, setting the {@link #getLocation() location} accordingly,
* and computing the activations eagerly (since the exception itself is not retained). */
public Backtrace(AbstractTruffleException exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ private String formatException(RubyException exception) {

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

Expand Down