RFR: 8311500: StackWalker.getCallerClass() can throw if invoked reflectively [v2]

Volker Simonis simonis at openjdk.org
Tue Jul 11 15:48:13 UTC 2023


On Wed, 5 Jul 2023 17:25:24 GMT, Volker Simonis <simonis at openjdk.org> wrote:

>> As the included jtreg test demonstrates, `StackWalker.getCallerClass()` can throw an `UnsupportedOperationException` if called reflectively. Currently this only happens if we invoke `StackWalker.getCallerClass()` recursively reflectively, but this issue will become more prominent once we fix [JDK-8285447](https://bugs.openjdk.org/browse/JDK-8285447). The gory details follow below:
>> 
>> The protocol between the Java API and the JVM for `StackWalker.getCallerClass()/walk()` is as follows:
>> - On the Java side, `StackWalker` calls into `StackStreamFactory` for the real work.
>> - For `StackWalker.getCallerClass()` `StackStreamFactory` basically creates a `Class[]` which will be passed down and filled in the JVM. For `StackWalker.walk()` it will normally be a `StackFrameInfo[]` (or a `LiveStackFrameInfo[]` if the internal `ExtendedOption.LOCALS_AND_OPERANDS` option was used).
>> - The default size of this arrays is currently `StackStreamFactory.SMALL_BATCH` which is 8 (but see [JDK-8285447](https://bugs.openjdk.org/browse/JDK-8285447)).
>> - `StackStreamFactory` than calls `AbstractStackWalker.callStackWalk()` which is a natively implemented in the VM by `JVM_CallStackWalk()`.
>> -  `JVM_CallStackWalk()` calls `StackWalk::walk()` which calls `StackWalk::fetchFirstBatch()` which calls `StackWalk::fill_in_frames()` which walks the stack and fills in the available class/stackframe slots in the passed in array until the array is full or there are no more stack frames,
>> - Once  `StackWalk::fill_in_frames()` returns, `StackWalk::fetchFirstBatch()` calls back to Java by invoking `AbstractStackWalker::doStackWalk()` to consume the result.
>> - `AbstractStackWalker::doStackWalk()` calls `consumeFrames()` (which is overridden depending on whether we initially called `getCallerClass()` or `walk()`) which consumes the frames until it either finishes (e.g. finds the caller class) or until there are no more frames.
>> - In the latter case `consumeFrames()` will call into the the VM again by calling `AbstractStackWalker.fetchStackFrames()` to fetch additional frames from the stack.
>> - `AbstractStackWalker.fetchStackFrames()` is implemented by `JVM_MoreStackWalk()` which calls `StackWalk::fetchNextBatch()` which calls `StackWalk::fill_in_frames()` (the same method that already fetched the initial batch of frames).
>> 
>> Following is a stacktrace of what I've explained so far:
>> 
>> Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
>> V  [libjvm.so+0x1...
>
> Volker Simonis has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Rename new parameter according to the HS coding conventions

As discussed before, the test for a `@CallerSensitive` method must be applied to the first non-reflective, non-methodhandle frame below `StackWalker::getCallerClass()`, independent from the batch in which it appears. Because we can not predict which batch this will be, I've removed the previous solution which added a special flag to distinguish the first from the follow up batches. Instead I've added a new flag `_needs_caller_sensitive_check` to the `BaseFrameStream` class because every invocation of `StackWalker::getCallerClass()` will always use a single instance of `BaseFrameStream`.

Currently the check for `@CallerSensitive` methods can only be done in the VM because for `StackWalker::getCallerClass()` we only pass the `Class` of each stack frame back to Java but not the corresponding `Method`. On the other hand, we currently skip reflective and methodhandle frames in Java (i.e. with `isReflectionFrame()` and `isMethodHandleFrame()`). In order to avoid the need to pass the stack frame methods back to Java I've therefore implemented (and combined) the the check for reflective and methodhandle frames in the VM (see `is_reflection_or_methodhandle_frame()`). Because `isMethodHandleFrame()` was only used in `ClassBuffer::consumeFrames()` it can be removed. I think we can also skip the remaining test for reflective frames in `AbstractStackWalker::peekFrame()` for the `getCallerClass()` case (because that check is now already done in the VM) but I'll leave that for [JDK-8285447](https://bugs.openjdk.org/browse/JDK-8285447).

I've also moved the `ReflectiveGetCallerClassTest.java` test under `java/lang/StackWalker/CallerSensitiveMethod` such that I can use the `java/util/CSM.java` class which gets injected into the base module for the new `@CallerSensitive` test.

Finally I realized that some `StackWalker` tests fail if we run with `-javaoption:-XX:+ShowHiddenFrames`. Ideally these tests should be fixed to make them agnostic to `ShowHiddenFrames` but for now I've added a `@requires vm.opt.ShowHiddenFrames == false` tag to exclude them if ran with `-XX:+ShowHiddenFrames`.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/14773#issuecomment-1631065562


More information about the core-libs-dev mailing list