TestInvokeDynamic.java revision 2942:08092deced3f
1/* 2 * Copyright (c) 2012, 2015, 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 * @bug 7194586 8003280 8006694 8010404 27 * @summary Add lambda tests 28 * Add back-end support for invokedynamic 29 * temporarily workaround combo tests are causing time out in several platforms 30 * @library ../lib 31 * @modules jdk.jdeps/com.sun.tools.classfile 32 * jdk.compiler/com.sun.tools.javac.api 33 * jdk.compiler/com.sun.tools.javac.code 34 * jdk.compiler/com.sun.tools.javac.jvm 35 * jdk.compiler/com.sun.tools.javac.tree 36 * jdk.compiler/com.sun.tools.javac.util 37 * @build JavacTestingAbstractThreadedTest 38 * @run main/othervm TestInvokeDynamic 39 */ 40 41// use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047) 42// see JDK-8006746 43 44import java.io.File; 45import java.net.URI; 46import java.util.ArrayList; 47import java.util.Arrays; 48import java.util.Locale; 49 50import javax.tools.Diagnostic; 51import javax.tools.JavaFileObject; 52import javax.tools.SimpleJavaFileObject; 53 54import com.sun.source.tree.MethodInvocationTree; 55import com.sun.source.tree.MethodTree; 56import com.sun.source.util.TaskEvent; 57import com.sun.source.util.TaskListener; 58import com.sun.source.util.TreeScanner; 59 60import com.sun.tools.classfile.Attribute; 61import com.sun.tools.classfile.BootstrapMethods_attribute; 62import com.sun.tools.classfile.ClassFile; 63import com.sun.tools.classfile.Code_attribute; 64import com.sun.tools.classfile.ConstantPool.*; 65import com.sun.tools.classfile.Instruction; 66import com.sun.tools.classfile.LineNumberTable_attribute; 67import com.sun.tools.classfile.Method; 68 69import com.sun.tools.javac.api.JavacTaskImpl; 70import com.sun.tools.javac.code.Symbol; 71import com.sun.tools.javac.code.Symbol.MethodSymbol; 72import com.sun.tools.javac.code.Symtab; 73import com.sun.tools.javac.code.Types; 74import com.sun.tools.javac.jvm.Pool; 75import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 76import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 77import com.sun.tools.javac.tree.JCTree.JCIdent; 78import com.sun.tools.javac.util.Context; 79import com.sun.tools.javac.util.Names; 80 81import static com.sun.tools.javac.jvm.ClassFile.*; 82 83public class TestInvokeDynamic 84 extends JavacTestingAbstractThreadedTest 85 implements Runnable { 86 87 enum StaticArgumentKind { 88 STRING("Hello!", "String", "Ljava/lang/String;") { 89 @Override 90 boolean check(CPInfo cpInfo) throws Exception { 91 return (cpInfo instanceof CONSTANT_String_info) && 92 ((CONSTANT_String_info)cpInfo).getString() 93 .equals(value); 94 } 95 }, 96 CLASS(null, "Class<?>", "Ljava/lang/Class;") { 97 @Override 98 boolean check(CPInfo cpInfo) throws Exception { 99 return (cpInfo instanceof CONSTANT_Class_info) && 100 ((CONSTANT_Class_info)cpInfo).getName() 101 .equals("java/lang/String"); 102 } 103 }, 104 INTEGER(1, "int", "I") { 105 @Override 106 boolean check(CPInfo cpInfo) throws Exception { 107 return (cpInfo instanceof CONSTANT_Integer_info) && 108 ((CONSTANT_Integer_info)cpInfo).value == 109 ((Integer)value).intValue(); 110 } 111 }, 112 LONG(1L, "long", "J") { 113 @Override 114 boolean check(CPInfo cpInfo) throws Exception { 115 return (cpInfo instanceof CONSTANT_Long_info) && 116 ((CONSTANT_Long_info)cpInfo).value == 117 ((Long)value).longValue(); 118 } 119 }, 120 FLOAT(1.0f, "float", "F") { 121 @Override 122 boolean check(CPInfo cpInfo) throws Exception { 123 return (cpInfo instanceof CONSTANT_Float_info) && 124 ((CONSTANT_Float_info)cpInfo).value == 125 ((Float)value).floatValue(); 126 } 127 }, 128 DOUBLE(1.0, "double","D") { 129 @Override 130 boolean check(CPInfo cpInfo) throws Exception { 131 return (cpInfo instanceof CONSTANT_Double_info) && 132 ((CONSTANT_Double_info)cpInfo).value == 133 ((Double)value).doubleValue(); 134 } 135 }, 136 METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") { 137 @Override 138 boolean check(CPInfo cpInfo) throws Exception { 139 if (!(cpInfo instanceof CONSTANT_MethodHandle_info)) 140 return false; 141 CONSTANT_MethodHandle_info handleInfo = 142 (CONSTANT_MethodHandle_info)cpInfo; 143 return handleInfo.getCPRefInfo().getClassName().equals("Array") && 144 handleInfo.reference_kind == RefKind.REF_invokeVirtual && 145 handleInfo.getCPRefInfo() 146 .getNameAndTypeInfo().getName().equals("clone") && 147 handleInfo.getCPRefInfo() 148 .getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;"); 149 } 150 }, 151 METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") { 152 @Override 153 boolean check(CPInfo cpInfo) throws Exception { 154 return (cpInfo instanceof CONSTANT_MethodType_info) && 155 ((CONSTANT_MethodType_info)cpInfo).getType() 156 .equals("()Ljava/lang/Object;"); 157 } 158 }; 159 160 Object value; 161 String sourceTypeStr; 162 String bytecodeTypeStr; 163 164 StaticArgumentKind(Object value, String sourceTypeStr, 165 String bytecodeTypeStr) { 166 this.value = value; 167 this.sourceTypeStr = sourceTypeStr; 168 this.bytecodeTypeStr = bytecodeTypeStr; 169 } 170 171 abstract boolean check(CPInfo cpInfo) throws Exception; 172 173 Object getValue(Symtab syms, Names names, Types types) { 174 switch (this) { 175 case STRING: 176 case INTEGER: 177 case LONG: 178 case FLOAT: 179 case DOUBLE: 180 return value; 181 case CLASS: 182 return syms.stringType.tsym; 183 case METHOD_HANDLE: 184 return new Pool.MethodHandle(REF_invokeVirtual, 185 syms.arrayCloneMethod, types); 186 case METHOD_TYPE: 187 return syms.arrayCloneMethod.type; 188 default: 189 throw new AssertionError(); 190 } 191 } 192 } 193 194 enum StaticArgumentsArity { 195 ZERO(0), 196 ONE(1), 197 TWO(2), 198 THREE(3); 199 200 int arity; 201 202 StaticArgumentsArity(int arity) { 203 this.arity = arity; 204 } 205 } 206 207 public static void main(String... args) throws Exception { 208 for (StaticArgumentsArity arity : StaticArgumentsArity.values()) { 209 if (arity.arity == 0) { 210 pool.execute(new TestInvokeDynamic(arity)); 211 } else { 212 for (StaticArgumentKind sak1 : StaticArgumentKind.values()) { 213 if (arity.arity == 1) { 214 pool.execute(new TestInvokeDynamic(arity, sak1)); 215 } else { 216 for (StaticArgumentKind sak2 : StaticArgumentKind.values()) { 217 if (arity.arity == 2) { 218 pool.execute(new TestInvokeDynamic(arity, sak1, sak2)); 219 } else { 220 for (StaticArgumentKind sak3 : StaticArgumentKind.values()) { 221 pool.execute( 222 new TestInvokeDynamic(arity, sak1, sak2, sak3)); 223 } 224 } 225 } 226 } 227 } 228 } 229 } 230 231 checkAfterExec(); 232 } 233 234 StaticArgumentsArity arity; 235 StaticArgumentKind[] saks; 236 DiagChecker dc; 237 238 TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) { 239 this.arity = arity; 240 this.saks = saks; 241 dc = new DiagChecker(); 242 } 243 244 public void run() { 245 int id = checkCount.incrementAndGet(); 246 JavaSource source = new JavaSource(id); 247 JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc, 248 Arrays.asList("-g"), null, Arrays.asList(source)); 249 Context context = ct.getContext(); 250 Symtab syms = Symtab.instance(context); 251 Names names = Names.instance(context); 252 Types types = Types.instance(context); 253 ct.addTaskListener(new Indifier(syms, names, types)); 254 try { 255 ct.generate(); 256 } catch (Throwable t) { 257 t.printStackTrace(); 258 throw new AssertionError( 259 String.format("Error thrown when compiling following code\n%s", 260 source.source)); 261 } 262 if (dc.diagFound) { 263 throw new AssertionError( 264 String.format("Diags found when compiling following code\n%s\n\n%s", 265 source.source, dc.printDiags())); 266 } 267 verifyBytecode(id); 268 } 269 270 void verifyBytecode(int id) { 271 File compiledTest = new File(String.format("Test%d.class", id)); 272 try { 273 ClassFile cf = ClassFile.read(compiledTest); 274 Method testMethod = null; 275 for (Method m : cf.methods) { 276 if (m.getName(cf.constant_pool).equals("test")) { 277 testMethod = m; 278 break; 279 } 280 } 281 if (testMethod == null) { 282 throw new Error("Test method not found"); 283 } 284 Code_attribute ea = 285 (Code_attribute)testMethod.attributes.get(Attribute.Code); 286 if (testMethod == null) { 287 throw new Error("Code attribute for test() method not found"); 288 } 289 290 int bsmIdx = -1; 291 292 for (Instruction i : ea.getInstructions()) { 293 if (i.getMnemonic().equals("invokedynamic")) { 294 CONSTANT_InvokeDynamic_info indyInfo = 295 (CONSTANT_InvokeDynamic_info)cf 296 .constant_pool.get(i.getShort(1)); 297 bsmIdx = indyInfo.bootstrap_method_attr_index; 298 if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) { 299 throw new 300 AssertionError("type mismatch for CONSTANT_InvokeDynamic_info"); 301 } 302 } 303 } 304 if (bsmIdx == -1) { 305 throw new Error("Missing invokedynamic in generated code"); 306 } 307 308 BootstrapMethods_attribute bsm_attr = 309 (BootstrapMethods_attribute)cf 310 .getAttribute(Attribute.BootstrapMethods); 311 if (bsm_attr.bootstrap_method_specifiers.length != 1) { 312 throw new Error("Bad number of method specifiers " + 313 "in BootstrapMethods attribute"); 314 } 315 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = 316 bsm_attr.bootstrap_method_specifiers[0]; 317 318 if (bsm_spec.bootstrap_arguments.length != arity.arity) { 319 throw new Error("Bad number of static invokedynamic args " + 320 "in BootstrapMethod attribute"); 321 } 322 323 int count = 0; 324 for (StaticArgumentKind sak : saks) { 325 if (!sak.check(cf.constant_pool 326 .get(bsm_spec.bootstrap_arguments[count]))) { 327 throw new Error("Bad static argument value " + sak); 328 } 329 count++; 330 } 331 332 CONSTANT_MethodHandle_info bsm_handle = 333 (CONSTANT_MethodHandle_info)cf.constant_pool 334 .get(bsm_spec.bootstrap_method_ref); 335 336 if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) { 337 throw new Error("Bad kind on boostrap method handle"); 338 } 339 340 CONSTANT_Methodref_info bsm_ref = 341 (CONSTANT_Methodref_info)cf.constant_pool 342 .get(bsm_handle.reference_index); 343 344 if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) { 345 throw new Error("Bad owner of boostrap method"); 346 } 347 348 if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) { 349 throw new Error("Bad boostrap method name"); 350 } 351 352 if (!bsm_ref.getNameAndTypeInfo() 353 .getType().equals(asBSMSignatureString())) { 354 throw new Error("Bad boostrap method type" + 355 bsm_ref.getNameAndTypeInfo().getType() + " " + 356 asBSMSignatureString()); 357 } 358 359 LineNumberTable_attribute lnt = 360 (LineNumberTable_attribute)ea.attributes.get(Attribute.LineNumberTable); 361 362 if (lnt == null) { 363 throw new Error("No LineNumberTable attribute"); 364 } 365 if (lnt.line_number_table_length != 3) { 366 throw new Error("Wrong number of entries in LineNumberTable"); 367 } 368 } catch (Exception e) { 369 e.printStackTrace(); 370 throw new Error("error reading " + compiledTest +": " + e); 371 } 372 } 373 374 String asBSMSignatureString() { 375 StringBuilder buf = new StringBuilder(); 376 buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"); 377 for (StaticArgumentKind sak : saks) { 378 buf.append(sak.bytecodeTypeStr); 379 } 380 buf.append(")Ljava/lang/invoke/CallSite;"); 381 return buf.toString(); 382 } 383 384 class JavaSource extends SimpleJavaFileObject { 385 386 static final String source_template = "import java.lang.invoke.*;\n" + 387 "class Bootstrap {\n" + 388 " public static CallSite bsm(MethodHandles.Lookup lookup, " + 389 "String name, MethodType methodType #SARGS) {\n" + 390 " return null;\n" + 391 " }\n" + 392 "}\n" + 393 "class Test#ID {\n" + 394 " void m() { }\n" + 395 " void test() {\n" + 396 " Object o = this; // marker statement \n" + 397 " m();\n" + 398 " }\n" + 399 "}"; 400 401 String source; 402 403 JavaSource(int id) { 404 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 405 source = source_template.replace("#SARGS", asSignatureString()) 406 .replace("#ID", String.valueOf(id)); 407 } 408 409 @Override 410 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 411 return source; 412 } 413 414 String asSignatureString() { 415 int count = 0; 416 StringBuilder buf = new StringBuilder(); 417 for (StaticArgumentKind sak : saks) { 418 buf.append(","); 419 buf.append(sak.sourceTypeStr); 420 buf.append(' '); 421 buf.append(String.format("x%d", count++)); 422 } 423 return buf.toString(); 424 } 425 } 426 427 class Indifier extends TreeScanner<Void, Void> implements TaskListener { 428 429 MethodSymbol bsm; 430 Symtab syms; 431 Names names; 432 Types types; 433 434 Indifier(Symtab syms, Names names, Types types) { 435 this.syms = syms; 436 this.names = names; 437 this.types = types; 438 } 439 440 @Override 441 public void started(TaskEvent e) { 442 //do nothing 443 } 444 445 @Override 446 public void finished(TaskEvent e) { 447 if (e.getKind() == TaskEvent.Kind.ANALYZE) { 448 scan(e.getCompilationUnit(), null); 449 } 450 } 451 452 @Override 453 public Void visitMethodInvocation(MethodInvocationTree node, Void p) { 454 super.visitMethodInvocation(node, p); 455 JCMethodInvocation apply = (JCMethodInvocation)node; 456 JCIdent ident = (JCIdent)apply.meth; 457 Symbol oldSym = ident.sym; 458 if (!oldSym.isConstructor()) { 459 Object[] staticArgs = new Object[arity.arity]; 460 for (int i = 0; i < arity.arity ; i++) { 461 staticArgs[i] = saks[i].getValue(syms, names, types); 462 } 463 ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name, 464 oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs); 465 } 466 return null; 467 } 468 469 @Override 470 public Void visitMethod(MethodTree node, Void p) { 471 super.visitMethod(node, p); 472 if (node.getName().toString().equals("bsm")) { 473 bsm = ((JCMethodDecl)node).sym; 474 } 475 return null; 476 } 477 } 478 479 static class DiagChecker 480 implements javax.tools.DiagnosticListener<JavaFileObject> { 481 482 boolean diagFound; 483 ArrayList<String> diags = new ArrayList<>(); 484 485 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 486 diags.add(diagnostic.getMessage(Locale.getDefault())); 487 diagFound = true; 488 } 489 490 String printDiags() { 491 StringBuilder buf = new StringBuilder(); 492 for (String s : diags) { 493 buf.append(s); 494 buf.append("\n"); 495 } 496 return buf.toString(); 497 } 498 } 499 500} 501