8009649: Lambda back-end should generate invokespecial for method handles referring to private instance methods

Fri, 22 Mar 2013 12:38:12 +0000

author
mcimadamore
date
Fri, 22 Mar 2013 12:38:12 +0000
changeset 1652
cc38a6723663
parent 1651
972474640b7d
child 1653
f3814edefb33

8009649: Lambda back-end should generate invokespecial for method handles referring to private instance methods
Summary: Private lambda methods should be accessed through invokespecial
Reviewed-by: jjg

src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java file | annotate | diff | comparison | revisions
test/tools/javac/lambda/bytecode/TestLambdaBytecode.java file | annotate | diff | comparison | revisions
     1.1 --- a/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java	Wed Mar 20 17:41:40 2013 -0700
     1.2 +++ b/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java	Fri Mar 22 12:38:12 2013 +0000
     1.3 @@ -1019,14 +1019,14 @@
     1.4              } else if (refSym.enclClass().isInterface()) {
     1.5                  return ClassFile.REF_invokeInterface;
     1.6              } else {
     1.7 -                return ClassFile.REF_invokeVirtual;
     1.8 +                return (refSym.flags() & PRIVATE) != 0 ?
     1.9 +                        ClassFile.REF_invokeSpecial :
    1.10 +                        ClassFile.REF_invokeVirtual;
    1.11              }
    1.12          }
    1.13      }
    1.14  
    1.15 -    // </editor-fold>
    1.16 -
    1.17 -    // <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">\
    1.18 +    // <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">
    1.19      /**
    1.20       * This visitor collects information about translation of a lambda expression.
    1.21       * More specifically, it keeps track of the enclosing contexts and captured locals
    1.22 @@ -1635,16 +1635,16 @@
    1.23               * Translate a symbol of a given kind into something suitable for the
    1.24               * synthetic lambda body
    1.25               */
    1.26 -            Symbol translate(String name, final Symbol sym, LambdaSymbolKind skind) {
    1.27 +            Symbol translate(Name name, final Symbol sym, LambdaSymbolKind skind) {
    1.28                  switch (skind) {
    1.29                      case CAPTURED_THIS:
    1.30                          return sym;  // self represented
    1.31                      case TYPE_VAR:
    1.32                          // Just erase the type var
    1.33 -                        return new VarSymbol(sym.flags(), names.fromString(name),
    1.34 +                        return new VarSymbol(sym.flags(), name,
    1.35                                  types.erasure(sym.type), sym.owner);
    1.36                      case CAPTURED_VAR:
    1.37 -                        return new VarSymbol(SYNTHETIC | FINAL, names.fromString(name), types.erasure(sym.type), translatedSym) {
    1.38 +                        return new VarSymbol(SYNTHETIC | FINAL, name, types.erasure(sym.type), translatedSym) {
    1.39                              @Override
    1.40                              public Symbol baseSymbol() {
    1.41                                  //keep mapping with original captured symbol
    1.42 @@ -1658,27 +1658,27 @@
    1.43  
    1.44              void addSymbol(Symbol sym, LambdaSymbolKind skind) {
    1.45                  Map<Symbol, Symbol> transMap = null;
    1.46 -                String preferredName;
    1.47 +                Name preferredName;
    1.48                  switch (skind) {
    1.49                      case CAPTURED_THIS:
    1.50                          transMap = capturedThis;
    1.51 -                        preferredName = "encl$" + capturedThis.size();
    1.52 +                        preferredName = names.fromString("encl$" + capturedThis.size());
    1.53                          break;
    1.54                      case CAPTURED_VAR:
    1.55                          transMap = capturedLocals;
    1.56 -                        preferredName = "cap$" + capturedLocals.size();
    1.57 +                        preferredName = names.fromString("cap$" + capturedLocals.size());
    1.58                          break;
    1.59                      case LOCAL_VAR:
    1.60                          transMap = lambdaLocals;
    1.61 -                        preferredName = sym.name.toString();
    1.62 +                        preferredName = sym.name;
    1.63                          break;
    1.64                      case PARAM:
    1.65                          transMap = lambdaParams;
    1.66 -                        preferredName = sym.name.toString();
    1.67 +                        preferredName = sym.name;
    1.68                          break;
    1.69                      case TYPE_VAR:
    1.70                          transMap = typeVars;
    1.71 -                        preferredName = sym.name.toString();
    1.72 +                        preferredName = sym.name;
    1.73                          break;
    1.74                      default: throw new AssertionError();
    1.75                  }
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/test/tools/javac/lambda/bytecode/TestLambdaBytecode.java	Fri Mar 22 12:38:12 2013 +0000
     2.3 @@ -0,0 +1,365 @@
     2.4 +/*
     2.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
     2.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     2.7 + *
     2.8 + * This code is free software; you can redistribute it and/or modify it
     2.9 + * under the terms of the GNU General Public License version 2 only, as
    2.10 + * published by the Free Software Foundation.
    2.11 + *
    2.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
    2.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    2.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    2.15 + * version 2 for more details (a copy is included in the LICENSE file that
    2.16 + * accompanied this code).
    2.17 + *
    2.18 + * You should have received a copy of the GNU General Public License version
    2.19 + * 2 along with this work; if not, write to the Free Software Foundation,
    2.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    2.21 + *
    2.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    2.23 + * or visit www.oracle.com if you need additional information or have any
    2.24 + * questions.
    2.25 + */
    2.26 +
    2.27 +/*
    2.28 + * @test
    2.29 + * @bug 8009649
    2.30 + * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods
    2.31 + * @library ../../lib
    2.32 + * @build JavacTestingAbstractThreadedTest
    2.33 + * @run main/othervm TestLambdaBytecode
    2.34 + */
    2.35 +
    2.36 +// use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
    2.37 +// see JDK-8006746
    2.38 +
    2.39 +import com.sun.tools.classfile.Attribute;
    2.40 +import com.sun.tools.classfile.BootstrapMethods_attribute;
    2.41 +import com.sun.tools.classfile.ClassFile;
    2.42 +import com.sun.tools.classfile.Code_attribute;
    2.43 +import com.sun.tools.classfile.ConstantPool.*;
    2.44 +import com.sun.tools.classfile.Instruction;
    2.45 +import com.sun.tools.classfile.Method;
    2.46 +
    2.47 +import com.sun.tools.javac.api.JavacTaskImpl;
    2.48 +
    2.49 +
    2.50 +import java.io.File;
    2.51 +import java.net.URI;
    2.52 +import java.util.ArrayList;
    2.53 +import java.util.Arrays;
    2.54 +import java.util.Locale;
    2.55 +
    2.56 +import javax.tools.Diagnostic;
    2.57 +import javax.tools.JavaFileObject;
    2.58 +import javax.tools.SimpleJavaFileObject;
    2.59 +
    2.60 +import static com.sun.tools.javac.jvm.ClassFile.*;
    2.61 +
    2.62 +public class TestLambdaBytecode
    2.63 +    extends JavacTestingAbstractThreadedTest
    2.64 +    implements Runnable {
    2.65 +
    2.66 +    enum ClassKind {
    2.67 +        CLASS("class"),
    2.68 +        INTERFACE("interface");
    2.69 +
    2.70 +        String classStr;
    2.71 +
    2.72 +        ClassKind(String classStr) {
    2.73 +            this.classStr = classStr;
    2.74 +        }
    2.75 +    }
    2.76 +
    2.77 +    enum AccessKind {
    2.78 +        PUBLIC("public"),
    2.79 +        PRIVATE("private");
    2.80 +
    2.81 +        String accessStr;
    2.82 +
    2.83 +        AccessKind(String accessStr) {
    2.84 +            this.accessStr = accessStr;
    2.85 +        }
    2.86 +    }
    2.87 +
    2.88 +    enum StaticKind {
    2.89 +        STATIC("static"),
    2.90 +        INSTANCE("");
    2.91 +
    2.92 +        String staticStr;
    2.93 +
    2.94 +        StaticKind(String staticStr) {
    2.95 +            this.staticStr = staticStr;
    2.96 +        }
    2.97 +    }
    2.98 +
    2.99 +    enum DefaultKind {
   2.100 +        DEFAULT("default"),
   2.101 +        NO_DEFAULT("");
   2.102 +
   2.103 +        String defaultStr;
   2.104 +
   2.105 +        DefaultKind(String defaultStr) {
   2.106 +            this.defaultStr = defaultStr;
   2.107 +        }
   2.108 +    }
   2.109 +
   2.110 +    enum ExprKind {
   2.111 +        LAMBDA("Runnable r = ()->{ target(); };");
   2.112 +
   2.113 +        String exprString;
   2.114 +
   2.115 +        ExprKind(String exprString) {
   2.116 +            this.exprString = exprString;
   2.117 +        }
   2.118 +    }
   2.119 +
   2.120 +    static class MethodKind {
   2.121 +        ClassKind ck;
   2.122 +        AccessKind ak;
   2.123 +        StaticKind sk;
   2.124 +        DefaultKind dk;
   2.125 +
   2.126 +        MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) {
   2.127 +            this.ck = ck;
   2.128 +            this.ak = ak;
   2.129 +            this.sk = sk;
   2.130 +            this.dk = dk;
   2.131 +        }
   2.132 +
   2.133 +        boolean inInterface() {
   2.134 +            return ck == ClassKind.INTERFACE;
   2.135 +        }
   2.136 +
   2.137 +        boolean isPrivate() {
   2.138 +            return ak == AccessKind.PRIVATE;
   2.139 +        }
   2.140 +
   2.141 +        boolean isStatic() {
   2.142 +            return sk == StaticKind.STATIC;
   2.143 +        }
   2.144 +
   2.145 +        boolean isDefault() {
   2.146 +            return dk == DefaultKind.DEFAULT;
   2.147 +        }
   2.148 +
   2.149 +        boolean isOK() {
   2.150 +            if (isDefault() && (!inInterface() || isStatic())) {
   2.151 +                return false;
   2.152 +            } else if (inInterface() &&
   2.153 +                    ((!isStatic() && !isDefault()) || isPrivate())) {
   2.154 +                return false;
   2.155 +            } else {
   2.156 +                return true;
   2.157 +            }
   2.158 +        }
   2.159 +
   2.160 +        String mods() {
   2.161 +            StringBuilder buf = new StringBuilder();
   2.162 +            buf.append(ak.accessStr);
   2.163 +            buf.append(' ');
   2.164 +            buf.append(sk.staticStr);
   2.165 +            buf.append(' ');
   2.166 +            buf.append(dk.defaultStr);
   2.167 +            return buf.toString();
   2.168 +        }
   2.169 +    }
   2.170 +
   2.171 +    public static void main(String... args) throws Exception {
   2.172 +        for (ClassKind ck : ClassKind.values()) {
   2.173 +            for (AccessKind ak1 : AccessKind.values()) {
   2.174 +                for (StaticKind sk1 : StaticKind.values()) {
   2.175 +                    for (DefaultKind dk1 : DefaultKind.values()) {
   2.176 +                        for (AccessKind ak2 : AccessKind.values()) {
   2.177 +                            for (StaticKind sk2 : StaticKind.values()) {
   2.178 +                                for (DefaultKind dk2 : DefaultKind.values()) {
   2.179 +                                    for (ExprKind ek : ExprKind.values()) {
   2.180 +                                        pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek));
   2.181 +                                    }
   2.182 +                                }
   2.183 +                            }
   2.184 +                        }
   2.185 +                    }
   2.186 +                }
   2.187 +            }
   2.188 +        }
   2.189 +
   2.190 +        checkAfterExec();
   2.191 +    }
   2.192 +
   2.193 +    MethodKind mk1, mk2;
   2.194 +    ExprKind ek;
   2.195 +    DiagChecker dc;
   2.196 +
   2.197 +    TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1,
   2.198 +            StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) {
   2.199 +        mk1 = new MethodKind(ck, ak1, sk1, dk1);
   2.200 +        mk2 = new MethodKind(ck, ak2, sk2, dk2);
   2.201 +        this.ek = ek;
   2.202 +        dc = new DiagChecker();
   2.203 +    }
   2.204 +
   2.205 +    public void run() {
   2.206 +        int id = checkCount.incrementAndGet();
   2.207 +        JavaSource source = new JavaSource(id);
   2.208 +        JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc,
   2.209 +                null, null, Arrays.asList(source));
   2.210 +        try {
   2.211 +            ct.generate();
   2.212 +        } catch (Throwable t) {
   2.213 +            t.printStackTrace();
   2.214 +            throw new AssertionError(
   2.215 +                    String.format("Error thrown when compiling following code\n%s",
   2.216 +                    source.source));
   2.217 +        }
   2.218 +        if (dc.diagFound) {
   2.219 +            boolean errorExpected = !mk1.isOK() || !mk2.isOK();
   2.220 +            errorExpected |= mk1.isStatic() && !mk2.isStatic();
   2.221 +
   2.222 +            if (!errorExpected) {
   2.223 +                throw new AssertionError(
   2.224 +                        String.format("Diags found when compiling following code\n%s\n\n%s",
   2.225 +                        source.source, dc.printDiags()));
   2.226 +            }
   2.227 +            return;
   2.228 +        }
   2.229 +        verifyBytecode(id, source);
   2.230 +    }
   2.231 +
   2.232 +    void verifyBytecode(int id, JavaSource source) {
   2.233 +        File compiledTest = new File(String.format("Test%d.class", id));
   2.234 +        try {
   2.235 +            ClassFile cf = ClassFile.read(compiledTest);
   2.236 +            Method testMethod = null;
   2.237 +            for (Method m : cf.methods) {
   2.238 +                if (m.getName(cf.constant_pool).equals("test")) {
   2.239 +                    testMethod = m;
   2.240 +                    break;
   2.241 +                }
   2.242 +            }
   2.243 +            if (testMethod == null) {
   2.244 +                throw new Error("Test method not found");
   2.245 +            }
   2.246 +            Code_attribute ea =
   2.247 +                    (Code_attribute)testMethod.attributes.get(Attribute.Code);
   2.248 +            if (testMethod == null) {
   2.249 +                throw new Error("Code attribute for test() method not found");
   2.250 +            }
   2.251 +
   2.252 +            int bsmIdx = -1;
   2.253 +
   2.254 +            for (Instruction i : ea.getInstructions()) {
   2.255 +                if (i.getMnemonic().equals("invokedynamic")) {
   2.256 +                    CONSTANT_InvokeDynamic_info indyInfo =
   2.257 +                         (CONSTANT_InvokeDynamic_info)cf
   2.258 +                            .constant_pool.get(i.getShort(1));
   2.259 +                    bsmIdx = indyInfo.bootstrap_method_attr_index;
   2.260 +                    if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) {
   2.261 +                        throw new
   2.262 +                            AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id));
   2.263 +                    }
   2.264 +                }
   2.265 +            }
   2.266 +            if (bsmIdx == -1) {
   2.267 +                throw new Error("Missing invokedynamic in generated code");
   2.268 +            }
   2.269 +
   2.270 +            BootstrapMethods_attribute bsm_attr =
   2.271 +                    (BootstrapMethods_attribute)cf
   2.272 +                    .getAttribute(Attribute.BootstrapMethods);
   2.273 +            if (bsm_attr.bootstrap_method_specifiers.length != 1) {
   2.274 +                throw new Error("Bad number of method specifiers " +
   2.275 +                        "in BootstrapMethods attribute");
   2.276 +            }
   2.277 +            BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
   2.278 +                    bsm_attr.bootstrap_method_specifiers[0];
   2.279 +
   2.280 +            if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
   2.281 +                throw new Error("Bad number of static invokedynamic args " +
   2.282 +                        "in BootstrapMethod attribute");
   2.283 +            }
   2.284 +
   2.285 +            CONSTANT_MethodHandle_info mh =
   2.286 +                    (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
   2.287 +
   2.288 +            boolean kindOK;
   2.289 +            switch (mh.reference_kind) {
   2.290 +                case REF_invokeStatic: kindOK = mk2.isStatic(); break;
   2.291 +                case REF_invokeSpecial: kindOK = !mk2.isStatic(); break;
   2.292 +                case REF_invokeInterface: kindOK = mk2.inInterface(); break;
   2.293 +                default:
   2.294 +                    kindOK = false;
   2.295 +            }
   2.296 +
   2.297 +            if (!kindOK) {
   2.298 +                throw new Error("Bad invoke kind in implementation method handle");
   2.299 +            }
   2.300 +
   2.301 +            if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
   2.302 +                throw new Error("Type mismatch in implementation method handle");
   2.303 +            }
   2.304 +        } catch (Exception e) {
   2.305 +            e.printStackTrace();
   2.306 +            throw new Error("error reading " + compiledTest +": " + e);
   2.307 +        }
   2.308 +    }
   2.309 +    String makeIndyType(int id) {
   2.310 +        StringBuilder buf = new StringBuilder();
   2.311 +        buf.append("(");
   2.312 +        if (!mk2.isStatic() || mk1.inInterface()) {
   2.313 +            buf.append(String.format("LTest%d;", id));
   2.314 +        }
   2.315 +        buf.append(")Ljava/lang/Runnable;");
   2.316 +        return buf.toString();
   2.317 +    }
   2.318 +
   2.319 +    static final int MF_ARITY = 3;
   2.320 +    static final String MH_SIG = "()V";
   2.321 +
   2.322 +    class JavaSource extends SimpleJavaFileObject {
   2.323 +
   2.324 +        static final String source_template =
   2.325 +                "#CK Test#ID {\n" +
   2.326 +                "   #MOD1 void test() { #EK }\n" +
   2.327 +                "   #MOD2 void target() { }\n" +
   2.328 +                "}\n";
   2.329 +
   2.330 +        String source;
   2.331 +
   2.332 +        JavaSource(int id) {
   2.333 +            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
   2.334 +            source = source_template.replace("#CK", mk1.ck.classStr)
   2.335 +                    .replace("#MOD1", mk1.mods())
   2.336 +                    .replace("#MOD2", mk2.mods())
   2.337 +                    .replace("#EK", ek.exprString)
   2.338 +                    .replace("#ID", String.valueOf(id));
   2.339 +        }
   2.340 +
   2.341 +        @Override
   2.342 +        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
   2.343 +            return source;
   2.344 +        }
   2.345 +    }
   2.346 +
   2.347 +    static class DiagChecker
   2.348 +        implements javax.tools.DiagnosticListener<JavaFileObject> {
   2.349 +
   2.350 +        boolean diagFound;
   2.351 +        ArrayList<String> diags = new ArrayList<>();
   2.352 +
   2.353 +        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
   2.354 +            diags.add(diagnostic.getMessage(Locale.getDefault()));
   2.355 +            diagFound = true;
   2.356 +        }
   2.357 +
   2.358 +        String printDiags() {
   2.359 +            StringBuilder buf = new StringBuilder();
   2.360 +            for (String s : diags) {
   2.361 +                buf.append(s);
   2.362 +                buf.append("\n");
   2.363 +            }
   2.364 +            return buf.toString();
   2.365 +        }
   2.366 +    }
   2.367 +
   2.368 +}

mercurial