8186050: StackFrame should provide the method signature
authormchung
Fri, 29 Sep 2017 11:33:08 -0700
changeset 47294 7d67bb6b0599
parent 47293 96a6bba0e695
child 47295 565363232665
8186050: StackFrame should provide the method signature Reviewed-by: alanb, bchristi, forax, plevart
src/java.base/share/classes/java/lang/StackFrameInfo.java
src/java.base/share/classes/java/lang/StackWalker.java
src/java.base/share/classes/java/lang/invoke/MemberName.java
src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java
test/jdk/java/lang/StackWalker/Basic.java
test/jdk/java/lang/StackWalker/SanityTest.java
--- a/src/java.base/share/classes/java/lang/StackFrameInfo.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/src/java.base/share/classes/java/lang/StackFrameInfo.java	Fri Sep 29 11:33:08 2017 -0700
@@ -29,6 +29,7 @@
 
 import static java.lang.StackWalker.Option.*;
 import java.lang.StackWalker.StackFrame;
+import java.lang.invoke.MethodType;
 
 class StackFrameInfo implements StackFrame {
     private final static JavaLangInvokeAccess JLIA =
@@ -79,6 +80,17 @@
     }
 
     @Override
+    public MethodType getMethodType() {
+        walker.ensureAccessEnabled(RETAIN_CLASS_REFERENCE);
+        return JLIA.getMethodType(memberName);
+    }
+
+    @Override
+    public String getDescriptor() {
+        return JLIA.getMethodDescriptor(memberName);
+    }
+
+    @Override
     public int getByteCodeIndex() {
         // bci not available for native methods
         if (isNativeMethod())
--- a/src/java.base/share/classes/java/lang/StackWalker.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/src/java.base/share/classes/java/lang/StackWalker.java	Fri Sep 29 11:33:08 2017 -0700
@@ -26,10 +26,12 @@
 
 import jdk.internal.reflect.CallerSensitive;
 
-import java.util.*;
+import java.lang.invoke.MethodType;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 /**
@@ -96,7 +98,7 @@
      * @since 9
      * @jvms 2.6
      */
-    public static interface StackFrame {
+    public interface StackFrame {
         /**
          * Gets the <a href="ClassLoader.html#name">binary name</a>
          * of the declaring class of the method represented by this stack frame.
@@ -128,6 +130,47 @@
         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
+         *
+         * @throws UnsupportedOperationException if this {@code StackWalker}
+         *         is not configured with {@link Option#RETAIN_CLASS_REFERENCE
+         *         Option.RETAIN_CLASS_REFERENCE}.
+         *
+         * @since 10
+         */
+        public default MethodType getMethodType() {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Returns the <i>descriptor</i> of the method represented by
+         * this stack frame as defined by
+         * <cite>The Java Virtual Machine Specification</cite>.
+         *
+         * @implSpec
+         * The default implementation throws {@code UnsupportedOperationException}.
+         *
+         * @return the descriptor of the method represented by
+         *         this stack frame
+         *
+         * @see MethodType#fromMethodDescriptorString(String, ClassLoader)
+         * @see MethodType#toMethodDescriptorString()
+         * @jvms 4.3.3 Method Descriptor
+         *
+         * @since 10
+         */
+        public default String getDescriptor() {
+            throw new UnsupportedOperationException();
+        }
+
+
+        /**
          * Returns the index to the code array of the {@code Code} attribute
          * containing the execution point represented by this stack frame.
          * The code array gives the actual bytes of Java Virtual Machine code
--- a/src/java.base/share/classes/java/lang/invoke/MemberName.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java	Fri Sep 29 11:33:08 2017 -0700
@@ -162,6 +162,29 @@
         return (MethodType) type;
     }
 
+    /** Return the descriptor of this member, which
+     *  must be a method or constructor.
+     */
+    String getMethodDescriptor() {
+        if (type == null) {
+            expandFromVM();
+            if (type == null) {
+                return null;
+            }
+        }
+        if (!isInvocable()) {
+            throw newIllegalArgumentException("not invocable, no method type");
+        }
+
+        // Get a snapshot of type which doesn't get changed by racing threads.
+        final Object type = this.type;
+        if (type instanceof String) {
+            return (String) type;
+        } else {
+            return getMethodType().toMethodDescriptorString();
+        }
+    }
+
     /** Return the actual type under which this method or constructor must be invoked.
      *  For non-static methods or constructors, this is the type with a leading parameter,
      *  a reference to declaring class.  For static methods, it is the same as the declared type.
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java	Fri Sep 29 11:33:08 2017 -0700
@@ -1786,6 +1786,18 @@
             }
 
             @Override
+            public MethodType getMethodType(Object mname) {
+                MemberName memberName = (MemberName)mname;
+                return memberName.getMethodType();
+            }
+
+            @Override
+            public String getMethodDescriptor(Object mname) {
+                MemberName memberName = (MemberName)mname;
+                return memberName.getMethodDescriptor();
+            }
+
+            @Override
             public boolean isNative(Object mname) {
                 MemberName memberName = (MemberName)mname;
                 return memberName.isNative();
--- a/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java	Fri Sep 29 11:33:08 2017 -0700
@@ -40,6 +40,18 @@
     String getName(Object mname);
 
     /**
+     * Returns the {@code MethodType} for the given MemberName.
+     * Used by {@see StackFrameInfo}.
+     */
+    MethodType getMethodType(Object mname);
+
+    /**
+     * Returns the descriptor for the given MemberName.
+     * Used by {@see StackFrameInfo}.
+     */
+    String getMethodDescriptor(Object mname);
+
+    /**
      * Returns {@code true} if the given MemberName is a native method. Used by
      * {@see StackFrameInfo}.
      */
--- a/test/jdk/java/lang/StackWalker/Basic.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/test/jdk/java/lang/StackWalker/Basic.java	Fri Sep 29 11:33:08 2017 -0700
@@ -29,8 +29,9 @@
  */
 
 import java.lang.StackWalker.StackFrame;
+import java.lang.invoke.MethodType;
 import java.util.List;
-import java.util.Objects;
+import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import static java.lang.StackWalker.Option.*;
@@ -74,6 +75,37 @@
                      found);
     }
 
+    @Test
+    public static void testMethodSignature() throws Exception {
+        List<StackFrame> frames = new StackBuilder(16, 16).build();
+        Map<String, MethodType> methodTypes = StackBuilder.methodTypes();
+        for (StackFrame f : frames) {
+            MethodType type = methodTypes.get(f.getMethodName());
+            if (type != null) {
+                System.out.format("%s.%s %s%n", f.getClassName(), f.getMethodName(),
+                                  f.getDescriptor());
+
+                String descriptor = f.getDescriptor();
+                if (!descriptor.equals(type.toMethodDescriptorString())) {
+                    throw new RuntimeException("Expected: " + type.toMethodDescriptorString()
+                        + " got: " + f.getDescriptor());
+                }
+
+                if (!f.getMethodType().equals(type)) {
+                    throw new RuntimeException("Expected: " + type
+                        + " got: " + f.getMethodType());
+                }
+
+                // verify descriptor returned by getDescriptor() before and after
+                // getMethodType() is called
+                if (!descriptor.equals(f.getDescriptor())) {
+                    throw new RuntimeException("Mismatched: " + descriptor
+                        + " got: " + f.getDescriptor());
+                }
+            }
+        }
+    }
+
     private final int depth;
     Basic(int depth) {
         this.depth = depth;
@@ -132,7 +164,7 @@
         }
     }
 
-    class StackBuilder {
+    static class StackBuilder {
         private final int stackDepth;
         private final int limit;
         private int depth = 0;
@@ -150,15 +182,17 @@
             trace("m1");
             m2();
         }
-        void m2() {
+        List m2() {
             trace("m2");
             m3();
+            return null;
         }
-        void m3() {
+        int m3() {
             trace("m3");
-            m4();
+            m4(null);
+            return 0;
         }
-        void m4() {
+        void m4(Object o) {
             trace("m4");
             int remaining = stackDepth-depth-1;
             if (remaining >= 4) {
@@ -184,6 +218,13 @@
             if (verbose)
                 System.out.format("%2d: %s%n", depth, methodname);
         }
+
+        static Map<String, MethodType> methodTypes() throws Exception {
+            return Map.of("m1", MethodType.methodType(void.class),
+                          "m2", MethodType.methodType(List.class),
+                          "m3", MethodType.methodType(int.class),
+                          "m4", MethodType.methodType(void.class, Object.class));
+        }
     }
 
 }
--- a/test/jdk/java/lang/StackWalker/SanityTest.java	Sat Sep 30 03:15:56 2017 +0530
+++ b/test/jdk/java/lang/StackWalker/SanityTest.java	Fri Sep 29 11:33:08 2017 -0700
@@ -79,4 +79,24 @@
             throw new RuntimeException("NPE expected");
         } catch (NullPointerException e) {}
     }
+
+
+    @Test
+    public static void testUOEFromGetDeclaringClass() {
+        try {
+            StackWalker sw = StackWalker.getInstance();
+            sw.forEach(StackWalker.StackFrame::getDeclaringClass);
+            throw new RuntimeException("UOE expected");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    @Test
+    public static void testUOEFromGetMethodType() {
+        try {
+            StackWalker sw = StackWalker.getInstance();
+            sw.forEach(StackWalker.StackFrame::getMethodType);
+            throw new RuntimeException("UOE expected");
+        } catch (UnsupportedOperationException expected) {}
+    }
 }