8153912: Reconsider StackFrame::getFileName and StackFrame::getLineNumber
authormchung
Mon, 09 May 2016 09:35:57 -0700
changeset 37819 8a2559d6fe5b
parent 37818 080ff3cf037d
child 37820 abe7aa589158
8153912: Reconsider StackFrame::getFileName and StackFrame::getLineNumber Summary: Add StackFrame::getByteCodeIndex method. Revised getFileName and getLineNumber method. Reviewed-by: dfuchs, bchristi
jdk/src/java.base/share/classes/java/lang/StackFrameInfo.java
jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java
jdk/src/java.base/share/classes/java/lang/StackWalker.java
jdk/src/java.base/share/classes/java/lang/invoke/MemberName.java
jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java
jdk/test/java/lang/StackWalker/EmbeddedStackWalkTest.java
jdk/test/java/lang/StackWalker/StackRecorderUtil.java
jdk/test/java/lang/StackWalker/TestBCI.java
--- a/jdk/src/java.base/share/classes/java/lang/StackFrameInfo.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/StackFrameInfo.java	Mon May 09 09:35:57 2016 -0700
@@ -29,21 +29,20 @@
 
 import static java.lang.StackWalker.Option.*;
 import java.lang.StackWalker.StackFrame;
-import java.lang.reflect.Module;
 import java.util.Optional;
 import java.util.OptionalInt;
 
 class StackFrameInfo implements StackFrame {
-    private final static JavaLangInvokeAccess jlInvokeAccess =
+    private final static JavaLangInvokeAccess JLIA =
         SharedSecrets.getJavaLangInvokeAccess();
 
     // Footprint improvement: MemberName::clazz can replace
     // StackFrameInfo::declaringClass.
 
-    final StackWalker walker;
-    final Class<?> declaringClass;
-    final Object memberName;
-    final short bci;
+    private final StackWalker walker;
+    private final Class<?> declaringClass;
+    private final Object memberName;
+    private final short bci;
     private volatile StackTraceElement ste;
 
     /*
@@ -54,9 +53,17 @@
         this.walker = walker;
         this.declaringClass = null;
         this.bci = -1;
-        this.memberName = jlInvokeAccess.newMemberName();
+        this.memberName = JLIA.newMemberName();
     }
 
+    // package-private called by StackStreamFactory to skip
+    // the capability check
+    Class<?> declaringClass() {
+        return declaringClass;
+    }
+
+    // ----- implementation of StackFrame methods
+
     @Override
     public String getClassName() {
         return declaringClass.getName();
@@ -70,31 +77,39 @@
 
     @Override
     public String getMethodName() {
-        return jlInvokeAccess.getName(memberName);
+        return JLIA.getName(memberName);
     }
 
     @Override
-    public final Optional<String> getFileName() {
-        StackTraceElement ste = toStackTraceElement();
-        return ste.getFileName() != null ? Optional.of(ste.getFileName()) : Optional.empty();
+    public int getByteCodeIndex() {
+        return bci;
     }
 
     @Override
-    public final OptionalInt getLineNumber() {
-        StackTraceElement ste = toStackTraceElement();
-        return ste.getLineNumber() > 0 ? OptionalInt.of(ste.getLineNumber()) : OptionalInt.empty();
+    public String getFileName() {
+        if (isNativeMethod())
+            return null;
+
+        return toStackTraceElement().getFileName();
     }
 
     @Override
-    public final boolean isNativeMethod() {
-        StackTraceElement ste = toStackTraceElement();
-        return ste.isNativeMethod();
+    public int getLineNumber() {
+        if (isNativeMethod())
+            return -2;
+
+        return toStackTraceElement().getLineNumber();
+    }
+
+
+    @Override
+    public boolean isNativeMethod() {
+        return JLIA.isNative(memberName);
     }
 
     @Override
     public String toString() {
-        StackTraceElement ste = toStackTraceElement();
-        return ste.toString();
+        return toStackTraceElement().toString();
     }
 
     /**
--- a/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/StackStreamFactory.java	Mon May 09 09:35:57 2016 -0700
@@ -492,7 +492,7 @@
 
             @Override
             final Class<?> at(int index) {
-                return stackFrames[index].declaringClass;
+                return stackFrames[index].declaringClass();
             }
         }
 
@@ -761,7 +761,7 @@
 
             @Override
             final Class<?> at(int index) {
-                return stackFrames[index].declaringClass;
+                return stackFrames[index].declaringClass();
             }
         }
 
--- a/jdk/src/java.base/share/classes/java/lang/StackWalker.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/StackWalker.java	Mon May 09 09:35:57 2016 -0700
@@ -127,6 +127,20 @@
         public Class<?> getDeclaringClass();
 
         /**
+         * 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
+         * that implement the method.
+         *
+         * @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.
+         *
+         * @jvms 4.7.3 The {@code Code} Attribute
+         */
+        public int getByteCodeIndex();
+
+        /**
          * Returns the name of the source file containing the execution point
          * represented by this stack frame.  Generally, this corresponds
          * to the {@code SourceFile} attribute of the relevant {@code class}
@@ -135,12 +149,12 @@
          * other than a file, such as an entry in a source repository.
          *
          * @return the name of the file containing the execution point
-         *         represented by this stack frame, or empty {@code Optional}
-         *         is unavailable.
+         *         represented by this stack frame, or {@code null} if
+         *         this information is unavailable.
          *
          * @jvms 4.7.10 The {@code SourceFile} Attribute
          */
-        public Optional<String> getFileName();
+        public String getFileName();
 
         /**
          * Returns the line number of the source line containing the execution
@@ -150,12 +164,12 @@
          * Specification</cite>.
          *
          * @return the line number of the source line containing the execution
-         *         point represented by this stack frame, or empty
-         *         {@code Optional} if this information is unavailable.
+         *         point represented by this stack frame, or a negative number if
+         *         this information is unavailable.
          *
          * @jvms 4.7.12 The {@code LineNumberTable} Attribute
          */
-        public OptionalInt getLineNumber();
+        public int getLineNumber();
 
         /**
          * Returns {@code true} if the method containing the execution point
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MemberName.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MemberName.java	Mon May 09 09:35:57 2016 -0700
@@ -25,6 +25,8 @@
 
 package java.lang.invoke;
 
+import jdk.internal.misc.JavaLangInvokeAccess;
+import jdk.internal.misc.SharedSecrets;
 import sun.invoke.util.BytecodeDescriptor;
 import sun.invoke.util.VerifyAccess;
 
@@ -1143,15 +1145,25 @@
     }
 
     static {
-        // Allow privileged classes outside of java.lang
-        jdk.internal.misc.SharedSecrets.setJavaLangInvokeAccess(new jdk.internal.misc.JavaLangInvokeAccess() {
+        // StackFrameInfo stores Member and this provides the shared secrets
+        // for stack walker to access MemberName information.
+        SharedSecrets.setJavaLangInvokeAccess(new JavaLangInvokeAccess() {
+            @Override
             public Object newMemberName() {
                 return new MemberName();
             }
+
+            @Override
             public String getName(Object mname) {
                 MemberName memberName = (MemberName)mname;
                 return memberName.getName();
             }
+
+            @Override
+            public boolean isNative(Object mname) {
+                MemberName memberName = (MemberName)mname;
+                return memberName.isNative();
+            }
         });
     }
 }
--- a/jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/src/java.base/share/classes/jdk/internal/misc/JavaLangInvokeAccess.java	Mon May 09 09:35:57 2016 -0700
@@ -35,4 +35,9 @@
      * Returns the name for the given MemberName
      */
     String getName(Object mname);
+
+    /**
+     * Returns {@code true} if the given MemberName is a native method
+     */
+    boolean isNative(Object mname);
 }
--- a/jdk/test/java/lang/StackWalker/EmbeddedStackWalkTest.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/test/java/lang/StackWalker/EmbeddedStackWalkTest.java	Mon May 09 09:35:57 2016 -0700
@@ -140,8 +140,8 @@
                 s.limit(BIG_LOOP)
                  .filter(f -> c.getName().equals(f.getClassName()) && mn.equals(f.getMethodName()))
                  .forEach(f -> {
-                    assertEquals(f.getFileName().get(), fileName);
-                    int line = f.getLineNumber().getAsInt();
+                    assertEquals(f.getFileName(), fileName);
+                    int line = f.getLineNumber();
                     assertTrue(line >= BEGIN_LINE && line <= END_LINE);
 
                     StackTraceElement st = f.toStackTraceElement();
--- a/jdk/test/java/lang/StackWalker/StackRecorderUtil.java	Mon May 09 18:11:02 2016 +0800
+++ b/jdk/test/java/lang/StackWalker/StackRecorderUtil.java	Mon May 09 09:35:57 2016 -0700
@@ -100,8 +100,8 @@
             }
             if (!Objects.equals(ste.getClassName(), sf.getClassName())
                 || !Objects.equals(ste.getMethodName(), sf.getMethodName())
-                || !Objects.equals(ste.getFileName(), sf.getFileName().orElse(null))
-                || !Objects.equals(ste.getLineNumber(), sf.getLineNumber().orElse(-1))
+                || !Objects.equals(ste.getFileName(), sf.getFileName())
+                || !Objects.equals(ste.getLineNumber(), sf.getLineNumber())
                 || !Objects.equals(ste.isNativeMethod(), sf.isNativeMethod())) {
                 throw new RuntimeException("StackFrame and StackTraceElement differ: " +
                         "sf=" + sf + ", ste=" + ste);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/StackWalker/TestBCI.java	Mon May 09 09:35:57 2016 -0700
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2016, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8140450
+ * @summary Basic test for the StackWalker::getByteCodeIndex method
+ * @modules jdk.jdeps/com.sun.tools.classfile
+ * @run main TestBCI
+ */
+
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.ConstantPoolException;
+import com.sun.tools.classfile.Descriptor;
+import com.sun.tools.classfile.LineNumberTable_attribute;
+import com.sun.tools.classfile.Method;
+
+import java.lang.StackWalker.StackFrame;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+
+public class TestBCI {
+    public static void main(String... args) throws Exception {
+        TestBCI test = new TestBCI(Walker.class);
+        System.out.println("Line number table:");
+        test.methods.values().stream()
+            .sorted(Comparator.comparing(MethodInfo::name).reversed())
+            .forEach(System.out::println);
+
+        // walk the stack
+        test.walk();
+    }
+
+    private final Map<String, MethodInfo> methods;
+    private final Class<?> clazz;
+    TestBCI(Class<?> c) throws ConstantPoolException, IOException {
+        Map<String, MethodInfo> methods;
+        String filename = c.getName().replace('.', '/') + ".class";
+        try (InputStream in = c.getResourceAsStream(filename)) {
+            ClassFile cf = ClassFile.read(in);
+            methods = Arrays.stream(cf.methods)
+                .map(m -> new MethodInfo(cf, m))
+                .collect(Collectors.toMap(MethodInfo::name, Function.identity()));
+        }
+        this.clazz = c;
+        this.methods = methods;
+    }
+
+    void walk() {
+        Walker walker = new Walker();
+        walker.m1();
+    }
+
+    void verify(StackFrame frame) {
+        if (frame.getDeclaringClass() != clazz)
+            return;
+
+        int bci = frame.getByteCodeIndex();
+        int lineNumber = frame.getLineNumber();
+        System.out.format("%s.%s bci %d (%s:%d)%n",
+                          frame.getClassName(), frame.getMethodName(), bci,
+                          frame.getFileName(), lineNumber);
+
+        MethodInfo method = methods.get(frame.getMethodName());
+        SortedSet<Integer> values = method.findLineNumbers(bci).get();
+        if (!values.contains(lineNumber)) {
+            throw new RuntimeException("line number for bci: " + bci + " "
+                + lineNumber + " not matched line number table: " + values);
+        }
+    }
+
+    /*
+     * BCIs in the execution stack when StackWalker::forEach is invoked
+     * will cover BCI range in the line number table.
+     */
+    class Walker {
+        final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
+        void m1() {
+            int i = (int)Math.random()+2;
+            m2(i*2);
+        }
+
+        void m2(int i) {
+            i++;
+            m3(i);
+        }
+
+        void m3(int i) {
+            i++; m4(i++);
+        }
+
+        int m4(int i) {
+            walker.forEach(TestBCI.this::verify);
+            return i;
+        }
+    }
+
+    static class MethodInfo {
+        final Method method;
+        final String name;
+        final String paramTypes;
+        final String returnType;
+        final Map<Integer, SortedSet<Integer>> bciToLineNumbers = new HashMap<>();
+        MethodInfo(ClassFile cf, Method m) {
+            this.method = m;
+
+            String name;
+            String paramTypes;
+            String returnType;
+            LineNumberTable_attribute.Entry[] lineNumberTable;
+            try {
+                // method name
+                name = m.getName(cf.constant_pool);
+                // signature
+                paramTypes = m.descriptor.getParameterTypes(cf.constant_pool);
+                returnType = m.descriptor.getReturnType(cf.constant_pool);
+                Code_attribute codeAttr = (Code_attribute)
+                    m.attributes.get(Attribute.Code);
+                lineNumberTable = ((LineNumberTable_attribute)
+                    codeAttr.attributes.get(Attribute.LineNumberTable)).line_number_table;
+            } catch (ConstantPoolException|Descriptor.InvalidDescriptor e) {
+                throw new RuntimeException(e);
+            }
+            this.name = name;
+            this.paramTypes = paramTypes;
+            this.returnType = returnType;
+            Arrays.stream(lineNumberTable).forEach(entry ->
+                bciToLineNumbers.computeIfAbsent(entry.start_pc, _n -> new TreeSet<>())
+                    .add(entry.line_number));
+        }
+
+        String name() {
+            return name;
+        }
+
+        Optional<SortedSet<Integer>> findLineNumbers(int value) {
+            return bciToLineNumbers.entrySet().stream()
+                    .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
+                    .filter(e -> e.getKey().intValue() <= value)
+                    .map(Map.Entry::getValue)
+                    .findFirst();
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(name);
+            sb.append(paramTypes).append(returnType).append(" ");
+            bciToLineNumbers.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .forEach(entry -> sb.append("bci:").append(entry.getKey()).append(" ")
+                                    .append(entry.getValue()).append(" "));
+            return sb.toString();
+        }
+    }
+
+}