Proposed API for JEP 259: Stack-Walking API

Paul Sandoz paul.sandoz at oracle.com
Tue Nov 3 11:28:53 UTC 2015


Hi Mandy,

This is very nice work.

Comments below, mostly minor stuff.


PlatformLogger.java
(and similar comments for duplicated code in LogRecord.java)
—

 542             static final StackWalker stackWalker;

Use capitals for static variable.


 556             private boolean lookingForLogger = true;
 557             /**

Missing line-feed.


 588         private String getCallerInfo() {
 589             Optional<StackWalker.StackFrame> frame = new CallerFinder().get();
 590             if (frame.isPresent()) {
 591                 StackWalker.StackFrame f = frame.get();
 592                 return f.getClassName() + " " + f.getMethodName();
 593             } else {
 594                 return name;
 595             }
 596         }


Consider using:

  return frame.map(f -> f.getClassName() + " " + f.getMethodName()).orElse(name);

You might want to highlight that CallerFinder is a stateful predicate.


LogRecord.java
—

 642         // Skip all frames until we have found the first logger frame.
 643         Optional<StackWalker.StackFrame> frame = new CallerFinder().get();
 644         if (frame.isPresent()) {
 645             StackWalker.StackFrame f = frame.get();
 646             setSourceClassName(f.getClassName());
 647             setSourceMethodName(f.getMethodName());
 648         }
 649         // We haven't found a suitable frame, so just punt.  This is
 650         // OK as we are only committed to making a "best effort" here.
 651     }

Consider using:

  frame.ifPresent(f -> {
    setSourceClassName(f.getClassName());
    setSourceMethodName(f.getMethodName());
  });


LiveStackFrame
—

  41     /**
  42      * Return the monitors held by this stack frames. This method returns
  43      * an empty array if no monitor is held by this stack frame.
  44      *
  45      * @return the monitors held by this stack frames
  46      */
  47     public Object[] getMonitors();

s/this stack frames/stack frame

Can the contents of the array be modified?


  49     /**
  50      * Gets the local variable array of this stack frame.
  51      *
  52      * <p>A single local variable can hold a value of type boolean, byte, char,
  53      * short, int, float, reference or returnAddress.  A pair of local varaibles
  54      * can hold a value of type long or double.  In other words,
  55      * a value of type long or type double occupies two consecutive local
  56      * variables.  For a value of primitive type, the element in the
  57      * local variable array is an {@link PrimitiveValue} object;
  58      * otherwise, the element is an {@code Object}.
  59      *
  60      * <p>A value of type long or double will be represented by
  61      * a {@code PrimitiveValue} object containing the value in the elements
  62      * at index <em>n</em> and <em>n</em>+1 in the local variable array.
  63      *
  64      * @return  the local variable array of this stack frame.
  65      */
  66     public Object[] getLocals();

s/varaibles/variables

(double slots are a PITA!)


StackStream
—

Suggest using something other than Stream in the name is this is really about Spliterator and traversal e.g. perhaps StackTraverser. Same for sub-types of StackStream.

 494     @Override
 495     public void forEachRemaining(Consumer<? super StackFrame> action) {
 496         throw new UnsupportedOperationException("Specialized StackWalker");
 497     }
 498
 499     @Override
 500     public boolean tryAdvance(Consumer<? super StackFrame> action) {
 501         throw new UnsupportedOperationException("Specialized StackWalker");
 502     }

Is this required?


StackWalker
—

  46  * The {@linkplain Stream#spliterator() spliterator} of the stream performs
  47  * the stack frame traversal in an {@linkplain Spliterator#ORDERED ordered} manner.

It’s probably best not to mention Spliterator. Suggest

  the stream reports stack frame elements in order, from the top most frame that represents the execution point at
  which the stack was generated to the bottom most frame...


  64  * <p> <em>Examples</em>

Use an @apiNote


  82 public final class StackWalker {
  83     /**
  84      * A {@code StackFrame} object represent a method invocation returned by
  85      * {@link StackWalker}.


s/object represent/object represents


  87      * <p> {@code StackFrame} objects returned by {@link StackWalker} may not implement
  88      * the {@link #getDeclaringClass()} method
  89      * depending on the requested {@linkplain Option stack walking options}.

Rather than "may not implement” suggest something like:

  The getDeclaring class method may be unsupported as determined by the stack walking options of a stack walker.


 118          * @throws UnsupportedOperationException if this {@code StackFrame}
 119          *         does not implement this method

if this StackFrame does not support… as determined by …


 137         public String getFileName();
 138         /**

Missing line-feed.


 151         /**
 152          * Returns true if the method containing the execution point
 153          * represented by this stack frame is a native method.
 154          *
 155          * @return {@code true} if the method containing the execution point
 156          *         represented by this stack frame is a native method.
 157          */
 158         public boolean isNativeMethod();

Consistently use {@code true}


 160         /**
 161          * Gets a {@code StackTraceElement} for this stack frame.
 162          *
 163          * @apiNote This method does not seem to be needed as user could simply
 164          * use StackFrame methods directly.
 165          *
 166          * @return {@code StackTraceElement} for this stack frame.
 167          *
 168          * */
 169         public default StackTraceElement asStackTraceElement() {

I guess that @apiNote is a TODO? Perhaps remove it if you think it is not needed, unless it is useful for compatibility reasons, it might be e.g. consider map(StackFrame::asStackTraceElement)



 238     private final List<? extends WalkerOption> options;

Do you require the bound here?


 241     /**
 242      * Gets a {@code StackWalker} instance.
 243      *
 244      * <p> This {@code StackWalker} obtains the method name and the class name
 245      * with all {@linkplain StackWalker.Option#SHOW_HIDDEN_FRAMES hidden frames}
 246      * skipped.
 247      */
 248     public StackWalker() {

s/Gets/Creates

Same for other constructor methods.


 252     /**
 253      * Gets a {@code StackWalker} instance with the given options specifying
 254      * the stack frame information it can access.  If no option is specified, this
 255      * {@code StackWalker} obtains the method name and the class name
 256      * with all {@linkplain StackWalker.Option#SHOW_HIDDEN_FRAMES hidden frames}
 257      * skipped.

If no options are given…

It might be easier to state as if the options were {CLASS_REFERENCE, SHOW_REFLECT_FRAMES}, perhaps it depends if the default is someone under specified i.e. is it guaranteed that the option set will be the complement of the single set SHOW_HIDDEN_FRAMES?

I guess any unrecognized options are ignored?


 324     /**
 325      * Applies the given function to the stream of {@code StackFrame}s
 326      * for the current thread, traversing from the top of the stack,
 327      * which is the method calling this method.
 328      *
 329      * <p>To find the first 10 calling frames, first skipping those frames whose
 330      *    declaring class belong to package {@code com.foo}:
 331      * <blockquote>
 332      * <pre>{@code List<StackFrame> frames = new StackWalker().walk((s) ->}

} -> {

 333      *     s.dropWhile(f -> f.getClassName().startsWith("com.foo."))
 334      *      .limit(10)
 335      *      .collect(Collectors.toList()));
 336      * </pre></blockquote>
 337      * @apiNote The {@code Stream<StackFrame>} object will be closed when
 338      * this method returns.  When a closed {@code Stream<StackFrame>} object
 339      * is reused, {@code IllegalStateException} will be thrown.

Yay, dropWhile :-)

Use an @apiNote for the example, the original note is i think normative.

Probably need to mention that parallel execution will is effectively disabled and stream pipeline execution will only occur on the current thread.



402     @CallerSensitive
 403     public <T> T walk(Function<Stream<StackFrame>, T> function,
 404                       Function<Integer, Integer> batchSizeMapper) {

Can you use IntUnaryOperator?


 430     /**
 431      * Gets the {@code Class} object of the caller invoking the method
 432      * that calls this {@code getCallerClass} method.
 433      *
 434      * <p>This is equivalent to calling
 435      * <blockquote>
 436      * <pre>{@code walk((s) ->}

} -> {

 437      *     s.map(StackFrame::getDeclaringClass)
 438      *      .skip(2)
 439      *      .findFirst()).orElse(Thread.currentThread().getClass());
 440      * </pre></blockquote>


 441      *
 442      * <p>For example, {@code Util::getResourceBundle} loads a resource bundle on behalf
 443      * of the caller.  It calls this {@code getCallerClass} method to find the method
 444      * calling {@code Util::getResourceBundle} and use the caller's class loader to
 445      * load the resource bundle. The caller class in this example is the {@code Foo} class.


use @apiNote


BatchSizeMapper
—

As usual i always advice using testng and data providers, up to you.

 105         return frameCount = stream
 106                 .map(f -> {
 107                     if (verbose) System.out.println(f);
 108                     return f;
 109                 })

You can use peek instead.


GetCallerClassTest
—

 105 class Test1 {
 106     static void test() {
 107         GetCallerClassTest.staticGetCallerClass(Test1.class);
 108         GetCallerClassTest.reflectiveGetCallerClass(Test1.class);
 109     }
 110 }
 111
 112 class Test2 {
 113     static void test() {
 114         GetCallerClassTest.staticGetCallerClass(Test2.class);
 115         GetCallerClassTest.reflectiveGetCallerClass(Test2.class);
 116     }
 117 }


Duplicate tests?

Suggest renaming tests because on failure it might easier to understand what failed.


HiddenFrames
—

  64     void walk() {
  65         Arrays.stream(new int[]{0})
  66               .forEach(i -> walker.walk(s -> {
  67                   s.forEach(this::checkFrame);
  68                   return null;
  69               }));

Can you use Stream.of(0).forEach instead.


Ran out of time :-)

Paul.


> On 30 Oct 2015, at 20:04, Mandy Chung <mandy.chung at oracle.com> wrote:
> 
> JEP 259:  http://openjdk.java.net/jeps/259
> 
> Javadoc for the proposed StackWalker API:
>   http://cr.openjdk.java.net/~mchung/jdk9/jep259/api/java/lang/StackWalker.html
> 
> A simple way to walk the stack:
> 
>   StackWalker walker = new StackWalker(StackWalker.Option.CLASS_REFERENCE);
>   walker.walk((s) ->  s.filter(f -> interestingClasses.contains(f.getDeclaringClass())).findFirst());
> 
> The current usage of sun.reflect.Reflection.getCallerClass(int depth) can be replaced with this StackWalker API.
> 
> Any feedback on the proposed API is appreciated.
> 
> Mandy
> 
> P.S. webrev of the current implementation:
>   http://cr.openjdk.java.net/~mchung/jdk9/jep259/webrev.00/
> 
> 
> 
> 
> 
> 
> 




More information about the core-libs-dev mailing list