1/* 2 * Copyright (c) 2014, 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 24import com.sun.tools.classfile.*; 25 26import java.io.IOException; 27import java.lang.annotation.Repeatable; 28import java.lang.annotation.Retention; 29import java.lang.annotation.RetentionPolicy; 30import java.util.Collection; 31import java.util.HashMap; 32import java.util.List; 33import java.util.Map; 34import java.util.stream.Stream; 35 36import static java.lang.String.format; 37import static java.util.stream.Collectors.*; 38 39/** 40 * Base class for LocalVariableTable and LocalVariableTypeTable attributes tests. 41 * To add tests cases you should extend this class. 42 * Then implement {@link #getVariableTables} to get LocalVariableTable or LocalVariableTypeTable attribute. 43 * Then add method with local variables. 44 * Finally, annotate method with information about expected variables and their types 45 * by several {@link LocalVariableTestBase.ExpectedLocals} annotations. 46 * To run test invoke {@link #test()} method. 47 * If there are variables with the same name, set different scopes for them. 48 * 49 * @see #test() 50 */ 51public abstract class LocalVariableTestBase extends TestBase { 52 public static final int DEFAULT_SCOPE = 0; 53 private final ClassFile classFile; 54 private final Class<?> clazz; 55 56 /** 57 * @param clazz class to test. Must contains annotated methods with expected results. 58 */ 59 public LocalVariableTestBase(Class<?> clazz) { 60 this.clazz = clazz; 61 try { 62 this.classFile = ClassFile.read(getClassFile(clazz)); 63 } catch (IOException | ConstantPoolException e) { 64 throw new IllegalArgumentException("Can't read classfile for specified class", e); 65 } 66 } 67 68 protected abstract List<VariableTable> getVariableTables(Code_attribute codeAttribute); 69 70 /** 71 * Finds expected variables with their type in VariableTable. 72 * Also does consistency checks, like variables from the same scope must point to different indexes. 73 */ 74 public void test() throws IOException { 75 List<java.lang.reflect.Method> testMethods = Stream.of(clazz.getDeclaredMethods()) 76 .filter(m -> m.getAnnotationsByType(ExpectedLocals.class).length > 0) 77 .collect(toList()); 78 int failed = 0; 79 for (java.lang.reflect.Method method : testMethods) { 80 try { 81 Map<String, String> expectedLocals2Types = new HashMap<>(); 82 Map<String, Integer> sig2scope = new HashMap<>(); 83 for (ExpectedLocals anno : method.getDeclaredAnnotationsByType(ExpectedLocals.class)) { 84 expectedLocals2Types.put(anno.name(), anno.type()); 85 sig2scope.put(anno.name() + "&" + anno.type(), anno.scope()); 86 } 87 88 test(method.getName(), expectedLocals2Types, sig2scope); 89 } catch (AssertionFailedException ex) { 90 System.err.printf("Test %s failed.%n", method.getName()); 91 ex.printStackTrace(); 92 failed++; 93 } 94 } 95 if (failed > 0) 96 throw new RuntimeException(format("Failed %d out of %d. See logs.", failed, testMethods.size())); 97 } 98 99 public void test(String methodName, Map<String, String> expectedLocals2Types, Map<String, Integer> sig2scope) 100 throws IOException { 101 102 for (Method m : classFile.methods) { 103 String mName = getString(m.name_index); 104 if (methodName.equals(mName)) { 105 System.out.println("Testing local variable table in method " + mName); 106 Code_attribute code_attribute = (Code_attribute) m.attributes.get(Attribute.Code); 107 108 List<? extends VariableTable> variableTables = getVariableTables(code_attribute); 109 generalLocalVariableTableCheck(variableTables); 110 111 List<VariableTable.Entry> entries = variableTables.stream() 112 .flatMap(table -> table.entries().stream()) 113 .collect(toList()); 114 115 generalEntriesCheck(entries, code_attribute); 116 assertIndexesAreUnique(entries, sig2scope); 117 checkNamesAndTypes(entries, expectedLocals2Types); 118 checkDoubleAndLongIndexes(entries, sig2scope, code_attribute.max_locals); 119 } 120 } 121 } 122 123 private void generalLocalVariableTableCheck(List<? extends VariableTable> variableTables) { 124 for (VariableTable localTable : variableTables) { 125 //only one per variable. 126 assertEquals(localTable.localVariableTableLength(), 127 localTable.entries().size(), "Incorrect local variable table length"); 128 //attribute length is offset(line_number_table_length) + element_size*element_count 129 assertEquals(localTable.attributeLength(), 130 2 + (5 * 2) * localTable.localVariableTableLength(), "Incorrect attribute length"); 131 } 132 } 133 134 private void generalEntriesCheck(List<VariableTable.Entry> entries, Code_attribute code_attribute) { 135 for (VariableTable.Entry e : entries) { 136 assertTrue(e.index() >= 0 && e.index() < code_attribute.max_locals, 137 "Index " + e.index() + " out of variable array. Size of array is " + code_attribute.max_locals); 138 assertTrue(e.startPC() >= 0, "StartPC is less then 0. StartPC = " + e.startPC()); 139 assertTrue(e.length() >= 0, "Length is less then 0. Length = " + e.length()); 140 assertTrue(e.startPC() + e.length() <= code_attribute.code_length, 141 format("StartPC+Length > code length.%n" + 142 "%s%n" + 143 "code_length = %s" 144 , e, code_attribute.code_length)); 145 } 146 } 147 148 private void checkNamesAndTypes(List<VariableTable.Entry> entries, 149 Map<String, String> expectedLocals2Types) { 150 Map<String, List<String>> actualNames2Types = entries.stream() 151 .collect( 152 groupingBy(VariableTable.Entry::name, 153 mapping(VariableTable.Entry::type, toList()))); 154 for (Map.Entry<String, String> name2type : expectedLocals2Types.entrySet()) { 155 String name = name2type.getKey(); 156 String type = name2type.getValue(); 157 158 assertTrue(actualNames2Types.containsKey(name), 159 format("There is no record for local variable %s%nEntries: %s", name, entries)); 160 161 assertTrue(actualNames2Types.get(name).contains(type), 162 format("Types are different for local variable %s%nExpected type: %s%nActual type: %s", 163 name, type, actualNames2Types.get(name))); 164 } 165 } 166 167 168 private void assertIndexesAreUnique(Collection<VariableTable.Entry> entries, Map<String, Integer> scopes) { 169 //check every scope separately 170 Map<Object, List<VariableTable.Entry>> entriesByScope = groupByScope(entries, scopes); 171 for (Map.Entry<Object, List<VariableTable.Entry>> mapEntry : entriesByScope.entrySet()) { 172 mapEntry.getValue().stream() 173 .collect(groupingBy(VariableTable.Entry::index)) 174 .entrySet() 175 .forEach(e -> 176 assertTrue(e.getValue().size() == 1, 177 "Multiple variables point to the same index in common scope. " + e.getValue())); 178 } 179 180 } 181 182 private void checkDoubleAndLongIndexes(Collection<VariableTable.Entry> entries, 183 Map<String, Integer> scopes, int maxLocals) { 184 //check every scope separately 185 Map<Object, List<VariableTable.Entry>> entriesByScope = groupByScope(entries, scopes); 186 for (List<VariableTable.Entry> entryList : entriesByScope.values()) { 187 Map<Integer, VariableTable.Entry> index2Entry = entryList.stream() 188 .collect(toMap(VariableTable.Entry::index, e -> e)); 189 190 entryList.stream() 191 .filter(e -> "J".equals(e.type()) || "D".equals(e.type())) 192 .forEach(e -> { 193 assertTrue(e.index() + 1 < maxLocals, 194 format("Index %s is out of variable array. Long and double occupy 2 cells." + 195 " Size of array is %d", e.index() + 1, maxLocals)); 196 assertTrue(!index2Entry.containsKey(e.index() + 1), 197 format("An entry points to the second cell of long/double entry.%n%s%n%s", e, 198 index2Entry.get(e.index() + 1))); 199 }); 200 } 201 } 202 203 private Map<Object, List<VariableTable.Entry>> groupByScope( 204 Collection<VariableTable.Entry> entries, Map<String, Integer> scopes) { 205 return entries.stream().collect(groupingBy(e -> scopes.getOrDefault(e.name() + "&" + e.type(), DEFAULT_SCOPE))); 206 } 207 208 protected String getString(int i) { 209 try { 210 return classFile.constant_pool.getUTF8Info(i).value; 211 } catch (ConstantPool.InvalidIndex | ConstantPool.UnexpectedEntry ex) { 212 ex.printStackTrace(); 213 throw new AssertionFailedException("Issue while reading constant pool"); 214 } 215 } 216 217 /** 218 * LocalVariableTable and LocalVariableTypeTable are similar. 219 * VariableTable interface is introduced to test this attributes in the same way without code duplication. 220 */ 221 interface VariableTable { 222 223 int localVariableTableLength(); 224 225 List<VariableTable.Entry> entries(); 226 227 int attributeLength(); 228 229 interface Entry { 230 231 int index(); 232 233 int startPC(); 234 235 int length(); 236 237 String name(); 238 239 String type(); 240 241 default String dump() { 242 return format("Entry{" + 243 "%n name = %s" + 244 "%n type = %s" + 245 "%n index = %d" + 246 "%n startPC = %d" + 247 "%n length = %d" + 248 "%n}", name(), type(), index(), startPC(), length()); 249 } 250 } 251 } 252 253 /** 254 * Used to store expected results in sources 255 */ 256 @Retention(RetentionPolicy.RUNTIME) 257 @Repeatable(Container.class) 258 @interface ExpectedLocals { 259 /** 260 * @return name of a local variable 261 */ 262 String name(); 263 264 /** 265 * @return type of local variable in the internal format. 266 */ 267 String type(); 268 269 //variables from different scopes can share the local variable table index and/or name. 270 int scope() default DEFAULT_SCOPE; 271 } 272 273 @Retention(RetentionPolicy.RUNTIME) 274 @interface Container { 275 ExpectedLocals[] value(); 276 } 277} 278