1 /* 2 * Copyright (c) 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 8238358 27 * @summary Lambda back-end should generate invokespecial for method handles referring to 28 * private instance methods when compiling with --release 14 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 TestLambdaBytecodeTargetRelease14 37 */ 38 39 import java.lang.classfile.*; 40 import java.lang.classfile.attribute.*; 41 import java.lang.classfile.constantpool.InvokeDynamicEntry; 42 import java.lang.classfile.constantpool.MethodHandleEntry; 43 import java.lang.classfile.instruction.InvokeDynamicInstruction; 44 45 import java.io.IOException; 46 import java.io.InputStream; 47 48 import combo.ComboInstance; 49 import combo.ComboParameter; 50 import combo.ComboTask.Result; 51 import combo.ComboTestHelper; 52 53 import java.lang.invoke.MethodHandleInfo; 54 import javax.tools.JavaFileObject; 55 56 public class TestLambdaBytecodeTargetRelease14 extends ComboInstance<TestLambdaBytecodeTargetRelease14> { 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<TestLambdaBytecodeTargetRelease14>() 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(TestLambdaBytecodeTargetRelease14::new, TestLambdaBytecodeTargetRelease14::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 .withOption("--release").withOption("14") 197 .generate(this::verifyBytecode); 198 } 199 200 void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) { 201 if (res.hasErrors()) { 202 boolean errorExpected = !mk1.isOK() || !mk2.isOK(); 203 errorExpected |= mk1.isStatic() && !mk2.isStatic(); 204 205 if (!errorExpected) { 206 fail("Diags found when compiling instance; " + res.compilationInfo()); 207 } 208 return; 209 } 210 try (InputStream is = res.get().iterator().next().openInputStream()) { 211 ClassModel cm = ClassFile.of().parse(is.readAllBytes()); 212 MethodModel testMethod = null; 213 for (MethodModel m : cm.methods()) { 214 if (m.methodName().equalsString("test")) { 215 testMethod = m; 216 break; 217 } 218 } 219 if (testMethod == null) { 220 fail("Test method not found"); 221 return; 222 } 223 CodeAttribute ea = testMethod.findAttribute(Attributes.code()).orElse(null); 224 if (ea == null) { 225 fail("Code attribute for test() method not found"); 226 return; 227 } 228 229 int bsmIdx = -1; 230 231 for (CodeElement ce : ea.elementList()) { 232 if (ce instanceof InvokeDynamicInstruction indy) { 233 InvokeDynamicEntry indyInfo = indy.invokedynamic(); 234 bsmIdx = indyInfo.bootstrap().bsmIndex(); 235 if (!indyInfo.type().equalsString(makeIndyType())) { 236 fail("type mismatch for CONSTANT_InvokeDynamic_info " + 237 res.compilationInfo() + "\n" + indyInfo.type().stringValue() + 238 "\n" + makeIndyType()); 239 return; 240 } 241 } 242 } 243 if (bsmIdx == -1) { 244 fail("Missing invokedynamic in generated code"); 245 return; 246 } 247 248 BootstrapMethodsAttribute bsm_attr = cm.findAttribute(Attributes.bootstrapMethods()).orElseThrow(); 249 if (bsm_attr.bootstrapMethodsSize() != 1) { 250 fail("Bad number of method specifiers " + 251 "in BootstrapMethods attribute"); 252 return; 253 } 254 BootstrapMethodEntry bsm_spec = bsm_attr.bootstrapMethods().get(0); 255 256 if (bsm_spec.arguments().size() != MF_ARITY) { 257 fail("Bad number of static invokedynamic args " + 258 "in BootstrapMethod attribute"); 259 return; 260 } 261 262 MethodHandleEntry mh = (MethodHandleEntry) bsm_spec.arguments().get(1); 263 264 boolean kindOK = switch (mh.kind()) { 265 case MethodHandleInfo.REF_invokeStatic -> mk2.isStatic(); 266 case MethodHandleInfo.REF_invokeSpecial -> !mk2.isStatic(); 267 case MethodHandleInfo.REF_invokeInterface -> mk2.inInterface(); 268 default -> false; 269 }; 270 271 if (!kindOK) { 272 fail("Bad invoke kind in implementation method handle"); 273 return; 274 } 275 276 if (!mh.reference().type().equalsString(MH_SIG)) { 277 fail("Type mismatch in implementation method handle"); 278 } 279 } catch (Exception e) { 280 e.printStackTrace(); 281 fail("error reading " + res.compilationInfo() + ": " + e); 282 } 283 } 284 285 String makeIndyType() { 286 StringBuilder buf = new StringBuilder(); 287 buf.append("("); 288 if (!mk2.isStatic()) { 289 buf.append("LTest;"); 290 } 291 buf.append(")Ljava/lang/Runnable;"); 292 return buf.toString(); 293 } 294 }