8009649: Lambda back-end should generate invokespecial for method handles referring to private instance methods
Private lambda methods should be accessed through invokespecial Reviewed-by: jjg
This commit is contained in:
parent
08c578cdcf
commit
2454c8c5ae
@ -1019,14 +1019,14 @@ public class LambdaToMethod extends TreeTranslator {
|
|||||||
} else if (refSym.enclClass().isInterface()) {
|
} else if (refSym.enclClass().isInterface()) {
|
||||||
return ClassFile.REF_invokeInterface;
|
return ClassFile.REF_invokeInterface;
|
||||||
} else {
|
} else {
|
||||||
return ClassFile.REF_invokeVirtual;
|
return (refSym.flags() & PRIVATE) != 0 ?
|
||||||
|
ClassFile.REF_invokeSpecial :
|
||||||
|
ClassFile.REF_invokeVirtual;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// </editor-fold>
|
// <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">
|
||||||
|
|
||||||
// <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">\
|
|
||||||
/**
|
/**
|
||||||
* This visitor collects information about translation of a lambda expression.
|
* This visitor collects information about translation of a lambda expression.
|
||||||
* More specifically, it keeps track of the enclosing contexts and captured locals
|
* More specifically, it keeps track of the enclosing contexts and captured locals
|
||||||
@ -1635,16 +1635,16 @@ public class LambdaToMethod extends TreeTranslator {
|
|||||||
* Translate a symbol of a given kind into something suitable for the
|
* Translate a symbol of a given kind into something suitable for the
|
||||||
* synthetic lambda body
|
* synthetic lambda body
|
||||||
*/
|
*/
|
||||||
Symbol translate(String name, final Symbol sym, LambdaSymbolKind skind) {
|
Symbol translate(Name name, final Symbol sym, LambdaSymbolKind skind) {
|
||||||
switch (skind) {
|
switch (skind) {
|
||||||
case CAPTURED_THIS:
|
case CAPTURED_THIS:
|
||||||
return sym; // self represented
|
return sym; // self represented
|
||||||
case TYPE_VAR:
|
case TYPE_VAR:
|
||||||
// Just erase the type var
|
// Just erase the type var
|
||||||
return new VarSymbol(sym.flags(), names.fromString(name),
|
return new VarSymbol(sym.flags(), name,
|
||||||
types.erasure(sym.type), sym.owner);
|
types.erasure(sym.type), sym.owner);
|
||||||
case CAPTURED_VAR:
|
case CAPTURED_VAR:
|
||||||
return new VarSymbol(SYNTHETIC | FINAL, names.fromString(name), types.erasure(sym.type), translatedSym) {
|
return new VarSymbol(SYNTHETIC | FINAL, name, types.erasure(sym.type), translatedSym) {
|
||||||
@Override
|
@Override
|
||||||
public Symbol baseSymbol() {
|
public Symbol baseSymbol() {
|
||||||
//keep mapping with original captured symbol
|
//keep mapping with original captured symbol
|
||||||
@ -1658,27 +1658,27 @@ public class LambdaToMethod extends TreeTranslator {
|
|||||||
|
|
||||||
void addSymbol(Symbol sym, LambdaSymbolKind skind) {
|
void addSymbol(Symbol sym, LambdaSymbolKind skind) {
|
||||||
Map<Symbol, Symbol> transMap = null;
|
Map<Symbol, Symbol> transMap = null;
|
||||||
String preferredName;
|
Name preferredName;
|
||||||
switch (skind) {
|
switch (skind) {
|
||||||
case CAPTURED_THIS:
|
case CAPTURED_THIS:
|
||||||
transMap = capturedThis;
|
transMap = capturedThis;
|
||||||
preferredName = "encl$" + capturedThis.size();
|
preferredName = names.fromString("encl$" + capturedThis.size());
|
||||||
break;
|
break;
|
||||||
case CAPTURED_VAR:
|
case CAPTURED_VAR:
|
||||||
transMap = capturedLocals;
|
transMap = capturedLocals;
|
||||||
preferredName = "cap$" + capturedLocals.size();
|
preferredName = names.fromString("cap$" + capturedLocals.size());
|
||||||
break;
|
break;
|
||||||
case LOCAL_VAR:
|
case LOCAL_VAR:
|
||||||
transMap = lambdaLocals;
|
transMap = lambdaLocals;
|
||||||
preferredName = sym.name.toString();
|
preferredName = sym.name;
|
||||||
break;
|
break;
|
||||||
case PARAM:
|
case PARAM:
|
||||||
transMap = lambdaParams;
|
transMap = lambdaParams;
|
||||||
preferredName = sym.name.toString();
|
preferredName = sym.name;
|
||||||
break;
|
break;
|
||||||
case TYPE_VAR:
|
case TYPE_VAR:
|
||||||
transMap = typeVars;
|
transMap = typeVars;
|
||||||
preferredName = sym.name.toString();
|
preferredName = sym.name;
|
||||||
break;
|
break;
|
||||||
default: throw new AssertionError();
|
default: throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8009649
|
||||||
|
* @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods
|
||||||
|
* @library ../../lib
|
||||||
|
* @build JavacTestingAbstractThreadedTest
|
||||||
|
* @run main/othervm TestLambdaBytecode
|
||||||
|
*/
|
||||||
|
|
||||||
|
// use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
|
||||||
|
// see JDK-8006746
|
||||||
|
|
||||||
|
import com.sun.tools.classfile.Attribute;
|
||||||
|
import com.sun.tools.classfile.BootstrapMethods_attribute;
|
||||||
|
import com.sun.tools.classfile.ClassFile;
|
||||||
|
import com.sun.tools.classfile.Code_attribute;
|
||||||
|
import com.sun.tools.classfile.ConstantPool.*;
|
||||||
|
import com.sun.tools.classfile.Instruction;
|
||||||
|
import com.sun.tools.classfile.Method;
|
||||||
|
|
||||||
|
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
|
||||||
|
import static com.sun.tools.javac.jvm.ClassFile.*;
|
||||||
|
|
||||||
|
public class TestLambdaBytecode
|
||||||
|
extends JavacTestingAbstractThreadedTest
|
||||||
|
implements Runnable {
|
||||||
|
|
||||||
|
enum ClassKind {
|
||||||
|
CLASS("class"),
|
||||||
|
INTERFACE("interface");
|
||||||
|
|
||||||
|
String classStr;
|
||||||
|
|
||||||
|
ClassKind(String classStr) {
|
||||||
|
this.classStr = classStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AccessKind {
|
||||||
|
PUBLIC("public"),
|
||||||
|
PRIVATE("private");
|
||||||
|
|
||||||
|
String accessStr;
|
||||||
|
|
||||||
|
AccessKind(String accessStr) {
|
||||||
|
this.accessStr = accessStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StaticKind {
|
||||||
|
STATIC("static"),
|
||||||
|
INSTANCE("");
|
||||||
|
|
||||||
|
String staticStr;
|
||||||
|
|
||||||
|
StaticKind(String staticStr) {
|
||||||
|
this.staticStr = staticStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DefaultKind {
|
||||||
|
DEFAULT("default"),
|
||||||
|
NO_DEFAULT("");
|
||||||
|
|
||||||
|
String defaultStr;
|
||||||
|
|
||||||
|
DefaultKind(String defaultStr) {
|
||||||
|
this.defaultStr = defaultStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExprKind {
|
||||||
|
LAMBDA("Runnable r = ()->{ target(); };");
|
||||||
|
|
||||||
|
String exprString;
|
||||||
|
|
||||||
|
ExprKind(String exprString) {
|
||||||
|
this.exprString = exprString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MethodKind {
|
||||||
|
ClassKind ck;
|
||||||
|
AccessKind ak;
|
||||||
|
StaticKind sk;
|
||||||
|
DefaultKind dk;
|
||||||
|
|
||||||
|
MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) {
|
||||||
|
this.ck = ck;
|
||||||
|
this.ak = ak;
|
||||||
|
this.sk = sk;
|
||||||
|
this.dk = dk;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean inInterface() {
|
||||||
|
return ck == ClassKind.INTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPrivate() {
|
||||||
|
return ak == AccessKind.PRIVATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isStatic() {
|
||||||
|
return sk == StaticKind.STATIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDefault() {
|
||||||
|
return dk == DefaultKind.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isOK() {
|
||||||
|
if (isDefault() && (!inInterface() || isStatic())) {
|
||||||
|
return false;
|
||||||
|
} else if (inInterface() &&
|
||||||
|
((!isStatic() && !isDefault()) || isPrivate())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String mods() {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append(ak.accessStr);
|
||||||
|
buf.append(' ');
|
||||||
|
buf.append(sk.staticStr);
|
||||||
|
buf.append(' ');
|
||||||
|
buf.append(dk.defaultStr);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
for (ClassKind ck : ClassKind.values()) {
|
||||||
|
for (AccessKind ak1 : AccessKind.values()) {
|
||||||
|
for (StaticKind sk1 : StaticKind.values()) {
|
||||||
|
for (DefaultKind dk1 : DefaultKind.values()) {
|
||||||
|
for (AccessKind ak2 : AccessKind.values()) {
|
||||||
|
for (StaticKind sk2 : StaticKind.values()) {
|
||||||
|
for (DefaultKind dk2 : DefaultKind.values()) {
|
||||||
|
for (ExprKind ek : ExprKind.values()) {
|
||||||
|
pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAfterExec();
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodKind mk1, mk2;
|
||||||
|
ExprKind ek;
|
||||||
|
DiagChecker dc;
|
||||||
|
|
||||||
|
TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1,
|
||||||
|
StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) {
|
||||||
|
mk1 = new MethodKind(ck, ak1, sk1, dk1);
|
||||||
|
mk2 = new MethodKind(ck, ak2, sk2, dk2);
|
||||||
|
this.ek = ek;
|
||||||
|
dc = new DiagChecker();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
int id = checkCount.incrementAndGet();
|
||||||
|
JavaSource source = new JavaSource(id);
|
||||||
|
JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc,
|
||||||
|
null, null, Arrays.asList(source));
|
||||||
|
try {
|
||||||
|
ct.generate();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
throw new AssertionError(
|
||||||
|
String.format("Error thrown when compiling following code\n%s",
|
||||||
|
source.source));
|
||||||
|
}
|
||||||
|
if (dc.diagFound) {
|
||||||
|
boolean errorExpected = !mk1.isOK() || !mk2.isOK();
|
||||||
|
errorExpected |= mk1.isStatic() && !mk2.isStatic();
|
||||||
|
|
||||||
|
if (!errorExpected) {
|
||||||
|
throw new AssertionError(
|
||||||
|
String.format("Diags found when compiling following code\n%s\n\n%s",
|
||||||
|
source.source, dc.printDiags()));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
verifyBytecode(id, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void verifyBytecode(int id, JavaSource source) {
|
||||||
|
File compiledTest = new File(String.format("Test%d.class", id));
|
||||||
|
try {
|
||||||
|
ClassFile cf = ClassFile.read(compiledTest);
|
||||||
|
Method testMethod = null;
|
||||||
|
for (Method m : cf.methods) {
|
||||||
|
if (m.getName(cf.constant_pool).equals("test")) {
|
||||||
|
testMethod = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (testMethod == null) {
|
||||||
|
throw new Error("Test method not found");
|
||||||
|
}
|
||||||
|
Code_attribute ea =
|
||||||
|
(Code_attribute)testMethod.attributes.get(Attribute.Code);
|
||||||
|
if (testMethod == null) {
|
||||||
|
throw new Error("Code attribute for test() method not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bsmIdx = -1;
|
||||||
|
|
||||||
|
for (Instruction i : ea.getInstructions()) {
|
||||||
|
if (i.getMnemonic().equals("invokedynamic")) {
|
||||||
|
CONSTANT_InvokeDynamic_info indyInfo =
|
||||||
|
(CONSTANT_InvokeDynamic_info)cf
|
||||||
|
.constant_pool.get(i.getShort(1));
|
||||||
|
bsmIdx = indyInfo.bootstrap_method_attr_index;
|
||||||
|
if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) {
|
||||||
|
throw new
|
||||||
|
AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bsmIdx == -1) {
|
||||||
|
throw new Error("Missing invokedynamic in generated code");
|
||||||
|
}
|
||||||
|
|
||||||
|
BootstrapMethods_attribute bsm_attr =
|
||||||
|
(BootstrapMethods_attribute)cf
|
||||||
|
.getAttribute(Attribute.BootstrapMethods);
|
||||||
|
if (bsm_attr.bootstrap_method_specifiers.length != 1) {
|
||||||
|
throw new Error("Bad number of method specifiers " +
|
||||||
|
"in BootstrapMethods attribute");
|
||||||
|
}
|
||||||
|
BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
|
||||||
|
bsm_attr.bootstrap_method_specifiers[0];
|
||||||
|
|
||||||
|
if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
|
||||||
|
throw new Error("Bad number of static invokedynamic args " +
|
||||||
|
"in BootstrapMethod attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSTANT_MethodHandle_info mh =
|
||||||
|
(CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
|
||||||
|
|
||||||
|
boolean kindOK;
|
||||||
|
switch (mh.reference_kind) {
|
||||||
|
case REF_invokeStatic: kindOK = mk2.isStatic(); break;
|
||||||
|
case REF_invokeSpecial: kindOK = !mk2.isStatic(); break;
|
||||||
|
case REF_invokeInterface: kindOK = mk2.inInterface(); break;
|
||||||
|
default:
|
||||||
|
kindOK = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kindOK) {
|
||||||
|
throw new Error("Bad invoke kind in implementation method handle");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
|
||||||
|
throw new Error("Type mismatch in implementation method handle");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new Error("error reading " + compiledTest +": " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String makeIndyType(int id) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append("(");
|
||||||
|
if (!mk2.isStatic() || mk1.inInterface()) {
|
||||||
|
buf.append(String.format("LTest%d;", id));
|
||||||
|
}
|
||||||
|
buf.append(")Ljava/lang/Runnable;");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int MF_ARITY = 3;
|
||||||
|
static final String MH_SIG = "()V";
|
||||||
|
|
||||||
|
class JavaSource extends SimpleJavaFileObject {
|
||||||
|
|
||||||
|
static final String source_template =
|
||||||
|
"#CK Test#ID {\n" +
|
||||||
|
" #MOD1 void test() { #EK }\n" +
|
||||||
|
" #MOD2 void target() { }\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
String source;
|
||||||
|
|
||||||
|
JavaSource(int id) {
|
||||||
|
super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
|
||||||
|
source = source_template.replace("#CK", mk1.ck.classStr)
|
||||||
|
.replace("#MOD1", mk1.mods())
|
||||||
|
.replace("#MOD2", mk2.mods())
|
||||||
|
.replace("#EK", ek.exprString)
|
||||||
|
.replace("#ID", String.valueOf(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DiagChecker
|
||||||
|
implements javax.tools.DiagnosticListener<JavaFileObject> {
|
||||||
|
|
||||||
|
boolean diagFound;
|
||||||
|
ArrayList<String> diags = new ArrayList<>();
|
||||||
|
|
||||||
|
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||||
|
diags.add(diagnostic.getMessage(Locale.getDefault()));
|
||||||
|
diagFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String printDiags() {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
for (String s : diags) {
|
||||||
|
buf.append(s);
|
||||||
|
buf.append("\n");
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user