1 /* 2 * Copyright (c) 2013, 2020, 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 8129962 8238358 27 * @summary Lambda back-end should generate invokevirtual for method handles referring to 28 * private instance methods as lambda proxy is a nestmate of the target clsas 29 * @library /tools/javac/lib 30 * @modules jdk.jdeps/com.sun.tools.classfile 31 * jdk.compiler/com.sun.tools.javac.api 32 * jdk.compiler/com.sun.tools.javac.file 33 * jdk.compiler/com.sun.tools.javac.util 34 * @build combo.ComboTestHelper 35 * @run main TestLambdaBytecode 36 */ 37 38 import com.sun.tools.classfile.Attribute; 39 import com.sun.tools.classfile.BootstrapMethods_attribute; 40 import com.sun.tools.classfile.ClassFile; 41 import com.sun.tools.classfile.Code_attribute; 42 import com.sun.tools.classfile.ConstantPool.*; 43 import com.sun.tools.classfile.Instruction; 44 import com.sun.tools.classfile.Method; 45 46 import java.io.IOException; 47 import java.io.InputStream; 48 49 import combo.ComboInstance; 50 import combo.ComboParameter; 51 import combo.ComboTask.Result; 52 import combo.ComboTestHelper; 53 54 import javax.tools.JavaFileObject; 55 56 public class TestLambdaBytecode extends ComboInstance<TestLambdaBytecode> { 57 58 static final int MF_ARITY = 3; 59 static final String MH_SIG = "()V"; 60 61 enum ClassKind implements ComboParameter { 62 CLASS("class"), 63 INTERFACE("interface"); 64 65 String classStr; 66 67 ClassKind(String classStr) { 68 this.classStr = classStr; 69 } 70 71 @Override 72 public String expand(String optParameter) { 73 return classStr; 74 } 75 } 76 77 enum AccessKind implements ComboParameter { 78 PUBLIC("public"), 79 PRIVATE("private"); 80 81 String accessStr; 82 83 AccessKind(String accessStr) { 84 this.accessStr = accessStr; 85 } 86 87 @Override 88 public String expand(String optParameter) { 89 return accessStr; 90 } 91 } 92 93 enum StaticKind implements ComboParameter { 94 STATIC("static"), 95 INSTANCE(""); 96 97 String staticStr; 98 99 StaticKind(String staticStr) { 100 this.staticStr = staticStr; 101 } 102 103 @Override 104 public String expand(String optParameter) { 105 return staticStr; 106 } 107 } 108 109 enum DefaultKind implements ComboParameter { 110 DEFAULT("default"), 111 NO_DEFAULT(""); 112 113 String defaultStr; 114 115 DefaultKind(String defaultStr) { 116 this.defaultStr = defaultStr; 117 } 118 119 @Override 120 public String expand(String optParameter) { 121 return defaultStr; 122 } 123 } 124 125 static class MethodKind { 126 ClassKind ck; 127 AccessKind ak; 128 StaticKind sk; 129 DefaultKind dk; 130 131 MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) { 132 this.ck = ck; 133 this.ak = ak; 134 this.sk = sk; 135 this.dk = dk; 136 } 137 138 boolean inInterface() { 139 return ck == ClassKind.INTERFACE; 140 } 141 142 boolean isPrivate() { 143 return ak == AccessKind.PRIVATE; 144 } 145 146 boolean isStatic() { 147 return sk == StaticKind.STATIC; 148 } 149 150 boolean isDefault() { 151 return dk == DefaultKind.DEFAULT; 152 } 153 154 boolean isOK() { 155 if (isDefault() && (!inInterface() || isStatic())) { 156 return false; 157 } else if (inInterface() && 158 ((!isStatic() && !isDefault()) || isPrivate())) { 159 return false; 160 } else { 161 return true; 162 } 163 } 164 } 165 166 public static void main(String... args) throws Exception { 167 new ComboTestHelper<TestLambdaBytecode>() 168 .withDimension("CLASSKIND", (x, ck) -> x.ck = ck, ClassKind.values()) 169 .withArrayDimension("ACCESS", (x, acc, idx) -> x.accessKinds[idx] = acc, 2, AccessKind.values()) 170 .withArrayDimension("STATIC", (x, sk, idx) -> x.staticKinds[idx] = sk, 2, StaticKind.values()) 171 .withArrayDimension("DEFAULT", (x, dk, idx) -> x.defaultKinds[idx] = dk, 2, DefaultKind.values()) 172 .run(TestLambdaBytecode::new, TestLambdaBytecode::init); 173 } 174 175 ClassKind ck; 176 AccessKind[] accessKinds = new AccessKind[2]; 177 StaticKind[] staticKinds = new StaticKind[2]; 178 DefaultKind[] defaultKinds = new DefaultKind[2]; 179 MethodKind mk1, mk2; 180 181 void init() { 182 mk1 = new MethodKind(ck, accessKinds[0], staticKinds[0], defaultKinds[0]); 183 mk2 = new MethodKind(ck, accessKinds[1], staticKinds[1], defaultKinds[1]); 184 } 185 186 String source_template = 187 "#{CLASSKIND} Test {\n" + 188 " #{ACCESS[0]} #{STATIC[0]} #{DEFAULT[0]} void test() { Runnable r = ()->{ target(); }; }\n" + 189 " #{ACCESS[1]} #{STATIC[1]} #{DEFAULT[1]} void target() { }\n" + 190 "}\n"; 191 192 @Override 193 public void doWork() throws IOException { 194 newCompilationTask() 195 .withSourceFromTemplate(source_template) 196 .generate(this::verifyBytecode); 197 } 198 199 void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) { 200 if (res.hasErrors()) { 201 boolean errorExpected = !mk1.isOK() || !mk2.isOK(); 202 errorExpected |= mk1.isStatic() && !mk2.isStatic(); 203 204 if (!errorExpected) { 205 fail("Diags found when compiling instance; " + res.compilationInfo()); 206 } 207 return; 208 } 209 try (InputStream is = res.get().iterator().next().openInputStream()) { 210 ClassFile cf = ClassFile.read(is); 211 Method testMethod = null; 212 for (Method m : cf.methods) { 213 if (m.getName(cf.constant_pool).equals("test")) { 214 testMethod = m; 215 break; 216 } 217 } 218 if (testMethod == null) { 219 fail("Test method not found"); 220 return; 221 } 222 Code_attribute ea = 223 (Code_attribute)testMethod.attributes.get(Attribute.Code); 224 if (testMethod == null) { 225 fail("Code attribute for test() method not found"); 226 return; 227 } 228 229 int bsmIdx = -1; 230 231 for (Instruction i : ea.getInstructions()) { 232 if (i.getMnemonic().equals("invokedynamic")) { 233 CONSTANT_InvokeDynamic_info indyInfo = 234 (CONSTANT_InvokeDynamic_info)cf 235 .constant_pool.get(i.getShort(1)); 236 bsmIdx = indyInfo.bootstrap_method_attr_index; 237 if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) { 238 fail("type mismatch for CONSTANT_InvokeDynamic_info " + 239 res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() + 240 "\n" + makeIndyType()); 241 return; 242 } 243 } 244 } 245 if (bsmIdx == -1) { 246 fail("Missing invokedynamic in generated code"); 247 return; 248 } 249 250 BootstrapMethods_attribute bsm_attr = 251 (BootstrapMethods_attribute)cf 252 .getAttribute(Attribute.BootstrapMethods); 253 if (bsm_attr.bootstrap_method_specifiers.length != 1) { 254 fail("Bad number of method specifiers " + 255 "in BootstrapMethods attribute"); 256 return; 257 } 258 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = 259 bsm_attr.bootstrap_method_specifiers[0]; 260 261 if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { 262 fail("Bad number of static invokedynamic args " + 263 "in BootstrapMethod attribute"); 264 return; 265 } 266 267 CONSTANT_MethodHandle_info mh = 268 (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); 269 270 boolean kindOK; 271 switch (mh.reference_kind) { 272 case REF_invokeStatic: kindOK = mk2.isStatic(); break; 273 case REF_invokeVirtual: kindOK = !mk2.isStatic() && !mk2.inInterface(); break; 274 case REF_invokeInterface: kindOK = mk2.inInterface(); break; 275 default: 276 kindOK = false; 277 } 278 279 if (!kindOK) { 280 fail("Bad invoke kind in implementation method handle: " + mh.reference_kind); 281 return; 282 } 283 284 if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { 285 fail("Type mismatch in implementation method handle"); 286 return; 287 } 288 } catch (Exception e) { 289 e.printStackTrace(); 290 fail("error reading " + res.compilationInfo() + ": " + e); 291 } 292 } 293 294 String makeIndyType() { 295 StringBuilder buf = new StringBuilder(); 296 buf.append("("); 297 if (!mk2.isStatic()) { 298 buf.append("LTest;"); 299 } 300 buf.append(")Ljava/lang/Runnable;"); 301 return buf.toString(); 302 } 303 }