7194586: Add back-end support for invokedynamic
Summary: Add support for invokedynamic bytecode instruction; includes suppot for generation of all related classfile attributes
Reviewed-by: jjg
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Symbol.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Symbol.java Tue Sep 25 11:53:18 2012 +0100
@@ -1068,6 +1068,10 @@
}
}
+ public boolean isDynamic() {
+ return false;
+ }
+
/** find a symbol that this (proxy method) symbol implements.
* @param c The class whose members are searched for
* implementations
@@ -1356,6 +1360,27 @@
}
}
+ /** A class for invokedynamic method calls.
+ */
+ public static class DynamicMethodSymbol extends MethodSymbol {
+
+ public Object[] staticArgs;
+ public Symbol bsm;
+ public int bsmKind;
+
+ public DynamicMethodSymbol(Name name, Symbol owner, int bsmKind, MethodSymbol bsm, Type type, Object[] staticArgs) {
+ super(0, name, type, owner);
+ this.bsm = bsm;
+ this.bsmKind = bsmKind;
+ this.staticArgs = staticArgs;
+ }
+
+ @Override
+ public boolean isDynamic() {
+ return true;
+ }
+ }
+
/** A class for predefined operators.
*/
public static class OperatorSymbol extends MethodSymbol {
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Symtab.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Symtab.java Tue Sep 25 11:53:18 2012 +0100
@@ -126,6 +126,7 @@
public final Type cloneableType;
public final Type serializableType;
public final Type methodHandleType;
+ public final Type methodTypeType;
public final Type nativeHeaderType;
public final Type throwableType;
public final Type errorType;
@@ -440,6 +441,7 @@
throwableType = enterClass("java.lang.Throwable");
serializableType = enterClass("java.io.Serializable");
methodHandleType = enterClass("java.lang.invoke.MethodHandle");
+ methodTypeType = enterClass("java.lang.invoke.MethodType");
errorType = enterClass("java.lang.Error");
illegalArgumentExceptionType = enterClass("java.lang.IllegalArgumentException");
interruptedExceptionType = enterClass("java.lang.InterruptedException");
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassFile.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassFile.java Tue Sep 25 11:53:18 2012 +0100
@@ -84,6 +84,16 @@
public final static int CONSTANT_MethodType = 16;
public final static int CONSTANT_InvokeDynamic = 18;
+ public final static int REF_getField = 1;
+ public final static int REF_getStatic = 2;
+ public final static int REF_putField = 3;
+ public final static int REF_putStatic = 4;
+ public final static int REF_invokeVirtual = 5;
+ public final static int REF_invokeStatic = 6;
+ public final static int REF_invokeSpecial = 7;
+ public final static int REF_newInvokeSpecial = 8;
+ public final static int REF_invokeInterface = 9;
+
public final static int MAX_PARAMETERS = 0xff;
public final static int MAX_DIMENSIONS = 0xff;
public final static int MAX_CODE = 0xffff;
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Tue Sep 25 11:53:18 2012 +0100
@@ -26,6 +26,8 @@
package com.sun.tools.javac.jvm;
import java.io.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Set;
import java.util.HashSet;
@@ -137,6 +139,11 @@
*/
ListBuffer<ClassSymbol> innerClassesQueue;
+ /** The bootstrap methods to be written in the corresponding class attribute
+ * (one for each invokedynamic)
+ */
+ Map<MethodSymbol, Pool.MethodHandle> bootstrapMethods;
+
/** The log to use for verbose output.
*/
private final Log log;
@@ -477,11 +484,27 @@
if (value instanceof MethodSymbol) {
MethodSymbol m = (MethodSymbol)value;
- poolbuf.appendByte((m.owner.flags() & INTERFACE) != 0
- ? CONSTANT_InterfaceMethodref
- : CONSTANT_Methodref);
- poolbuf.appendChar(pool.put(m.owner));
- poolbuf.appendChar(pool.put(nameType(m)));
+ if (!m.isDynamic()) {
+ poolbuf.appendByte((m.owner.flags() & INTERFACE) != 0
+ ? CONSTANT_InterfaceMethodref
+ : CONSTANT_Methodref);
+ poolbuf.appendChar(pool.put(m.owner));
+ poolbuf.appendChar(pool.put(nameType(m)));
+ } else {
+ //invokedynamic
+ DynamicMethodSymbol dynSym = (DynamicMethodSymbol)m;
+ Pool.MethodHandle handle = new Pool.MethodHandle(dynSym.bsmKind, dynSym.bsm, names);
+ bootstrapMethods.put(dynSym, handle);
+ //init cp entries
+ pool.put(names.BootstrapMethods);
+ pool.put(handle);
+ for (Object staticArg : dynSym.staticArgs) {
+ pool.put(staticArg);
+ }
+ poolbuf.appendByte(CONSTANT_InvokeDynamic);
+ poolbuf.appendChar(bootstrapMethods.size() - 1);
+ poolbuf.appendChar(pool.put(nameType(dynSym)));
+ }
} else if (value instanceof VarSymbol) {
VarSymbol v = (VarSymbol)value;
poolbuf.appendByte(CONSTANT_Fieldref);
@@ -526,11 +549,20 @@
} else if (value instanceof String) {
poolbuf.appendByte(CONSTANT_String);
poolbuf.appendChar(pool.put(names.fromString((String)value)));
+ } else if (value instanceof MethodType) {
+ MethodType mtype = (MethodType)value;
+ poolbuf.appendByte(CONSTANT_MethodType);
+ poolbuf.appendChar(pool.put(typeSig(mtype)));
} else if (value instanceof Type) {
Type type = (Type)value;
if (type.tag == CLASS) enterInner((ClassSymbol)type.tsym);
poolbuf.appendByte(CONSTANT_Class);
poolbuf.appendChar(pool.put(xClassName(type)));
+ } else if (value instanceof Pool.MethodHandle) {
+ Pool.MethodHandle ref = (Pool.MethodHandle)value;
+ poolbuf.appendByte(CONSTANT_MethodHandle);
+ poolbuf.appendByte(ref.refKind);
+ poolbuf.appendChar(pool.put(ref.refSym));
} else {
Assert.error("writePool " + value);
}
@@ -914,6 +946,25 @@
endAttr(alenIdx);
}
+ /** Write "bootstrapMethods" attribute.
+ */
+ void writeBootstrapMethods() {
+ int alenIdx = writeAttr(names.BootstrapMethods);
+ databuf.appendChar(bootstrapMethods.size());
+ for (Map.Entry<MethodSymbol, Pool.MethodHandle> entry : bootstrapMethods.entrySet()) {
+ DynamicMethodSymbol dsym = (DynamicMethodSymbol)entry.getKey();
+ //write BSM handle
+ databuf.appendChar(pool.get(entry.getValue()));
+ //write static args length
+ databuf.appendChar(dsym.staticArgs.length);
+ //write static args array
+ for (Object o : dsym.staticArgs) {
+ databuf.appendChar(pool.get(o));
+ }
+ }
+ endAttr(alenIdx);
+ }
+
/** Write field symbol, entering all references into constant pool.
*/
void writeField(VarSymbol v) {
@@ -1483,6 +1534,7 @@
pool = c.pool;
innerClasses = null;
innerClassesQueue = null;
+ bootstrapMethods = new LinkedHashMap<MethodSymbol, Pool.MethodHandle>();
Type supertype = types.supertype(c.type);
List<Type> interfaces = types.interfaces(c.type);
@@ -1589,6 +1641,12 @@
writeInnerClasses();
acount++;
}
+
+ if (!bootstrapMethods.isEmpty()) {
+ writeBootstrapMethods();
+ acount++;
+ }
+
endAttrs(acountIdx, acount);
poolbuf.appendBytes(databuf.elems, 0, databuf.length);
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/Code.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/Code.java Tue Sep 25 11:53:18 2012 +0100
@@ -903,6 +903,8 @@
if (o instanceof Double) return syms.doubleType;
if (o instanceof ClassSymbol) return syms.classType;
if (o instanceof Type.ArrayType) return syms.classType;
+ if (o instanceof Type.MethodType) return syms.methodTypeType;
+ if (o instanceof Pool.MethodHandle) return syms.methodHandleType;
throw new AssertionError(o);
}
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/Gen.java Tue Sep 25 11:53:18 2012 +0100
@@ -2103,6 +2103,8 @@
result = res;
} else if (sym.kind == VAR && sym.owner.kind == MTH) {
result = items.makeLocalItem((VarSymbol)sym);
+ } else if (isInvokeDynamic(sym)) {
+ result = items.makeDynamicItem(sym);
} else if ((sym.flags() & STATIC) != 0) {
if (!isAccessSuper(env.enclMethod))
sym = binaryQualifier(sym, env.enclClass.type);
@@ -2152,8 +2154,12 @@
result = items.
makeImmediateItem(sym.type, ((VarSymbol) sym).getConstValue());
} else {
- if (!accessSuper)
+ if (isInvokeDynamic(sym)) {
+ result = items.makeDynamicItem(sym);
+ return;
+ } else if (!accessSuper) {
sym = binaryQualifier(sym, tree.selected.type);
+ }
if ((sym.flags() & STATIC) != 0) {
if (!selectSuper && (ssym == null || ssym.kind != TYP))
base = base.load();
@@ -2174,6 +2180,10 @@
}
}
+ public boolean isInvokeDynamic(Symbol sym) {
+ return sym.kind == MTH && ((MethodSymbol)sym).isDynamic();
+ }
+
public void visitLiteral(JCLiteral tree) {
if (tree.type.tag == TypeTags.BOT) {
code.emitop0(aconst_null);
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/Items.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/Items.java Tue Sep 25 11:53:18 2012 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2012, 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
@@ -110,6 +110,13 @@
return stackItem[Code.typecode(type)];
}
+ /** Make an item representing a dynamically invoked method.
+ * @param member The represented symbol.
+ */
+ Item makeDynamicItem(Symbol member) {
+ return new DynamicItem(member);
+ }
+
/** Make an item representing an indexed expression.
* @param type The expression's type.
*/
@@ -457,6 +464,35 @@
}
}
+ /** An item representing a dynamic call site.
+ */
+ class DynamicItem extends StaticItem {
+ DynamicItem(Symbol member) {
+ super(member);
+ }
+
+ Item load() {
+ assert false;
+ return null;
+ }
+
+ void store() {
+ assert false;
+ }
+
+ Item invoke() {
+ // assert target.hasNativeInvokeDynamic();
+ MethodType mtype = (MethodType)member.erasure(types);
+ int rescode = Code.typecode(mtype.restype);
+ code.emitInvokedynamic(pool.put(member), mtype);
+ return stackItem[rescode];
+ }
+
+ public String toString() {
+ return "dynamic(" + member + ")";
+ }
+ }
+
/** An item representing an instance variable or method.
*/
class MemberItem extends Item {
--- a/langtools/src/share/classes/com/sun/tools/javac/jvm/Pool.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/jvm/Pool.java Tue Sep 25 11:53:18 2012 +0100
@@ -25,9 +25,17 @@
package com.sun.tools.javac.jvm;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Kinds;
import java.util.*;
+import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.util.Assert;
+import com.sun.tools.javac.util.Filter;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
/** An internal structure that corresponds to the constant pool of a classfile.
*
@@ -167,4 +175,90 @@
v.type.hashCode();
}
}
+
+ public static class MethodHandle {
+
+ /** Reference kind - see ClassFile */
+ int refKind;
+
+ /** Reference symbol */
+ Symbol refSym;
+
+ /** Reference to the name table */
+ Names names;
+
+ public MethodHandle(int refKind, Symbol refSym, Names names) {
+ this.refKind = refKind;
+ this.refSym = refSym;
+ this.names = names;
+ checkConsistent();
+ }
+ public boolean equals(Object other) {
+ if (!(other instanceof MethodHandle)) return false;
+ MethodHandle mr = (MethodHandle) other;
+ if (mr.refKind != refKind) return false;
+ Symbol o = mr.refSym;
+ return
+ o.name == refSym.name &&
+ o.owner == refSym.owner &&
+ o.type.equals(refSym.type);
+ }
+ public int hashCode() {
+ return
+ refKind * 65 +
+ refSym.name.hashCode() * 33 +
+ refSym.owner.hashCode() * 9 +
+ refSym.type.hashCode();
+ }
+
+ /**
+ * Check consistency of reference kind and symbol (see JVMS 4.4.8)
+ */
+ @SuppressWarnings("fallthrough")
+ private void checkConsistent() {
+ boolean staticOk = false;
+ int expectedKind = -1;
+ Filter<Name> nameFilter = nonInitFilter;
+ boolean interfaceOwner = false;
+ switch (refKind) {
+ case ClassFile.REF_getStatic:
+ case ClassFile.REF_putStatic:
+ staticOk = true;
+ case ClassFile.REF_getField:
+ case ClassFile.REF_putField:
+ expectedKind = Kinds.VAR;
+ break;
+ case ClassFile.REF_newInvokeSpecial:
+ nameFilter = initFilter;
+ expectedKind = Kinds.MTH;
+ break;
+ case ClassFile.REF_invokeInterface:
+ interfaceOwner = true;
+ expectedKind = Kinds.MTH;
+ break;
+ case ClassFile.REF_invokeStatic:
+ staticOk = true;
+ case ClassFile.REF_invokeVirtual:
+ case ClassFile.REF_invokeSpecial:
+ expectedKind = Kinds.MTH;
+ break;
+ }
+ Assert.check(!refSym.isStatic() || staticOk);
+ Assert.check(refSym.kind == expectedKind);
+ Assert.check(nameFilter.accepts(refSym.name));
+ Assert.check(!refSym.owner.isInterface() || interfaceOwner);
+ }
+ //where
+ Filter<Name> nonInitFilter = new Filter<Name>() {
+ public boolean accepts(Name n) {
+ return n != names.init && n != names.clinit;
+ }
+ };
+
+ Filter<Name> initFilter = new Filter<Name>() {
+ public boolean accepts(Name n) {
+ return n == names.init;
+ }
+ };
+ }
}
--- a/langtools/src/share/classes/com/sun/tools/javac/util/Names.java Tue Sep 25 11:52:37 2012 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/util/Names.java Tue Sep 25 11:53:18 2012 +0100
@@ -169,6 +169,9 @@
public final Name ex;
public final Name package_info;
+ // lambda-related
+ public final Name BootstrapMethods;
+
public final Name.Table table;
public Names(Context context) {
@@ -296,6 +299,9 @@
deprecated = fromString("deprecated");
ex = fromString("ex");
package_info = fromString("package-info");
+
+ //lambda-related
+ BootstrapMethods = fromString("BootstrapMethods");
}
protected Name.Table createTable(Options options) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/lambda/TestInvokeDynamic.java Tue Sep 25 11:53:18 2012 +0100
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2012, 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 7194586
+ *
+ * @summary Add back-end support for invokedynamic
+ *
+ */
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.source.util.TreeScanner;
+
+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 com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.jvm.Pool;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Names;
+
+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.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+import static com.sun.tools.javac.jvm.ClassFile.*;
+
+public class TestInvokeDynamic {
+
+ static int checkCount = 0;
+
+ enum StaticArgumentKind {
+ STRING("Hello!", "String", "Ljava/lang/String;") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_String_info) &&
+ ((CONSTANT_String_info)cpInfo).getString().equals(value);
+ }
+ },
+ CLASS(null, "Class<?>", "Ljava/lang/Class;") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_Class_info) &&
+ ((CONSTANT_Class_info)cpInfo).getName().equals("java/lang/String");
+ }
+ },
+ INTEGER(1, "int", "I") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_Integer_info) &&
+ ((CONSTANT_Integer_info)cpInfo).value == ((Integer)value).intValue();
+ }
+ },
+ LONG(1L, "long", "J") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_Long_info) &&
+ ((CONSTANT_Long_info)cpInfo).value == ((Long)value).longValue();
+ }
+ },
+ FLOAT(1.0f, "float", "F") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_Float_info) &&
+ ((CONSTANT_Float_info)cpInfo).value == ((Float)value).floatValue();
+ }
+ },
+ DOUBLE(1.0, "double","D") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_Double_info) &&
+ ((CONSTANT_Double_info)cpInfo).value == ((Double)value).doubleValue();
+ }
+ },
+ METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ if (!(cpInfo instanceof CONSTANT_MethodHandle_info)) return false;
+ CONSTANT_MethodHandle_info handleInfo = (CONSTANT_MethodHandle_info)cpInfo;
+ return handleInfo.getCPRefInfo().getClassName().equals("Array") &&
+ handleInfo.reference_kind == RefKind.REF_invokeVirtual &&
+ handleInfo.getCPRefInfo().getNameAndTypeInfo().getName().equals("clone") &&
+ handleInfo.getCPRefInfo().getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;");
+ }
+ },
+ METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") {
+ @Override
+ boolean check(CPInfo cpInfo) throws Exception {
+ return (cpInfo instanceof CONSTANT_MethodType_info) &&
+ ((CONSTANT_MethodType_info)cpInfo).getType().equals("()Ljava/lang/Object;");
+ }
+ };
+
+ Object value;
+ String sourceTypeStr;
+ String bytecodeTypeStr;
+
+ StaticArgumentKind(Object value, String sourceTypeStr, String bytecodeTypeStr) {
+ this.value = value;
+ this.sourceTypeStr = sourceTypeStr;
+ this.bytecodeTypeStr = bytecodeTypeStr;
+ }
+
+ abstract boolean check(CPInfo cpInfo) throws Exception;
+
+ Object getValue(Symtab syms, Names names) {
+ switch (this) {
+ case STRING:
+ case INTEGER:
+ case LONG:
+ case FLOAT:
+ case DOUBLE:
+ return value;
+ case CLASS:
+ return syms.stringType.tsym;
+ case METHOD_HANDLE:
+ return new Pool.MethodHandle(REF_invokeVirtual, syms.arrayCloneMethod, names);
+ case METHOD_TYPE:
+ return syms.arrayCloneMethod.type;
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+
+ enum StaticArgumentsArity {
+ ZERO(0),
+ ONE(1),
+ TWO(2),
+ THREE(3);
+
+ int arity;
+
+ StaticArgumentsArity(int arity) {
+ this.arity = arity;
+ }
+ }
+
+ public static void main(String... args) throws Exception {
+ // Create a single file manager and compiler and reuse it for each compile to save time.
+ StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null);
+ final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
+ for (StaticArgumentsArity arity : StaticArgumentsArity.values()) {
+ if (arity.arity == 0) {
+ new TestInvokeDynamic(arity).compileAndCheck(fm, tool);
+ } else {
+ for (StaticArgumentKind sak1 : StaticArgumentKind.values()) {
+ if (arity.arity == 1) {
+ new TestInvokeDynamic(arity, sak1).compileAndCheck(fm, tool);
+ } else {
+ for (StaticArgumentKind sak2 : StaticArgumentKind.values()) {
+ if (arity.arity == 2) {
+ new TestInvokeDynamic(arity, sak1, sak2).compileAndCheck(fm, tool);
+ } else {
+ for (StaticArgumentKind sak3 : StaticArgumentKind.values()) {
+ new TestInvokeDynamic(arity, sak1, sak2, sak3).compileAndCheck(fm, tool);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ System.out.println("Total checks made: " + checkCount);
+ }
+
+ StaticArgumentsArity arity;
+ StaticArgumentKind[] saks;
+ JavaSource source;
+ DiagChecker dc;
+
+ TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) {
+ this.arity = arity;
+ this.saks = saks;
+ source = new JavaSource();
+ dc = new DiagChecker();
+ }
+
+ void compileAndCheck(JavaFileManager fm, JavaCompiler tool) throws Exception {
+ JavacTaskImpl ct = (JavacTaskImpl)tool.getTask(null, fm, dc,
+ null, null, Arrays.asList(source));
+ Context context = ct.getContext();
+ Symtab syms = Symtab.instance(context);
+ Names names = Names.instance(context);
+ ct.addTaskListener(new Indifier(syms, names));
+ 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) {
+ throw new AssertionError(String.format("Diags found when compiling following code\n%s\n\n%s", source.source, dc.printDiags()));
+ }
+ verifyBytecode();
+ }
+
+ void verifyBytecode() {
+ File compiledTest = new File("Test.class");
+ 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("()V")) {
+ throw new AssertionError("type mismatch for CONSTANT_InvokeDynamic_info");
+ }
+ }
+ }
+ 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 != arity.arity) {
+ throw new Error("Bad number of static invokedynamic args in BootstrapMethod attribute");
+ }
+
+ int count = 0;
+ for (StaticArgumentKind sak : saks) {
+ if (!sak.check(cf.constant_pool.get(bsm_spec.bootstrap_arguments[count]))) {
+ throw new Error("Bad static argument value " + sak);
+ }
+ count++;
+ }
+
+ CONSTANT_MethodHandle_info bsm_handle =
+ (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_method_ref);
+
+ if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) {
+ throw new Error("Bad kind on boostrap method handle");
+ }
+
+ CONSTANT_Methodref_info bsm_ref =
+ (CONSTANT_Methodref_info)cf.constant_pool.get(bsm_handle.reference_index);
+
+ if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) {
+ throw new Error("Bad owner of boostrap method");
+ }
+
+ if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) {
+ throw new Error("Bad boostrap method name");
+ }
+
+ if (!bsm_ref.getNameAndTypeInfo().getType().equals(asBSMSignatureString())) {
+ throw new Error("Bad boostrap method type" + bsm_ref.getNameAndTypeInfo().getType() + " " + asBSMSignatureString());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new Error("error reading " + compiledTest +": " + e);
+ }
+ }
+
+ String asBSMSignatureString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;");
+ for (StaticArgumentKind sak : saks) {
+ buf.append(sak.bytecodeTypeStr);
+ }
+ buf.append(")Ljava/lang/invoke/CallSite;");
+ return buf.toString();
+ }
+
+ class JavaSource extends SimpleJavaFileObject {
+
+ static final String source_template = "import java.lang.invoke.*;\n" +
+ "class Bootstrap {\n" +
+ " public static CallSite bsm(MethodHandles.Lookup lookup, String name, MethodType methodType #SARGS) {\n" +
+ " return null;\n" +
+ " }\n" +
+ "}\n" +
+ "class Test {\n" +
+ " void m() { }\n" +
+ " void test() { m(); }\n" +
+ "}";
+
+ String source;
+
+ JavaSource() {
+ super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+ source = source_template.replace("#SARGS", asSignatureString());
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return source;
+ }
+
+ String asSignatureString() {
+ int count = 0;
+ StringBuilder buf = new StringBuilder();
+ for (StaticArgumentKind sak : saks) {
+ buf.append(",");
+ buf.append(sak.sourceTypeStr);
+ buf.append(' ');
+ buf.append(String.format("x%d", count++));
+ }
+ return buf.toString();
+ }
+ }
+
+ class Indifier extends TreeScanner<Void, Void> implements TaskListener {
+
+ MethodSymbol bsm;
+ Symtab syms;
+ Names names;
+
+ Indifier(Symtab syms, Names names) {
+ this.syms = syms;
+ this.names = names;
+ }
+
+ @Override
+ public void started(TaskEvent e) {
+ //do nothing
+ }
+
+ @Override
+ public void finished(TaskEvent e) {
+ if (e.getKind() == TaskEvent.Kind.ANALYZE) {
+ scan(e.getCompilationUnit(), null);
+ }
+ }
+
+ @Override
+ public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+ super.visitMethodInvocation(node, p);
+ JCMethodInvocation apply = (JCMethodInvocation)node;
+ JCIdent ident = (JCIdent)apply.meth;
+ Symbol oldSym = ident.sym;
+ if (!oldSym.isConstructor()) {
+ Object[] staticArgs = new Object[arity.arity];
+ for (int i = 0; i < arity.arity ; i++) {
+ staticArgs[i] = saks[i].getValue(syms, names);
+ }
+ ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name, oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitMethod(MethodTree node, Void p) {
+ super.visitMethod(node, p);
+ if (node.getName().toString().equals("bsm")) {
+ bsm = ((JCMethodDecl)node).sym;
+ }
+ return null;
+ }
+ }
+
+ 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();
+ }
+ }
+}