/* * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import jdk.internal.reflect.MethodAccessor; import jdk.internal.reflect.ConstructorAccessor; import java.lang.StackWalker.Option; import java.lang.StackWalker.StackFrame; import java.lang.annotation.Native; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.HashSet; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.vm.Continuation; import jdk.internal.vm.ContinuationScope; import sun.security.action.GetPropertyAction; import static java.lang.StackStreamFactory.WalkerState.*; /** * StackStreamFactory class provides static factory methods * to get different kinds of stack walker/traverser. * * AbstractStackWalker provides the basic stack walking support * fetching stack frames from VM in batches. * * AbstractStackWalker subclass is specialized for a specific kind of stack traversal * to avoid overhead of Stream/Lambda * 1. Support traversing Stream * 2. StackWalker::getCallerClass * 3. AccessControlContext getting ProtectionDomain */ final class StackStreamFactory { private StackStreamFactory() {} // Stack walk implementation classes to be excluded during stack walking // lazily add subclasses when they are loaded. private static final Set> stackWalkImplClasses = init(); // Number of elements in the buffer reserved for VM to use private static final int RESERVED_ELEMENTS = 1; private static final int MIN_BATCH_SIZE = RESERVED_ELEMENTS + 2; private static final int SMALL_BATCH = 8; private static final int BATCH_SIZE = 32; private static final int LARGE_BATCH_SIZE = 256; // These flags must match the values maintained in the VM @Native private static final int DEFAULT_MODE = 0x0; @Native private static final int CLASS_INFO_ONLY = 0x2; @Native private static final int SHOW_HIDDEN_FRAMES = 0x20; // LambdaForms are hidden by the VM @Native private static final int FILL_LIVE_STACK_FRAMES = 0x100; /* * For Throwable to use StackWalker, set useNewThrowable to true. * Performance work and extensive testing is needed to replace the * VM built-in backtrace filled in Throwable with the StackWalker. */ static final boolean isDebug = "true".equals(GetPropertyAction.privilegedGetProperty("stackwalk.debug")); static StackFrameTraverser makeStackTraverser(StackWalker walker, Function, ? extends T> function) { if (walker.hasLocalsOperandsOption()) { return new LiveStackInfoTraverser<>(walker, function); } else { return new StackFrameTraverser<>(walker, function); } } /** * Gets a stack stream to find caller class. */ static CallerClassFinder makeCallerFinder(StackWalker walker) { return new CallerClassFinder(walker); } enum WalkerState { NEW, // the stream is new and stack walking has not started OPEN, // the stream is open when it is being traversed. CLOSED; // the stream is closed when the stack walking is done } private static int toStackWalkMode(StackWalker walker, int mode) { int newMode = mode; if (walker.hasOption(Option.DROP_METHOD_INFO)) newMode |= CLASS_INFO_ONLY; if (walker.hasOption(Option.SHOW_HIDDEN_FRAMES)) newMode |= SHOW_HIDDEN_FRAMES; if (walker.hasLocalsOperandsOption()) newMode |= FILL_LIVE_STACK_FRAMES; return newMode; } /** * Subclass of AbstractStackWalker implements a specific stack walking logic. * It needs to set up the frame buffer and stack walking mode. * * It initiates the VM stack walking via the callStackWalk method that serves * as the anchored frame and VM will call up to AbstractStackWalker::doStackWalk. * * @param the type of the result returned from stack walking * @param the type of the data gathered for each frame. * For example, StackFrameInfo for StackWalker::walk or * Class for StackWalker::getCallerClass */ abstract static class AbstractStackWalker { protected final StackWalker walker; protected final Thread thread; protected final int maxDepth; protected final int mode; protected int depth; // traversed stack depth protected FrameBuffer frameBuffer; protected long anchor; protected final ContinuationScope contScope; protected Continuation continuation; // buffers to fill in stack frame information protected AbstractStackWalker(StackWalker walker, int mode) { this(walker, mode, Integer.MAX_VALUE); } protected AbstractStackWalker(StackWalker walker, int mode, int maxDepth) { this.thread = Thread.currentThread(); this.mode = mode; this.walker = walker; this.maxDepth = maxDepth; this.depth = 0; ContinuationScope scope = walker.getContScope(); if (scope == null && thread.isVirtual()) { this.contScope = VirtualThread.continuationScope(); this.continuation = null; } else { this.contScope = scope; this.continuation = walker.getContinuation(); } } /** * A callback method to consume the stack frames. This method is invoked * once stack walking begins (i.e. it is only invoked when walkFrames is called). * * Each specialized AbstractStackWalker subclass implements the consumeFrames method * to control the following: * 1. fetch the subsequent batches of stack frames * 2. reuse or expand the allocated buffers * 3. create specialized StackFrame objects * * @return the number of consumed frames */ protected abstract R consumeFrames(); /** * Initialize FrameBuffer. Subclass should implement this method to * create its custom frame buffers. */ protected abstract void initFrameBuffer(); /** * Returns the suggested next batch size. * * Subclass should override this method to change the batch size * * @param lastBatchSize last batch size * @return suggested batch size */ protected abstract int batchSize(int lastBatchSize); /* * Returns the next batch size, always >= minimum batch size * * Subclass may override this method if the minimum batch size is different. */ protected int getNextBatchSize() { int lastBatchSize = depth == 0 ? 0 : frameBuffer.currentBatchSize(); int nextBatchSize = batchSize(lastBatchSize); if (isDebug) { System.err.println("last batch size = " + lastBatchSize + " next batch size = " + nextBatchSize); } return nextBatchSize >= MIN_BATCH_SIZE ? nextBatchSize : MIN_BATCH_SIZE; } /* * Checks if this stream is in the given state. Otherwise, throws IllegalStateException. * * VM also validates this stream if it's anchored for stack walking * when stack frames are fetched for each batch. */ final void checkState(WalkerState state) { if (thread != Thread.currentThread()) { throw new IllegalStateException("Invalid thread walking this stack stream: " + Thread.currentThread().getName() + " " + thread.getName()); } switch (state) { case NEW: if (anchor != 0) { throw new IllegalStateException("This stack stream is being reused."); } break; case OPEN: if (anchor == 0 || anchor == -1L) { throw new IllegalStateException("This stack stream is not valid for walking."); } break; case CLOSED: if (anchor != -1L) { throw new IllegalStateException("This stack stream is not closed."); } } } /* * Close this stream. This stream becomes invalid to walk. */ private void close() { this.anchor = -1L; } /* * Walks stack frames until {@link #consumeFrames} is done consuming * the frames it is interested in. */ final R walk() { checkState(NEW); return (continuation != null) ? Continuation.wrapWalk(continuation, contScope, this::walkHelper) : walkHelper(); } private final R walkHelper() { try { // VM will need to stabilize the stack before walking. It will invoke // the AbstractStackWalker::doStackWalk method once it fetches the first batch. // the callback will be invoked within the scope of the callStackWalk frame. return beginStackWalk(); } finally { close(); // done traversal; close the stream } } private boolean skipReflectionFrames() { return !walker.hasOption(Option.SHOW_REFLECT_FRAMES) && !walker.hasOption(Option.SHOW_HIDDEN_FRAMES); } /* * Returns {@code Class} object at the current frame; * or {@code null} if no more frame. If advanceToNextBatch is true, * it will only fetch the next batch. */ final Class peekFrame() { while (frameBuffer.isActive() && depth < maxDepth) { if (frameBuffer.isEmpty()) { // fetch another batch of stack frames getNextBatch(); } else { Class c = frameBuffer.get(); if (skipReflectionFrames() && isReflectionFrame(c)) { if (isDebug) System.err.println(" skip: frame " + frameBuffer.getIndex() + " " + c); frameBuffer.next(); depth++; continue; } else { return c; } } } return null; } /* * This method is only invoked by VM. * * It will invoke the consumeFrames method to start the stack walking * with the first batch of stack frames. Each specialized AbstractStackWalker * subclass implements the consumeFrames method to control the following: * 1. fetch the subsequent batches of stack frames * 2. reuse or expand the allocated buffers * 3. create specialized StackFrame objects */ private Object doStackWalk(long anchor, int skipFrames, int numFrames, int bufStartIndex, int bufEndIndex) { checkState(NEW); frameBuffer.check(skipFrames); if (isDebug) { System.err.format("doStackWalk: skip %d start %d end %d nframes %d%n", skipFrames, bufStartIndex, bufEndIndex, numFrames); } this.anchor = anchor; // set anchor for this bulk stack frame traversal frameBuffer.setBatch(depth, bufStartIndex, numFrames); // traverse all frames and perform the action on the stack frames, if specified return consumeFrames(); } /* * Get next batch of stack frames. */ private int getNextBatch() { if (!frameBuffer.isActive() || (depth == maxDepth) || (frameBuffer.isAtBottom() && !hasMoreContinuations())) { if (isDebug) { System.out.format(" more stack walk done%n"); } frameBuffer.freeze(); // stack walk done return 0; } // VM ends the batch when it reaches the bottom of a continuation // i.e. Continuation::enter. The stack walker will set the continuation // to its parent to continue. // Note that the current batch could have no stack frame filled. This could // happen when Continuation::enter is the last element of the frame buffer // filled in the last batch and it needs to fetch another batch in order to // detect reaching the bottom. if (frameBuffer.isAtBottom() && hasMoreContinuations()) { if (isDebug) { System.out.format(" set continuation to %s%n", continuation.getParent()); } setContinuation(continuation.getParent()); } int numFrames = fetchStackFrames(); if (numFrames == 0 && !hasMoreContinuations()) { frameBuffer.freeze(); // done stack walking } return numFrames; } private boolean hasMoreContinuations() { return (continuation != null) && (continuation.getScope() != contScope) && (continuation.getParent() != null); } private void setContinuation(Continuation cont) { this.continuation = cont; setContinuation(anchor, frameBuffer.frames(), cont); } /* * This method traverses the next stack frame and returns the Class * invoking that stack frame. * * This method can only be called during the walk method. This is intended * to be used to walk the stack frames in one single invocation and * this stack stream will be invalidated once walk is done. * * @see #tryNextFrame */ final Class nextFrame() { if (!hasNext()) { return null; } Class c = frameBuffer.next(); depth++; return c; } /* * Returns true if there is next frame to be traversed. * This skips hidden frames unless this StackWalker has * {@link Option#SHOW_REFLECT_FRAMES} */ final boolean hasNext() { return peekFrame() != null; } /** * Begin stack walking - pass the allocated arrays to the VM to fill in * stack frame information. * * VM first anchors the frame of the current thread. A traversable stream * on this thread's stack will be opened. The VM will fetch the first batch * of stack frames and call AbstractStackWalker::doStackWalk to invoke the * stack walking function on each stack frame. * * If all fetched stack frames are traversed, AbstractStackWalker::fetchStackFrames will * fetch the next batch of stack frames to continue. */ private R beginStackWalk() { // initialize buffers for VM to fill the stack frame info initFrameBuffer(); return callStackWalk(mode, 0, contScope, continuation, frameBuffer.currentBatchSize(), frameBuffer.startIndex(), frameBuffer.frames()); } /* * Fetches a new batch of stack frames. This method returns * the number of stack frames filled in this batch. * * When it reaches the bottom of a continuation, i.e. Continuation::enter, * VM ends the batch and let the stack walker to set the continuation * to its parent and continue the stack walking. It may return zero. */ private int fetchStackFrames() { int startIndex = frameBuffer.startIndex(); // If the last batch didn't fetch any frames, keep the current batch size. int lastBatchFrameCount = frameBuffer.numFrames(); int batchSize = getNextBatchSize(); frameBuffer.resize(batchSize); int numFrames = fetchStackFrames(mode, anchor, lastBatchFrameCount, batchSize, startIndex, frameBuffer.frames()); if (isDebug) { System.out.format(" more stack walk got %d frames start %d batch size %d%n", numFrames, frameBuffer.startIndex(), batchSize); } frameBuffer.setBatch(depth, startIndex, numFrames); return numFrames; } /** * Begins stack walking. This method anchors this frame and invokes * AbstractStackWalker::doStackWalk after fetching the first batch of stack frames. * * @param mode mode of stack walking * @param skipframes number of frames to be skipped before filling the frame buffer. * @param contScope the continuation scope to walk. * @param continuation the continuation to walk, or {@code null} if walking a thread. * @param bufferSize the buffer size * @param startIndex start index of the frame buffers to be filled. * @param frames Either a {@link ClassFrameInfo} array, if mode is {@link #CLASS_INFO_ONLY} * or a {@link StackFrameInfo} (or derivative) array otherwise. * @return Result of AbstractStackWalker::doStackWalk */ private native R callStackWalk(int mode, int skipframes, ContinuationScope contScope, Continuation continuation, int bufferSize, int startIndex, T[] frames); /** * Fetch the next batch of stack frames. * * @param mode mode of stack walking * @param anchor * @param lastBatchFrameCount the number of frames filled in the last batch. * @param bufferSize the buffer size * @param startIndex start index of the frame buffers to be filled. * @param frames Either a {@link ClassFrameInfo} array, if mode is {@link #CLASS_INFO_ONLY} * or a {@link StackFrameInfo} (or derivative) array otherwise. * * @return the number of frames filled in this batch */ private native int fetchStackFrames(int mode, long anchor, int lastBatchFrameCount, int bufferSize, int startIndex, T[] frames); private native void setContinuation(long anchor, T[] frames, Continuation cont); } /* * This StackFrameTraverser supports {@link Stream} traversal. * * This class implements Spliterator::forEachRemaining and Spliterator::tryAdvance. */ static class StackFrameTraverser extends AbstractStackWalker implements Spliterator { static { stackWalkImplClasses.add(StackFrameTraverser.class); } private static final int CHARACTERISTICS = Spliterator.ORDERED | Spliterator.IMMUTABLE; final Function, ? extends T> function; // callback StackFrameTraverser(StackWalker walker, Function, ? extends T> function) { super(walker, toStackWalkMode(walker, DEFAULT_MODE)); this.function = function; } /** * Returns next StackFrame object in the current batch of stack frames; * or null if no more stack frame. */ StackFrame nextStackFrame() { if (!hasNext()) { return null; } StackFrame frame = frameBuffer.nextStackFrame(); depth++; return frame; } @Override protected T consumeFrames() { checkState(OPEN); Stream stream = StreamSupport.stream(this, false); if (function != null) { return function.apply(stream); } else throw new UnsupportedOperationException(); } @Override protected void initFrameBuffer() { this.frameBuffer = walker.hasOption(Option.DROP_METHOD_INFO) ? new ClassFrameBuffer(walker, getNextBatchSize()) : new StackFrameBuffer<>(StackFrameInfo.class, walker, getNextBatchSize()); } @Override protected int batchSize(int lastBatchSize) { if (lastBatchSize == 0) { // First batch, use estimateDepth if not exceed the large batch size return walker.estimateDepth() == 0 ? SMALL_BATCH : Math.min(walker.estimateDepth() + RESERVED_ELEMENTS, LARGE_BATCH_SIZE); } else { // expand only if last batch was full and the buffer size <= 32 // to minimize the number of unneeded frames decoded. return (lastBatchSize > BATCH_SIZE || !frameBuffer.isFull()) ? lastBatchSize : Math.min(lastBatchSize*2, BATCH_SIZE); } } // ------- Implementation of Spliterator @Override public Spliterator trySplit() { return null; // ordered stream and do not allow to split } @Override public long estimateSize() { return maxDepth; } @Override public int characteristics() { return CHARACTERISTICS; } @Override public void forEachRemaining(Consumer action) { checkState(OPEN); for (int n = 0; n < maxDepth; n++) { StackFrame frame = nextStackFrame(); if (frame == null) break; action.accept(frame); } } @Override public boolean tryAdvance(Consumer action) { checkState(OPEN); int index = frameBuffer.getIndex(); if (hasNext()) { StackFrame frame = nextStackFrame(); action.accept(frame); if (isDebug) { System.err.println("tryAdvance: " + index + " " + frame); } return true; } if (isDebug) { System.err.println("tryAdvance: " + index + " NO element"); } return false; } } static class StackFrameBuffer extends FrameBuffer { final StackWalker walker; private final Class type; private final Constructor ctor; private T[] stackFrames; StackFrameBuffer(Class type, StackWalker walker, int initialBatchSize) { super(initialBatchSize); this.walker = walker; this.type = type; try { this.ctor = type.getDeclaredConstructor(StackWalker.class); } catch (NoSuchMethodException e) { throw new InternalError(e); } this.stackFrames = fill(allocateArray(initialBatchSize), START_POS, initialBatchSize); } @Override T[] frames() { return stackFrames; } @SuppressWarnings("unchecked") T[] allocateArray(int size) { return (T[])Array.newInstance(type, size); } T[] fill(T[] array, int startIndex, int size) { try { for (int i = startIndex; i < size; i++) { array[i] = ctor.newInstance(walker); } } catch (ReflectiveOperationException e) { throw new InternalError(e); } return array; } @Override void resize(int size) { if (!isActive()) throw new IllegalStateException("inactive frame buffer can't be resized"); assert startIndex() == START_POS : "bad start index " + startIndex() + " expected " + START_POS; if (stackFrames.length < size) { T[] newFrames = allocateArray(size); // copy initial magic... System.arraycopy(stackFrames, 0, newFrames, 0, startIndex()); stackFrames = newFrames; } fill(stackFrames, startIndex(), size); currentBatchSize = size; } @Override T nextStackFrame() { if (isEmpty()) { throw new NoSuchElementException("origin=" + origin + " fence=" + fence); } T frame = stackFrames[origin]; origin++; return frame; } @Override final Class at(int index) { return stackFrames[index].declaringClass(); } } /* * Buffer for ClassFrameInfo. It allocates ClassFrameInfo via bytecode * invocation instead of via core reflection to minimize the overhead. */ static class ClassFrameBuffer extends StackFrameBuffer { ClassFrameBuffer(StackWalker walker, int initialBatchSize) { super(ClassFrameInfo.class, walker, initialBatchSize); } @Override ClassFrameInfo[] allocateArray(int size) { return new ClassFrameInfo[size]; } @Override ClassFrameInfo[] fill(ClassFrameInfo[] array, int startIndex, int size) { for (int i = startIndex; i < size; i++) { array[i] = new ClassFrameInfo(walker); } return array; } } /* * CallerClassFinder is specialized to return Class for each stack frame. * StackFrame is not requested. */ static final class CallerClassFinder extends AbstractStackWalker { static { stackWalkImplClasses.add(CallerClassFinder.class); } private Class caller; CallerClassFinder(StackWalker walker) { super(walker, toStackWalkMode(walker, CLASS_INFO_ONLY)); } Class findCaller() { walk(); return caller; } @Override protected Integer consumeFrames() { checkState(OPEN); int n = 0; ClassFrameInfo curFrame = null; // StackWalker::getCallerClass method // 0: caller-sensitive method // 1: caller class ClassFrameInfo[] frames = new ClassFrameInfo[2]; while (n < 2 && hasNext() && (curFrame = frameBuffer.nextStackFrame()) != null) { caller = curFrame.declaringClass(); if (curFrame.isHidden() || isReflectionFrame(caller) || isMethodHandleFrame(caller)) { if (isDebug) System.err.println(" skip: frame " + frameBuffer.getIndex() + " " + curFrame); continue; } frames[n++] = curFrame; } if (isDebug) { System.err.println("0: " + frames[0]); System.err.println("1: " + frames[1]); } if (frames[1] == null) { throw new IllegalCallerException("no caller frame: " + Arrays.toString(frames)); } if (frames[0].isCallerSensitive()) { throw new UnsupportedOperationException("StackWalker::getCallerClass called from @CallerSensitive " + Arrays.toString(frames)); } return n; } /* * Typically finding the caller class only needs to walk two stack frames * 0: StackWalker::getCallerClass * 1: API * 2: caller class * * So start the initial batch size with the minimum size. */ @Override protected void initFrameBuffer() { this.frameBuffer = new ClassFrameBuffer(walker, MIN_BATCH_SIZE); } @Override protected int batchSize(int lastBatchSize) { // this method is only called when the caller class is not found in // the first batch. getCallerClass may be invoked via core reflection. // So increase the next batch size as there may be implementation-specific // frames before reaching the caller class's frame. return SMALL_BATCH; } @Override protected int getNextBatchSize() { return SMALL_BATCH; } } static final class LiveStackInfoTraverser extends StackFrameTraverser { static { stackWalkImplClasses.add(LiveStackInfoTraverser.class); } LiveStackInfoTraverser(StackWalker walker, Function, ? extends T> function) { super(walker, function); } @Override protected void initFrameBuffer() { this.frameBuffer = new StackFrameBuffer<>(LiveStackFrameInfo.class, walker, getNextBatchSize()); } } /* * Frame buffer * * Each specialized AbstractStackWalker subclass may subclass the FrameBuffer. */ abstract static class FrameBuffer { static final int START_POS = RESERVED_ELEMENTS; // buffers for VM to fill stack frame info int currentBatchSize; // current batch size int origin; // index to the current traversed stack frame int fence; // index past the last frame of the current batch FrameBuffer(int initialBatchSize) { if (initialBatchSize < MIN_BATCH_SIZE) { throw new IllegalArgumentException(initialBatchSize + " < minimum batch size: " + MIN_BATCH_SIZE); } this.origin = START_POS; this.fence = 0; this.currentBatchSize = initialBatchSize; } /** * Returns an array of frames that may be used to store frame objects * when walking the stack. * * May be an array of {@code Class} if the {@code AbstractStackWalker} * mode is {@link #CLASS_INFO_ONLY}, or an array of * {@link StackFrameInfo} (or derivative) array otherwise. * * @return An array of frames that may be used to store frame objects * when walking the stack. Must not be null. */ abstract F[] frames(); // must not return null /** * Resizes the buffers for VM to fill in the next batch of stack frames. * *

Subclass may override this method to manage the allocated buffers. * * @param size new batch size * */ abstract void resize(int size); /** * Return the class at the given position in the current batch. * @param index the position of the frame. * @return the class at the given position in the current batch. */ abstract Class at(int index); // ------ subclass may override the following methods ------- /* * Returns the start index for this frame buffer is refilled. * * This implementation reuses the allocated buffer for the next batch * of stack frames. For subclass to retain the fetched stack frames, * it should override this method to return the index at which the frame * should be filled in for the next batch. */ int startIndex() { return START_POS; } /** * Returns next StackFrame object in the current batch of stack frames */ F nextStackFrame() { throw new InternalError("should not reach here"); } // ------ FrameBuffer implementation ------ final int currentBatchSize() { return currentBatchSize; } /* * Tests if this frame buffer is empty. All frames are fetched. */ final boolean isEmpty() { return origin >= fence || (origin == START_POS && fence == 0); } /* * Returns the number of stack frames filled in the current batch */ final int numFrames() { if (!isActive()) throw new IllegalStateException(); return fence - startIndex(); } /* * Freezes this frame buffer. The stack stream source is done fetching. */ final void freeze() { origin = 0; fence = 0; } /* * Tests if this frame buffer is active. It is inactive when * it is done for traversal. */ final boolean isActive() { return origin > 0; } final boolean isFull() { return fence == currentBatchSize; } /* * Tests if this frame buffer is at the end of the stack * and all frames have been traversed. */ final boolean isAtBottom() { return origin > 0 && origin >= fence && fence < currentBatchSize; } /** * Gets the class at the current frame and move to the next frame. */ final Class next() { if (isEmpty()) { throw new NoSuchElementException("origin=" + origin + " fence=" + fence); } Class c = at(origin); origin++; if (isDebug) { int index = origin-1; System.out.format(" next frame at %d: %s (origin %d fence %d)%n", index, c.getName(), index, fence); } return c; } /** * Gets the class at the current frame. */ final Class get() { if (isEmpty()) { throw new NoSuchElementException("origin=" + origin + " fence=" + fence); } return at(origin); } /* * Returns the index of the current frame. */ final int getIndex() { return origin; } /* * Set a new batch of stack frames that have been filled in this frame buffer. */ final void setBatch(int depth, int startIndex, int numFrames) { if (startIndex <= 0 || numFrames < 0) throw new IllegalArgumentException("startIndex=" + startIndex + " numFrames=" + numFrames); this.origin = startIndex; this.fence = startIndex + numFrames; for (int i = startIndex; i < fence; i++) { if (isDebug) System.err.format(" frame %d: %s%n", i, at(i)); if (depth == 0 && filterStackWalkImpl(at(i))) { // filter the frames due to the stack stream implementation origin++; } else { break; } } } /* * Checks if the origin is the expected start index. */ final void check(int skipFrames) { int index = skipFrames + START_POS; if (origin != index) { // stack walk must continue with the previous frame depth throw new IllegalStateException("origin " + origin + " != " + index); } } } private static native boolean checkStackWalkModes(); // avoid loading other subclasses as they may not be used private static Set> init() { if (!checkStackWalkModes()) { throw new InternalError("StackWalker mode values do not match with JVM"); } Set> classes = new HashSet<>(); classes.add(StackWalker.class); classes.add(StackStreamFactory.class); classes.add(AbstractStackWalker.class); return classes; } private static boolean filterStackWalkImpl(Class c) { return stackWalkImplClasses.contains(c) || c.getPackageName().equals("java.util.stream"); } // MethodHandle frames are not hidden and CallerClassFinder has // to filter them out private static boolean isMethodHandleFrame(Class c) { return c.getPackageName().equals("java.lang.invoke"); } private static boolean isReflectionFrame(Class c) { return c == Method.class || c == Constructor.class || MethodAccessor.class.isAssignableFrom(c) || ConstructorAccessor.class.isAssignableFrom(c); } }