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