|
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 */ |
|
24 import java.io.File; |
|
25 import java.io.IOException; |
|
26 import java.lang.reflect.Method; |
|
27 import java.util.ArrayList; |
|
28 import java.util.Arrays; |
|
29 import java.util.HashMap; |
|
30 import java.util.List; |
|
31 import java.util.Map; |
|
32 import java.util.StringJoiner; |
|
33 |
|
34 import org.testng.annotations.BeforeMethod; |
|
35 import org.testng.annotations.Test; |
|
36 |
|
37 import tools.javac.combo.*; |
|
38 |
|
39 import 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 |
|
60 public 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 } |