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