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  * @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 TestLambdaBytecodeTargetRelease14
  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 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             ClassFile cf = ClassFile.read(is);
 212             Method testMethod = null;
 213             for (Method m : cf.methods) {
 214                 if (m.getName(cf.constant_pool).equals("test")) {
 215                     testMethod = m;
 216                     break;
 217                 }
 218             }
 219             if (testMethod == null) {
 220                 fail("Test method not found");
 221                 return;
 222             }
 223             Code_attribute ea =
 224                     (Code_attribute)testMethod.attributes.get(Attribute.Code);
 225             if (testMethod == null) {
 226                 fail("Code attribute for test() method not found");
 227                 return;
 228             }
 229 
 230             int bsmIdx = -1;
 231 
 232             for (Instruction i : ea.getInstructions()) {
 233                 if (i.getMnemonic().equals("invokedynamic")) {
 234                     CONSTANT_InvokeDynamic_info indyInfo =
 235                          (CONSTANT_InvokeDynamic_info)cf
 236                             .constant_pool.get(i.getShort(1));
 237                     bsmIdx = indyInfo.bootstrap_method_attr_index;
 238                     if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) {
 239                         fail("type mismatch for CONSTANT_InvokeDynamic_info " +
 240                                 res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() +
 241                                 "\n" + makeIndyType());
 242                         return;
 243                     }
 244                 }
 245             }
 246             if (bsmIdx == -1) {
 247                 fail("Missing invokedynamic in generated code");
 248                 return;
 249             }
 250 
 251             BootstrapMethods_attribute bsm_attr =
 252                     (BootstrapMethods_attribute)cf
 253                     .getAttribute(Attribute.BootstrapMethods);
 254             if (bsm_attr.bootstrap_method_specifiers.length != 1) {
 255                 fail("Bad number of method specifiers " +
 256                         "in BootstrapMethods attribute");
 257                 return;
 258             }
 259             BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
 260                     bsm_attr.bootstrap_method_specifiers[0];
 261 
 262             if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
 263                 fail("Bad number of static invokedynamic args " +
 264                         "in BootstrapMethod attribute");
 265                 return;
 266             }
 267 
 268             CONSTANT_MethodHandle_info mh =
 269                     (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
 270 
 271             boolean kindOK;
 272             switch (mh.reference_kind) {
 273                 case REF_invokeStatic: kindOK = mk2.isStatic(); break;
 274                 case REF_invokeSpecial: kindOK = !mk2.isStatic(); break;
 275                 case REF_invokeInterface: kindOK = mk2.inInterface(); break;
 276                 default:
 277                     kindOK = false;
 278             }
 279 
 280             if (!kindOK) {
 281                 fail("Bad invoke kind in implementation method handle");
 282                 return;
 283             }
 284 
 285             if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
 286                 fail("Type mismatch in implementation method handle");
 287                 return;
 288             }
 289         } catch (Exception e) {
 290             e.printStackTrace();
 291             fail("error reading " + res.compilationInfo() + ": " + e);
 292         }
 293     }
 294 
 295     String makeIndyType() {
 296         StringBuilder buf = new StringBuilder();
 297         buf.append("(");
 298         if (!mk2.isStatic()) {
 299             buf.append("LTest;");
 300         }
 301         buf.append(")Ljava/lang/Runnable;");
 302         return buf.toString();
 303     }
 304 }