1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/test/tools/javac/lambda/TestInvokeDynamic.java Tue Sep 25 11:53:18 2012 +0100 1.3 @@ -0,0 +1,442 @@ 1.4 +/* 1.5 + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. 1.11 + * 1.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.15 + * version 2 for more details (a copy is included in the LICENSE file that 1.16 + * accompanied this code). 1.17 + * 1.18 + * You should have received a copy of the GNU General Public License version 1.19 + * 2 along with this work; if not, write to the Free Software Foundation, 1.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.21 + * 1.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.23 + * or visit www.oracle.com if you need additional information or have any 1.24 + * questions. 1.25 + */ 1.26 + 1.27 +/* 1.28 + * @test 1.29 + * @bug 7194586 1.30 + * 1.31 + * @summary Add back-end support for invokedynamic 1.32 + * 1.33 + */ 1.34 + 1.35 +import com.sun.source.tree.MethodInvocationTree; 1.36 +import com.sun.source.tree.MethodTree; 1.37 +import com.sun.source.util.TaskEvent; 1.38 +import com.sun.source.util.TaskListener; 1.39 +import com.sun.source.util.TreeScanner; 1.40 + 1.41 +import com.sun.tools.classfile.Attribute; 1.42 +import com.sun.tools.classfile.BootstrapMethods_attribute; 1.43 +import com.sun.tools.classfile.ClassFile; 1.44 +import com.sun.tools.classfile.Code_attribute; 1.45 +import com.sun.tools.classfile.ConstantPool.*; 1.46 +import com.sun.tools.classfile.Instruction; 1.47 +import com.sun.tools.classfile.Method; 1.48 + 1.49 +import com.sun.tools.javac.api.JavacTaskImpl; 1.50 +import com.sun.tools.javac.api.JavacTool; 1.51 +import com.sun.tools.javac.code.Symbol; 1.52 +import com.sun.tools.javac.code.Symbol.MethodSymbol; 1.53 +import com.sun.tools.javac.code.Symtab; 1.54 +import com.sun.tools.javac.jvm.Pool; 1.55 +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 1.56 +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 1.57 +import com.sun.tools.javac.tree.JCTree.JCIdent; 1.58 +import com.sun.tools.javac.util.Context; 1.59 +import com.sun.tools.javac.util.Names; 1.60 + 1.61 +import java.io.File; 1.62 +import java.net.URI; 1.63 +import java.util.ArrayList; 1.64 +import java.util.Arrays; 1.65 +import java.util.Locale; 1.66 + 1.67 +import javax.tools.Diagnostic; 1.68 +import javax.tools.JavaCompiler; 1.69 +import javax.tools.JavaFileManager; 1.70 +import javax.tools.JavaFileObject; 1.71 +import javax.tools.SimpleJavaFileObject; 1.72 +import javax.tools.StandardJavaFileManager; 1.73 +import javax.tools.ToolProvider; 1.74 + 1.75 +import static com.sun.tools.javac.jvm.ClassFile.*; 1.76 + 1.77 +public class TestInvokeDynamic { 1.78 + 1.79 + static int checkCount = 0; 1.80 + 1.81 + enum StaticArgumentKind { 1.82 + STRING("Hello!", "String", "Ljava/lang/String;") { 1.83 + @Override 1.84 + boolean check(CPInfo cpInfo) throws Exception { 1.85 + return (cpInfo instanceof CONSTANT_String_info) && 1.86 + ((CONSTANT_String_info)cpInfo).getString().equals(value); 1.87 + } 1.88 + }, 1.89 + CLASS(null, "Class<?>", "Ljava/lang/Class;") { 1.90 + @Override 1.91 + boolean check(CPInfo cpInfo) throws Exception { 1.92 + return (cpInfo instanceof CONSTANT_Class_info) && 1.93 + ((CONSTANT_Class_info)cpInfo).getName().equals("java/lang/String"); 1.94 + } 1.95 + }, 1.96 + INTEGER(1, "int", "I") { 1.97 + @Override 1.98 + boolean check(CPInfo cpInfo) throws Exception { 1.99 + return (cpInfo instanceof CONSTANT_Integer_info) && 1.100 + ((CONSTANT_Integer_info)cpInfo).value == ((Integer)value).intValue(); 1.101 + } 1.102 + }, 1.103 + LONG(1L, "long", "J") { 1.104 + @Override 1.105 + boolean check(CPInfo cpInfo) throws Exception { 1.106 + return (cpInfo instanceof CONSTANT_Long_info) && 1.107 + ((CONSTANT_Long_info)cpInfo).value == ((Long)value).longValue(); 1.108 + } 1.109 + }, 1.110 + FLOAT(1.0f, "float", "F") { 1.111 + @Override 1.112 + boolean check(CPInfo cpInfo) throws Exception { 1.113 + return (cpInfo instanceof CONSTANT_Float_info) && 1.114 + ((CONSTANT_Float_info)cpInfo).value == ((Float)value).floatValue(); 1.115 + } 1.116 + }, 1.117 + DOUBLE(1.0, "double","D") { 1.118 + @Override 1.119 + boolean check(CPInfo cpInfo) throws Exception { 1.120 + return (cpInfo instanceof CONSTANT_Double_info) && 1.121 + ((CONSTANT_Double_info)cpInfo).value == ((Double)value).doubleValue(); 1.122 + } 1.123 + }, 1.124 + METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") { 1.125 + @Override 1.126 + boolean check(CPInfo cpInfo) throws Exception { 1.127 + if (!(cpInfo instanceof CONSTANT_MethodHandle_info)) return false; 1.128 + CONSTANT_MethodHandle_info handleInfo = (CONSTANT_MethodHandle_info)cpInfo; 1.129 + return handleInfo.getCPRefInfo().getClassName().equals("Array") && 1.130 + handleInfo.reference_kind == RefKind.REF_invokeVirtual && 1.131 + handleInfo.getCPRefInfo().getNameAndTypeInfo().getName().equals("clone") && 1.132 + handleInfo.getCPRefInfo().getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;"); 1.133 + } 1.134 + }, 1.135 + METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") { 1.136 + @Override 1.137 + boolean check(CPInfo cpInfo) throws Exception { 1.138 + return (cpInfo instanceof CONSTANT_MethodType_info) && 1.139 + ((CONSTANT_MethodType_info)cpInfo).getType().equals("()Ljava/lang/Object;"); 1.140 + } 1.141 + }; 1.142 + 1.143 + Object value; 1.144 + String sourceTypeStr; 1.145 + String bytecodeTypeStr; 1.146 + 1.147 + StaticArgumentKind(Object value, String sourceTypeStr, String bytecodeTypeStr) { 1.148 + this.value = value; 1.149 + this.sourceTypeStr = sourceTypeStr; 1.150 + this.bytecodeTypeStr = bytecodeTypeStr; 1.151 + } 1.152 + 1.153 + abstract boolean check(CPInfo cpInfo) throws Exception; 1.154 + 1.155 + Object getValue(Symtab syms, Names names) { 1.156 + switch (this) { 1.157 + case STRING: 1.158 + case INTEGER: 1.159 + case LONG: 1.160 + case FLOAT: 1.161 + case DOUBLE: 1.162 + return value; 1.163 + case CLASS: 1.164 + return syms.stringType.tsym; 1.165 + case METHOD_HANDLE: 1.166 + return new Pool.MethodHandle(REF_invokeVirtual, syms.arrayCloneMethod, names); 1.167 + case METHOD_TYPE: 1.168 + return syms.arrayCloneMethod.type; 1.169 + default: 1.170 + throw new AssertionError(); 1.171 + } 1.172 + } 1.173 + } 1.174 + 1.175 + enum StaticArgumentsArity { 1.176 + ZERO(0), 1.177 + ONE(1), 1.178 + TWO(2), 1.179 + THREE(3); 1.180 + 1.181 + int arity; 1.182 + 1.183 + StaticArgumentsArity(int arity) { 1.184 + this.arity = arity; 1.185 + } 1.186 + } 1.187 + 1.188 + public static void main(String... args) throws Exception { 1.189 + // Create a single file manager and compiler and reuse it for each compile to save time. 1.190 + StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null); 1.191 + final JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); 1.192 + for (StaticArgumentsArity arity : StaticArgumentsArity.values()) { 1.193 + if (arity.arity == 0) { 1.194 + new TestInvokeDynamic(arity).compileAndCheck(fm, tool); 1.195 + } else { 1.196 + for (StaticArgumentKind sak1 : StaticArgumentKind.values()) { 1.197 + if (arity.arity == 1) { 1.198 + new TestInvokeDynamic(arity, sak1).compileAndCheck(fm, tool); 1.199 + } else { 1.200 + for (StaticArgumentKind sak2 : StaticArgumentKind.values()) { 1.201 + if (arity.arity == 2) { 1.202 + new TestInvokeDynamic(arity, sak1, sak2).compileAndCheck(fm, tool); 1.203 + } else { 1.204 + for (StaticArgumentKind sak3 : StaticArgumentKind.values()) { 1.205 + new TestInvokeDynamic(arity, sak1, sak2, sak3).compileAndCheck(fm, tool); 1.206 + } 1.207 + } 1.208 + } 1.209 + } 1.210 + } 1.211 + } 1.212 + } 1.213 + 1.214 + System.out.println("Total checks made: " + checkCount); 1.215 + } 1.216 + 1.217 + StaticArgumentsArity arity; 1.218 + StaticArgumentKind[] saks; 1.219 + JavaSource source; 1.220 + DiagChecker dc; 1.221 + 1.222 + TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) { 1.223 + this.arity = arity; 1.224 + this.saks = saks; 1.225 + source = new JavaSource(); 1.226 + dc = new DiagChecker(); 1.227 + } 1.228 + 1.229 + void compileAndCheck(JavaFileManager fm, JavaCompiler tool) throws Exception { 1.230 + JavacTaskImpl ct = (JavacTaskImpl)tool.getTask(null, fm, dc, 1.231 + null, null, Arrays.asList(source)); 1.232 + Context context = ct.getContext(); 1.233 + Symtab syms = Symtab.instance(context); 1.234 + Names names = Names.instance(context); 1.235 + ct.addTaskListener(new Indifier(syms, names)); 1.236 + try { 1.237 + ct.generate(); 1.238 + } catch (Throwable t) { 1.239 + t.printStackTrace(); 1.240 + throw new AssertionError(String.format("Error thrown when compiling following code\n%s", source.source)); 1.241 + } 1.242 + if (dc.diagFound) { 1.243 + throw new AssertionError(String.format("Diags found when compiling following code\n%s\n\n%s", source.source, dc.printDiags())); 1.244 + } 1.245 + verifyBytecode(); 1.246 + } 1.247 + 1.248 + void verifyBytecode() { 1.249 + File compiledTest = new File("Test.class"); 1.250 + try { 1.251 + ClassFile cf = ClassFile.read(compiledTest); 1.252 + Method testMethod = null; 1.253 + for (Method m : cf.methods) { 1.254 + if (m.getName(cf.constant_pool).equals("test")) { 1.255 + testMethod = m; 1.256 + break; 1.257 + } 1.258 + } 1.259 + if (testMethod == null) { 1.260 + throw new Error("Test method not found"); 1.261 + } 1.262 + Code_attribute ea = (Code_attribute)testMethod.attributes.get(Attribute.Code); 1.263 + if (testMethod == null) { 1.264 + throw new Error("Code attribute for test() method not found"); 1.265 + } 1.266 + 1.267 + int bsmIdx = -1; 1.268 + 1.269 + for (Instruction i : ea.getInstructions()) { 1.270 + if (i.getMnemonic().equals("invokedynamic")) { 1.271 + CONSTANT_InvokeDynamic_info indyInfo = 1.272 + (CONSTANT_InvokeDynamic_info)cf.constant_pool.get(i.getShort(1)); 1.273 + bsmIdx = indyInfo.bootstrap_method_attr_index; 1.274 + if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) { 1.275 + throw new AssertionError("type mismatch for CONSTANT_InvokeDynamic_info"); 1.276 + } 1.277 + } 1.278 + } 1.279 + if (bsmIdx == -1) { 1.280 + throw new Error("Missing invokedynamic in generated code"); 1.281 + } 1.282 + 1.283 + BootstrapMethods_attribute bsm_attr = (BootstrapMethods_attribute)cf.getAttribute(Attribute.BootstrapMethods); 1.284 + if (bsm_attr.bootstrap_method_specifiers.length != 1) { 1.285 + throw new Error("Bad number of method specifiers in BootstrapMethods attribute"); 1.286 + } 1.287 + BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = 1.288 + bsm_attr.bootstrap_method_specifiers[0]; 1.289 + 1.290 + if (bsm_spec.bootstrap_arguments.length != arity.arity) { 1.291 + throw new Error("Bad number of static invokedynamic args in BootstrapMethod attribute"); 1.292 + } 1.293 + 1.294 + int count = 0; 1.295 + for (StaticArgumentKind sak : saks) { 1.296 + if (!sak.check(cf.constant_pool.get(bsm_spec.bootstrap_arguments[count]))) { 1.297 + throw new Error("Bad static argument value " + sak); 1.298 + } 1.299 + count++; 1.300 + } 1.301 + 1.302 + CONSTANT_MethodHandle_info bsm_handle = 1.303 + (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_method_ref); 1.304 + 1.305 + if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) { 1.306 + throw new Error("Bad kind on boostrap method handle"); 1.307 + } 1.308 + 1.309 + CONSTANT_Methodref_info bsm_ref = 1.310 + (CONSTANT_Methodref_info)cf.constant_pool.get(bsm_handle.reference_index); 1.311 + 1.312 + if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) { 1.313 + throw new Error("Bad owner of boostrap method"); 1.314 + } 1.315 + 1.316 + if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) { 1.317 + throw new Error("Bad boostrap method name"); 1.318 + } 1.319 + 1.320 + if (!bsm_ref.getNameAndTypeInfo().getType().equals(asBSMSignatureString())) { 1.321 + throw new Error("Bad boostrap method type" + bsm_ref.getNameAndTypeInfo().getType() + " " + asBSMSignatureString()); 1.322 + } 1.323 + } catch (Exception e) { 1.324 + e.printStackTrace(); 1.325 + throw new Error("error reading " + compiledTest +": " + e); 1.326 + } 1.327 + } 1.328 + 1.329 + String asBSMSignatureString() { 1.330 + StringBuilder buf = new StringBuilder(); 1.331 + buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"); 1.332 + for (StaticArgumentKind sak : saks) { 1.333 + buf.append(sak.bytecodeTypeStr); 1.334 + } 1.335 + buf.append(")Ljava/lang/invoke/CallSite;"); 1.336 + return buf.toString(); 1.337 + } 1.338 + 1.339 + class JavaSource extends SimpleJavaFileObject { 1.340 + 1.341 + static final String source_template = "import java.lang.invoke.*;\n" + 1.342 + "class Bootstrap {\n" + 1.343 + " public static CallSite bsm(MethodHandles.Lookup lookup, String name, MethodType methodType #SARGS) {\n" + 1.344 + " return null;\n" + 1.345 + " }\n" + 1.346 + "}\n" + 1.347 + "class Test {\n" + 1.348 + " void m() { }\n" + 1.349 + " void test() { m(); }\n" + 1.350 + "}"; 1.351 + 1.352 + String source; 1.353 + 1.354 + JavaSource() { 1.355 + super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 1.356 + source = source_template.replace("#SARGS", asSignatureString()); 1.357 + } 1.358 + 1.359 + @Override 1.360 + public CharSequence getCharContent(boolean ignoreEncodingErrors) { 1.361 + return source; 1.362 + } 1.363 + 1.364 + String asSignatureString() { 1.365 + int count = 0; 1.366 + StringBuilder buf = new StringBuilder(); 1.367 + for (StaticArgumentKind sak : saks) { 1.368 + buf.append(","); 1.369 + buf.append(sak.sourceTypeStr); 1.370 + buf.append(' '); 1.371 + buf.append(String.format("x%d", count++)); 1.372 + } 1.373 + return buf.toString(); 1.374 + } 1.375 + } 1.376 + 1.377 + class Indifier extends TreeScanner<Void, Void> implements TaskListener { 1.378 + 1.379 + MethodSymbol bsm; 1.380 + Symtab syms; 1.381 + Names names; 1.382 + 1.383 + Indifier(Symtab syms, Names names) { 1.384 + this.syms = syms; 1.385 + this.names = names; 1.386 + } 1.387 + 1.388 + @Override 1.389 + public void started(TaskEvent e) { 1.390 + //do nothing 1.391 + } 1.392 + 1.393 + @Override 1.394 + public void finished(TaskEvent e) { 1.395 + if (e.getKind() == TaskEvent.Kind.ANALYZE) { 1.396 + scan(e.getCompilationUnit(), null); 1.397 + } 1.398 + } 1.399 + 1.400 + @Override 1.401 + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { 1.402 + super.visitMethodInvocation(node, p); 1.403 + JCMethodInvocation apply = (JCMethodInvocation)node; 1.404 + JCIdent ident = (JCIdent)apply.meth; 1.405 + Symbol oldSym = ident.sym; 1.406 + if (!oldSym.isConstructor()) { 1.407 + Object[] staticArgs = new Object[arity.arity]; 1.408 + for (int i = 0; i < arity.arity ; i++) { 1.409 + staticArgs[i] = saks[i].getValue(syms, names); 1.410 + } 1.411 + ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name, oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs); 1.412 + } 1.413 + return null; 1.414 + } 1.415 + 1.416 + @Override 1.417 + public Void visitMethod(MethodTree node, Void p) { 1.418 + super.visitMethod(node, p); 1.419 + if (node.getName().toString().equals("bsm")) { 1.420 + bsm = ((JCMethodDecl)node).sym; 1.421 + } 1.422 + return null; 1.423 + } 1.424 + } 1.425 + 1.426 + static class DiagChecker implements javax.tools.DiagnosticListener<JavaFileObject> { 1.427 + 1.428 + boolean diagFound; 1.429 + ArrayList<String> diags = new ArrayList<>(); 1.430 + 1.431 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 1.432 + diags.add(diagnostic.getMessage(Locale.getDefault())); 1.433 + diagFound = true; 1.434 + } 1.435 + 1.436 + String printDiags() { 1.437 + StringBuilder buf = new StringBuilder(); 1.438 + for (String s : diags) { 1.439 + buf.append(s); 1.440 + buf.append("\n"); 1.441 + } 1.442 + return buf.toString(); 1.443 + } 1.444 + } 1.445 +}