1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8140450
27 * @summary Basic test for the StackWalker::getByteCodeIndex method
28 * @modules jdk.jdeps/com.sun.tools.classfile
29 * @run main TestBCI
30 */
31
32import com.sun.tools.classfile.Attribute;
33import com.sun.tools.classfile.ClassFile;
34import com.sun.tools.classfile.Code_attribute;
35import com.sun.tools.classfile.ConstantPoolException;
36import com.sun.tools.classfile.Descriptor;
37import com.sun.tools.classfile.LineNumberTable_attribute;
38import com.sun.tools.classfile.Method;
39
40import java.lang.StackWalker.StackFrame;
41import java.io.IOException;
42import java.io.InputStream;
43import java.util.Arrays;
44import java.util.Comparator;
45import java.util.HashMap;
46import java.util.Map;
47import java.util.Optional;
48import java.util.SortedSet;
49import java.util.TreeSet;
50import java.util.function.Function;
51import java.util.stream.Collectors;
52
53import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
54
55public class TestBCI {
56    public static void main(String... args) throws Exception {
57        TestBCI test = new TestBCI(Walker.class);
58        System.out.println("Line number table:");
59        test.methods.values().stream()
60            .sorted(Comparator.comparing(MethodInfo::name).reversed())
61            .forEach(System.out::println);
62
63        // walk the stack
64        test.walk();
65    }
66
67    private final Map<String, MethodInfo> methods;
68    private final Class<?> clazz;
69    TestBCI(Class<?> c) throws ConstantPoolException, IOException {
70        Map<String, MethodInfo> methods;
71        String filename = c.getName().replace('.', '/') + ".class";
72        try (InputStream in = c.getResourceAsStream(filename)) {
73            ClassFile cf = ClassFile.read(in);
74            methods = Arrays.stream(cf.methods)
75                .map(m -> new MethodInfo(cf, m))
76                .collect(Collectors.toMap(MethodInfo::name, Function.identity()));
77        }
78        this.clazz = c;
79        this.methods = methods;
80    }
81
82    void walk() {
83        Walker walker = new Walker();
84        walker.m1();
85    }
86
87    void verify(StackFrame frame) {
88        if (frame.getDeclaringClass() != clazz)
89            return;
90
91        int bci = frame.getByteCodeIndex();
92        int lineNumber = frame.getLineNumber();
93        System.out.format("%s.%s bci %d (%s:%d)%n",
94                          frame.getClassName(), frame.getMethodName(), bci,
95                          frame.getFileName(), lineNumber);
96
97        MethodInfo method = methods.get(frame.getMethodName());
98        SortedSet<Integer> values = method.findLineNumbers(bci).get();
99        if (!values.contains(lineNumber)) {
100            throw new RuntimeException("line number for bci: " + bci + " "
101                + lineNumber + " not matched line number table: " + values);
102        }
103    }
104
105    /*
106     * BCIs in the execution stack when StackWalker::forEach is invoked
107     * will cover BCI range in the line number table.
108     */
109    class Walker {
110        final StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
111        void m1() {
112            int i = (int)Math.random()+2;
113            m2(i*2);
114        }
115
116        void m2(int i) {
117            i++;
118            m3(i);
119        }
120
121        void m3(int i) {
122            i++; m4(i++);
123        }
124
125        int m4(int i) {
126            walker.forEach(TestBCI.this::verify);
127            return i;
128        }
129    }
130
131    static class MethodInfo {
132        final Method method;
133        final String name;
134        final String paramTypes;
135        final String returnType;
136        final Map<Integer, SortedSet<Integer>> bciToLineNumbers = new HashMap<>();
137        MethodInfo(ClassFile cf, Method m) {
138            this.method = m;
139
140            String name;
141            String paramTypes;
142            String returnType;
143            LineNumberTable_attribute.Entry[] lineNumberTable;
144            try {
145                // method name
146                name = m.getName(cf.constant_pool);
147                // signature
148                paramTypes = m.descriptor.getParameterTypes(cf.constant_pool);
149                returnType = m.descriptor.getReturnType(cf.constant_pool);
150                Code_attribute codeAttr = (Code_attribute)
151                    m.attributes.get(Attribute.Code);
152                lineNumberTable = ((LineNumberTable_attribute)
153                    codeAttr.attributes.get(Attribute.LineNumberTable)).line_number_table;
154            } catch (ConstantPoolException|Descriptor.InvalidDescriptor e) {
155                throw new RuntimeException(e);
156            }
157            this.name = name;
158            this.paramTypes = paramTypes;
159            this.returnType = returnType;
160            Arrays.stream(lineNumberTable).forEach(entry ->
161                bciToLineNumbers.computeIfAbsent(entry.start_pc, _n -> new TreeSet<>())
162                    .add(entry.line_number));
163        }
164
165        String name() {
166            return name;
167        }
168
169        Optional<SortedSet<Integer>> findLineNumbers(int value) {
170            return bciToLineNumbers.entrySet().stream()
171                    .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
172                    .filter(e -> e.getKey().intValue() <= value)
173                    .map(Map.Entry::getValue)
174                    .findFirst();
175        }
176
177        @Override
178        public String toString() {
179            StringBuilder sb = new StringBuilder();
180            sb.append(name);
181            sb.append(paramTypes).append(returnType).append(" ");
182            bciToLineNumbers.entrySet().stream()
183                .sorted(Map.Entry.comparingByKey())
184                .forEach(entry -> sb.append("bci:").append(entry.getKey()).append(" ")
185                                    .append(entry.getValue()).append(" "));
186            return sb.toString();
187        }
188    }
189
190}
191