blob: 300f61c12df4c3e750f1bb718e43122fa3a7feed [file] [log] [blame]
/*
* 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()) {
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();
}
}
}