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