TestLambdaBytecode.java revision 2942:08092deced3f
1/* 2 * Copyright (c) 2013, 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 8009649 27 * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods 28 * @library ../../lib 29 * @modules jdk.jdeps/com.sun.tools.classfile 30 * jdk.compiler/com.sun.tools.javac.api 31 * @build JavacTestingAbstractThreadedTest 32 * @run main/othervm TestLambdaBytecode 33 */ 34 35// use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047) 36// see JDK-8006746 37 38import com.sun.tools.classfile.Attribute; 39import com.sun.tools.classfile.BootstrapMethods_attribute; 40import com.sun.tools.classfile.ClassFile; 41import com.sun.tools.classfile.Code_attribute; 42import com.sun.tools.classfile.ConstantPool.*; 43import com.sun.tools.classfile.Instruction; 44import com.sun.tools.classfile.Method; 45 46import com.sun.tools.javac.api.JavacTaskImpl; 47 48 49import java.io.File; 50import java.net.URI; 51import java.util.ArrayList; 52import java.util.Arrays; 53import java.util.Locale; 54 55import javax.tools.Diagnostic; 56import javax.tools.JavaFileObject; 57import javax.tools.SimpleJavaFileObject; 58 59import static com.sun.tools.javac.jvm.ClassFile.*; 60 61public class TestLambdaBytecode 62 extends JavacTestingAbstractThreadedTest 63 implements Runnable { 64 65 enum ClassKind { 66 CLASS("class"), 67 INTERFACE("interface"); 68 69 String classStr; 70 71 ClassKind(String classStr) { 72 this.classStr = classStr; 73 } 74 } 75 76 enum AccessKind { 77 PUBLIC("public"), 78 PRIVATE("private"); 79 80 String accessStr; 81 82 AccessKind(String accessStr) { 83 this.accessStr = accessStr; 84 } 85 } 86 87 enum StaticKind { 88 STATIC("static"), 89 INSTANCE(""); 90 91 String staticStr; 92 93 StaticKind(String staticStr) { 94 this.staticStr = staticStr; 95 } 96 } 97 98 enum DefaultKind { 99 DEFAULT("default"), 100 NO_DEFAULT(""); 101 102 String defaultStr; 103 104 DefaultKind(String defaultStr) { 105 this.defaultStr = defaultStr; 106 } 107 } 108 109 enum ExprKind { 110 LAMBDA("Runnable r = ()->{ target(); };"); 111 112 String exprString; 113 114 ExprKind(String exprString) { 115 this.exprString = exprString; 116 } 117 } 118 119 static class MethodKind { 120 ClassKind ck; 121 AccessKind ak; 122 StaticKind sk; 123 DefaultKind dk; 124 125 MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) { 126 this.ck = ck; 127 this.ak = ak; 128 this.sk = sk; 129 this.dk = dk; 130 } 131 132 boolean inInterface() { 133 return ck == ClassKind.INTERFACE; 134 } 135 136 boolean isPrivate() { 137 return ak == AccessKind.PRIVATE; 138 } 139 140 boolean isStatic() { 141 return sk == StaticKind.STATIC; 142 } 143 144 boolean isDefault() { 145 return dk == DefaultKind.DEFAULT; 146 } 147 148 boolean isOK() { 149 if (isDefault() && (!inInterface() || isStatic())) { 150 return false; 151 } else if (inInterface() && 152 ((!isStatic() && !isDefault()) || isPrivate())) { 153 return false; 154 } else { 155 return true; 156 } 157 } 158 159 String mods() { 160 StringBuilder buf = new StringBuilder(); 161 buf.append(ak.accessStr); 162 buf.append(' '); 163 buf.append(sk.staticStr); 164 buf.append(' '); 165 buf.append(dk.defaultStr); 166 return buf.toString(); 167 } 168 } 169 170 public static void main(String... args) throws Exception { 171 for (ClassKind ck : ClassKind.values()) { 172 for (AccessKind ak1 : AccessKind.values()) { 173 for (StaticKind sk1 : StaticKind.values()) { 174 for (DefaultKind dk1 : DefaultKind.values()) { 175 for (AccessKind ak2 : AccessKind.values()) { 176 for (StaticKind sk2 : StaticKind.values()) { 177 for (DefaultKind dk2 : DefaultKind.values()) { 178 for (ExprKind ek : ExprKind.values()) { 179 pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek)); 180 } 181 } 182 } 183 } 184 } 185 } 186 } 187 } 188 189 checkAfterExec(); 190 } 191 192 MethodKind mk1, mk2; 193 ExprKind ek; 194 DiagChecker dc; 195 196 TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1, 197 StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) { 198 mk1 = new MethodKind(ck, ak1, sk1, dk1); 199 mk2 = new MethodKind(ck, ak2, sk2, dk2); 200 this.ek = ek; 201 dc = new DiagChecker(); 202 } 203 204 public void run() { 205 int id = checkCount.incrementAndGet(); 206 JavaSource source = new JavaSource(id); 207 JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc, 208 null, null, Arrays.asList(source)); 209 try { 210 ct.generate(); 211 } catch (Throwable t) { 212 t.printStackTrace(); 213 throw new AssertionError( 214 String.format("Error thrown when compiling following code\n%s", 215 source.source)); 216 } 217 if (dc.diagFound) { 218 boolean errorExpected = !mk1.isOK() || !mk2.isOK(); 219 errorExpected |= mk1.isStatic() && !mk2.isStatic(); 220 221 if (!errorExpected) { 222 throw new AssertionError( 223 String.format("Diags found when compiling following code\n%s\n\n%s", 224 source.source, dc.printDiags())); 225 } 226 return; 227 } 228 verifyBytecode(id, source); 229 } 230 231 void verifyBytecode(int id, JavaSource source) { 232 File compiledTest = new File(String.format("Test%d.class", id)); 233 try { 234 ClassFile cf = ClassFile.read(compiledTest); 235 Method testMethod = null; 236 for (Method m : cf.methods) { 237 if (m.getName(cf.constant_pool).equals("test")) { 238 testMethod = m; 239 break; 240 } 241 } 242 if (testMethod == null) { 243 throw new Error("Test method not found"); 244 } 245 Code_attribute ea = 246 (Code_attribute)testMethod.attributes.get(Attribute.Code); 247 if (testMethod == null) { 248 throw new Error("Code attribute for test() method not found"); 249 } 250 251 int bsmIdx = -1; 252 253 for (Instruction i : ea.getInstructions()) { 254 if (i.getMnemonic().equals("invokedynamic")) { 255 CONSTANT_InvokeDynamic_info indyInfo = 256 (CONSTANT_InvokeDynamic_info)cf 257 .constant_pool.get(i.getShort(1)); 258 bsmIdx = indyInfo.bootstrap_method_attr_index; 259 if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) { 260 throw new 261 AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id)); 262 } 263 } 264 } 265 if (bsmIdx == -1) { 266 throw new Error("Missing invokedynamic in generated code"); 267 } 268 269 BootstrapMethods_attribute bsm_attr = 270 (BootstrapMethods_attribute)cf 271 .getAttribute(Attribute.BootstrapMethods); 272 if (bsm_attr.bootstrap_method_specifiers.length != 1) { 273 throw new Error("Bad number of method specifiers " + 274 "in BootstrapMethods attribute"); 275 } 276 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = 277 bsm_attr.bootstrap_method_specifiers[0]; 278 279 if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { 280 throw new Error("Bad number of static invokedynamic args " + 281 "in BootstrapMethod attribute"); 282 } 283 284 CONSTANT_MethodHandle_info mh = 285 (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); 286 287 boolean kindOK; 288 switch (mh.reference_kind) { 289 case REF_invokeStatic: kindOK = mk2.isStatic(); break; 290 case REF_invokeSpecial: kindOK = !mk2.isStatic(); break; 291 case REF_invokeInterface: kindOK = mk2.inInterface(); break; 292 default: 293 kindOK = false; 294 } 295 296 if (!kindOK) { 297 throw new Error("Bad invoke kind in implementation method handle"); 298 } 299 300 if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { 301 throw new Error("Type mismatch in implementation method handle"); 302 } 303 } catch (Exception e) { 304 e.printStackTrace(); 305 throw new Error("error reading " + compiledTest +": " + e); 306 } 307 } 308 String makeIndyType(int id) { 309 StringBuilder buf = new StringBuilder(); 310 buf.append("("); 311 if (!mk2.isStatic()) { 312 buf.append(String.format("LTest%d;", id)); 313 } 314 buf.append(")Ljava/lang/Runnable;"); 315 return buf.toString(); 316 } 317 318 static final int MF_ARITY = 3; 319 static final String MH_SIG = "()V"; 320 321 class JavaSource extends SimpleJavaFileObject { 322 323 static final String source_template = 324 "#CK Test#ID {\n" + 325 " #MOD1 void test() { #EK }\n" + 326 " #MOD2 void target() { }\n" + 327 "}\n"; 328 329 String source; 330 331 JavaSource(int id) { 332 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 333 source = source_template.replace("#CK", mk1.ck.classStr) 334 .replace("#MOD1", mk1.mods()) 335 .replace("#MOD2", mk2.mods()) 336 .replace("#EK", ek.exprString) 337 .replace("#ID", String.valueOf(id)); 338 } 339 340 @Override 341 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 342 return source; 343 } 344 } 345 346 static class DiagChecker 347 implements javax.tools.DiagnosticListener<JavaFileObject> { 348 349 boolean diagFound; 350 ArrayList<String> diags = new ArrayList<>(); 351 352 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 353 diags.add(diagnostic.getMessage(Locale.getDefault())); 354 diagFound = true; 355 } 356 357 String printDiags() { 358 StringBuilder buf = new StringBuilder(); 359 for (String s : diags) { 360 buf.append(s); 361 buf.append("\n"); 362 } 363 return buf.toString(); 364 } 365 } 366 367} 368