8268829: Provide an optimized way to walk the stack with Class object only

8210375: StackWalker::getCallerClass throws UnsupportedOperationException

Reviewed-by: coleenp, dfuchs, bchristi
This commit is contained in:
Mandy Chung 2023-09-07 21:37:40 +00:00
parent 716201c77d
commit 111ecdbaf5
34 changed files with 982 additions and 622 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* 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
@ -30,8 +30,10 @@ 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;
@ -74,10 +76,10 @@ final class StackStreamFactory {
// These flags must match the values maintained in the VM
@Native private static final int DEFAULT_MODE = 0x0;
@Native private static final int FILL_CLASS_REFS_ONLY = 0x2;
@Native private static final int GET_CALLER_CLASS = 0x4;
@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
@ -89,10 +91,11 @@ final class StackStreamFactory {
static <T> StackFrameTraverser<T>
makeStackTraverser(StackWalker walker, Function<? super Stream<StackFrame>, ? extends T> function)
{
if (walker.hasLocalsOperandsOption())
if (walker.hasLocalsOperandsOption()) {
return new LiveStackInfoTraverser<>(walker, function);
else
} else {
return new StackFrameTraverser<>(walker, function);
}
}
/**
@ -108,6 +111,17 @@ final class StackStreamFactory {
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.
@ -124,7 +138,7 @@ final class StackStreamFactory {
protected final StackWalker walker;
protected final Thread thread;
protected final int maxDepth;
protected final long mode;
protected final int mode;
protected int depth; // traversed stack depth
protected FrameBuffer<? extends T> frameBuffer;
protected long anchor;
@ -137,7 +151,7 @@ final class StackStreamFactory {
}
protected AbstractStackWalker(StackWalker walker, int mode, int maxDepth) {
this.thread = Thread.currentThread();
this.mode = toStackWalkMode(walker, mode);
this.mode = mode;
this.walker = walker;
this.maxDepth = maxDepth;
this.depth = 0;
@ -151,16 +165,6 @@ final class StackStreamFactory {
}
}
private int toStackWalkMode(StackWalker walker, int mode) {
int newMode = mode;
if (walker.hasOption(Option.SHOW_HIDDEN_FRAMES) &&
(mode & FILL_CLASS_REFS_ONLY) != FILL_CLASS_REFS_ONLY)
newMode |= SHOW_HIDDEN_FRAMES;
if (walker.hasLocalsOperandsOption())
newMode |= FILL_LIVE_STACK_FRAMES;
return newMode;
}
/**
* 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).
@ -450,11 +454,11 @@ final class StackStreamFactory {
* @param continuation the continuation to walk, or {@code null} if walking a thread.
* @param batchSize the batch size, max. number of elements to be filled in the frame buffers.
* @param startIndex start index of the frame buffers to be filled.
* @param frames Either a Class<?> array, if mode is {@link #FILL_CLASS_REFS_ONLY}
* @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(long mode, int skipframes,
private native R callStackWalk(int mode, int skipframes,
ContinuationScope contScope, Continuation continuation,
int batchSize, int startIndex,
T[] frames);
@ -466,12 +470,12 @@ final class StackStreamFactory {
* @param anchor
* @param batchSize the batch size, max. number of elements to be filled in the frame buffers.
* @param startIndex start index of the frame buffers to be filled.
* @param frames Either a Class<?> array, if mode is {@link #FILL_CLASS_REFS_ONLY}
* @param frames Either a {@link ClassFrameInfo} array, if mode is {@link #CLASS_INFO_ONLY}
* or a {@link StackFrameInfo} (or derivative) array otherwise.
*
* @return the end index to the frame buffers
*/
private native int fetchStackFrames(long mode, long anchor,
private native int fetchStackFrames(int mode, long anchor,
int batchSize, int startIndex,
T[] frames);
@ -483,7 +487,7 @@ final class StackStreamFactory {
*
* This class implements Spliterator::forEachRemaining and Spliterator::tryAdvance.
*/
static class StackFrameTraverser<T> extends AbstractStackWalker<T, StackFrameInfo>
static class StackFrameTraverser<T> extends AbstractStackWalker<T, StackFrame>
implements Spliterator<StackFrame>
{
static {
@ -491,70 +495,11 @@ final class StackStreamFactory {
}
private static final int CHARACTERISTICS = Spliterator.ORDERED | Spliterator.IMMUTABLE;
final class StackFrameBuffer extends FrameBuffer<StackFrameInfo> {
private StackFrameInfo[] stackFrames;
StackFrameBuffer(int initialBatchSize) {
super(initialBatchSize);
this.stackFrames = new StackFrameInfo[initialBatchSize];
for (int i = START_POS; i < initialBatchSize; i++) {
stackFrames[i] = new StackFrameInfo(walker);
}
}
@Override
StackFrameInfo[] frames() {
return stackFrames;
}
@Override
void resize(int startIndex, int elements) {
if (!isActive())
throw new IllegalStateException("inactive frame buffer can't be resized");
assert startIndex == START_POS :
"bad start index " + startIndex + " expected " + START_POS;
int size = startIndex+elements;
if (stackFrames.length < size) {
StackFrameInfo[] newFrames = new StackFrameInfo[size];
// copy initial magic...
System.arraycopy(stackFrames, 0, newFrames, 0, startIndex);
stackFrames = newFrames;
}
for (int i = startIndex; i < size; i++) {
stackFrames[i] = new StackFrameInfo(walker);
}
currentBatchSize = size;
}
@Override
StackFrameInfo nextStackFrame() {
if (isEmpty()) {
throw new NoSuchElementException("origin=" + origin + " fence=" + fence);
}
StackFrameInfo frame = stackFrames[origin];
origin++;
return frame;
}
@Override
final Class<?> at(int index) {
return stackFrames[index].declaringClass();
}
}
final Function<? super Stream<StackFrame>, ? extends T> function; // callback
StackFrameTraverser(StackWalker walker,
Function<? super Stream<StackFrame>, ? extends T> function) {
this(walker, function, DEFAULT_MODE);
}
StackFrameTraverser(StackWalker walker,
Function<? super Stream<StackFrame>, ? extends T> function,
int mode) {
super(walker, mode);
super(walker, toStackWalkMode(walker, DEFAULT_MODE));
this.function = function;
}
@ -567,7 +512,7 @@ final class StackStreamFactory {
return null;
}
StackFrameInfo frame = frameBuffer.nextStackFrame();
StackFrame frame = frameBuffer.nextStackFrame();
depth++;
return frame;
}
@ -584,7 +529,9 @@ final class StackStreamFactory {
@Override
protected void initFrameBuffer() {
this.frameBuffer = new StackFrameBuffer(getNextBatchSize());
this.frameBuffer = walker.hasOption(Option.DROP_METHOD_INFO)
? new ClassFrameBuffer(walker, getNextBatchSize())
: new StackFrameBuffer<>(StackFrameInfo.class, walker, getNextBatchSize());
}
@Override
@ -651,11 +598,108 @@ final class StackStreamFactory {
}
}
static class StackFrameBuffer<T extends ClassFrameInfo> extends FrameBuffer<T> {
final StackWalker walker;
private final Class<T> type;
private final Constructor<T> ctor;
private T[] stackFrames;
StackFrameBuffer(Class<T> 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 startIndex, int elements) {
if (!isActive())
throw new IllegalStateException("inactive frame buffer can't be resized");
assert startIndex == START_POS :
"bad start index " + startIndex + " expected " + START_POS;
int size = startIndex+elements;
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<ClassFrameInfo> {
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<Integer, Class<?>> {
static final class CallerClassFinder extends AbstractStackWalker<Integer, ClassFrameInfo> {
static {
stackWalkImplClasses.add(CallerClassFinder.class);
}
@ -663,52 +707,7 @@ final class StackStreamFactory {
private Class<?> caller;
CallerClassFinder(StackWalker walker) {
super(walker, FILL_CLASS_REFS_ONLY|GET_CALLER_CLASS);
}
static final class ClassBuffer extends FrameBuffer<Class<?>> {
Class<?>[] classes; // caller class for fast path
ClassBuffer(int batchSize) {
super(batchSize);
classes = new Class<?>[batchSize];
}
@Override
Class<?>[] frames() { return classes;}
@Override
final Class<?> at(int index) { return classes[index];}
// ------ subclass may override the following methods -------
/**
* Resizes the buffers for VM to fill in the next batch of stack frames.
* The next batch will start at the given startIndex with the maximum number
* of elements.
*
* <p> Subclass may override this method to manage the allocated buffers.
*
* @param startIndex the start index for the first frame of the next batch to fill in.
* @param elements the number of elements for the next batch to fill in.
*
*/
@Override
void resize(int startIndex, int elements) {
if (!isActive())
throw new IllegalStateException("inactive frame buffer can't be resized");
assert startIndex == START_POS :
"bad start index " + startIndex + " expected " + START_POS;
int size = startIndex+elements;
if (classes.length < size) {
// copy the elements in classes array to the newly allocated one.
// classes[0] is a Thread object
Class<?>[] prev = classes;
classes = new Class<?>[size];
System.arraycopy(prev, 0, classes, 0, startIndex);
}
currentBatchSize = size;
}
super(walker, toStackWalkMode(walker, CLASS_INFO_ONLY));
}
Class<?> findCaller() {
@ -720,25 +719,37 @@ final class StackStreamFactory {
protected Integer consumeFrames() {
checkState(OPEN);
int n = 0;
Class<?>[] frames = new Class<?>[2];
// skip the API calling this getCallerClass method
// 0: StackWalker::getCallerClass
// 1: caller-sensitive method
// 2: caller class
while (n < 2 && (caller = nextFrame()) != null) {
if (isMethodHandleFrame(caller)) { continue; }
if (isReflectionFrame(caller)) { continue; }
frames[n++] = caller;
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");
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;
}
@Override
protected void initFrameBuffer() {
this.frameBuffer = new ClassBuffer(getNextBatchSize());
this.frameBuffer = new ClassFrameBuffer(walker, getNextBatchSize());
}
@Override
@ -756,70 +767,15 @@ final class StackStreamFactory {
static {
stackWalkImplClasses.add(LiveStackInfoTraverser.class);
}
// VM will fill in all method info and live stack info directly in StackFrameInfo
final class LiveStackFrameBuffer extends FrameBuffer<LiveStackFrameInfo> {
private LiveStackFrameInfo[] stackFrames;
LiveStackFrameBuffer(int initialBatchSize) {
super(initialBatchSize);
this.stackFrames = new LiveStackFrameInfo[initialBatchSize];
for (int i = START_POS; i < initialBatchSize; i++) {
stackFrames[i] = new LiveStackFrameInfo(walker);
}
}
@Override
LiveStackFrameInfo[] frames() {
return stackFrames;
}
@Override
void resize(int startIndex, int elements) {
if (!isActive()) {
throw new IllegalStateException("inactive frame buffer can't be resized");
}
assert startIndex == START_POS :
"bad start index " + startIndex + " expected " + START_POS;
int size = startIndex + elements;
if (stackFrames.length < size) {
LiveStackFrameInfo[] newFrames = new LiveStackFrameInfo[size];
// copy initial magic...
System.arraycopy(stackFrames, 0, newFrames, 0, startIndex);
stackFrames = newFrames;
}
for (int i = startIndex(); i < size; i++) {
stackFrames[i] = new LiveStackFrameInfo(walker);
}
currentBatchSize = size;
}
@Override
LiveStackFrameInfo nextStackFrame() {
if (isEmpty()) {
throw new NoSuchElementException("origin=" + origin + " fence=" + fence);
}
LiveStackFrameInfo frame = stackFrames[origin];
origin++;
return frame;
}
@Override
final Class<?> at(int index) {
return stackFrames[index].declaringClass();
}
}
LiveStackInfoTraverser(StackWalker walker,
Function<? super Stream<StackFrame>, ? extends T> function) {
super(walker, function, DEFAULT_MODE);
super(walker, function);
}
@Override
protected void initFrameBuffer() {
this.frameBuffer = new LiveStackFrameBuffer(getNextBatchSize());
this.frameBuffer = new StackFrameBuffer<>(LiveStackFrameInfo.class, walker, getNextBatchSize());
}
}
@ -851,7 +807,7 @@ final class StackStreamFactory {
* when walking the stack.
*
* May be an array of {@code Class<?>} if the {@code AbstractStackWalker}
* mode is {@link #FILL_CLASS_REFS_ONLY}, or an array of
* 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
@ -1021,22 +977,20 @@ final class StackStreamFactory {
private static boolean filterStackWalkImpl(Class<?> c) {
return stackWalkImplClasses.contains(c) ||
c.getName().startsWith("java.util.stream.");
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.getName().startsWith("java.lang.invoke.");
return c.getPackageName().equals("java.lang.invoke");
}
private static boolean isReflectionFrame(Class<?> c) {
// ## should filter all @Hidden frames?
return c == Method.class ||
c == Constructor.class ||
MethodAccessor.class.isAssignableFrom(c) ||
ConstructorAccessor.class.isAssignableFrom(c) ||
c.getName().startsWith("java.lang.invoke.LambdaForm");
ConstructorAccessor.class.isAssignableFrom(c);
}
}