1 /* 2 * Copyright (c) 2011, 2019, 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 * @summary Proxy Generator Combo tests 27 * @library /test/langtools/tools/javac/lib . 28 * @modules jdk.compiler/com.sun.tools.javac.api 29 * jdk.compiler/com.sun.tools.javac.code 30 * jdk.compiler/com.sun.tools.javac.comp 31 * jdk.compiler/com.sun.tools.javac.file 32 * jdk.compiler/com.sun.tools.javac.main 33 * jdk.compiler/com.sun.tools.javac.tree 34 * jdk.compiler/com.sun.tools.javac.util 35 * @build combo.ComboTestHelper 36 * @run main/othervm ProxyGeneratorCombo 37 * @run main/othervm -Djdk.proxy.ProxyGenerator.v49=true ProxyGeneratorCombo 38 */ 39 40 import java.io.IOException; 41 import java.lang.reflect.InvocationHandler; 42 import java.lang.reflect.InvocationTargetException; 43 import java.lang.reflect.Method; 44 import java.lang.reflect.Modifier; 45 import java.lang.reflect.Proxy; 46 import java.lang.reflect.UndeclaredThrowableException; 47 import java.util.Arrays; 48 import java.util.ArrayList; 49 import java.util.Collections; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Set; 55 import java.util.StringJoiner; 56 57 import combo.ComboInstance; 58 import combo.ComboParameter; 59 import combo.ComboTask.Result; 60 import combo.ComboTestHelper; 61 62 public class ProxyGeneratorCombo extends ComboInstance<ProxyGeneratorCombo> { 63 64 // The unique number to qualify interface names, unique across multiple runs 65 private static int uniqueId = 0; 66 67 /** 68 * Class Access kinds. 69 */ 70 enum ClassAccessKind implements ComboParameter { 71 PUBLIC("public"), 72 PACKAGE(""), 73 ; 74 75 String classAccessTemplate; 76 77 ClassAccessKind(String classAccessTemplate) { 78 this.classAccessTemplate = classAccessTemplate; 79 } 80 81 @Override 82 public String expand(String optParameter) { 83 return classAccessTemplate; 84 } 85 } 86 87 /** 88 * Signatures of methods to be tested. 89 */ 90 enum MethodsKind implements ComboParameter { 91 NONE(""), 92 ZERO("#{METHODACCESS} void zero() #{EXCEPTION};"), 93 ONE("#{METHODACCESS} void one(#{ARG[0]} a) #{EXCEPTION};"), 94 TWO("#{METHODACCESS} void one(#{ARG[0]} b);\n" + 95 "#{METHODACCESS} void two(#{ARG[0]} a, #{ARG[1]} b);"), 96 THREE("#{METHODACCESS} void one(#{ARG[0]} a);\n" + 97 "#{METHODACCESS} void two(#{ARG[0]} a, #{ARG[1]} b);\n" + 98 "#{METHODACCESS} void three(#{ARG[0]} a, #{ARG[1]} b, #{ARG[0]} c);"); 99 100 String methodsKindTemplate; 101 102 MethodsKind(String methodsKindTemplate) { 103 this.methodsKindTemplate = methodsKindTemplate; 104 } 105 106 @Override 107 public String expand(String optParameter) { 108 return methodsKindTemplate; 109 } 110 } 111 112 /** 113 * Type of arguments to insert in method signatures 114 */ 115 enum ArgumentKind implements ComboParameter { 116 BOOLEAN("boolean"), 117 BYTE("byte"), 118 CHAR("char"), 119 SHORT("short"), 120 INT("int"), 121 LONG("long"), 122 FLOAT("float"), 123 DOUBLE("double"), 124 STRING("String"); 125 126 String argumentsKindTemplate; 127 128 ArgumentKind(String argumentsKindTemplate) { 129 this.argumentsKindTemplate = argumentsKindTemplate; 130 } 131 132 @Override 133 public String expand(String optParameter) { 134 return argumentsKindTemplate; 135 } 136 } 137 138 /** 139 * Exceptions to be added to zero and one methods. 140 */ 141 enum ExceptionKind implements ComboParameter { 142 NONE(null), 143 EXCEPTION(java.lang.Exception.class), 144 RUNTIME_EXCEPTION(java.lang.RuntimeException.class), 145 ILLEGAL_ARGUMENT_EXCEPTION(java.lang.IllegalArgumentException.class), 146 IOEXCEPTION(java.io.IOException.class), 147 /** 148 * Used only for throw testing, is empty for throws clause in the source, 149 */ 150 UNDECLARED_EXCEPTION(Exception1.class), 151 ; 152 153 Class<? extends Throwable> exceptionKindClass; 154 155 ExceptionKind(Class<? extends Throwable> exceptionKindClass) { 156 this.exceptionKindClass = exceptionKindClass; 157 } 158 159 @Override 160 public String expand(String optParameter) { 161 return exceptionKindClass == null || exceptionKindClass == Exception1.class 162 ? "" : "throws " + exceptionKindClass.getName(); 163 } 164 } 165 166 /** 167 * Extra interfaces to be added. 168 */ 169 enum MultiInterfacesKind implements ComboParameter { 170 NONE(new Class<?>[0]), 171 INTERFACE_WITH_EXCEPTION(new Class<?>[] {InterfaceWithException.class}), 172 ; 173 174 Class<?>[] multiInterfaceClasses; 175 176 MultiInterfacesKind(Class<?>[] multiInterfaceClasses) { 177 this.multiInterfaceClasses = multiInterfaceClasses; 178 } 179 180 @Override 181 // Not used for expansion only execution 182 public String expand(String optParameter) { 183 throw new RuntimeException("NYI"); 184 } 185 186 Class<?>[] classes() { 187 return multiInterfaceClasses; 188 } 189 } 190 191 @Override 192 public int id() { 193 return ++uniqueId; 194 } 195 196 protected void fail(String msg, Throwable thrown) { 197 super.fail(msg); 198 thrown.printStackTrace(); 199 } 200 201 /** 202 * Test interface with a "one(int)" method. 203 */ 204 interface InterfaceWithException { 205 // The signature must match the ONE MethodsKind above 206 void one(int a) throws RuntimeException, IOException; 207 } 208 209 210 /** 211 * Main to generate combinations and run the tests. 212 * @param args unused 213 * @throws Exception In case of failure 214 */ 215 public static void main(String... args) throws Exception { 216 217 // Test variations of access declarations 218 new ComboTestHelper<ProxyGeneratorCombo>() 219 .withDimension("CLASSACCESS", ClassAccessKind.values()) 220 .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC}) 221 .withDimension("METHODS", ProxyGeneratorCombo::saveMethod, 222 new MethodsKind[] {MethodsKind.NONE, MethodsKind.ZERO, MethodsKind.ONE}) 223 .withDimension("ARG[0]", new ArgumentKind[] {ArgumentKind.INT}) 224 .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException, 225 new ExceptionKind[]{ExceptionKind.NONE}) 226 .run(ProxyGeneratorCombo::new); 227 228 // Test variations of argument types 229 new ComboTestHelper<ProxyGeneratorCombo>() 230 .withDimension("CLASSACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC}) 231 .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC}) 232 .withDimension("METHODS", ProxyGeneratorCombo::saveMethod, 233 MethodsKind.values()) 234 .withArrayDimension("ARG", ProxyGeneratorCombo::saveArg, 2, 235 ArgumentKind.values()) 236 .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException, 237 new ExceptionKind[]{ExceptionKind.NONE}) 238 .withFilter(ProxyGeneratorCombo::filter) 239 .run(ProxyGeneratorCombo::new); 240 241 // Test for conflicts in Exceptions on methods with the same signatures 242 new ComboTestHelper<ProxyGeneratorCombo>() 243 .withDimension("CLASSACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC}) 244 .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC}) 245 .withDimension("METHODS", ProxyGeneratorCombo::saveMethod, new MethodsKind[] { 246 MethodsKind.ZERO}) 247 .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException, 248 ExceptionKind.values()) 249 .withDimension("MULTI_INTERFACES", ProxyGeneratorCombo::saveInterface, 250 new MultiInterfacesKind[] {MultiInterfacesKind.NONE}) 251 .run(ProxyGeneratorCombo::new); 252 } 253 254 /** 255 * Basic template. 256 */ 257 String template = "#{CLASSACCESS} interface #{TESTNAME} {\n" + 258 "#{METHODS}" + 259 "}"; 260 261 // Saved values of Combo values 262 private MultiInterfacesKind currInterface = MultiInterfacesKind.NONE; 263 private MethodsKind currMethod = MethodsKind.NONE; 264 private ExceptionKind currException = ExceptionKind.NONE; 265 private ArgumentKind[] currArgs = new ArgumentKind[0]; 266 267 void saveInterface(ComboParameter s) { 268 currInterface = (MultiInterfacesKind)s; 269 } 270 271 void saveMethod(ComboParameter s) { 272 currMethod = (MethodsKind)s; 273 } 274 275 void saveException(ComboParameter s) { 276 currException = (ExceptionKind)s; 277 } 278 279 void saveArg(ComboParameter s, int index) { 280 if (index >= currArgs.length) { 281 currArgs = Arrays.copyOf(currArgs, index + 1); 282 } 283 currArgs[index] = (ArgumentKind)s; 284 } 285 286 /** 287 * Filter out needless tests (mostly with more variations of arguments than needed). 288 * @return true to run the test, false if not 289 */ 290 boolean filter() { 291 if ((currMethod == MethodsKind.NONE || currMethod == MethodsKind.ZERO) && 292 currArgs.length >= 2) { 293 return currArgs[0] == ArgumentKind.INT && 294 currArgs[1] == ArgumentKind.INT; 295 } 296 if (currMethod == MethodsKind.ONE && 297 currArgs.length >= 2 ) { 298 return currArgs[0] == currArgs[1]; 299 } 300 return true; 301 } 302 303 /** 304 * Generate the source file and compile. 305 * Generate a proxy for the interface and test the resulting Proxy 306 * for the methods, exceptions and handling of a thrown exception 307 * @throws IOException catch all IOException 308 */ 309 @Override 310 public void doWork() throws IOException { 311 String cp = System.getProperty("test.classes"); 312 String ifaceName = "Interface_" + this.id(); 313 newCompilationTask() 314 .withSourceFromTemplate(ifaceName, template.replace("#{TESTNAME}", ifaceName)) 315 .withOption("-d") 316 .withOption(cp) 317 .generate(this::checkCompile); 318 try { 319 ClassLoader loader = ClassLoader.getSystemClassLoader(); 320 Class<?> tc = Class.forName(ifaceName); 321 InvocationHandler handler = 322 new ProxyHandler(currException.exceptionKindClass); 323 324 // Construct array of interfaces for the proxy 325 Class<?>[] interfaces = new Class<?>[currInterface.classes().length + 1]; 326 interfaces[0] = tc; 327 System.arraycopy(currInterface.classes(), 0, 328 interfaces, 1, 329 currInterface.classes().length); 330 331 Object proxy = Proxy.newProxyInstance(loader, interfaces, handler); 332 if (!Proxy.isProxyClass(proxy.getClass())) { 333 fail("generated proxy is not a proxy class"); 334 return; 335 } 336 for (Class<?> i : interfaces) { 337 if (!i.isAssignableFrom(proxy.getClass())) { 338 fail("proxy is not assignable to " + i.getName()); 339 } 340 } 341 try { 342 String s = proxy.toString(); 343 } catch (Exception ex) { 344 ex.printStackTrace(); 345 fail("proxy.toString() threw an exception"); 346 } 347 348 checkDeclaredProxyExceptions(proxy, interfaces); 349 350 if (currMethod == MethodsKind.ZERO && currException != ExceptionKind.NONE) { 351 checkThrowsException(proxy, interfaces); 352 } 353 354 } catch (Exception ex) { 355 throw new RuntimeException("doWork unexpected", ex); 356 } 357 } 358 359 /** 360 * Check that the exceptions declared on the proxy match the declarations for 361 * exceptions from the interfaces. 362 * 363 * @param proxy a proxy object 364 * @param interfaces the interfaces that defined it 365 */ 366 void checkDeclaredProxyExceptions(Object proxy, Class<?>[] interfaces) { 367 var allMethods = allMethods(Arrays.asList(interfaces)); 368 Method[] methods = proxy.getClass().getDeclaredMethods(); 369 for (Method m : methods) { 370 String sig = toShortSignature(m); 371 var imethods = allMethods.get(sig); 372 if (imethods != null) { 373 var expectedEx = Set.copyOf(Arrays.asList(m.getExceptionTypes())); 374 var exs = Set.copyOf(extractExceptions(imethods)); 375 if (!expectedEx.equals(exs)) { 376 System.out.printf("mismatch on exceptions for method %s:%nExpected: " + 377 "%s%nActual: %s%n", 378 sig, expectedEx, exs); 379 fail("Exceptions declared on proxy don't match interface methods"); 380 } 381 } 382 } 383 } 384 385 void checkThrowsException(Object proxy, Class<?>[] interfaces) { 386 ProxyHandler ph = (ProxyHandler)(Proxy.getInvocationHandler(proxy)); 387 try { 388 Method m = proxy.getClass().getDeclaredMethod("zero"); 389 m.invoke(proxy); 390 fail("Missing exception: " + ph.exceptionClass); 391 } catch (NoSuchMethodException nsme) { 392 System.out.printf("No method 'zero()' to test exceptions with%n"); 393 for (var cl : interfaces) { 394 System.out.printf(" i/f %s: %s%n", cl, Arrays.toString(cl.getMethods())); 395 } 396 Method[] methods = proxy.getClass().getMethods(); 397 System.out.printf(" Proxy methods: %s%n", Arrays.toString(methods)); 398 fail("No such method test bug", nsme); 399 } catch (InvocationTargetException actual) { 400 ph.checkThrownException(actual.getTargetException()); 401 } catch (IllegalAccessException iae) { 402 fail("IllegalAccessException", iae); 403 } 404 } 405 406 /** 407 * Exceptions known to be supported by all methods with the same signature. 408 * @return a list of universal exception types 409 */ 410 private static List<Class<?>> extractExceptions(List<Method> methods) { 411 // for all methods with the same signature 412 // start with the exceptions from the first method 413 // while there are any exceptions remaining 414 // look at the next method 415 List<Class<?>> exceptions = null; 416 for (Method m : methods) { 417 var e = m.getExceptionTypes(); 418 if (e.length == 0) 419 return emptyClassList(); 420 List<Class<?>> elist = Arrays.asList(e); 421 if (exceptions == null) { 422 exceptions = elist; // initialize to first method exceptions 423 } else { 424 // for each exception 425 // if it is compatible (both ways) with any of the existing exceptions continue 426 // else remove the current exception 427 var okExceptions = new HashSet<Class<?>>(); 428 for (int j = 0; j < exceptions.size(); j++) { 429 var ex = exceptions.get(j); 430 for (int i = 0; i < elist.size();i++) { 431 var ci = elist.get(i); 432 433 if (ci.isAssignableFrom(ex)) { 434 okExceptions.add(ex); 435 } 436 if (ex.isAssignableFrom(ci)) { 437 okExceptions.add(ci); 438 } 439 } 440 } 441 if (exceptions.isEmpty()) { 442 // The empty set terminates the search for a common set of exceptions 443 return emptyClassList(); 444 } 445 // Use the new set for the next iteration 446 exceptions = List.copyOf(okExceptions); 447 } 448 } 449 return (exceptions == null) ? emptyClassList() : exceptions; 450 } 451 452 /** 453 * An empty correctly typed list of classes. 454 * @return An empty typed list of classes 455 */ 456 @SuppressWarnings("unchecked") 457 static List<Class<?>> emptyClassList() { 458 return Collections.EMPTY_LIST; 459 } 460 461 /** 462 * Accumulate all of the unique methods. 463 * 464 * @param interfaces a list of interfaces 465 * @return a map from signature to List of methods, unique by signature 466 */ 467 private static Map<String, List<Method>> allMethods(List<Class<?>> interfaces) { 468 Map<String, List<Method>> methods = new HashMap<>(); 469 for (Class<?> c : interfaces) { 470 for (Method m : c.getMethods()) { 471 if (!Modifier.isStatic(m.getModifiers())) { 472 String sig = toShortSignature(m); 473 methods.computeIfAbsent(sig, s -> new ArrayList<Method>()) 474 .add(m); 475 } 476 } 477 } 478 return methods; 479 } 480 481 /** 482 * The signature of a method without the return type. 483 * @param m a Method 484 * @return the signature with method name and parameters 485 */ 486 static String toShortSignature(Method m) { 487 StringJoiner sj = new StringJoiner(",", m.getName() + "(", ")"); 488 for (Class<?> parameterType : m.getParameterTypes()) { 489 sj.add(parameterType.getTypeName()); 490 } 491 return sj.toString(); 492 } 493 494 /** 495 * Report any compilation errors. 496 * @param res the result 497 */ 498 void checkCompile(Result<?> res) { 499 if (res.hasErrors()) { 500 fail("invalid diagnostics for source:\n" + 501 res.compilationInfo() + 502 "\nFound error: " + res.hasErrors()); 503 } 504 } 505 506 /** 507 * The Handler for the proxy includes the method to invoke the proxy 508 * and the expected exception, if any. 509 */ 510 class ProxyHandler implements InvocationHandler { 511 512 private final Class<? extends Throwable> exceptionClass; 513 514 ProxyHandler(Class<? extends Throwable> throwable) { 515 this.exceptionClass = throwable; 516 } 517 518 /** 519 * Invoke a method on the proxy or return a value. 520 * @param proxy the proxy instance that the method was invoked on 521 * @param method a method 522 * @param args some args 523 * @return 524 * @throws Throwable a throwable 525 */ 526 @Override 527 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 528 if (method.getName().equals("toString")) { 529 return "Proxy" + System.identityHashCode(proxy); 530 } 531 if (method.getName().equals("zero")) { 532 if (exceptionClass != null) { 533 throw exceptionClass.getDeclaredConstructor().newInstance(); 534 } 535 } 536 return "meth: " + method.toString(); 537 } 538 539 /** 540 * Check that the expected exception was thrown. 541 * Special case is handled for Exception1 which does not appear in the 542 * throws clause of the method so UndeclaredThrowableException is expected. 543 */ 544 void checkThrownException(Throwable thrown) { 545 if (exceptionClass == Exception1.class && 546 thrown instanceof UndeclaredThrowableException && 547 ((UndeclaredThrowableException)thrown).getUndeclaredThrowable() instanceof Exception1) { 548 // Exception1 caused UndeclaredThrowableException 549 return; 550 } else if (exceptionClass == Exception1.class) { 551 fail("UndeclaredThrowableException", thrown); 552 } 553 554 if (exceptionClass != null && 555 !exceptionClass.equals(thrown.getClass())) { 556 throw new RuntimeException("Wrong exception thrown: expected: " + exceptionClass + 557 ", actual: " + thrown.getClass()); 558 } 559 } 560 } 561 562 /** 563 * Exception to be thrown as a test of InvocationTarget. 564 */ 565 static class Exception1 extends Exception { 566 private static final long serialVersionUID = 1L; 567 Exception1() {} 568 } 569 }