JDK-8314811 : Provide an optimized way to walk the stack with Class object only
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 22
  • Submitted: 2023-08-22
  • Updated: 2023-09-06
  • Resolved: 2023-09-06
Related Reports
CSR :  
Description
Summary
-------

Add `StackWalker.Option::DROP_METHOD_INFO` enum constant that requests the stack walker 
to drop the method information

Problem
-------

`StackWalker::walk` creates one `StackFrame` per frame and the current implementation
allocates one `StackFrameInfo` and one `MemberName` objects per frame. Some frameworks
like logging may only need to know about the class information and are not interested in
the method name, BCI, source file name etc.  For example, finding the caller class with filtering
out its implementation classes.  It's similar to `StackWalker::getCallerClass` but allows 
a predicate to filter out the element.  
The cost of getting the method information is not small and so providing a custom way to
walk the stack without the method information is useful.

Solution
--------

Add `StackWalker.Option::DROP_METHOD_INFO` enum constant that requests the stack walker 
to drop the method information.  If the application or framework only needs the class information,
dropping the method information can save the overhead of extracting the method information,
and provides a faster way in inspecting just the class information on the stack.  This saving is 
quite significant.

Specification
-------------
 1. Define a new `StackWalker.Option::DROP_METHOD_INFO` constant
 2. Update `StackFrame` spec to throw `UnsupportedOperationException` if the method information is dropped.
 3. `StackWalker` class spec update

 
```
+        /**
+         * Drops the method information from {@code StackFrame}s
+         * walked by this {@code StackWalker}.
+         *
+         * <p> A {@code StackWalker} configured with this option will drop
+         * the {@linkplain StackFrame#getMethodName() method name},
+         * the {@linkplain StackFrame#getMethodType() method type},
+         * the {@linkplain StackFrame#getLineNumber() line number},
+         * the {@linkplain StackFrame#getByteCodeIndex() bytecode index},
+         * the {@linkplain StackFrame#getFileName() source file name} and
+         * {@linkplain StackFrame#isNativeMethod() native method or not}.
+         *
+         * @since 22
+         */
+        DROP_METHOD_INFO,
```

Update `StackFrame` spec to throw `UnsupportedOperationException` when
accessing the method information which has been dropped

```
     /**
      * A {@code StackFrame} object represents a method invocation returned by
      * {@link StackWalker}.
      *
-     * <p> The {@link #getDeclaringClass()} method may be unsupported as determined
-     * by the {@linkplain Option stack walking options} of a {@linkplain
-     * StackWalker stack walker}.
+     * <p>  {@linkplain Option <em>Stack walker options</em>} configure the stack
+     * frame information obtained by a {@code StackWalker}.
+     * If the stack walker is configured with {@link Option#DROP_METHOD_INFO
+     * DROP_METHOD_INFO} option, method information such as
+     * the {@linkplain StackFrame#getMethodName() method name},
+     * the {@linkplain StackFrame#getLineNumber() line number},
+     * the {@linkplain StackFrame#getByteCodeIndex() bytecode index}, etc
+     * will be dropped.
+     * If the stack walker is configured with {@link Option#RETAIN_CLASS_REFERENCE
+     * RETAIN_CLASS_REFERENCE} option, the {@link #getDeclaringClass() Class} object
+     * will be retained for access.
      *
      * @since 9
-     * @jvms 2.6
+     * @jvms 2.6 Frames
      */
     public interface StackFrame {
         /**
-         * Gets the <a href="ClassLoader.html#binary-name">binary name</a>
-         * of the declaring class of the method represented by this stack frame.
-         *
-         * @return the binary name of the declaring class of the method
-         *         represented by this stack frame
+         * {@return the <a href="ClassLoader.html#binary-name">binary name</a>
+         * of the declaring class of the method represented by this stack frame}
          *
          * @jls 13.1 The Form of a Binary
          */
         public String getClassName();
         /**
-         * Gets the name of the method represented by this stack frame.
-         * @return the name of the method represented by this stack frame
+         * {@return the name of the method represented by this stack frame}
+         *
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
          */
         public String getMethodName();
 
         /**
-         * Gets the declaring {@code Class} for the method represented by
-         * this stack frame.
-         *
-         * @return the declaring {@code Class} of the method represented by
-         * this stack frame
+         * {@return the declaring {@code Class} for the method represented by
+         * this stack frame}
          *
-         * @throws UnsupportedOperationException if this {@code StackWalker}
-         *         is not configured with {@link Option#RETAIN_CLASS_REFERENCE
-         *         Option.RETAIN_CLASS_REFERENCE}.
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         without {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option
          */
         public Class<?> getDeclaringClass();
 
         /**
          * Returns the {@link MethodType} representing the parameter types and
          * the return type for the method represented by this stack frame.
          *
          * @implSpec
          * The default implementation throws {@code UnsupportedOperationException}.
          *
-         * @return the {@code MethodType} for this stack frame
+         * @return the {@code MethodType} of the method represented by this stack frame
          *
-         * @throws UnsupportedOperationException if this {@code StackWalker}
-         *         is not configured with {@link Option#RETAIN_CLASS_REFERENCE
-         *         Option.RETAIN_CLASS_REFERENCE}.
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option or
+         *         without {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option
          *
          * @since 10
          */
         public default MethodType getMethodType() {
                 throw new UnsupportedOperationException();
@@ -159,10 +163,13 @@
          * The default implementation throws {@code UnsupportedOperationException}.
          *
          * @return the descriptor of the method represented by
          *         this stack frame
          *
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
+         *
          * @see MethodType#fromMethodDescriptorString(String, ClassLoader)
          * @see MethodType#toMethodDescriptorString()
          * @jvms 4.3.3 Method Descriptor
          *
          * @since 10
@@ -180,10 +187,13 @@
          *
          * @return the index to the code array of the {@code Code} attribute
          *         containing the execution point represented by this stack frame,
          *         or a negative number if the method is native.
          *
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
+         *
          * @jvms 4.7.3 The {@code Code} Attribute
          */
         public int getByteCodeIndex();
 
         /**
@@ -196,10 +206,13 @@
          *
          * @return the name of the file containing the execution point
          *         represented by this stack frame, or {@code null} if
          *         this information is unavailable.
          *
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
+         *
          * @jvms 4.7.10 The {@code SourceFile} Attribute
          */
         public String getFileName();
 
         /**
@@ -211,27 +224,31 @@
          *
          * @return the line number of the source line containing the execution
          *         point represented by this stack frame, or a negative number if
          *         this information is unavailable.
          *
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
+         *
          * @jvms 4.7.12 The {@code LineNumberTable} Attribute
          */
         public int getLineNumber();

         /**
-         * Returns {@code true} if the method containing the execution point
-         * represented by this stack frame is a native method.
+         * {@return {@code true} if the method containing the execution point
+         * represented by this stack frame is a native method}
          *
-         * @return {@code true} if the method containing the execution point
-         *         represented by this stack frame is a native method.
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
          */
         public boolean isNativeMethod();
 
         /**
-         * Gets a {@code StackTraceElement} for this stack frame.
+         * {@return {@code StackTraceElement} for this stack frame}
          *
-         * @return {@code StackTraceElement} for this stack frame.
+         * @throws UnsupportedOperationException if the {@code StackWalker} is configured
+         *         with {@link Option#DROP_METHOD_INFO DROP_METHOD_INFO} option
          */
         public StackTraceElement toStackTraceElement();
    
```

StackWalker class spec update:

```
diff a/src/java.base/share/classes/java/lang/StackWalker.java b/src/java.base/share/classes/java/lang/StackWalker.java
--- a/src/java.base/share/classes/java/lang/StackWalker.java
+++ b/src/java.base/share/classes/java/lang/StackWalker.java
@@ -1,7 +1,7 @@
 /*
- * 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
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this
@@ -47,41 +47,42 @@
  * the bottom most frame.
  * The {@code StackFrame} stream is closed when the {@code walk} method returns.
  * If an attempt is made to reuse the closed stream,
  * {@code IllegalStateException} will be thrown.
  *
- * <p> The {@linkplain Option <em>stack walking options</em>} of a
- * {@code StackWalker} determines the information of
- * {@link StackFrame StackFrame} objects to be returned.
- * By default, stack frames of the reflection API and implementation
- * classes are {@linkplain Option#SHOW_HIDDEN_FRAMES hidden}
- * and {@code StackFrame}s have the class name and method name
- * available but not the {@link StackFrame#getDeclaringClass() Class reference}.
+ * <p> {@linkplain Option <em>Stack walker options</em>} configure the stack frame
+ * information obtained by a {@code StackWalker}.
+ * By default, the class name and method information are collected but
+ * not the {@link StackFrame#getDeclaringClass() Class reference}.
+ * The method information can be dropped via the {@link Option#DROP_METHOD_INFO
+ * DROP_METHOD_INFO} option. The {@code Class} object can be retained for
+ * access via the {@link Option#RETAIN_CLASS_REFERENCE RETAIN_CLASS_REFERENCE} option.
+ * Stack frames of the reflection API and implementation classes are
+ * {@linkplain Option#SHOW_HIDDEN_FRAMES hidden} by default.
  *
  * <p> {@code StackWalker} is thread-safe. Multiple threads can share
  * a single {@code StackWalker} object to traverse its own stack.
  * A permission check is performed when a {@code StackWalker} is created,
  * according to the options it requests.
  * No further permission check is done at stack walking time.
  *
  * @apiNote
  * Examples
  *
- * <p>1. To find the first caller filtering a known list of implementation class:
- * <pre>{@code
- *     StackWalker walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
+ * <p>1. To find the first caller filtering out a known list of implementation class:
+ * {@snippet lang="java" :
+ *     StackWalker walker = StackWalker.getInstance(Set.of(Option.DROP_METHOD_INFO, Option.RETAIN_CLASS_REFERENCE));
  *     Optional<Class<?>> callerClass = walker.walk(s ->
- *         s.map(StackFrame::getDeclaringClass)
- *          .filter(interestingClasses::contains)
- *          .findFirst());
- * }</pre>
+ *             s.map(StackFrame::getDeclaringClass)
+ *              .filter(Predicate.not(implClasses::contains))
+ *              .findFirst());
+ * }
  *
  * <p>2. To snapshot the top 10 stack frames of the current thread,
- * <pre>{@code
- *     List<StackFrame> stack = StackWalker.getInstance().walk(s ->
- *         s.limit(10).collect(Collectors.toList()));
- * }</pre>
+ * {@snippet lang="java" :
+ *     List<StackFrame> stack = StackWalker.getInstance().walk(s -> s.limit(10).toList());
+ * }
  *
  * Unless otherwise noted, passing a {@code null} argument to a
  * constructor or method in this {@code StackWalker} class
  * will cause a {@link NullPointerException NullPointerException}
  * to be thrown.
```


Comments
Moving to Approved.
06-09-2023