Code Review for JEP 259: Stack-Walking API

Peter Levart peter.levart at gmail.com
Tue Nov 17 20:57:28 UTC 2015


Hi Mandy,

On 11/17/2015 01:13 AM, Mandy Chung wrote:
> I’d like to get the code review done by this week.
>
> I renamed the static factory method from create to getInstance since “create” implies to create a new instance but the method returns a cached instance instead.  I also changed the spec of getCallerClass per [1].  There is not much change since webrev.01.
>
> Webrev:
>     http://cr.openjdk.java.net/~mchung/jdk9/jep259/webrev.02
>
> javadoc:
>     http://cr.openjdk.java.net/~mchung/jdk9/jep259/api/
>
> Mandy
> [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-November/036589.html

Just read the javadoc so-far...

There are several mistakes in getCallerClass() API Note (some probably a 
left-over from previous iterations of the API):

  471      * <p> If this {@code getCallerClass} method is called by the 
entry point
  472      * of a thread, the {@code Class} of the method invoked at 
thread's start
  473      * time will be returned.

Hm, I can't decipher that. What about something like:

If this {@code getCallerClass} method is called by the entry point of a 
thread - a method overriding {@link Thread#run} (that's the only 
possibility),
the declaring class of the method overriding {@link Thread#run} will be 
returned.

Or, if you accept my latest suggestion:

If this {@code getCallerClass} method is called by the entry point of a 
thread - a method overriding {@link Thread#run},
{@link IllegalArgumentException} will be thrown.


  474      *
  475      * @apiNote
  476      * For example, {@code ResourceBundleUtil::getBundle} loads a 
resource bundle
  477      * on behalf of the caller.  It calls this {@code 
getCallerClass} method
  478      * to find the method calling {@code Util::getResourceBundle} 
and use the caller's
  479      * class loader to load the resource bundle. The caller class 
in this example
  480      * is the {@code MyTool} class.
  481      *
  482      * <pre>{@code
  483      *     class ResourceBundleUtil {
  484      *         private final StackWalker walker = 
StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
  485      *         public ResourceBundle getBundle(String bundleName) {
  486      *             Class<?> caller = walker.getCallerClass();
  487      *             return ResourceBundle.getBundle(bundleName, 
caller.getClassLoader());
  488      *         }
  489      *     }
  490      *
  491      *     class MyTool {
  492      *         private void init() {
  493      *             ResourceBundle rb = 
Util.getResourceBundle("mybundle");
  494      *         }
  495      *     }
  496      * }</pre>

- threre's no ResourceBundle.getBundle(String, ClassLoader) method.
- Util -> ResourceBundleUtil (or ResourceBundleUtil -> Util)


  497      *
  498      * An equivalent way to find the caller class using the
  499      * {@link StackWalker#walk walk} method is as follows
  500      * (filtering the reflection frames, {@code MethodHandle} and 
hidden frames
  501      * not shown below):
  502      * <pre>{@code
  503      *     Class<?> caller = walker.walk(s ->
  504      *         s.map(StackFrame::getDeclaringClass)
  505      *          .skip(2)
  506      *          .findFirst());
  507      * }</pre>

- Stream.findFirst() returns Optional<E>, not E.

  508      *
  509      * When the {@code getCallerClass} method is invoked from 
thread {@code t}'s
  510      * entry point, i.e. {@code PrimeRun::run}, it returns {@code 
PrimeRun} class
  511      * that is the first stack frame below the stack frame for 
{@code getCallerClass}
  512      * instead.
  513      *
  514      * <pre>{@code
  515      *     class PrimeRun implements Runnable {
  516      *         public void run() {
  517      *             Class<?> c = 
StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
  518      *                                     .getCallerClass();
  519      *         }
  520      *     }
  521      *     Thread t = new Thread(new PrimeRun()).start();
  522      * }</pre>
  523      *
  524      * Similarly, when the {@code getCallerClass} method is called 
from the
  525      * {@code static public void main} method launched by the 
{@code java} launcher,
  526      * it returns the {@code Class} of the {@code main} method.
  527      *
  528      * @return {@code Class} object of the caller's caller 
invoking this method;
  529      * or the {@code Class} object of the method invoked by a 
thread at start time.

- In above example, t's entry point is Thread::run (not PrimeRun::run). 
Thread::run then delegates to PrimeRun::run:

public class Thread {
     ...
     public void run() {
         if (target != null) {
             target.run();
         }
     }


...so this example is not really suitable to describe the effect of 
invoking getCallerClass from thread's entry-point.

An example of that kind would be something like:

class PrimeThread extends Thread {
     @Override public void run() {
         Class<?> c = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
                                     .getCallerClass();
     }
}

Thread t = new PrimeThread().start();


There are only three situations to consider:

- the method overriding Thread::run, called as thread's entry-point
- the static main method, called by java launcher
- any method called by JNI from newly attached thread

The last is special - It's not any method called by JNI. If Java calls a 
native method and that native method calls-back to Java via JNI, the 
method calling native method will be on the stack and getCallerClass() 
invoked from the called-back method will return it (this can be seen 
when observing reflection calls that go through 
sun.reflect.NativeMethodAccessorImpl).


Regards, Peter




More information about the core-libs-dev mailing list