|
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 package test.java.lang.invoke.MethodHandles; |
|
24 |
|
25 import com.oracle.testlibrary.jsr292.Helper; |
|
26 import jdk.testlibrary.Asserts; |
|
27 |
|
28 import java.lang.invoke.MethodHandle; |
|
29 import java.lang.invoke.MethodHandles; |
|
30 import java.lang.invoke.MethodType; |
|
31 import java.lang.reflect.Array; |
|
32 import java.util.*; |
|
33 import java.util.function.BiFunction; |
|
34 import java.util.function.Function; |
|
35 import java.util.function.Supplier; |
|
36 |
|
37 /* @test |
|
38 * @library /lib/testlibrary/jsr292 /lib/testlibrary/ |
|
39 * @compile CatchExceptionTest.java |
|
40 * @run main/othervm -esa test.java.lang.invoke.MethodHandles.CatchExceptionTest |
|
41 */ |
|
42 public class CatchExceptionTest { |
|
43 private static final List<Class<?>> ARGS_CLASSES; |
|
44 protected static final int MAX_ARITY = Helper.MAX_ARITY - 1; |
|
45 static { |
|
46 Class<?> classes[] = { |
|
47 Object.class, |
|
48 long.class, |
|
49 int.class, |
|
50 byte.class, |
|
51 Integer[].class, |
|
52 double[].class, |
|
53 String.class, |
|
54 }; |
|
55 List<Class<?>> list = new ArrayList<>(MAX_ARITY); |
|
56 for (int i = 0; i < MAX_ARITY; ++i) { |
|
57 list.add(classes[Helper.RNG.nextInt(classes.length)]); |
|
58 } |
|
59 ARGS_CLASSES = Collections.unmodifiableList(list); |
|
60 } |
|
61 |
|
62 private final TestCase testCase; |
|
63 private final int nargs; |
|
64 private final int argsCount; |
|
65 private final MethodHandle catcher; |
|
66 private int dropped; |
|
67 private MethodHandle thrower; |
|
68 |
|
69 |
|
70 public CatchExceptionTest(TestCase testCase, final boolean isVararg, final int argsCount, |
|
71 final int catchDrops) { |
|
72 this.testCase = testCase; |
|
73 this.dropped = catchDrops; |
|
74 if (Helper.IS_VERBOSE) { |
|
75 System.out.printf("CatchException::CatchException(%s, isVararg=%b " + |
|
76 "argsCount=%d catchDrops=%d)%n", |
|
77 testCase, isVararg, argsCount, catchDrops |
|
78 ); |
|
79 } |
|
80 MethodHandle thrower = testCase.thrower; |
|
81 int throwerLen = thrower.type().parameterCount(); |
|
82 List<Class<?>> classes; |
|
83 int extra = Math.max(0, argsCount - throwerLen); |
|
84 classes = getThrowerParams(isVararg, extra); |
|
85 this.argsCount = throwerLen + classes.size(); |
|
86 thrower = Helper.addTrailingArgs(thrower, this.argsCount, classes); |
|
87 if (isVararg && argsCount > throwerLen) { |
|
88 MethodType mt = thrower.type(); |
|
89 Class<?> lastParam = mt.parameterType(mt.parameterCount() - 1); |
|
90 thrower = thrower.asVarargsCollector(lastParam); |
|
91 } |
|
92 this.thrower = thrower; |
|
93 this.dropped = Math.min(this.argsCount, catchDrops); |
|
94 catcher = testCase.getCatcher(getCatcherParams()); |
|
95 nargs = Math.max(2, this.argsCount); |
|
96 } |
|
97 |
|
98 public static void main(String[] args) throws Throwable { |
|
99 for (CatchExceptionTest test : TestFactory.MANDATORY_TEST_CASES) { |
|
100 test.runTest(); |
|
101 } |
|
102 TestFactory factory = new TestFactory(); |
|
103 CatchExceptionTest test; |
|
104 while ((test = factory.nextTest()) != null ) { |
|
105 test.runTest(); |
|
106 } |
|
107 } |
|
108 |
|
109 private List<Class<?>> getThrowerParams(boolean isVararg, int argsCount) { |
|
110 boolean unmodifiable = true; |
|
111 List<Class<?>> classes; |
|
112 classes = ARGS_CLASSES.subList(0, |
|
113 Math.min(argsCount, (MAX_ARITY / 2) - 1)); |
|
114 int extra = 0; |
|
115 if (argsCount >= MAX_ARITY / 2) { |
|
116 classes = new ArrayList<>(classes); |
|
117 unmodifiable = false; |
|
118 extra = (int) classes.stream().filter(Helper::isDoubleCost).count(); |
|
119 int i = classes.size(); |
|
120 while (classes.size() + extra < argsCount) { |
|
121 Class<?> aClass = ARGS_CLASSES.get(i); |
|
122 if (Helper.isDoubleCost(aClass)) { |
|
123 ++extra; |
|
124 if (classes.size() + extra >= argsCount) { |
|
125 break; |
|
126 } |
|
127 } |
|
128 classes.add(aClass); |
|
129 } |
|
130 } |
|
131 if (isVararg && classes.size() > 0) { |
|
132 if (unmodifiable) { |
|
133 classes = new ArrayList<>(classes); |
|
134 } |
|
135 int last = classes.size() - 1; |
|
136 Class<?> aClass = classes.get(classes.size() - 1); |
|
137 aClass = Array.newInstance(aClass, 2).getClass(); |
|
138 classes.set(last, aClass); |
|
139 } |
|
140 return classes; |
|
141 } |
|
142 |
|
143 |
|
144 private List<Class<?>> getCatcherParams() { |
|
145 int catchArgc = 1 + this.argsCount - dropped; |
|
146 List<Class<?>> result = new ArrayList<>( |
|
147 thrower.type().parameterList().subList(0, catchArgc - 1)); |
|
148 // prepend throwable |
|
149 result.add(0, testCase.throwableClass); |
|
150 return result; |
|
151 } |
|
152 |
|
153 private void runTest() { |
|
154 Helper.clear(); |
|
155 |
|
156 Object[] args = Helper.randomArgs( |
|
157 argsCount, thrower.type().parameterArray()); |
|
158 Object arg0 = Helper.MISSING_ARG; |
|
159 Object arg1 = testCase.thrown; |
|
160 if (argsCount > 0) { |
|
161 arg0 = args[0]; |
|
162 } |
|
163 if (argsCount > 1) { |
|
164 args[1] = arg1; |
|
165 } |
|
166 Asserts.assertEQ(nargs, thrower.type().parameterCount()); |
|
167 if (argsCount < nargs) { |
|
168 Object[] appendArgs = {arg0, arg1}; |
|
169 appendArgs = Arrays.copyOfRange(appendArgs, argsCount, nargs); |
|
170 thrower = MethodHandles.insertArguments( |
|
171 thrower, argsCount, appendArgs); |
|
172 } |
|
173 Asserts.assertEQ(argsCount, thrower.type().parameterCount()); |
|
174 |
|
175 MethodHandle target = MethodHandles.catchException( |
|
176 testCase.filter(thrower), testCase.throwableClass, |
|
177 testCase.filter(catcher)); |
|
178 |
|
179 Asserts.assertEQ(thrower.type(), target.type()); |
|
180 Asserts.assertEQ(argsCount, target.type().parameterCount()); |
|
181 |
|
182 Object returned; |
|
183 try { |
|
184 returned = target.invokeWithArguments(args); |
|
185 } catch (Throwable ex) { |
|
186 testCase.assertCatch(ex); |
|
187 returned = ex; |
|
188 } |
|
189 |
|
190 testCase.assertReturn(returned, arg0, arg1, dropped, args); |
|
191 } |
|
192 } |
|
193 |
|
194 class TestFactory { |
|
195 public static final List<CatchExceptionTest> MANDATORY_TEST_CASES = new ArrayList<>(); |
|
196 |
|
197 private static final int MIN_TESTED_ARITY = 10; |
|
198 |
|
199 static { |
|
200 for (int[] args : new int[][]{ |
|
201 {0, 0}, |
|
202 {MIN_TESTED_ARITY, 0}, |
|
203 {MIN_TESTED_ARITY, MIN_TESTED_ARITY}, |
|
204 {CatchExceptionTest.MAX_ARITY, 0}, |
|
205 {CatchExceptionTest.MAX_ARITY, CatchExceptionTest.MAX_ARITY}, |
|
206 }) { |
|
207 MANDATORY_TEST_CASES.addAll(createTests(args[0], args[1])); |
|
208 } |
|
209 } |
|
210 |
|
211 private int count; |
|
212 private int args; |
|
213 private int dropArgs; |
|
214 private int currentMaxDrops; |
|
215 private int maxArgs; |
|
216 private int maxDrops; |
|
217 private int constructor; |
|
218 private int constructorSize; |
|
219 private boolean isVararg; |
|
220 |
|
221 public TestFactory() { |
|
222 if (Helper.IS_THOROUGH) { |
|
223 maxArgs = maxDrops = CatchExceptionTest.MAX_ARITY; |
|
224 } else { |
|
225 maxArgs = MIN_TESTED_ARITY |
|
226 + Helper.RNG.nextInt(CatchExceptionTest.MAX_ARITY |
|
227 - MIN_TESTED_ARITY) |
|
228 + 1; |
|
229 maxDrops = MIN_TESTED_ARITY |
|
230 + Helper.RNG.nextInt(maxArgs - MIN_TESTED_ARITY) |
|
231 + 1; |
|
232 args = 1; |
|
233 } |
|
234 |
|
235 if (Helper.IS_VERBOSE) { |
|
236 System.out.printf("maxArgs = %d%nmaxDrops = %d%n", |
|
237 maxArgs, maxDrops); |
|
238 } |
|
239 constructorSize = TestCase.CONSTRUCTORS.size(); |
|
240 } |
|
241 |
|
242 private static List<CatchExceptionTest> createTests(int argsCount, |
|
243 int catchDrops) { |
|
244 if (catchDrops > argsCount || argsCount < 0 || catchDrops < 0) { |
|
245 throw new IllegalArgumentException("argsCount = " + argsCount |
|
246 + ", catchDrops = " + catchDrops |
|
247 ); |
|
248 } |
|
249 List<CatchExceptionTest> result = new ArrayList<>( |
|
250 TestCase.CONSTRUCTORS.size()); |
|
251 for (Supplier<TestCase> constructor : TestCase.CONSTRUCTORS) { |
|
252 result.add(new CatchExceptionTest(constructor.get(), |
|
253 /* isVararg = */ true, |
|
254 argsCount, |
|
255 catchDrops)); |
|
256 result.add(new CatchExceptionTest(constructor.get(), |
|
257 /* isVararg = */ false, |
|
258 argsCount, |
|
259 catchDrops)); |
|
260 } |
|
261 return result; |
|
262 } |
|
263 |
|
264 /** |
|
265 * @return next test from test matrix: |
|
266 * {varArgs, noVarArgs} x TestCase.rtypes x TestCase.THROWABLES x {1, .., maxArgs } x {1, .., maxDrops} |
|
267 */ |
|
268 public CatchExceptionTest nextTest() { |
|
269 if (constructor < constructorSize) { |
|
270 return createTest(); |
|
271 } |
|
272 constructor = 0; |
|
273 count++; |
|
274 if (!Helper.IS_THOROUGH && count > Helper.TEST_LIMIT) { |
|
275 System.out.println("test limit is exceeded"); |
|
276 return null; |
|
277 } |
|
278 if (dropArgs <= currentMaxDrops) { |
|
279 if (dropArgs == 1) { |
|
280 if (Helper.IS_THOROUGH || Helper.RNG.nextBoolean()) { |
|
281 ++dropArgs; |
|
282 return createTest(); |
|
283 } else if (Helper.IS_VERBOSE) { |
|
284 System.out.printf( |
|
285 "argsCount=%d : \"drop\" scenarios are skipped%n", |
|
286 args); |
|
287 } |
|
288 } else { |
|
289 ++dropArgs; |
|
290 return createTest(); |
|
291 } |
|
292 } |
|
293 |
|
294 if (args <= maxArgs) { |
|
295 dropArgs = 1; |
|
296 currentMaxDrops = Math.min(args, maxDrops); |
|
297 ++args; |
|
298 return createTest(); |
|
299 } |
|
300 return null; |
|
301 } |
|
302 |
|
303 private CatchExceptionTest createTest() { |
|
304 if (!Helper.IS_THOROUGH) { |
|
305 return new CatchExceptionTest( |
|
306 TestCase.CONSTRUCTORS.get(constructor++).get(), |
|
307 Helper.RNG.nextBoolean(), args, dropArgs); |
|
308 } else { |
|
309 if (isVararg) { |
|
310 isVararg = false; |
|
311 return new CatchExceptionTest( |
|
312 TestCase.CONSTRUCTORS.get(constructor++).get(), |
|
313 isVararg, args, dropArgs); |
|
314 } else { |
|
315 isVararg = true; |
|
316 return new CatchExceptionTest( |
|
317 TestCase.CONSTRUCTORS.get(constructor).get(), |
|
318 isVararg, args, dropArgs); |
|
319 } |
|
320 } |
|
321 } |
|
322 } |
|
323 |
|
324 class TestCase<T> { |
|
325 private static enum ThrowMode { |
|
326 NOTHING, |
|
327 CAUGHT, |
|
328 UNCAUGHT, |
|
329 ADAPTER |
|
330 } |
|
331 |
|
332 @SuppressWarnings("unchecked") |
|
333 public static final List<Supplier<TestCase>> CONSTRUCTORS; |
|
334 private static final MethodHandle FAKE_IDENTITY; |
|
335 private static final MethodHandle THROW_OR_RETURN; |
|
336 private static final MethodHandle CATCHER; |
|
337 |
|
338 static { |
|
339 try { |
|
340 MethodHandles.Lookup lookup = MethodHandles.lookup(); |
|
341 THROW_OR_RETURN = lookup.findStatic( |
|
342 TestCase.class, |
|
343 "throwOrReturn", |
|
344 MethodType.methodType(Object.class, Object.class, |
|
345 Throwable.class) |
|
346 ); |
|
347 CATCHER = lookup.findStatic( |
|
348 TestCase.class, |
|
349 "catcher", |
|
350 MethodType.methodType(Object.class, Object.class)); |
|
351 FAKE_IDENTITY = lookup.findVirtual( |
|
352 TestCase.class, "fakeIdentity", |
|
353 MethodType.methodType(Object.class, Object.class)); |
|
354 |
|
355 } catch (NoSuchMethodException | IllegalAccessException e) { |
|
356 throw new Error(e); |
|
357 } |
|
358 PartialConstructor[] constructors = { |
|
359 create(Object.class, Object.class::cast), |
|
360 create(String.class, Objects::toString), |
|
361 create(int[].class, x -> new int[]{Objects.hashCode(x)}), |
|
362 create(long.class, |
|
363 x -> Objects.hashCode(x) & (-1L >>> 32)), |
|
364 create(void.class, TestCase::noop)}; |
|
365 Throwable[] throwables = { |
|
366 new ClassCastException("testing"), |
|
367 new java.io.IOException("testing"), |
|
368 new LinkageError("testing")}; |
|
369 List<Supplier<TestCase>> list = new ArrayList<>(constructors.length * |
|
370 throwables.length * ThrowMode.values().length); |
|
371 //noinspection unchecked |
|
372 for (PartialConstructor f : constructors) { |
|
373 for (ThrowMode mode : ThrowMode.values()) { |
|
374 for (Throwable t : throwables) { |
|
375 list.add(f.apply(mode, t)); |
|
376 } |
|
377 } |
|
378 } |
|
379 CONSTRUCTORS = Collections.unmodifiableList(list); |
|
380 } |
|
381 |
|
382 public final Class<T> rtype; |
|
383 public final ThrowMode throwMode; |
|
384 public final Throwable thrown; |
|
385 public final Class<? extends Throwable> throwableClass; |
|
386 /** |
|
387 * MH which takes 2 args (Object,Throwable), 1st is the return value, |
|
388 * 2nd is the exception which will be thrown, if it's supposed in current |
|
389 * {@link #throwMode}. |
|
390 */ |
|
391 public final MethodHandle thrower; |
|
392 private final Function<Object, T> cast; |
|
393 protected MethodHandle filter; |
|
394 private int fakeIdentityCount; |
|
395 |
|
396 private TestCase(Class<T> rtype, Function<Object, T> cast, |
|
397 ThrowMode throwMode, Throwable thrown) |
|
398 throws NoSuchMethodException, IllegalAccessException { |
|
399 this.cast = cast; |
|
400 filter = MethodHandles.lookup().findVirtual( |
|
401 Function.class, |
|
402 "apply", |
|
403 MethodType.methodType(Object.class, Object.class)) |
|
404 .bindTo(cast); |
|
405 this.rtype = rtype; |
|
406 this.throwMode = throwMode; |
|
407 this.throwableClass = thrown.getClass(); |
|
408 switch (throwMode) { |
|
409 case NOTHING: |
|
410 this.thrown = null; |
|
411 break; |
|
412 case ADAPTER: |
|
413 case UNCAUGHT: |
|
414 this.thrown = new Error("do not catch this"); |
|
415 break; |
|
416 default: |
|
417 this.thrown = thrown; |
|
418 } |
|
419 |
|
420 MethodHandle throwOrReturn = THROW_OR_RETURN; |
|
421 if (throwMode == ThrowMode.ADAPTER) { |
|
422 MethodHandle fakeIdentity = FAKE_IDENTITY.bindTo(this); |
|
423 for (int i = 0; i < 10; ++i) { |
|
424 throwOrReturn = MethodHandles.filterReturnValue( |
|
425 throwOrReturn, fakeIdentity); |
|
426 } |
|
427 } |
|
428 thrower = throwOrReturn.asType(MethodType.genericMethodType(2)); |
|
429 } |
|
430 |
|
431 private static Void noop(Object x) { |
|
432 return null; |
|
433 } |
|
434 |
|
435 private static <T2> PartialConstructor create( |
|
436 Class<T2> rtype, Function<Object, T2> cast) { |
|
437 return (t, u) -> () -> { |
|
438 try { |
|
439 return new TestCase<>(rtype, cast, t, u); |
|
440 } catch (NoSuchMethodException | IllegalAccessException e) { |
|
441 throw new Error(e); |
|
442 } |
|
443 }; |
|
444 } |
|
445 |
|
446 private static <T extends Throwable> |
|
447 Object throwOrReturn(Object normal, T exception) throws T { |
|
448 if (exception != null) { |
|
449 Helper.called("throwOrReturn/throw", normal, exception); |
|
450 throw exception; |
|
451 } |
|
452 Helper.called("throwOrReturn/normal", normal, exception); |
|
453 return normal; |
|
454 } |
|
455 |
|
456 private static <T extends Throwable> |
|
457 Object catcher(Object o) { |
|
458 Helper.called("catcher", o); |
|
459 return o; |
|
460 } |
|
461 |
|
462 public MethodHandle filter(MethodHandle target) { |
|
463 return MethodHandles.filterReturnValue(target, filter); |
|
464 } |
|
465 |
|
466 public MethodHandle getCatcher(List<Class<?>> classes) { |
|
467 return MethodHandles.filterReturnValue(Helper.AS_LIST.asType( |
|
468 MethodType.methodType(Object.class, classes)), |
|
469 CATCHER |
|
470 ); |
|
471 } |
|
472 |
|
473 @Override |
|
474 public String toString() { |
|
475 return "TestCase{" + |
|
476 "rtype=" + rtype + |
|
477 ", throwMode=" + throwMode + |
|
478 ", throwableClass=" + throwableClass + |
|
479 '}'; |
|
480 } |
|
481 |
|
482 public String callName() { |
|
483 return "throwOrReturn/" + |
|
484 (throwMode == ThrowMode.NOTHING |
|
485 ? "normal" |
|
486 : "throw"); |
|
487 } |
|
488 |
|
489 public void assertReturn(Object returned, Object arg0, Object arg1, |
|
490 int catchDrops, Object... args) { |
|
491 int lag = 0; |
|
492 if (throwMode == ThrowMode.CAUGHT) { |
|
493 lag = 1; |
|
494 } |
|
495 Helper.assertCalled(lag, callName(), arg0, arg1); |
|
496 |
|
497 if (throwMode == ThrowMode.NOTHING) { |
|
498 assertEQ(cast.apply(arg0), returned); |
|
499 } else if (throwMode == ThrowMode.CAUGHT) { |
|
500 List<Object> catchArgs = new ArrayList<>(Arrays.asList(args)); |
|
501 // catcher receives an initial subsequence of target arguments: |
|
502 catchArgs.subList(args.length - catchDrops, args.length).clear(); |
|
503 // catcher also receives the exception, prepended: |
|
504 catchArgs.add(0, thrown); |
|
505 Helper.assertCalled("catcher", catchArgs); |
|
506 assertEQ(cast.apply(catchArgs), returned); |
|
507 } |
|
508 Asserts.assertEQ(0, fakeIdentityCount); |
|
509 } |
|
510 |
|
511 private void assertEQ(T t, Object returned) { |
|
512 if (rtype.isArray()) { |
|
513 Asserts.assertEQ(t.getClass(), returned.getClass()); |
|
514 int n = Array.getLength(t); |
|
515 Asserts.assertEQ(n, Array.getLength(returned)); |
|
516 for (int i = 0; i < n; ++i) { |
|
517 Asserts.assertEQ(Array.get(t, i), Array.get(returned, i)); |
|
518 } |
|
519 } else { |
|
520 Asserts.assertEQ(t, returned); |
|
521 } |
|
522 } |
|
523 |
|
524 private Object fakeIdentity(Object x) { |
|
525 System.out.println("should throw through this!"); |
|
526 ++fakeIdentityCount; |
|
527 return x; |
|
528 } |
|
529 |
|
530 public void assertCatch(Throwable ex) { |
|
531 try { |
|
532 Asserts.assertSame(thrown, ex, |
|
533 "must get the out-of-band exception"); |
|
534 } catch (Throwable t) { |
|
535 ex.printStackTrace(); |
|
536 } |
|
537 } |
|
538 |
|
539 public interface PartialConstructor |
|
540 extends BiFunction<ThrowMode, Throwable, Supplier<TestCase>> { |
|
541 } |
|
542 } |