diff --git a/src/java.base/share/classes/java/lang/StackFrameInfo.java b/src/java.base/share/classes/java/lang/StackFrameInfo.java index 4f3cc1e980c..3666a834218 100644 --- a/src/java.base/share/classes/java/lang/StackFrameInfo.java +++ b/src/java.base/share/classes/java/lang/StackFrameInfo.java @@ -29,6 +29,7 @@ import jdk.internal.misc.SharedSecrets; 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 = @@ -78,6 +79,17 @@ class StackFrameInfo implements StackFrame { return JLIA.getName(memberName); } + @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 diff --git a/src/java.base/share/classes/java/lang/StackWalker.java b/src/java.base/share/classes/java/lang/StackWalker.java index 7e4bc1ebf88..a079ac53384 100644 --- a/src/java.base/share/classes/java/lang/StackWalker.java +++ b/src/java.base/share/classes/java/lang/StackWalker.java @@ -26,10 +26,12 @@ package java.lang; 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 @@ public final class StackWalker { * @since 9 * @jvms 2.6 */ - public static interface StackFrame { + public interface StackFrame { /** * Gets the binary name * of the declaring class of the method represented by this stack frame. @@ -127,6 +129,47 @@ public final class StackWalker { */ 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 descriptor of the method represented by + * this stack frame as defined by + * The Java Virtual Machine Specification. + * + * @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. diff --git a/src/java.base/share/classes/java/lang/invoke/MemberName.java b/src/java.base/share/classes/java/lang/invoke/MemberName.java index 86a625b2eed..c67f5fb3d89 100644 --- a/src/java.base/share/classes/java/lang/invoke/MemberName.java +++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java @@ -162,6 +162,29 @@ import static java.lang.invoke.MethodHandleStatics.newInternalError; 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. diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index 963e0cd24a8..348a15921f0 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -1785,6 +1785,18 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; return memberName.getName(); } + @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; diff --git a/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java b/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java index c710c6543ef..0d283644868 100644 --- a/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java +++ b/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java @@ -39,6 +39,18 @@ public interface JavaLangInvokeAccess { */ 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}. diff --git a/test/jdk/java/lang/StackWalker/Basic.java b/test/jdk/java/lang/StackWalker/Basic.java index 8dc7627a90d..e7b8070cf46 100644 --- a/test/jdk/java/lang/StackWalker/Basic.java +++ b/test/jdk/java/lang/StackWalker/Basic.java @@ -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 @@ public class Basic { found); } + @Test + public static void testMethodSignature() throws Exception { + List frames = new StackBuilder(16, 16).build(); + Map 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 @@ public class Basic { } } - class StackBuilder { + static class StackBuilder { private final int stackDepth; private final int limit; private int depth = 0; @@ -150,15 +182,17 @@ public class Basic { 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 @@ public class Basic { if (verbose) System.out.format("%2d: %s%n", depth, methodname); } + + static Map 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)); + } } } diff --git a/test/jdk/java/lang/StackWalker/SanityTest.java b/test/jdk/java/lang/StackWalker/SanityTest.java index 538c6616114..25deb0cf87d 100644 --- a/test/jdk/java/lang/StackWalker/SanityTest.java +++ b/test/jdk/java/lang/StackWalker/SanityTest.java @@ -79,4 +79,24 @@ public class SanityTest { 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) {} + } }