JDK-8043814 : JEP 259: Stack-Walking API
  • Type: JEP
  • Component: core-libs
  • Priority: P3
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 9
  • Submitted: 2014-05-22
  • Updated: 2017-07-18
  • Resolved: 2016-06-22
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8140450 :  
Description
Summary
-------

Define an efficient standard API for stack walking that allows easy
filtering of, and lazy access to, the information in stack traces.

Non-Goal
---------

   - It is not a goal to convert all existing stack walking code in the JDK to use this new API.

Motivation
----------

There is no standard API to traverse selected frames on the execution
stack efficiently and access the `Class` instance of each frame.

There are existing APIs that provide access to a thread's stack:

   - `Throwable::getStackTrace` and `Thread::getStackTrace` return an
     array of `StackTraceElement` objects, which contain the class name
     and method name of each stack-trace element.

   - `SecurityManager::getClassContext` is a protected method, which
     allows a `SecurityManager` subclass to access the class context.

These APIs require the VM to eagerly capture a snapshot of the entire
stack, and they return information representing the entire stack.  There
is no way to avoid the cost of examining all the frames if the caller is
only interested in the top few frames on the stack.  Both the
`Throwable::getStackTrace` and `Thread::getStackTrace` methods return an
array of `StackTraceElement` objects, which contain class names and
method names but not the actual `Class` instances.  For applications
interested in the entire stack, the specification allows the VM
implementation to omit some frames in the stack for performance.  In
other words, `Thread::getStackTrace` may return a partial stack trace.

These APIs do not satisfy the use cases that currently depend upon the
JDK-internal `sun.reflect.Reflection::getCallerClass` method, or else
their performance overhead is intolerable.  These use cases include:

  - Walk the stack until the immediate caller's class is found.  Every
    JDK caller-sensitive API looks up its immediate caller's class in
    order to determine the behavior of the API.  For example, the
    `Class::forName` and `ResourceBundle::getBundle` methods use the
    immediate caller's class loader to load a class and a resource bundle
    respectively.  Reflective APIs such as `Class::getMethod` use the
    immediate caller's class loader to determine the security checks to
    be performed.

  - Walk the stack, filtering out the stack frames of specific
    implementation classes to find the first non-filtered frame.  The
    `java.util.logging` API, Log4j, and the Groovy runtime filter
    intermediate stack frames (typically implementation-specific and
    reflection frames) to find the caller's class.

  - Walk the stack to find all protection domains, until the first
    privileged frame is reached.  This is required in order to do
    permission checks.

  - Walk the entire stack, possibly with a depth limit.  This is required
    to generate the stack trace of any `Throwable` object, and to
    implement the `Thread::dumpStack` method.


Description
-----------

This JEP will define a stack-walking API that allows laziness and frame
filtering, supports short walks that stop at a frame matching given
criteria, and also supports long walks that traverse the entire stack.

The JVM will be enhanced to provide a flexible mechanism to traverse and
materialize the required stack-frame information and allow efficient lazy
access to additional stack frames when required.  Native JVM transitions
will be minimized.  The implementation will need to have a stable view of
a thread's stack: Returning a stream holding a stack pointer for further
manipulation in an uncontrolled manner will not work since, as soon as
the stream factory returns, the JVM will be free to reorganize the
control stack (via deoptimization, for example).  This will influence the
API's definition.

The API will specify its behavior when running with a security manager,
so that access to the `Class` objects in stack frames do not compromise
security.  

The proposal is to define a capability-based `StackWalker` API
to traverse the stack.  The security permission check will be performed
on each `StackWalker` object when it is constructed rather than each time
it is used.  It will define the following methods:

    public <T> T walk(Function<Stream<StackFrame>, T> function);
    public Class<?> getCallerClass();

The `walk` method opens a sequential stream of `StackFrame` for the current thread
and then applies the function with the `StackFrame` stream.
The spliterator of the stream performs the stack frame traversal in an ordered manner.
The `Stream<StackFrame>` object can only be traversed once and will be closed 
when the `walk` method returns.  The stream becomes invalid to use once it is closed.
For example, to find the first caller filtering a known list of implementation class:

    Optional<Class<?>> frame = new StackWalker().walk((s) ->
    {
        s.filter(f -> interestingClasses.contains(f.getDeclaringClass()))
         .map(StackFrame::getDeclaringClass)
         .findFirst();
    }); 

To snapshot the stack trace of the current thread,

    List<StackFrame> stack =
         new StackWalker().walk((s) -> s.collect(Collectors.toList())); 

The `getCallerClass()` method is for convenience to find the caller's frame and is
the replacement for `sun.reflect.Reflection.getCallerClass`.   An equivalent way to get the caller class using the `walk` method is:

    walk((s) -> s.map(StackFrame::declaringClass).skip(2).findFirst());

Alternatives
------------

An alternative API choice would be for the `walk` method to return `Stream<StackFrame>`.
Such an alternative will not work as the returned stream object may be used in an uncontrolled manner
for further manipulation.  When a stream of stack frames is created, as soon as 
the stream factory returns,  the JVM is free to reorganize the control stack (via deoptimization, for example)
and there is no robust way to detect if the stack has been mutated.   

Instead, similar to `AccessController::doPrivileged`, at least one native method must be 
created which will establish its own stack frame and then provide controlled access 
to the JVM's stack walking logic, for older frames. 
When this native method returns, that capability must be deactivated, or else made inaccessible 
in some other way.  In this way, we can do efficient lazy access to stack frames, on a stable view
of the thread's own control stack.

Comments
Updating Scope to "SE" to accurately reflect impact to Java SE APIs (new java.lang.*Stack*, etc.).
08-12-2016

All subtasks are completed. This JEP is now closed.
22-06-2016

The implementation for this JEP has been pushed to jdk9/hs-rt. http://hg.openjdk.java.net/jdk9/hs-rt/jdk/rev/94838afd5e5b http://hg.openjdk.java.net/jdk9/hs-rt/hotspot/rev/f671d5510375
23-11-2015

Javadoc of the proposed StackWalker API is: http://cr.openjdk.java.net/~mchung/jdk9/jep259/api/java/lang/StackWalker.html JEP 259 defines the StackWalker API and provides the replacement of sun.reflect.Reflection.getCallerClass(int) and getCallerClass() methods. JEP 259 replaces java.util.logging, Thread::dumpThread and Thread::getStackTrace to use StackWalker API. JDK-8141239 is separate from this JEP to enable Throwable's backtrace to use the StackWalker API as Throwable is performance sensitive and requires performance work and may also require works from the hotspot team.
05-11-2015