All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.truffle.api.TruffleStackTrace Maven / Gradle / Ivy

Go to download

Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.pkl.thirdparty.truffle.api;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;

import org.pkl.thirdparty.graalvm.polyglot.PolyglotException;

import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.frame.Frame;
import org.pkl.thirdparty.truffle.api.frame.FrameInstance;
import org.pkl.thirdparty.truffle.api.frame.FrameInstance.FrameAccess;
import org.pkl.thirdparty.truffle.api.frame.FrameInstanceVisitor;
import org.pkl.thirdparty.truffle.api.frame.MaterializedFrame;
import org.pkl.thirdparty.truffle.api.nodes.ControlFlowException;
import org.pkl.thirdparty.truffle.api.nodes.Node;

/**
 * Represents a guest language stack trace.
 *
 * A TruffleStackTrace is automatically added when a {@link Throwable} passes through a
 * {@link CallTarget call target}. {@link ControlFlowException} and {@link PolyglotException} do not
 * get a TruffleStackTrace. An internal or host {@link Throwable} is added a TruffleStackTrace, as
 * long as suppression is not disabled for this throwable, via a Throwable added to the list of
 * {@linkplain Throwable#addSuppressed(Throwable) suppressed exceptions}.
 * 

* A guest language stack trace element is automatically added by the Truffle runtime every time the * {@link Throwable} passes through a {@link CallTarget call target}. This is incremental and * therefore efficient if the exception is later caught in the same compilation unit. *

* Note that if the Throwable is caught, its stack trace should be filled eagerly with * {@link #fillIn(Throwable)}, unless it can be guaranteed to be re-thrown in the same * {@link CallTarget call target}, or that the stack trace will not be used. * * @see #getStackTrace(Throwable) getStackTrace(Throwable) to retrieve the guest language stack * trace from a {@link Throwable}. * @since 19.0 */ @SuppressWarnings("serial") public final class TruffleStackTrace extends Exception { private static final TruffleStackTrace EMPTY = new TruffleStackTrace(Collections.emptyList(), 0); private List frames; private final int lazyFrames; // contains host exception frames private Throwable materializedHostException; private TruffleStackTrace(List frames, int lazyFrames) { this.frames = frames; this.lazyFrames = lazyFrames; } /** * Called when an exception leaves the guest boundary and is passed to the host language. This * requires us to capture the host stack frames to build a polyglot stack trace. This can be * done lazily because if an exception stays inside a guest language (is thrown and caught in * the guest language) there is no need to pay the price for host frames. If the error is an * internal error then the exception (e.g. NullPointerException) has already captured the host * stack trace and this host exception stack trace is not used. */ private void materializeHostException() { if (this.materializedHostException == null) { this.materializedHostException = new Exception(); } } /** * @since 19.0 */ @SuppressWarnings("sync-override") @Override public Throwable fillInStackTrace() { return this; } StackTraceElement[] getInternalStackTrace() { Throwable hostException = this.materializedHostException; if (hostException == null) { hostException = this; } StackTraceElement[] hostFrames = hostException.getStackTrace(); if (lazyFrames == 0) { return hostFrames; } else { StackTraceElement[] extended = new StackTraceElement[hostFrames.length + lazyFrames]; System.arraycopy(hostFrames, 0, extended, lazyFrames, hostFrames.length); return extended; } } /** * @since 19.0 */ @Override public String toString() { return "Attached Guest Language Frames (" + frames.size() + ")"; } /** * Returns the guest language frames that are stored in this throwable or null if * no guest language frames can ever be stored in this throwable. This method fills in the * stacktrace by calling {@link #fillIn(Throwable)}, so it is not necessary to call * {@link #fillIn(Throwable)} before. The returned list is not modifiable. The number of stack * trace elements that are filled in can be customized by the {@code stackTraceElementLimit} * parameter of the * {@link org.pkl.thirdparty.truffle.api.exception.AbstractTruffleException#AbstractTruffleException(String, Throwable, int, Node) * AbstractTruffleException constructor}. * * @param throwable the {@link Throwable} instance to look for guest language frames * @throws NullPointerException if the {@link Throwable} is null * @since 19.0 */ @TruffleBoundary public static List getStackTrace(Throwable throwable) { TruffleStackTrace stack = fillIn(throwable); if (stack != null) { return stack.frames; } return null; } /** * Returns asynchronous guest language stack frames that led to the execution of given * {@link CallTarget} on the given {@link Frame}. Returns null if no asynchronous * stack is known. Call this with a context entered only. *

* Languages might not provide asynchronous stack frames by default for performance reasons. * Instruments might need to instruct languages to provide the asynchronous stacks. * * @return a list of asynchronous frames, or null. * @since 20.1.0 */ @TruffleBoundary public static List getAsynchronousStackTrace(CallTarget target, Frame frame) { Objects.requireNonNull(target, "CallTarget must not be null"); Objects.requireNonNull(frame, "Frame must not be null"); assert LanguageAccessor.ENGINE.hasCurrentContext(); return LanguageAccessor.ACCESSOR.nodeSupport().findAsynchronousFrames(target, frame); } private static LazyStackTrace findImpl(Throwable t) { assert !(t instanceof ControlFlowException); for (Throwable suppressed : t.getSuppressed()) { if (suppressed instanceof LazyStackTrace) { return (LazyStackTrace) suppressed; } } return null; } /** * Fills in the guest language stack frames from the current frames on the stack. If the stack * was already filled before then this method has no effect. The number stack trace elements * that are filled in can be customized by the {@code stackTraceElementLimit} parameter of the * {@link org.pkl.thirdparty.truffle.api.exception.AbstractTruffleException#AbstractTruffleException(String, Throwable, int, Node) * AbstractTruffleException constructor}. * * The implementation attaches a lightweight exception object as a suppressed exception to * internal and host (but not guest) exceptions. * * @param throwable the {@link Throwable} to fill * @throws NullPointerException if the {@link Throwable} is null * @since 19.0 */ @TruffleBoundary public static TruffleStackTrace fillIn(Throwable throwable) { Objects.requireNonNull(throwable); if (throwable instanceof ControlFlowException) { return EMPTY; } LazyStackTrace lazy = getOrCreateLazyStackTrace(throwable); if (lazy.stackTrace != null) { // stack trace already exists return lazy.stackTrace; } int stackFrameLimit; Node topCallSite; boolean isTruffleException = LanguageAccessor.EXCEPTIONS.isException(throwable); if (isTruffleException) { topCallSite = LanguageAccessor.EXCEPTIONS.getLocation(throwable); stackFrameLimit = LanguageAccessor.EXCEPTIONS.getStackTraceElementLimit(throwable); } else { topCallSite = null; stackFrameLimit = -1; } // add the lazily captured stack frames above the manually queried ones List elements = new ArrayList<>(); TracebackElement currentElement = lazy.current; while (currentElement != null) { elements.add(currentElement); currentElement = currentElement.last; } List frames = new ArrayList<>(); for (ListIterator iterator = elements.listIterator(elements.size()); iterator.hasPrevious();) { TracebackElement element = iterator.previous(); if (element.root != null) { frames.add(new TruffleStackTraceElement(topCallSite, element.root, element.frame)); topCallSite = null; } if (element.callNode != null) { topCallSite = element.callNode; } } int lazyFrames = frames.size(); // attach the remaining stack trace elements addStackFrames(stackFrameLimit, lazyFrames, topCallSite, frames); TruffleStackTrace fullStackTrace = new TruffleStackTrace(frames, lazyFrames); // capture host stack trace for guest language exceptions; // internal and host language exceptions already have a stack trace attached. if (isTruffleException && !isHostException(throwable)) { fullStackTrace.materializeHostException(); } lazy.stackTrace = fullStackTrace; return fullStackTrace; } private static boolean isHostException(Throwable throwable) { Object polyglotEngine = LanguageAccessor.ENGINE.getCurrentPolyglotEngine(); return polyglotEngine != null && LanguageAccessor.ENGINE.getHostService(polyglotEngine).isHostException(throwable); } private static final class TracebackElement { private final TracebackElement last; private final Node callNode; private final RootCallTarget root; private final MaterializedFrame frame; TracebackElement(TracebackElement last, Node callNode, RootCallTarget root, MaterializedFrame frame) { this.last = last; this.callNode = callNode; this.root = root; this.frame = frame; } } static final class LazyStackTrace extends Throwable { LazyStackTrace() { super(null, null, false, false); } /** * The root of a linked list of pieces of information about the stack trace of the * exception. Only used, i.e., non-null, as long as the exception wasn't queried for the * full stack trace. */ private TracebackElement current; /** * This field is initialized iff the full stack trace was queried. */ private TruffleStackTrace stackTrace; /** * The number of non-internal root nodes that was traversed so far. */ public int frameCount; public TruffleStackTrace getInternalStackTrace() { return stackTrace; } @Override public String toString() { return "Attached Guest Language Frames (" + (frameCount + (stackTrace != null ? stackTrace.frames.size() : 0)) + ")"; } } static void addStackFrameInfo(Node callNode, RootCallTarget root, Throwable t, Frame currentFrame) { if (t instanceof ControlFlowException) { // control flow exceptions should never have to get a stack trace. return; } if (t instanceof PolyglotException) { // Normally, polyglot exceptions should not even end up here, with the exception of // those thrown by Value call targets. In any case, we do not want to attach a cause. return; } boolean isTProfiled = CompilerDirectives.isPartialEvaluationConstant(t.getClass()); MaterializedFrame frame = null; if (currentFrame != null && root.getRootNode().isCaptureFramesForTrace()) { frame = currentFrame.materialize(); } callInnerAddStackFrameInfo(isTProfiled, callNode, root, t, frame); } private static void callInnerAddStackFrameInfo(boolean isTProfiled, Node callNode, RootCallTarget root, Throwable t, MaterializedFrame currentFrame) { if (isTProfiled) { innerAddStackFrameInfo(callNode, root, t, currentFrame); } else { innerAddStackFrameInfoBoundary(callNode, root, t, currentFrame); } } @TruffleBoundary private static void innerAddStackFrameInfoBoundary(Node callNode, RootCallTarget root, Throwable t, MaterializedFrame currentFrame) { innerAddStackFrameInfo(callNode, root, t, currentFrame); } private static void innerAddStackFrameInfo(Node callNode, RootCallTarget root, Throwable t, MaterializedFrame currentFrame) { if (!LanguageAccessor.EXCEPTIONS.isException(t)) { /* * Capture as much information as possible for internal errors. This branch should not * be reached by host exceptions as they should have already been wrapped in a * HostException in the guest-to-host call root node. */ fillIn(t); return; } int stackTraceElementLimit = LanguageAccessor.EXCEPTIONS.getStackTraceElementLimit(t); LazyStackTrace lazy = (LazyStackTrace) LanguageAccessor.EXCEPTIONS.getLazyStackTrace(t); if (lazy == null) { lazy = new LazyStackTrace(); LanguageAccessor.EXCEPTIONS.setLazyStackTrace(t, lazy); } appendLazyStackTrace(callNode, root, currentFrame, lazy, stackTraceElementLimit); } @TruffleBoundary static LazyStackTrace getOrCreateLazyStackTrace(Throwable throwable) { LazyStackTrace lazy; if (LanguageAccessor.EXCEPTIONS.isException(throwable)) { lazy = (LazyStackTrace) LanguageAccessor.EXCEPTIONS.getLazyStackTrace(throwable); if (lazy == null) { lazy = new LazyStackTrace(); LanguageAccessor.EXCEPTIONS.setLazyStackTrace(throwable, lazy); } } else { lazy = findImpl(throwable); if (lazy == null) { lazy = new LazyStackTrace(); if (!tryAddSuppressed(throwable, lazy)) { // Avoid attempt to capture a lazy stack trace for immutable exceptions. lazy.stackTrace = EMPTY; } } } return lazy; } private static boolean tryAddSuppressed(Throwable throwable, LazyStackTrace lazy) { if (throwable instanceof StackOverflowError || throwable instanceof OutOfMemoryError) { /* * These VM errors are immutable if thrown by the JVM. Regardless, we treat them as * immutable if manually constructed, too. This is useful for singleton errors that * don't have suppression disabled but should have. */ return false; } throwable.addSuppressed(lazy); if (throwable.getSuppressed().length == 0) { // Suppression has been disabled for this exception. return false; } return true; } private static void appendLazyStackTrace(Node callNode, RootCallTarget root, MaterializedFrame currentFrame, LazyStackTrace lazy, int stackTraceElementLimit) { if (lazy.stackTrace == null) { if (stackTraceElementLimit >= 0 && lazy.frameCount >= stackTraceElementLimit) { return; } lazy.current = new TracebackElement(lazy.current, callNode, root, currentFrame); if (root != null && LanguageAccessor.ACCESSOR.nodeSupport().countsTowardsStackTraceLimit(root.getRootNode())) { lazy.frameCount++; } } } private static void addStackFrames(int stackFrameLimit, int lazyFrames, final Node topCallSite, List frames) { if (stackFrameLimit >= 0 && lazyFrames >= stackFrameLimit) { // early exit: avoid costly iterateFrames call if enough frames have been recorded // lazily return; } Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { boolean first = true; int stackFrameIndex = lazyFrames; @Override public FrameInstance visitFrame(FrameInstance frameInstance) { if (stackFrameLimit >= 0 && stackFrameIndex >= stackFrameLimit) { // no more frames to create return frameInstance; } Node location = frameInstance.getCallNode(); RootCallTarget target = (RootCallTarget) frameInstance.getCallTarget(); if (first) { location = topCallSite; first = false; } boolean captureFrames = target != null && target.getRootNode().isCaptureFramesForTrace(); Frame frame = captureFrames ? frameInstance.getFrame(FrameAccess.READ_ONLY) : null; frames.add(new TruffleStackTraceElement(location, target, frame)); first = false; if (target != null && LanguageAccessor.ACCESSOR.nodeSupport().countsTowardsStackTraceLimit(target.getRootNode())) { stackFrameIndex++; } return null; } }); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy