Fri, 22 Mar 2013 12:38:12 +0000
8009649: Lambda back-end should generate invokespecial for method handles referring to private instance methods
Summary: Private lambda methods should be accessed through invokespecial
Reviewed-by: jjg
1.1 --- a/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Wed Mar 20 17:41:40 2013 -0700 1.2 +++ b/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Fri Mar 22 12:38:12 2013 +0000 1.3 @@ -1019,14 +1019,14 @@ 1.4 } else if (refSym.enclClass().isInterface()) { 1.5 return ClassFile.REF_invokeInterface; 1.6 } else { 1.7 - return ClassFile.REF_invokeVirtual; 1.8 + return (refSym.flags() & PRIVATE) != 0 ? 1.9 + ClassFile.REF_invokeSpecial : 1.10 + ClassFile.REF_invokeVirtual; 1.11 } 1.12 } 1.13 } 1.14 1.15 - // </editor-fold> 1.16 - 1.17 - // <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">\ 1.18 + // <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer"> 1.19 /** 1.20 * This visitor collects information about translation of a lambda expression. 1.21 * More specifically, it keeps track of the enclosing contexts and captured locals 1.22 @@ -1635,16 +1635,16 @@ 1.23 * Translate a symbol of a given kind into something suitable for the 1.24 * synthetic lambda body 1.25 */ 1.26 - Symbol translate(String name, final Symbol sym, LambdaSymbolKind skind) { 1.27 + Symbol translate(Name name, final Symbol sym, LambdaSymbolKind skind) { 1.28 switch (skind) { 1.29 case CAPTURED_THIS: 1.30 return sym; // self represented 1.31 case TYPE_VAR: 1.32 // Just erase the type var 1.33 - return new VarSymbol(sym.flags(), names.fromString(name), 1.34 + return new VarSymbol(sym.flags(), name, 1.35 types.erasure(sym.type), sym.owner); 1.36 case CAPTURED_VAR: 1.37 - return new VarSymbol(SYNTHETIC | FINAL, names.fromString(name), types.erasure(sym.type), translatedSym) { 1.38 + return new VarSymbol(SYNTHETIC | FINAL, name, types.erasure(sym.type), translatedSym) { 1.39 @Override 1.40 public Symbol baseSymbol() { 1.41 //keep mapping with original captured symbol 1.42 @@ -1658,27 +1658,27 @@ 1.43 1.44 void addSymbol(Symbol sym, LambdaSymbolKind skind) { 1.45 Map<Symbol, Symbol> transMap = null; 1.46 - String preferredName; 1.47 + Name preferredName; 1.48 switch (skind) { 1.49 case CAPTURED_THIS: 1.50 transMap = capturedThis; 1.51 - preferredName = "encl$" + capturedThis.size(); 1.52 + preferredName = names.fromString("encl$" + capturedThis.size()); 1.53 break; 1.54 case CAPTURED_VAR: 1.55 transMap = capturedLocals; 1.56 - preferredName = "cap$" + capturedLocals.size(); 1.57 + preferredName = names.fromString("cap$" + capturedLocals.size()); 1.58 break; 1.59 case LOCAL_VAR: 1.60 transMap = lambdaLocals; 1.61 - preferredName = sym.name.toString(); 1.62 + preferredName = sym.name; 1.63 break; 1.64 case PARAM: 1.65 transMap = lambdaParams; 1.66 - preferredName = sym.name.toString(); 1.67 + preferredName = sym.name; 1.68 break; 1.69 case TYPE_VAR: 1.70 transMap = typeVars; 1.71 - preferredName = sym.name.toString(); 1.72 + preferredName = sym.name; 1.73 break; 1.74 default: throw new AssertionError(); 1.75 }
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/test/tools/javac/lambda/bytecode/TestLambdaBytecode.java Fri Mar 22 12:38:12 2013 +0000 2.3 @@ -0,0 +1,365 @@ 2.4 +/* 2.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 2.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.7 + * 2.8 + * This code is free software; you can redistribute it and/or modify it 2.9 + * under the terms of the GNU General Public License version 2 only, as 2.10 + * published by the Free Software Foundation. 2.11 + * 2.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 2.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 2.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 2.15 + * version 2 for more details (a copy is included in the LICENSE file that 2.16 + * accompanied this code). 2.17 + * 2.18 + * You should have received a copy of the GNU General Public License version 2.19 + * 2 along with this work; if not, write to the Free Software Foundation, 2.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2.21 + * 2.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2.23 + * or visit www.oracle.com if you need additional information or have any 2.24 + * questions. 2.25 + */ 2.26 + 2.27 +/* 2.28 + * @test 2.29 + * @bug 8009649 2.30 + * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods 2.31 + * @library ../../lib 2.32 + * @build JavacTestingAbstractThreadedTest 2.33 + * @run main/othervm TestLambdaBytecode 2.34 + */ 2.35 + 2.36 +// use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047) 2.37 +// see JDK-8006746 2.38 + 2.39 +import com.sun.tools.classfile.Attribute; 2.40 +import com.sun.tools.classfile.BootstrapMethods_attribute; 2.41 +import com.sun.tools.classfile.ClassFile; 2.42 +import com.sun.tools.classfile.Code_attribute; 2.43 +import com.sun.tools.classfile.ConstantPool.*; 2.44 +import com.sun.tools.classfile.Instruction; 2.45 +import com.sun.tools.classfile.Method; 2.46 + 2.47 +import com.sun.tools.javac.api.JavacTaskImpl; 2.48 + 2.49 + 2.50 +import java.io.File; 2.51 +import java.net.URI; 2.52 +import java.util.ArrayList; 2.53 +import java.util.Arrays; 2.54 +import java.util.Locale; 2.55 + 2.56 +import javax.tools.Diagnostic; 2.57 +import javax.tools.JavaFileObject; 2.58 +import javax.tools.SimpleJavaFileObject; 2.59 + 2.60 +import static com.sun.tools.javac.jvm.ClassFile.*; 2.61 + 2.62 +public class TestLambdaBytecode 2.63 + extends JavacTestingAbstractThreadedTest 2.64 + implements Runnable { 2.65 + 2.66 + enum ClassKind { 2.67 + CLASS("class"), 2.68 + INTERFACE("interface"); 2.69 + 2.70 + String classStr; 2.71 + 2.72 + ClassKind(String classStr) { 2.73 + this.classStr = classStr; 2.74 + } 2.75 + } 2.76 + 2.77 + enum AccessKind { 2.78 + PUBLIC("public"), 2.79 + PRIVATE("private"); 2.80 + 2.81 + String accessStr; 2.82 + 2.83 + AccessKind(String accessStr) { 2.84 + this.accessStr = accessStr; 2.85 + } 2.86 + } 2.87 + 2.88 + enum StaticKind { 2.89 + STATIC("static"), 2.90 + INSTANCE(""); 2.91 + 2.92 + String staticStr; 2.93 + 2.94 + StaticKind(String staticStr) { 2.95 + this.staticStr = staticStr; 2.96 + } 2.97 + } 2.98 + 2.99 + enum DefaultKind { 2.100 + DEFAULT("default"), 2.101 + NO_DEFAULT(""); 2.102 + 2.103 + String defaultStr; 2.104 + 2.105 + DefaultKind(String defaultStr) { 2.106 + this.defaultStr = defaultStr; 2.107 + } 2.108 + } 2.109 + 2.110 + enum ExprKind { 2.111 + LAMBDA("Runnable r = ()->{ target(); };"); 2.112 + 2.113 + String exprString; 2.114 + 2.115 + ExprKind(String exprString) { 2.116 + this.exprString = exprString; 2.117 + } 2.118 + } 2.119 + 2.120 + static class MethodKind { 2.121 + ClassKind ck; 2.122 + AccessKind ak; 2.123 + StaticKind sk; 2.124 + DefaultKind dk; 2.125 + 2.126 + MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) { 2.127 + this.ck = ck; 2.128 + this.ak = ak; 2.129 + this.sk = sk; 2.130 + this.dk = dk; 2.131 + } 2.132 + 2.133 + boolean inInterface() { 2.134 + return ck == ClassKind.INTERFACE; 2.135 + } 2.136 + 2.137 + boolean isPrivate() { 2.138 + return ak == AccessKind.PRIVATE; 2.139 + } 2.140 + 2.141 + boolean isStatic() { 2.142 + return sk == StaticKind.STATIC; 2.143 + } 2.144 + 2.145 + boolean isDefault() { 2.146 + return dk == DefaultKind.DEFAULT; 2.147 + } 2.148 + 2.149 + boolean isOK() { 2.150 + if (isDefault() && (!inInterface() || isStatic())) { 2.151 + return false; 2.152 + } else if (inInterface() && 2.153 + ((!isStatic() && !isDefault()) || isPrivate())) { 2.154 + return false; 2.155 + } else { 2.156 + return true; 2.157 + } 2.158 + } 2.159 + 2.160 + String mods() { 2.161 + StringBuilder buf = new StringBuilder(); 2.162 + buf.append(ak.accessStr); 2.163 + buf.append(' '); 2.164 + buf.append(sk.staticStr); 2.165 + buf.append(' '); 2.166 + buf.append(dk.defaultStr); 2.167 + return buf.toString(); 2.168 + } 2.169 + } 2.170 + 2.171 + public static void main(String... args) throws Exception { 2.172 + for (ClassKind ck : ClassKind.values()) { 2.173 + for (AccessKind ak1 : AccessKind.values()) { 2.174 + for (StaticKind sk1 : StaticKind.values()) { 2.175 + for (DefaultKind dk1 : DefaultKind.values()) { 2.176 + for (AccessKind ak2 : AccessKind.values()) { 2.177 + for (StaticKind sk2 : StaticKind.values()) { 2.178 + for (DefaultKind dk2 : DefaultKind.values()) { 2.179 + for (ExprKind ek : ExprKind.values()) { 2.180 + pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek)); 2.181 + } 2.182 + } 2.183 + } 2.184 + } 2.185 + } 2.186 + } 2.187 + } 2.188 + } 2.189 + 2.190 + checkAfterExec(); 2.191 + } 2.192 + 2.193 + MethodKind mk1, mk2; 2.194 + ExprKind ek; 2.195 + DiagChecker dc; 2.196 + 2.197 + TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1, 2.198 + StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) { 2.199 + mk1 = new MethodKind(ck, ak1, sk1, dk1); 2.200 + mk2 = new MethodKind(ck, ak2, sk2, dk2); 2.201 + this.ek = ek; 2.202 + dc = new DiagChecker(); 2.203 + } 2.204 + 2.205 + public void run() { 2.206 + int id = checkCount.incrementAndGet(); 2.207 + JavaSource source = new JavaSource(id); 2.208 + JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc, 2.209 + null, null, Arrays.asList(source)); 2.210 + try { 2.211 + ct.generate(); 2.212 + } catch (Throwable t) { 2.213 + t.printStackTrace(); 2.214 + throw new AssertionError( 2.215 + String.format("Error thrown when compiling following code\n%s", 2.216 + source.source)); 2.217 + } 2.218 + if (dc.diagFound) { 2.219 + boolean errorExpected = !mk1.isOK() || !mk2.isOK(); 2.220 + errorExpected |= mk1.isStatic() && !mk2.isStatic(); 2.221 + 2.222 + if (!errorExpected) { 2.223 + throw new AssertionError( 2.224 + String.format("Diags found when compiling following code\n%s\n\n%s", 2.225 + source.source, dc.printDiags())); 2.226 + } 2.227 + return; 2.228 + } 2.229 + verifyBytecode(id, source); 2.230 + } 2.231 + 2.232 + void verifyBytecode(int id, JavaSource source) { 2.233 + File compiledTest = new File(String.format("Test%d.class", id)); 2.234 + try { 2.235 + ClassFile cf = ClassFile.read(compiledTest); 2.236 + Method testMethod = null; 2.237 + for (Method m : cf.methods) { 2.238 + if (m.getName(cf.constant_pool).equals("test")) { 2.239 + testMethod = m; 2.240 + break; 2.241 + } 2.242 + } 2.243 + if (testMethod == null) { 2.244 + throw new Error("Test method not found"); 2.245 + } 2.246 + Code_attribute ea = 2.247 + (Code_attribute)testMethod.attributes.get(Attribute.Code); 2.248 + if (testMethod == null) { 2.249 + throw new Error("Code attribute for test() method not found"); 2.250 + } 2.251 + 2.252 + int bsmIdx = -1; 2.253 + 2.254 + for (Instruction i : ea.getInstructions()) { 2.255 + if (i.getMnemonic().equals("invokedynamic")) { 2.256 + CONSTANT_InvokeDynamic_info indyInfo = 2.257 + (CONSTANT_InvokeDynamic_info)cf 2.258 + .constant_pool.get(i.getShort(1)); 2.259 + bsmIdx = indyInfo.bootstrap_method_attr_index; 2.260 + if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) { 2.261 + throw new 2.262 + AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id)); 2.263 + } 2.264 + } 2.265 + } 2.266 + if (bsmIdx == -1) { 2.267 + throw new Error("Missing invokedynamic in generated code"); 2.268 + } 2.269 + 2.270 + BootstrapMethods_attribute bsm_attr = 2.271 + (BootstrapMethods_attribute)cf 2.272 + .getAttribute(Attribute.BootstrapMethods); 2.273 + if (bsm_attr.bootstrap_method_specifiers.length != 1) { 2.274 + throw new Error("Bad number of method specifiers " + 2.275 + "in BootstrapMethods attribute"); 2.276 + } 2.277 + BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = 2.278 + bsm_attr.bootstrap_method_specifiers[0]; 2.279 + 2.280 + if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { 2.281 + throw new Error("Bad number of static invokedynamic args " + 2.282 + "in BootstrapMethod attribute"); 2.283 + } 2.284 + 2.285 + CONSTANT_MethodHandle_info mh = 2.286 + (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); 2.287 + 2.288 + boolean kindOK; 2.289 + switch (mh.reference_kind) { 2.290 + case REF_invokeStatic: kindOK = mk2.isStatic(); break; 2.291 + case REF_invokeSpecial: kindOK = !mk2.isStatic(); break; 2.292 + case REF_invokeInterface: kindOK = mk2.inInterface(); break; 2.293 + default: 2.294 + kindOK = false; 2.295 + } 2.296 + 2.297 + if (!kindOK) { 2.298 + throw new Error("Bad invoke kind in implementation method handle"); 2.299 + } 2.300 + 2.301 + if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { 2.302 + throw new Error("Type mismatch in implementation method handle"); 2.303 + } 2.304 + } catch (Exception e) { 2.305 + e.printStackTrace(); 2.306 + throw new Error("error reading " + compiledTest +": " + e); 2.307 + } 2.308 + } 2.309 + String makeIndyType(int id) { 2.310 + StringBuilder buf = new StringBuilder(); 2.311 + buf.append("("); 2.312 + if (!mk2.isStatic() || mk1.inInterface()) { 2.313 + buf.append(String.format("LTest%d;", id)); 2.314 + } 2.315 + buf.append(")Ljava/lang/Runnable;"); 2.316 + return buf.toString(); 2.317 + } 2.318 + 2.319 + static final int MF_ARITY = 3; 2.320 + static final String MH_SIG = "()V"; 2.321 + 2.322 + class JavaSource extends SimpleJavaFileObject { 2.323 + 2.324 + static final String source_template = 2.325 + "#CK Test#ID {\n" + 2.326 + " #MOD1 void test() { #EK }\n" + 2.327 + " #MOD2 void target() { }\n" + 2.328 + "}\n"; 2.329 + 2.330 + String source; 2.331 + 2.332 + JavaSource(int id) { 2.333 + super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 2.334 + source = source_template.replace("#CK", mk1.ck.classStr) 2.335 + .replace("#MOD1", mk1.mods()) 2.336 + .replace("#MOD2", mk2.mods()) 2.337 + .replace("#EK", ek.exprString) 2.338 + .replace("#ID", String.valueOf(id)); 2.339 + } 2.340 + 2.341 + @Override 2.342 + public CharSequence getCharContent(boolean ignoreEncodingErrors) { 2.343 + return source; 2.344 + } 2.345 + } 2.346 + 2.347 + static class DiagChecker 2.348 + implements javax.tools.DiagnosticListener<JavaFileObject> { 2.349 + 2.350 + boolean diagFound; 2.351 + ArrayList<String> diags = new ArrayList<>(); 2.352 + 2.353 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 2.354 + diags.add(diagnostic.getMessage(Locale.getDefault())); 2.355 + diagFound = true; 2.356 + } 2.357 + 2.358 + String printDiags() { 2.359 + StringBuilder buf = new StringBuilder(); 2.360 + for (String s : diags) { 2.361 + buf.append(s); 2.362 + buf.append("\n"); 2.363 + } 2.364 + return buf.toString(); 2.365 + } 2.366 + } 2.367 + 2.368 +}