BridgeMethodTestCase.java revision 3170:dc017a37aac5
1/* 2 * Copyright (c) 2013, 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 java.io.File; 25import java.io.IOException; 26import java.lang.reflect.Method; 27import java.util.ArrayList; 28import java.util.Arrays; 29import java.util.HashMap; 30import java.util.List; 31import java.util.Map; 32import java.util.StringJoiner; 33 34import org.testng.annotations.BeforeMethod; 35import org.testng.annotations.Test; 36 37import tools.javac.combo.*; 38 39import static org.testng.Assert.fail; 40 41/** 42 * BridgeMethodTestCase -- used for asserting linkage to bridges under separate compilation. 43 * 44 * Example test case: 45 * public void test1() throws IOException, ReflectiveOperationException { 46 * compileSpec("C(Bc1(A))"); 47 * assertLinkage("C", LINKAGE_ERROR, "B1"); 48 * recompileSpec("C(Bc1(Ac0))", "A"); 49 * assertLinkage("C", "A0", "B1"); 50 * } 51 * 52 * This compiles A, B, and C, asserts that C.m()Object does not exist, asserts 53 * that C.m()Number eventually invokes B.m()Number, recompiles B, and then asserts 54 * that the result of calling C.m()Object now arrives at A. 55 * 56 * @author Brian Goetz 57 */ 58 59@Test 60public abstract class BridgeMethodTestCase extends JavacTemplateTestBase { 61 62 private static final String TYPE_LETTERS = "ABCDIJK"; 63 64 private static final String BASE_INDEX_CLASS = "class C0 {\n" + 65 " int val;\n" + 66 " C0(int val) { this.val = val; }\n" + 67 " public int getVal() { return val; }\n" + 68 "}\n"; 69 private static final String INDEX_CLASS_TEMPLATE = "class C#ID extends C#PREV {\n" + 70 " C#ID(int val) { super(val); }\n" + 71 "}\n"; 72 73 74 75 protected static String LINKAGE_ERROR = "-1"; 76 77 private List<File> compileDirs = new ArrayList<>(); 78 79 /** 80 * Compile all the classes in a class spec, and put them on the classpath. 81 * 82 * The spec is the specification for a nest of classes, using the following notation 83 * A, B represent abstract classes 84 * C represents a concrete class 85 * I, J, K represent interfaces 86 * Lowercase 'c' following a class means that the method m() is concrete 87 * Lowercase 'a' following a class or interface means that the method m() is abstract 88 * Lowercase 'd' following an interface means that the method m() is default 89 * A number 0, 1, or 2 following the lowercase letter indicates the return type of that method 90 * 0 = Object, 1 = Number, 2 = Integer (these form an inheritance chain so bridges are generated) 91 * A classes supertypes follow its method spec, in parentheses 92 * Examples: 93 * C(Ia0, Jd0) -- C extends I and J, I has abstract m()Object, J has default m()Object 94 * Cc1(Ia0) -- C has concrete m()Number, extends I with abstract m()Object 95 * If a type must appear multiple times, its full spec must be in the first occurrence 96 * Example: 97 * C(I(Kd0), J(K)) 98 */ 99 protected void compileSpec(String spec) throws IOException { 100 compileSpec(spec, false); 101 } 102 103 /** 104 * Compile all the classes in a class spec, and assert that there were compilation errors. 105 */ 106 protected void compileSpec(String spec, String... errorKeys) throws IOException { 107 compileSpec(spec, false, errorKeys); 108 } 109 110 protected void compileSpec(String spec, boolean debug, String... errorKeys) throws IOException { 111 ClassModel cm = new Parser(spec).parseClassModel(); 112 for (int i = 0; i <= cm.maxIndex() ; i++) { 113 if (debug) System.out.println(indexClass(i)); 114 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i))); 115 } 116 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet()) { 117 if (debug) System.out.println(e.getValue().toSource()); 118 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource())); 119 } 120 compileDirs.add(compile(true)); 121 resetSourceFiles(); 122 if (errorKeys.length == 0) 123 assertCompileSucceeded(); 124 else 125 assertCompileErrors(errorKeys); 126 } 127 128 /** 129 * Recompile only a subset of classes in the class spec, as named by names, 130 * and put them on the classpath such that they shadow earlier versions of that class. 131 */ 132 protected void recompileSpec(String spec, String... names) throws IOException { 133 List<String> nameList = Arrays.asList(names); 134 ClassModel cm = new Parser(spec).parseClassModel(); 135 for (int i = 0; i <= cm.maxIndex() ; i++) { 136 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i))); 137 } 138 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet()) 139 if (nameList.contains(e.getKey())) 140 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource())); 141 compileDirs.add(compile(Arrays.asList(classPaths()), true)); 142 resetSourceFiles(); 143 assertCompileSucceeded(); 144 } 145 146 protected void assertLinkage(String name, String... expected) throws ReflectiveOperationException { 147 for (int i=0; i<expected.length; i++) { 148 String e = expected[i]; 149 if (e.equals(LINKAGE_ERROR)) { 150 try { 151 int actual = invoke(name, i); 152 fail("Expected linkage error, got" + fromNum(actual)); 153 } 154 catch (LinkageError x) { 155 // success 156 } 157 } 158 else { 159 if (e.length() == 1) 160 e += "0"; 161 int expectedInt = toNum(e); 162 int actual = invoke(name, i); 163 if (expectedInt != actual) 164 fail(String.format("Expected %s but found %s for %s.m()%d", fromNum(expectedInt), fromNum(actual), name, i)); 165 } 166 } 167 } 168 169 private Map<String, ClassModel> classes(ClassModel cm) { 170 HashMap<String, ClassModel> m = new HashMap<>(); 171 classesHelper(cm, m); 172 return m; 173 } 174 175 private String indexClass(int index) { 176 if (index == 0) { 177 return BASE_INDEX_CLASS; 178 } else { 179 return INDEX_CLASS_TEMPLATE 180 .replace("#ID", String.valueOf(index)) 181 .replace("#PREV", String.valueOf(index - 1)); 182 } 183 } 184 185 private static String overrideName(int index) { 186 return "C" + index; 187 } 188 189 private void classesHelper(ClassModel cm, Map<String, ClassModel> m) { 190 if (!m.containsKey(cm.name)) 191 m.put(cm.name, cm); 192 for (ClassModel s : cm.supertypes) 193 classesHelper(s, m); 194 } 195 196 private static String fromNum(int num) { 197 return String.format("%c%d", TYPE_LETTERS.charAt(num / 10), num % 10); 198 } 199 200 private static int toNum(String name, int index) { 201 return 10*(TYPE_LETTERS.indexOf(name.charAt(0))) + index; 202 } 203 204 private static int toNum(String string) { 205 return 10*(TYPE_LETTERS.indexOf(string.charAt(0))) + Integer.parseInt(string.substring(1, 2)); 206 } 207 208 private int invoke(String name, int index) throws ReflectiveOperationException { 209 File[] files = classPaths(); 210 Class clazz = loadClass(name, files); 211 Method[] ms = clazz.getMethods(); 212 for (Method m : ms) { 213 if (m.getName().equals("m") && m.getReturnType().getName().equals(overrideName(index))) { 214 m.setAccessible(true); 215 Object instance = clazz.newInstance(); 216 Object c0 = m.invoke(instance); 217 Method getVal = c0.getClass().getMethod("getVal"); 218 getVal.setAccessible(true); 219 return (int)getVal.invoke(c0); 220 } 221 } 222 throw new NoSuchMethodError("cannot find method m()" + index + " in class " + name); 223 } 224 225 private File[] classPaths() { 226 File[] files = new File[compileDirs.size()]; 227 for (int i=0; i<files.length; i++) 228 files[files.length - i - 1] = compileDirs.get(i); 229 return files; 230 } 231 232 @BeforeMethod 233 @Override 234 public void reset() { 235 compileDirs.clear(); 236 super.reset(); 237 } 238 239 private static class ClassModel { 240 241 enum MethodType { 242 ABSTRACT('a'), CONCRETE('c'), DEFAULT('d'); 243 244 public final char designator; 245 246 MethodType(char designator) { 247 this.designator = designator; 248 } 249 250 public static MethodType find(char c) { 251 for (MethodType m : values()) 252 if (m.designator == c) 253 return m; 254 throw new IllegalArgumentException(); 255 } 256 } 257 258 private final String name; 259 private final boolean isInterface; 260 private final List<ClassModel> supertypes; 261 private final MethodType methodType; 262 private final int methodIndex; 263 264 private ClassModel(String name, 265 boolean anInterface, 266 List<ClassModel> supertypes, 267 MethodType methodType, 268 int methodIndex) { 269 this.name = name; 270 isInterface = anInterface; 271 this.supertypes = supertypes; 272 this.methodType = methodType; 273 this.methodIndex = methodIndex; 274 } 275 276 @Override 277 public String toString() { 278 StringBuilder sb = new StringBuilder(); 279 sb.append(name); 280 if (methodType != null) { 281 sb.append(methodType.designator); 282 sb.append(methodIndex); 283 } 284 if (!supertypes.isEmpty()) { 285 sb.append("("); 286 for (int i=0; i<supertypes.size(); i++) { 287 if (i > 0) 288 sb.append(","); 289 sb.append(supertypes.get(i).toString()); 290 } 291 sb.append(")"); 292 } 293 return sb.toString(); 294 } 295 296 int maxIndex() { 297 int maxSoFar = methodIndex; 298 for (ClassModel cm : supertypes) { 299 maxSoFar = Math.max(cm.maxIndex(), maxSoFar); 300 } 301 return maxSoFar; 302 } 303 304 public String toSource() { 305 String extendsClause = ""; 306 String implementsClause = ""; 307 String methodBody = ""; 308 boolean isAbstract = "AB".contains(name); 309 310 for (ClassModel s : supertypes) { 311 if (!s.isInterface) { 312 extendsClause = String.format("extends %s", s.name); 313 break; 314 } 315 } 316 317 StringJoiner sj = new StringJoiner(", "); 318 for (ClassModel s : supertypes) 319 if (s.isInterface) 320 sj.add(s.name); 321 if (sj.length() > 0) { 322 if (isInterface) 323 implementsClause = "extends " + sj.toString(); 324 else 325 implementsClause = "implements " + sj.toString(); 326 } 327 if (methodType != null) { 328 switch (methodType) { 329 case ABSTRACT: 330 methodBody = String.format("public abstract %s m();", overrideName(methodIndex)); 331 break; 332 case CONCRETE: 333 methodBody = String.format("public %s m() { return new %s(%d); };", 334 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex)); 335 break; 336 case DEFAULT: 337 methodBody = String.format("public default %s m() { return new %s(%d); };", 338 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex)); 339 break; 340 341 } 342 } 343 344 return String.format("public %s %s %s %s %s { %s }", isAbstract ? "abstract" : "", 345 isInterface ? "interface" : "class", 346 name, extendsClause, implementsClause, methodBody); 347 } 348 } 349 350 private static class Parser { 351 private final String input; 352 private final char[] chars; 353 private int index; 354 355 private Parser(String s) { 356 input = s; 357 chars = s.toCharArray(); 358 } 359 360 private char peek() { 361 return index < chars.length ? chars[index] : 0; 362 } 363 364 private boolean peek(String validChars) { 365 return validChars.indexOf(peek()) >= 0; 366 } 367 368 private char advanceIf(String validChars) { 369 if (peek(validChars)) 370 return chars[index++]; 371 else 372 return 0; 373 } 374 375 private char advanceIfDigit() { 376 return advanceIf("0123456789"); 377 } 378 379 private int index() { 380 StringBuilder buf = new StringBuilder(); 381 char c = advanceIfDigit(); 382 while (c != 0) { 383 buf.append(c); 384 c = advanceIfDigit(); 385 } 386 return Integer.valueOf(buf.toString()); 387 } 388 389 private char advance() { 390 return chars[index++]; 391 } 392 393 private char expect(String validChars) { 394 char c = advanceIf(validChars); 395 if (c == 0) 396 throw new IllegalArgumentException(String.format("Expecting %s at position %d of %s", validChars, index, input)); 397 return c; 398 } 399 400 public ClassModel parseClassModel() { 401 List<ClassModel> supers = new ArrayList<>(); 402 char name = expect(TYPE_LETTERS); 403 boolean isInterface = "IJK".indexOf(name) >= 0; 404 ClassModel.MethodType methodType = peek(isInterface ? "ad" : "ac") ? ClassModel.MethodType.find(advance()) : null; 405 int methodIndex = 0; 406 if (methodType != null) { 407 methodIndex = index(); 408 } 409 if (peek() == '(') { 410 advance(); 411 supers.add(parseClassModel()); 412 while (peek() == ',') { 413 advance(); 414 supers.add(parseClassModel()); 415 } 416 expect(")"); 417 } 418 return new ClassModel(new String(new char[]{ name }), isInterface, supers, methodType, methodIndex); 419 } 420 } 421} 422