8294960: Convert java.base/java.lang.invoke package to use the Classfile API to generate lambdas and method handles
Co-authored-by: Claes Redestad <redestad@openjdk.org> Reviewed-by: redestad, liach
This commit is contained in:
parent
50bed6c67b
commit
01ee4241b7
@ -25,13 +25,12 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.FieldVisitor;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.invoke.util.BytecodeName;
|
||||
|
||||
import java.lang.classfile.*;
|
||||
import java.lang.classfile.attribute.ExceptionsAttribute;
|
||||
import java.lang.classfile.attribute.SourceFileAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.invoke.LambdaForm.BasicType;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -42,12 +41,19 @@ import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.invoke.LambdaForm.*;
|
||||
import jdk.internal.constant.MethodTypeDescImpl;
|
||||
import jdk.internal.constant.ReferenceClassDescImpl;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.invoke.util.BytecodeName;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic;
|
||||
import static java.lang.invoke.MethodHandleStatics.*;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
/**
|
||||
* Class specialization code.
|
||||
@ -57,6 +63,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
*/
|
||||
/*non-public*/
|
||||
abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesData> {
|
||||
|
||||
private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;");
|
||||
private static final ClassDesc CD_BoundMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/BoundMethodHandle;");
|
||||
|
||||
private final Class<T> topClass;
|
||||
private final Class<K> keyType;
|
||||
private final Class<S> metaType;
|
||||
@ -404,7 +414,7 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
|
||||
buf.append(basicType.basicTypeChar());
|
||||
} else {
|
||||
buf.append('V');
|
||||
end.append(classSig(type));
|
||||
end.append(type.descriptorString());
|
||||
}
|
||||
}
|
||||
String typeString;
|
||||
@ -572,8 +582,9 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
|
||||
}
|
||||
|
||||
// These are named like constants because there is only one per specialization scheme:
|
||||
private final String SPECIES_DATA = classBCName(metaType);
|
||||
private final String SPECIES_DATA_SIG = classSig(SPECIES_DATA);
|
||||
|
||||
private final ClassDesc CD_SPECIES_DATA = classDesc(metaType);
|
||||
private final MethodTypeDesc MTD_SPECIES_DATA = MethodTypeDescImpl.ofValidated(CD_SPECIES_DATA);
|
||||
private final String SPECIES_DATA_NAME = sdAccessor.getName();
|
||||
private final int SPECIES_DATA_MODS = sdAccessor.getModifiers();
|
||||
private final List<String> TRANSFORM_NAMES; // derived from transformMethods
|
||||
@ -595,268 +606,207 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
|
||||
TRANSFORM_TYPES = List.of(tts.toArray(new MethodType[0]));
|
||||
TRANSFORM_MODS = List.of(tms.toArray(new Integer[0]));
|
||||
}
|
||||
private static final MethodTypeDesc MTD_TRANFORM_HELPER = MethodTypeDescImpl.ofValidated(CD_MethodHandle, CD_int);
|
||||
private static final int ACC_PPP = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED;
|
||||
|
||||
/*non-public*/
|
||||
byte[] generateConcreteSpeciesCodeFile(String className0, ClassSpecializer<T,K,S>.SpeciesData speciesData) {
|
||||
final String className = classBCName(className0);
|
||||
final String superClassName = classBCName(speciesData.deriveSuperClass());
|
||||
final ClassDesc classDesc = ClassDesc.of(className0);
|
||||
final ClassDesc superClassDesc = classDesc(speciesData.deriveSuperClass());
|
||||
return ClassFile.of().build(classDesc, clb -> {
|
||||
clb.withFlags(ACC_FINAL | ACC_SUPER)
|
||||
.withSuperclass(superClassDesc)
|
||||
.with(SourceFileAttribute.of(classDesc.displayName()))
|
||||
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
||||
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
|
||||
cw.visit(CLASSFILE_VERSION, NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, superClassName, null);
|
||||
// emit static types and BMH_SPECIES fields
|
||||
.withField(sdFieldName, CD_SPECIES_DATA, ACC_STATIC);
|
||||
|
||||
final String sourceFile = className.substring(className.lastIndexOf('.')+1);
|
||||
cw.visitSource(sourceFile, null);
|
||||
|
||||
// emit static types and BMH_SPECIES fields
|
||||
FieldVisitor fw = cw.visitField(NOT_ACC_PUBLIC + ACC_STATIC, sdFieldName, SPECIES_DATA_SIG, null, null);
|
||||
fw.visitAnnotation(STABLE_SIG, true);
|
||||
fw.visitEnd();
|
||||
|
||||
// handy holder for dealing with groups of typed values (ctor arguments and fields)
|
||||
class Var {
|
||||
final int index;
|
||||
final String name;
|
||||
final Class<?> type;
|
||||
final String desc;
|
||||
final BasicType basicType;
|
||||
final int slotIndex;
|
||||
Var(int index, int slotIndex) {
|
||||
this.index = index;
|
||||
this.slotIndex = slotIndex;
|
||||
name = null; type = null; desc = null;
|
||||
basicType = BasicType.V_TYPE;
|
||||
}
|
||||
Var(String name, Class<?> type, Var prev) {
|
||||
int slotIndex = prev.nextSlotIndex();
|
||||
int index = prev.nextIndex();
|
||||
if (name == null) name = "x";
|
||||
if (name.endsWith("#"))
|
||||
name = name.substring(0, name.length()-1) + index;
|
||||
assert(!type.equals(void.class));
|
||||
String desc = classSig(type);
|
||||
BasicType basicType = BasicType.basicType(type);
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.desc = desc;
|
||||
this.basicType = basicType;
|
||||
this.slotIndex = slotIndex;
|
||||
}
|
||||
Var lastOf(List<Var> vars) {
|
||||
int n = vars.size();
|
||||
return (n == 0 ? this : vars.get(n-1));
|
||||
}
|
||||
<X> List<Var> fromTypes(List<X> types) {
|
||||
Var prev = this;
|
||||
ArrayList<Var> result = new ArrayList<>(types.size());
|
||||
int i = 0;
|
||||
for (X x : types) {
|
||||
String vn = name;
|
||||
Class<?> vt;
|
||||
if (x instanceof Class<?> cl) {
|
||||
vt = cl;
|
||||
// make the names friendlier if debugging
|
||||
assert((vn = vn + "_" + (i++)) != null);
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
Var v = (Var) x;
|
||||
vn = v.name;
|
||||
vt = v.type;
|
||||
// handy holder for dealing with groups of typed values (ctor arguments and fields)
|
||||
class Var {
|
||||
final int index;
|
||||
final String name;
|
||||
final Class<?> type;
|
||||
final ClassDesc desc;
|
||||
final BasicType basicType;
|
||||
final int slotIndex;
|
||||
Var(int index, int slotIndex) {
|
||||
this.index = index;
|
||||
this.slotIndex = slotIndex;
|
||||
name = null; type = null; desc = null;
|
||||
basicType = BasicType.V_TYPE;
|
||||
}
|
||||
Var(String name, Class<?> type, Var prev) {
|
||||
int slotIndex = prev.nextSlotIndex();
|
||||
int index = prev.nextIndex();
|
||||
if (name == null) name = "x";
|
||||
if (name.endsWith("#"))
|
||||
name = name.substring(0, name.length()-1) + index;
|
||||
assert(!type.equals(void.class));
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.desc = classDesc(type);
|
||||
this.basicType = BasicType.basicType(type);
|
||||
this.slotIndex = slotIndex;
|
||||
}
|
||||
Var lastOf(List<Var> vars) {
|
||||
int n = vars.size();
|
||||
return (n == 0 ? this : vars.get(n-1));
|
||||
}
|
||||
<X> List<Var> fromTypes(List<X> types) {
|
||||
Var prev = this;
|
||||
ArrayList<Var> result = new ArrayList<>(types.size());
|
||||
int i = 0;
|
||||
for (X x : types) {
|
||||
String vn = name;
|
||||
Class<?> vt;
|
||||
if (x instanceof Class<?> cl) {
|
||||
vt = cl;
|
||||
// make the names friendlier if debugging
|
||||
assert((vn = vn + "_" + (i++)) != null);
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
Var v = (Var) x;
|
||||
vn = v.name;
|
||||
vt = v.type;
|
||||
}
|
||||
prev = new Var(vn, vt, prev);
|
||||
result.add(prev);
|
||||
}
|
||||
prev = new Var(vn, vt, prev);
|
||||
result.add(prev);
|
||||
return result;
|
||||
}
|
||||
|
||||
int slotSize() { return basicType.basicTypeSlots(); }
|
||||
int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); }
|
||||
int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; }
|
||||
boolean isInHeap() { return slotIndex < 0; }
|
||||
void emitLoadInstruction(CodeBuilder cob) {
|
||||
cob.loadLocal(basicType.btKind, slotIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int slotSize() { return basicType.basicTypeSlots(); }
|
||||
int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); }
|
||||
int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; }
|
||||
boolean isInHeap() { return slotIndex < 0; }
|
||||
void emitVarInstruction(int asmop, MethodVisitor mv) {
|
||||
if (asmop == ALOAD)
|
||||
asmop = typeLoadOp(basicType.basicTypeChar());
|
||||
else
|
||||
throw new AssertionError("bad op="+asmop+" for desc="+desc);
|
||||
mv.visitVarInsn(asmop, slotIndex);
|
||||
}
|
||||
public void emitFieldInsn(int asmop, MethodVisitor mv) {
|
||||
mv.visitFieldInsn(asmop, className, name, desc);
|
||||
}
|
||||
}
|
||||
final Var NO_THIS = new Var(0, 0),
|
||||
AFTER_THIS = new Var(0, 1),
|
||||
IN_HEAP = new Var(0, -1);
|
||||
|
||||
final Var NO_THIS = new Var(0, 0),
|
||||
AFTER_THIS = new Var(0, 1),
|
||||
IN_HEAP = new Var(0, -1);
|
||||
|
||||
// figure out the field types
|
||||
final List<Class<?>> fieldTypes = speciesData.fieldTypes();
|
||||
final List<Var> fields = new ArrayList<>(fieldTypes.size());
|
||||
{
|
||||
Var nextF = IN_HEAP;
|
||||
for (Class<?> ft : fieldTypes) {
|
||||
String fn = chooseFieldName(ft, nextF.nextIndex());
|
||||
nextF = new Var(fn, ft, nextF);
|
||||
fields.add(nextF);
|
||||
}
|
||||
}
|
||||
|
||||
// emit bound argument fields
|
||||
for (Var field : fields) {
|
||||
cw.visitField(ACC_FINAL, field.name, field.desc, null, null).visitEnd();
|
||||
}
|
||||
|
||||
MethodVisitor mv;
|
||||
|
||||
// emit implementation of speciesData()
|
||||
mv = cw.visitMethod((SPECIES_DATA_MODS & ACC_PPP) + ACC_FINAL,
|
||||
SPECIES_DATA_NAME, "()" + SPECIES_DATA_SIG, null, null);
|
||||
mv.visitCode();
|
||||
mv.visitFieldInsn(GETSTATIC, className, sdFieldName, SPECIES_DATA_SIG);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
|
||||
// figure out the constructor arguments
|
||||
MethodType superCtorType = ClassSpecializer.this.baseConstructorType();
|
||||
MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes);
|
||||
|
||||
// emit constructor
|
||||
{
|
||||
mv = cw.visitMethod(ACC_PRIVATE,
|
||||
"<init>", methodSig(thisCtorType), null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0); // this
|
||||
|
||||
final List<Var> ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList());
|
||||
for (Var ca : ctorArgs) {
|
||||
ca.emitVarInstruction(ALOAD, mv);
|
||||
// figure out the field types
|
||||
final List<Class<?>> fieldTypes = speciesData.fieldTypes();
|
||||
final List<Var> fields = new ArrayList<>(fieldTypes.size());
|
||||
{
|
||||
Var nextF = IN_HEAP;
|
||||
for (Class<?> ft : fieldTypes) {
|
||||
String fn = chooseFieldName(ft, nextF.nextIndex());
|
||||
nextF = new Var(fn, ft, nextF);
|
||||
fields.add(nextF);
|
||||
}
|
||||
}
|
||||
|
||||
// super(ca...)
|
||||
mv.visitMethodInsn(INVOKESPECIAL, superClassName,
|
||||
"<init>", methodSig(superCtorType), false);
|
||||
|
||||
// store down fields
|
||||
Var lastFV = AFTER_THIS.lastOf(ctorArgs);
|
||||
for (Var f : fields) {
|
||||
// this.argL1 = argL1
|
||||
mv.visitVarInsn(ALOAD, 0); // this
|
||||
lastFV = new Var(f.name, f.type, lastFV);
|
||||
lastFV.emitVarInstruction(ALOAD, mv);
|
||||
f.emitFieldInsn(PUTFIELD, mv);
|
||||
// emit bound argument fields
|
||||
for (Var field : fields) {
|
||||
clb.withField(field.name, field.desc, ACC_FINAL);
|
||||
}
|
||||
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
// emit implementation of speciesData()
|
||||
clb.withMethodBody(SPECIES_DATA_NAME, MTD_SPECIES_DATA, (SPECIES_DATA_MODS & ACC_PPP) | ACC_FINAL,
|
||||
cob -> cob.getstatic(classDesc, sdFieldName, CD_SPECIES_DATA)
|
||||
.areturn());
|
||||
|
||||
// emit make() ...factory method wrapping constructor
|
||||
{
|
||||
// figure out the constructor arguments
|
||||
MethodType superCtorType = ClassSpecializer.this.baseConstructorType();
|
||||
MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes);
|
||||
|
||||
// emit constructor
|
||||
clb.withMethodBody(INIT_NAME, methodDesc(thisCtorType), ACC_PRIVATE, cob -> {
|
||||
cob.aload(0); // this
|
||||
|
||||
final List<Var> ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList());
|
||||
for (Var ca : ctorArgs) {
|
||||
ca.emitLoadInstruction(cob);
|
||||
}
|
||||
|
||||
// super(ca...)
|
||||
cob.invokespecial(superClassDesc, INIT_NAME, methodDesc(superCtorType));
|
||||
|
||||
// store down fields
|
||||
Var lastFV = AFTER_THIS.lastOf(ctorArgs);
|
||||
for (Var f : fields) {
|
||||
// this.argL1 = argL1
|
||||
cob.aload(0); // this
|
||||
lastFV = new Var(f.name, f.type, lastFV);
|
||||
lastFV.emitLoadInstruction(cob);
|
||||
cob.putfield(classDesc, f.name, f.desc);
|
||||
}
|
||||
|
||||
cob.return_();
|
||||
});
|
||||
|
||||
// emit make() ...factory method wrapping constructor
|
||||
MethodType ftryType = thisCtorType.changeReturnType(topClass());
|
||||
mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_STATIC,
|
||||
"make", methodSig(ftryType), null, null);
|
||||
mv.visitCode();
|
||||
// make instance
|
||||
mv.visitTypeInsn(NEW, className);
|
||||
mv.visitInsn(DUP);
|
||||
// load factory method arguments: ctarg... and arg...
|
||||
for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) {
|
||||
v.emitVarInstruction(ALOAD, mv);
|
||||
}
|
||||
|
||||
// finally, invoke the constructor and return
|
||||
mv.visitMethodInsn(INVOKESPECIAL, className,
|
||||
"<init>", methodSig(thisCtorType), false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
// For each transform, emit the customized override of the transform method.
|
||||
// This method mixes together some incoming arguments (from the transform's
|
||||
// static type signature) with the field types themselves, and passes
|
||||
// the resulting mish-mosh of values to a method handle produced by
|
||||
// the species itself. (Typically this method handle is the factory
|
||||
// method of this species or a related one.)
|
||||
for (int whichtm = 0; whichtm < TRANSFORM_NAMES.size(); whichtm++) {
|
||||
final String TNAME = TRANSFORM_NAMES.get(whichtm);
|
||||
final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm);
|
||||
final int TMODS = TRANSFORM_MODS.get(whichtm);
|
||||
mv = cw.visitMethod((TMODS & ACC_PPP) | ACC_FINAL,
|
||||
TNAME, TTYPE.toMethodDescriptorString(), null, E_THROWABLE);
|
||||
mv.visitCode();
|
||||
// return a call to the corresponding "transform helper", something like this:
|
||||
// MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg)
|
||||
mv.visitFieldInsn(GETSTATIC, className,
|
||||
sdFieldName, SPECIES_DATA_SIG);
|
||||
emitIntConstant(whichtm, mv);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA,
|
||||
"transformHelper", "(I)" + MH_SIG, false);
|
||||
|
||||
List<Var> targs = AFTER_THIS.fromTypes(TTYPE.parameterList());
|
||||
List<Var> tfields = new ArrayList<>(fields);
|
||||
// mix them up and load them for the transform helper:
|
||||
List<Var> helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields);
|
||||
List<Class<?>> helperTypes = new ArrayList<>(helperArgs.size());
|
||||
for (Var ha : helperArgs) {
|
||||
helperTypes.add(ha.basicType.basicTypeClass());
|
||||
if (ha.isInHeap()) {
|
||||
assert(tfields.contains(ha));
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
ha.emitFieldInsn(GETFIELD, mv);
|
||||
} else {
|
||||
assert(targs.contains(ha));
|
||||
ha.emitVarInstruction(ALOAD, mv);
|
||||
clb.withMethodBody("make", methodDesc(ftryType), ACC_STATIC, cob -> {
|
||||
// make instance
|
||||
cob.new_(classDesc)
|
||||
.dup();
|
||||
// load factory method arguments: ctarg... and arg...
|
||||
for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) {
|
||||
v.emitLoadInstruction(cob);
|
||||
}
|
||||
|
||||
// finally, invoke the constructor and return
|
||||
cob.invokespecial(classDesc, INIT_NAME, methodDesc(thisCtorType))
|
||||
.areturn();
|
||||
});
|
||||
|
||||
// For each transform, emit the customized override of the transform method.
|
||||
// This method mixes together some incoming arguments (from the transform's
|
||||
// static type signature) with the field types themselves, and passes
|
||||
// the resulting mish-mosh of values to a method handle produced by
|
||||
// the species itself. (Typically this method handle is the factory
|
||||
// method of this species or a related one.)
|
||||
for (int i = 0; i < TRANSFORM_NAMES.size(); i++) {
|
||||
final int whichtm = i;
|
||||
final String TNAME = TRANSFORM_NAMES.get(whichtm);
|
||||
final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm);
|
||||
final int TMODS = TRANSFORM_MODS.get(whichtm);
|
||||
clb.withMethod(TNAME, methodDesc(TTYPE), (TMODS & ACC_PPP) | ACC_FINAL, mb -> {
|
||||
mb.with(ExceptionsAttribute.ofSymbols(CD_Throwable))
|
||||
.withCode(cob -> {
|
||||
// return a call to the corresponding "transform helper", something like this:
|
||||
// MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg)
|
||||
cob.getstatic(classDesc, sdFieldName, CD_SPECIES_DATA)
|
||||
.loadConstant(whichtm)
|
||||
.invokevirtual(CD_SPECIES_DATA, "transformHelper", MTD_TRANFORM_HELPER);
|
||||
|
||||
List<Var> targs = AFTER_THIS.fromTypes(TTYPE.parameterList());
|
||||
List<Var> tfields = new ArrayList<>(fields);
|
||||
// mix them up and load them for the transform helper:
|
||||
List<Var> helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields);
|
||||
ClassDesc[] helperTypes = new ClassDesc[helperArgs.size()];
|
||||
for (int hi = 0; hi < helperTypes.length; hi++) {
|
||||
Var ha = helperArgs.get(hi);
|
||||
helperTypes[hi] = ha.basicType.basicTypeWrapper().basicClassDescriptor();
|
||||
if (ha.isInHeap()) {
|
||||
assert(tfields.contains(ha));
|
||||
cob.aload(0);
|
||||
cob.getfield(classDesc, ha.name, ha.desc);
|
||||
} else {
|
||||
assert(targs.contains(ha));
|
||||
ha.emitLoadInstruction(cob);
|
||||
}
|
||||
}
|
||||
|
||||
// jump into the helper (which is probably a factory method)
|
||||
final Class<?> rtype = TTYPE.returnType();
|
||||
if (!rtype.isPrimitive()) {
|
||||
cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDescImpl.ofValidated(CD_Object, helperTypes))
|
||||
.checkcast(classDesc(rtype))
|
||||
.areturn();
|
||||
} else {
|
||||
throw newInternalError("NYI: transform of type "+rtype);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// jump into the helper (which is probably a factory method)
|
||||
final Class<?> rtype = TTYPE.returnType();
|
||||
final BasicType rbt = BasicType.basicType(rtype);
|
||||
MethodType invokeBasicType = MethodType.methodType(rbt.basicTypeClass(), helperTypes);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, MH,
|
||||
"invokeBasic", methodSig(invokeBasicType), false);
|
||||
if (rbt == BasicType.L_TYPE) {
|
||||
mv.visitTypeInsn(CHECKCAST, classBCName(rtype));
|
||||
mv.visitInsn(ARETURN);
|
||||
} else {
|
||||
throw newInternalError("NYI: transform of type "+rtype);
|
||||
}
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
cw.visitEnd();
|
||||
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private int typeLoadOp(char t) {
|
||||
return switch (t) {
|
||||
case 'L' -> ALOAD;
|
||||
case 'I' -> ILOAD;
|
||||
case 'J' -> LLOAD;
|
||||
case 'F' -> FLOAD;
|
||||
case 'D' -> DLOAD;
|
||||
default -> throw newInternalError("unrecognized type " + t);
|
||||
};
|
||||
}
|
||||
|
||||
private void emitIntConstant(int con, MethodVisitor mv) {
|
||||
if (ICONST_M1 - ICONST_0 <= con && con <= ICONST_5 - ICONST_0)
|
||||
mv.visitInsn(ICONST_0 + con);
|
||||
else if (con == (byte) con)
|
||||
mv.visitIntInsn(BIPUSH, con);
|
||||
else if (con == (short) con)
|
||||
mv.visitIntInsn(SIPUSH, con);
|
||||
else {
|
||||
mv.visitLdcInsn(con);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
@ -990,39 +940,25 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
|
||||
|
||||
|
||||
// Other misc helpers:
|
||||
private static final String MH = "java/lang/invoke/MethodHandle";
|
||||
private static final String MH_SIG = "L" + MH + ";";
|
||||
private static final String STABLE = "jdk/internal/vm/annotation/Stable";
|
||||
private static final String STABLE_SIG = "L" + STABLE + ";";
|
||||
private static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" };
|
||||
static {
|
||||
assert(MH_SIG.equals(classSig(MethodHandle.class)));
|
||||
assert(MH.equals(classBCName(MethodHandle.class)));
|
||||
}
|
||||
|
||||
static String methodSig(MethodType mt) {
|
||||
return mt.toMethodDescriptorString();
|
||||
}
|
||||
static String classSig(Class<?> cls) {
|
||||
if (cls.isPrimitive() || cls.isArray())
|
||||
return MethodType.methodType(cls).toMethodDescriptorString().substring(2);
|
||||
return classSig(classBCName(cls));
|
||||
}
|
||||
static String classSig(String bcName) {
|
||||
assert(bcName.indexOf('.') < 0);
|
||||
assert(!bcName.endsWith(";"));
|
||||
assert(!bcName.startsWith("["));
|
||||
return "L" + bcName + ";";
|
||||
}
|
||||
static String classBCName(Class<?> cls) {
|
||||
return classBCName(className(cls));
|
||||
}
|
||||
static String classBCName(String str) {
|
||||
assert(str.indexOf('/') < 0) : str;
|
||||
return str.replace('.', '/');
|
||||
}
|
||||
static String className(Class<?> cls) {
|
||||
assert(!cls.isArray() && !cls.isPrimitive());
|
||||
return cls.getName();
|
||||
|
||||
static ClassDesc classDesc(Class<?> cls) {
|
||||
return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
|
||||
: cls == Object.class ? CD_Object
|
||||
: cls == MethodType.class ? CD_MethodType
|
||||
: cls == LambdaForm.class ? CD_LambdaForm
|
||||
: cls == BoundMethodHandle.class ? CD_BoundMethodHandle
|
||||
: ReferenceClassDescImpl.ofValidated(cls.descriptorString());
|
||||
}
|
||||
|
||||
static MethodTypeDesc methodDesc(MethodType mt) {
|
||||
var params = new ClassDesc[mt.parameterCount()];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
params[i] = classDesc(mt.parameterType(i));
|
||||
}
|
||||
return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params);
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,11 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.attribute.SourceFileAttribute;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@ -38,10 +39,10 @@ import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static java.lang.invoke.LambdaForm.BasicType.*;
|
||||
import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION;
|
||||
import static java.lang.invoke.MethodTypeForm.*;
|
||||
import static java.lang.invoke.LambdaForm.Kind.*;
|
||||
import static java.lang.invoke.MethodTypeForm.*;
|
||||
|
||||
/**
|
||||
* Helper class to assist the GenerateJLIClassesPlugin to get access to
|
||||
@ -557,19 +558,14 @@ class GenerateJLIClassesHelper {
|
||||
* a class with a specified name.
|
||||
*/
|
||||
private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) {
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
|
||||
cw.visit(CLASSFILE_VERSION, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
|
||||
className, null, InvokerBytecodeGenerator.INVOKER_SUPER_NAME, null);
|
||||
cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null);
|
||||
|
||||
for (int i = 0; i < forms.length; i++) {
|
||||
InvokerBytecodeGenerator g
|
||||
= new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType());
|
||||
g.setClassWriter(cw);
|
||||
g.addMethod();
|
||||
}
|
||||
|
||||
return cw.toByteArray();
|
||||
return ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> {
|
||||
clb.withFlags(ACC_PRIVATE | ACC_FINAL | ACC_SUPER)
|
||||
.withSuperclass(InvokerBytecodeGenerator.INVOKER_SUPER_DESC)
|
||||
.with(SourceFileAttribute.of(className.substring(className.lastIndexOf('/') + 1)));
|
||||
for (int i = 0; i < forms.length; i++) {
|
||||
new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()).addMethod(clb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static LambdaForm makeReinvokerFor(MethodType type) {
|
||||
|
@ -26,23 +26,40 @@
|
||||
package java.lang.invoke;
|
||||
|
||||
import jdk.internal.misc.CDS;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import jdk.internal.util.ClassFileDumper;
|
||||
import sun.invoke.util.BytecodeDescriptor;
|
||||
import sun.invoke.util.VerifyAccess;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.classfile.ClassBuilder;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.FieldBuilder;
|
||||
import java.lang.classfile.MethodBuilder;
|
||||
import java.lang.classfile.Opcode;
|
||||
import java.lang.classfile.TypeKind;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.DynamicConstantDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION;
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import java.lang.classfile.attribute.ExceptionsAttribute;
|
||||
import java.lang.classfile.constantpool.ClassEntry;
|
||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||
import java.lang.classfile.constantpool.MethodRefEntry;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
import jdk.internal.constant.ConstantUtils;
|
||||
import jdk.internal.constant.MethodTypeDescImpl;
|
||||
import jdk.internal.constant.ReferenceClassDescImpl;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
/**
|
||||
* Lambda metafactory implementation which dynamically creates an
|
||||
@ -51,42 +68,29 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
* @see LambdaMetafactory
|
||||
*/
|
||||
/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory {
|
||||
private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
|
||||
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
|
||||
private static final String NAME_CTOR = "<init>";
|
||||
private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$";
|
||||
|
||||
//Serialization support
|
||||
private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
|
||||
private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException";
|
||||
private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;";
|
||||
private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V";
|
||||
private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V";
|
||||
|
||||
private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
|
||||
private static final String NAME_METHOD_READ_OBJECT = "readObject";
|
||||
private static final String NAME_METHOD_WRITE_OBJECT = "writeObject";
|
||||
|
||||
private static final String DESCR_CLASS = "Ljava/lang/Class;";
|
||||
private static final String DESCR_STRING = "Ljava/lang/String;";
|
||||
private static final String DESCR_OBJECT = "Ljava/lang/Object;";
|
||||
private static final String DESCR_CTOR_SERIALIZED_LAMBDA
|
||||
= "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I"
|
||||
+ DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V";
|
||||
|
||||
private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION = "(Ljava/lang/String;)V";
|
||||
private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION};
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private static final ClassDesc[] EMPTY_CLASSDESC_ARRAY = ConstantUtils.EMPTY_CLASSDESC;
|
||||
|
||||
// Static builders to avoid lambdas
|
||||
record FieldFlags(int flags) implements Consumer<FieldBuilder> {
|
||||
@Override
|
||||
public void accept(FieldBuilder fb) {
|
||||
fb.withFlags(flags);
|
||||
}
|
||||
};
|
||||
record MethodBody(Consumer<CodeBuilder> code) implements Consumer<MethodBuilder> {
|
||||
@Override
|
||||
public void accept(MethodBuilder mb) {
|
||||
mb.withCode(code);
|
||||
}
|
||||
};
|
||||
|
||||
// For dumping generated classes to disk, for debugging purposes
|
||||
private static final ClassFileDumper lambdaProxyClassFileDumper;
|
||||
|
||||
private static final boolean disableEagerInitialization;
|
||||
|
||||
// condy to load implMethod from class data
|
||||
private static final ConstantDynamic implMethodCondy;
|
||||
|
||||
static {
|
||||
// To dump the lambda proxy classes, set this system property:
|
||||
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
|
||||
@ -96,23 +100,18 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization";
|
||||
disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey);
|
||||
|
||||
// condy to load implMethod from class data
|
||||
MethodType classDataMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class);
|
||||
Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
|
||||
classDataMType.descriptorString(), false);
|
||||
implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm);
|
||||
}
|
||||
|
||||
// See context values in AbstractValidatingLambdaMetafactory
|
||||
private final String implMethodClassName; // Name of type containing implementation "CC"
|
||||
private final ClassDesc implMethodClassDesc; // Name of type containing implementation "CC"
|
||||
private final String implMethodName; // Name of implementation method "impl"
|
||||
private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;"
|
||||
private final MethodTypeDesc implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;"
|
||||
private final MethodType constructorType; // Generated class constructor type "(CC)void"
|
||||
private final ClassWriter cw; // ASM class writer
|
||||
private final MethodTypeDesc constructorTypeDesc;// Type descriptor for the generated class constructor type "(CC)void"
|
||||
private final String[] argNames; // Generated names for the constructor arguments
|
||||
private final String[] argDescs; // Type descriptors for the constructor arguments
|
||||
private final String lambdaClassName; // Generated name for the generated class "X$$Lambda"
|
||||
private final ClassDesc[] argDescs; // Type descriptors for the constructor arguments
|
||||
private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1"
|
||||
private final ClassDesc lambdaClassDesc; // Type descriptor for the generated class "X$$Lambda$1"
|
||||
private final boolean useImplMethodHandle; // use MethodHandle invocation instead of symbolic bytecode invocation
|
||||
|
||||
/**
|
||||
@ -168,11 +167,13 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
super(caller, factoryType, interfaceMethodName, interfaceMethodType,
|
||||
implementation, dynamicMethodType,
|
||||
isSerializable, altInterfaces, altMethods);
|
||||
implMethodClassName = implClass.getName().replace('.', '/');
|
||||
implMethodClassDesc = implClassDesc(implClass);
|
||||
implMethodName = implInfo.getName();
|
||||
implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
|
||||
implMethodDesc = methodDesc(implInfo.getMethodType());
|
||||
constructorType = factoryType.changeReturnType(Void.TYPE);
|
||||
constructorTypeDesc = methodDesc(constructorType);
|
||||
lambdaClassName = lambdaClassName(targetClass);
|
||||
lambdaClassDesc = ClassDesc.ofInternalName(lambdaClassName);
|
||||
// If the target class invokes a protected method inherited from a
|
||||
// superclass in a different package, or does 'invokespecial', the
|
||||
// lambda class has no access to the resolved method, or does
|
||||
@ -182,19 +183,19 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
// situation by generating bridges in the target class)
|
||||
useImplMethodHandle = (Modifier.isProtected(implInfo.getModifiers()) &&
|
||||
!VerifyAccess.isSamePackage(targetClass, implInfo.getDeclaringClass())) ||
|
||||
implKind == H_INVOKESPECIAL ||
|
||||
implKind == H_INVOKESTATIC && implClass.isHidden();
|
||||
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
implKind == MethodHandleInfo.REF_invokeSpecial ||
|
||||
implKind == MethodHandleInfo.REF_invokeStatic && implClass.isHidden();
|
||||
int parameterCount = factoryType.parameterCount();
|
||||
if (parameterCount > 0) {
|
||||
argNames = new String[parameterCount];
|
||||
argDescs = new String[parameterCount];
|
||||
argDescs = new ClassDesc[parameterCount];
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
argNames[i] = "arg$" + (i + 1);
|
||||
argDescs[i] = BytecodeDescriptor.unparse(factoryType.parameterType(i));
|
||||
argDescs[i] = classDesc(factoryType.parameterType(i));
|
||||
}
|
||||
} else {
|
||||
argNames = argDescs = EMPTY_STRING_ARRAY;
|
||||
argNames = EMPTY_STRING_ARRAY;
|
||||
argDescs = EMPTY_CLASSDESC_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,65 +301,63 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
* is not found
|
||||
*/
|
||||
private Class<?> generateInnerClass() throws LambdaConversionException {
|
||||
String[] interfaceNames;
|
||||
String interfaceName = interfaceClass.getName().replace('.', '/');
|
||||
List<ClassDesc> interfaces;
|
||||
ClassDesc interfaceDesc = classDesc(interfaceClass);
|
||||
boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(interfaceClass);
|
||||
if (altInterfaces.length == 0) {
|
||||
interfaceNames = new String[]{interfaceName};
|
||||
interfaces = List.of(interfaceDesc);
|
||||
} else {
|
||||
// Assure no duplicate interfaces (ClassFormatError)
|
||||
Set<String> itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1);
|
||||
itfs.add(interfaceName);
|
||||
Set<ClassDesc> itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1);
|
||||
itfs.add(interfaceDesc);
|
||||
for (Class<?> i : altInterfaces) {
|
||||
itfs.add(i.getName().replace('.', '/'));
|
||||
itfs.add(classDesc(i));
|
||||
accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(i);
|
||||
}
|
||||
interfaceNames = itfs.toArray(new String[itfs.size()]);
|
||||
interfaces = List.copyOf(itfs);
|
||||
}
|
||||
final boolean finalAccidentallySerializable = accidentallySerializable;
|
||||
final byte[] classBytes = ClassFile.of().build(lambdaClassDesc, new Consumer<ClassBuilder>() {
|
||||
@Override
|
||||
public void accept(ClassBuilder clb) {
|
||||
clb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC)
|
||||
.withInterfaceSymbols(interfaces);
|
||||
// Generate final fields to be filled in by constructor
|
||||
for (int i = 0; i < argDescs.length; i++) {
|
||||
clb.withField(argNames[i], argDescs[i], new FieldFlags(ACC_PRIVATE | ACC_FINAL));
|
||||
}
|
||||
|
||||
cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
|
||||
lambdaClassName, null,
|
||||
JAVA_LANG_OBJECT, interfaceNames);
|
||||
generateConstructor(clb);
|
||||
|
||||
// Generate final fields to be filled in by constructor
|
||||
for (int i = 0; i < argDescs.length; i++) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
|
||||
argNames[i],
|
||||
argDescs[i],
|
||||
null, null);
|
||||
fv.visitEnd();
|
||||
}
|
||||
if (factoryType.parameterCount() == 0 && disableEagerInitialization) {
|
||||
generateClassInitializer(clb);
|
||||
}
|
||||
|
||||
generateConstructor();
|
||||
// Forward the SAM method
|
||||
clb.withMethod(interfaceMethodName,
|
||||
methodDesc(interfaceMethodType),
|
||||
ACC_PUBLIC,
|
||||
forwardingMethod(interfaceMethodType));
|
||||
|
||||
if (factoryType.parameterCount() == 0 && disableEagerInitialization) {
|
||||
generateClassInitializer();
|
||||
}
|
||||
// Forward the bridges
|
||||
if (altMethods != null) {
|
||||
for (MethodType mt : altMethods) {
|
||||
clb.withMethod(interfaceMethodName,
|
||||
methodDesc(mt),
|
||||
ACC_PUBLIC | ACC_BRIDGE,
|
||||
forwardingMethod(mt));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward the SAM method
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName,
|
||||
interfaceMethodType.toMethodDescriptorString(), null, null);
|
||||
new ForwardingMethodGenerator(mv).generate(interfaceMethodType);
|
||||
|
||||
// Forward the altMethods
|
||||
if (altMethods != null) {
|
||||
for (MethodType mt : altMethods) {
|
||||
mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName,
|
||||
mt.toMethodDescriptorString(), null, null);
|
||||
new ForwardingMethodGenerator(mv).generate(mt);
|
||||
if (isSerializable)
|
||||
generateSerializationFriendlyMethods(clb);
|
||||
else if (finalAccidentallySerializable)
|
||||
generateSerializationHostileMethods(clb);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSerializable)
|
||||
generateSerializationFriendlyMethods();
|
||||
else if (accidentallySerializable)
|
||||
generateSerializationHostileMethods();
|
||||
|
||||
cw.visitEnd();
|
||||
});
|
||||
|
||||
// Define the generated class in this VM.
|
||||
|
||||
final byte[] classBytes = cw.toByteArray();
|
||||
try {
|
||||
// this class is linked at the indy callsite; so define a hidden nestmate
|
||||
var classdata = useImplMethodHandle? implementation : null;
|
||||
@ -373,237 +372,214 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
/**
|
||||
* Generate a static field and a static initializer that sets this field to an instance of the lambda
|
||||
*/
|
||||
private void generateClassInitializer() {
|
||||
String lambdaTypeDescriptor = factoryType.returnType().descriptorString();
|
||||
private void generateClassInitializer(ClassBuilder clb) {
|
||||
ClassDesc lambdaTypeDescriptor = classDesc(factoryType.returnType());
|
||||
|
||||
// Generate the static final field that holds the lambda singleton
|
||||
FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL,
|
||||
LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, null, null);
|
||||
fv.visitEnd();
|
||||
clb.withField(LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, new FieldFlags(ACC_PRIVATE | ACC_STATIC | ACC_FINAL));
|
||||
|
||||
// Instantiate the lambda and store it to the static final field
|
||||
MethodVisitor clinit = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
|
||||
clinit.visitCode();
|
||||
|
||||
clinit.visitTypeInsn(NEW, lambdaClassName);
|
||||
clinit.visitInsn(Opcodes.DUP);
|
||||
assert factoryType.parameterCount() == 0;
|
||||
clinit.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString(), false);
|
||||
clinit.visitFieldInsn(PUTSTATIC, lambdaClassName, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor);
|
||||
|
||||
clinit.visitInsn(RETURN);
|
||||
clinit.visitMaxs(-1, -1);
|
||||
clinit.visitEnd();
|
||||
clb.withMethod(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new MethodBody(new Consumer<CodeBuilder>() {
|
||||
@Override
|
||||
public void accept(CodeBuilder cob) {
|
||||
assert factoryType.parameterCount() == 0;
|
||||
cob.new_(lambdaClassDesc)
|
||||
.dup()
|
||||
.invokespecial(lambdaClassDesc, INIT_NAME, constructorTypeDesc)
|
||||
.putstatic(lambdaClassDesc, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor)
|
||||
.return_();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the constructor for the class
|
||||
*/
|
||||
private void generateConstructor() {
|
||||
private void generateConstructor(ClassBuilder clb) {
|
||||
// Generate constructor
|
||||
MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR,
|
||||
constructorType.toMethodDescriptorString(), null, null);
|
||||
ctor.visitCode();
|
||||
ctor.visitVarInsn(ALOAD, 0);
|
||||
ctor.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR,
|
||||
METHOD_DESCRIPTOR_VOID, false);
|
||||
int parameterCount = factoryType.parameterCount();
|
||||
for (int i = 0, lvIndex = 0; i < parameterCount; i++) {
|
||||
ctor.visitVarInsn(ALOAD, 0);
|
||||
Class<?> argType = factoryType.parameterType(i);
|
||||
ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1);
|
||||
lvIndex += getParameterSize(argType);
|
||||
ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]);
|
||||
}
|
||||
ctor.visitInsn(RETURN);
|
||||
// Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
|
||||
ctor.visitMaxs(-1, -1);
|
||||
ctor.visitEnd();
|
||||
clb.withMethod(INIT_NAME, constructorTypeDesc, ACC_PRIVATE,
|
||||
new MethodBody(new Consumer<CodeBuilder>() {
|
||||
@Override
|
||||
public void accept(CodeBuilder cob) {
|
||||
cob.aload(0)
|
||||
.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
||||
int parameterCount = factoryType.parameterCount();
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
cob.aload(0);
|
||||
Class<?> argType = factoryType.parameterType(i);
|
||||
cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i));
|
||||
cob.putfield(lambdaClassDesc, argNames[i], argDescs[i]);
|
||||
}
|
||||
cob.return_();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static class SerializationSupport {
|
||||
// Serialization support
|
||||
private static final ClassDesc CD_SerializedLambda = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/SerializedLambda;");
|
||||
private static final ClassDesc CD_ObjectOutputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectOutputStream;");
|
||||
private static final ClassDesc CD_ObjectInputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectInputStream;");
|
||||
private static final MethodTypeDesc MTD_Object = MethodTypeDescImpl.ofValidated(CD_Object);
|
||||
private static final MethodTypeDesc MTD_void_ObjectOutputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectOutputStream);
|
||||
private static final MethodTypeDesc MTD_void_ObjectInputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectInputStream);
|
||||
|
||||
private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
|
||||
private static final String NAME_METHOD_READ_OBJECT = "readObject";
|
||||
private static final String NAME_METHOD_WRITE_OBJECT = "writeObject";
|
||||
|
||||
static final ClassDesc CD_NotSerializableException = ReferenceClassDescImpl.ofValidated("Ljava/io/NotSerializableException;");
|
||||
static final MethodTypeDesc MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION = MethodTypeDescImpl.ofValidated(CD_void, CD_String);
|
||||
static final MethodTypeDesc MTD_CTOR_SERIALIZED_LAMBDA = MethodTypeDescImpl.ofValidated(CD_void,
|
||||
CD_Class, CD_String, CD_String, CD_String, CD_int, CD_String, CD_String, CD_String, CD_String, ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a writeReplace method that supports serialization
|
||||
*/
|
||||
private void generateSerializationFriendlyMethods() {
|
||||
TypeConvertingMethodAdapter mv
|
||||
= new TypeConvertingMethodAdapter(
|
||||
cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
|
||||
NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE,
|
||||
null, null));
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitLdcInsn(Type.getType(targetClass));
|
||||
mv.visitLdcInsn(factoryType.returnType().getName().replace('.', '/'));
|
||||
mv.visitLdcInsn(interfaceMethodName);
|
||||
mv.visitLdcInsn(interfaceMethodType.toMethodDescriptorString());
|
||||
mv.visitLdcInsn(implInfo.getReferenceKind());
|
||||
mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/'));
|
||||
mv.visitLdcInsn(implInfo.getName());
|
||||
mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString());
|
||||
mv.visitLdcInsn(dynamicMethodType.toMethodDescriptorString());
|
||||
mv.iconst(argDescs.length);
|
||||
mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT);
|
||||
for (int i = 0; i < argDescs.length; i++) {
|
||||
mv.visitInsn(DUP);
|
||||
mv.iconst(i);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]);
|
||||
mv.boxIfTypePrimitive(Type.getType(argDescs[i]));
|
||||
mv.visitInsn(AASTORE);
|
||||
}
|
||||
mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR,
|
||||
DESCR_CTOR_SERIALIZED_LAMBDA, false);
|
||||
mv.visitInsn(ARETURN);
|
||||
// Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
private void generateSerializationFriendlyMethods(ClassBuilder clb) {
|
||||
clb.withMethod(SerializationSupport.NAME_METHOD_WRITE_REPLACE, SerializationSupport.MTD_Object, ACC_PRIVATE | ACC_FINAL,
|
||||
new MethodBody(new Consumer<CodeBuilder>() {
|
||||
@Override
|
||||
public void accept(CodeBuilder cob) {
|
||||
cob.new_(SerializationSupport.CD_SerializedLambda)
|
||||
.dup()
|
||||
.ldc(classDesc(targetClass))
|
||||
.ldc(factoryType.returnType().getName().replace('.', '/'))
|
||||
.ldc(interfaceMethodName)
|
||||
.ldc(interfaceMethodType.toMethodDescriptorString())
|
||||
.ldc(implInfo.getReferenceKind())
|
||||
.ldc(implInfo.getDeclaringClass().getName().replace('.', '/'))
|
||||
.ldc(implInfo.getName())
|
||||
.ldc(implInfo.getMethodType().toMethodDescriptorString())
|
||||
.ldc(dynamicMethodType.toMethodDescriptorString())
|
||||
.loadConstant(argDescs.length)
|
||||
.anewarray(CD_Object);
|
||||
for (int i = 0; i < argDescs.length; i++) {
|
||||
cob.dup()
|
||||
.loadConstant(i)
|
||||
.aload(0)
|
||||
.getfield(lambdaClassDesc, argNames[i], argDescs[i]);
|
||||
TypeConvertingMethodAdapter.boxIfTypePrimitive(cob, TypeKind.from(argDescs[i]));
|
||||
cob.aastore();
|
||||
}
|
||||
cob.invokespecial(SerializationSupport.CD_SerializedLambda, INIT_NAME,
|
||||
SerializationSupport.MTD_CTOR_SERIALIZED_LAMBDA)
|
||||
.areturn();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a readObject/writeObject method that is hostile to serialization
|
||||
*/
|
||||
private void generateSerializationHostileMethods() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
|
||||
NAME_METHOD_WRITE_OBJECT, DESCR_METHOD_WRITE_OBJECT,
|
||||
null, SER_HOSTILE_EXCEPTIONS);
|
||||
mv.visitCode();
|
||||
mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitLdcInsn("Non-serializable lambda");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR,
|
||||
DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false);
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
|
||||
mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
|
||||
NAME_METHOD_READ_OBJECT, DESCR_METHOD_READ_OBJECT,
|
||||
null, SER_HOSTILE_EXCEPTIONS);
|
||||
mv.visitCode();
|
||||
mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitLdcInsn("Non-serializable lambda");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR,
|
||||
DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false);
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
private void generateSerializationHostileMethods(ClassBuilder clb) {
|
||||
var hostileMethod = new Consumer<MethodBuilder>() {
|
||||
@Override
|
||||
public void accept(MethodBuilder mb) {
|
||||
ConstantPoolBuilder cp = mb.constantPool();
|
||||
ClassEntry nseCE = cp.classEntry(SerializationSupport.CD_NotSerializableException);
|
||||
mb.with(ExceptionsAttribute.of(nseCE))
|
||||
.withCode(new Consumer<CodeBuilder>() {
|
||||
@Override
|
||||
public void accept(CodeBuilder cob) {
|
||||
cob.new_(nseCE)
|
||||
.dup()
|
||||
.ldc("Non-serializable lambda")
|
||||
.invokespecial(cp.methodRefEntry(nseCE, cp.nameAndTypeEntry(INIT_NAME,
|
||||
SerializationSupport.MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION)))
|
||||
.athrow();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
clb.withMethod(SerializationSupport.NAME_METHOD_WRITE_OBJECT, SerializationSupport.MTD_void_ObjectOutputStream,
|
||||
ACC_PRIVATE + ACC_FINAL, hostileMethod);
|
||||
clb.withMethod(SerializationSupport.NAME_METHOD_READ_OBJECT, SerializationSupport.MTD_void_ObjectInputStream,
|
||||
ACC_PRIVATE + ACC_FINAL, hostileMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class generates a method body which calls the lambda implementation
|
||||
* This method generates a method body which calls the lambda implementation
|
||||
* method, converting arguments, as needed.
|
||||
*/
|
||||
private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter {
|
||||
|
||||
ForwardingMethodGenerator(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
void generate(MethodType methodType) {
|
||||
visitCode();
|
||||
|
||||
if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
|
||||
visitTypeInsn(NEW, implMethodClassName);
|
||||
visitInsn(DUP);
|
||||
}
|
||||
if (useImplMethodHandle) {
|
||||
visitLdcInsn(implMethodCondy);
|
||||
}
|
||||
for (int i = 0; i < argNames.length; i++) {
|
||||
visitVarInsn(ALOAD, 0);
|
||||
visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]);
|
||||
}
|
||||
|
||||
convertArgumentTypes(methodType);
|
||||
|
||||
if (useImplMethodHandle) {
|
||||
MethodType mtype = implInfo.getMethodType();
|
||||
if (implKind != MethodHandleInfo.REF_invokeStatic) {
|
||||
mtype = mtype.insertParameterTypes(0, implClass);
|
||||
Consumer<MethodBuilder> forwardingMethod(MethodType methodType) {
|
||||
return new MethodBody(new Consumer<CodeBuilder>() {
|
||||
@Override
|
||||
public void accept(CodeBuilder cob) {
|
||||
if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
|
||||
cob.new_(implMethodClassDesc)
|
||||
.dup();
|
||||
}
|
||||
visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle",
|
||||
"invokeExact", mtype.descriptorString(), false);
|
||||
} else {
|
||||
// Invoke the method we want to forward to
|
||||
visitMethodInsn(invocationOpcode(), implMethodClassName,
|
||||
implMethodName, implMethodDesc,
|
||||
implClass.isInterface());
|
||||
if (useImplMethodHandle) {
|
||||
ConstantPoolBuilder cp = cob.constantPool();
|
||||
cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA), List.of()),
|
||||
cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle)));
|
||||
}
|
||||
for (int i = 0; i < argNames.length; i++) {
|
||||
cob.aload(0)
|
||||
.getfield(lambdaClassDesc, argNames[i], argDescs[i]);
|
||||
}
|
||||
|
||||
convertArgumentTypes(cob, methodType);
|
||||
|
||||
if (useImplMethodHandle) {
|
||||
MethodType mtype = implInfo.getMethodType();
|
||||
if (implKind != MethodHandleInfo.REF_invokeStatic) {
|
||||
mtype = mtype.insertParameterTypes(0, implClass);
|
||||
}
|
||||
cob.invokevirtual(CD_MethodHandle, "invokeExact", methodDesc(mtype));
|
||||
} else {
|
||||
// Invoke the method we want to forward to
|
||||
cob.invoke(invocationOpcode(), implMethodClassDesc, implMethodName, implMethodDesc, implClass.isInterface());
|
||||
}
|
||||
// Convert the return value (if any) and return it
|
||||
// Note: if adapting from non-void to void, the 'return'
|
||||
// instruction will pop the unneeded result
|
||||
Class<?> implReturnClass = implMethodType.returnType();
|
||||
Class<?> samReturnClass = methodType.returnType();
|
||||
TypeConvertingMethodAdapter.convertType(cob, implReturnClass, samReturnClass, samReturnClass);
|
||||
cob.return_(TypeKind.from(samReturnClass));
|
||||
}
|
||||
// Convert the return value (if any) and return it
|
||||
// Note: if adapting from non-void to void, the 'return'
|
||||
// instruction will pop the unneeded result
|
||||
Class<?> implReturnClass = implMethodType.returnType();
|
||||
Class<?> samReturnClass = methodType.returnType();
|
||||
convertType(implReturnClass, samReturnClass, samReturnClass);
|
||||
visitInsn(getReturnOpcode(samReturnClass));
|
||||
// Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored
|
||||
visitMaxs(-1, -1);
|
||||
visitEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void convertArgumentTypes(MethodType samType) {
|
||||
int lvIndex = 0;
|
||||
int samParametersLength = samType.parameterCount();
|
||||
int captureArity = factoryType.parameterCount();
|
||||
for (int i = 0; i < samParametersLength; i++) {
|
||||
Class<?> argType = samType.parameterType(i);
|
||||
visitVarInsn(getLoadOpcode(argType), lvIndex + 1);
|
||||
lvIndex += getParameterSize(argType);
|
||||
convertType(argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i));
|
||||
}
|
||||
}
|
||||
|
||||
private int invocationOpcode() throws InternalError {
|
||||
return switch (implKind) {
|
||||
case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC;
|
||||
case MethodHandleInfo.REF_newInvokeSpecial -> INVOKESPECIAL;
|
||||
case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL;
|
||||
case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE;
|
||||
case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL;
|
||||
default -> throw new InternalError("Unexpected invocation kind: " + implKind);
|
||||
};
|
||||
private void convertArgumentTypes(CodeBuilder cob, MethodType samType) {
|
||||
int samParametersLength = samType.parameterCount();
|
||||
int captureArity = factoryType.parameterCount();
|
||||
for (int i = 0; i < samParametersLength; i++) {
|
||||
Class<?> argType = samType.parameterType(i);
|
||||
cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i));
|
||||
TypeConvertingMethodAdapter.convertType(cob, argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i));
|
||||
}
|
||||
}
|
||||
|
||||
static int getParameterSize(Class<?> c) {
|
||||
if (c == Void.TYPE) {
|
||||
return 0;
|
||||
} else if (c == Long.TYPE || c == Double.TYPE) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
private Opcode invocationOpcode() throws InternalError {
|
||||
return switch (implKind) {
|
||||
case MethodHandleInfo.REF_invokeStatic -> Opcode.INVOKESTATIC;
|
||||
case MethodHandleInfo.REF_newInvokeSpecial -> Opcode.INVOKESPECIAL;
|
||||
case MethodHandleInfo.REF_invokeVirtual -> Opcode.INVOKEVIRTUAL;
|
||||
case MethodHandleInfo.REF_invokeInterface -> Opcode.INVOKEINTERFACE;
|
||||
case MethodHandleInfo.REF_invokeSpecial -> Opcode.INVOKESPECIAL;
|
||||
default -> throw new InternalError("Unexpected invocation kind: " + implKind);
|
||||
};
|
||||
}
|
||||
|
||||
static int getLoadOpcode(Class<?> c) {
|
||||
if(c == Void.TYPE) {
|
||||
throw new InternalError("Unexpected void type of load opcode");
|
||||
}
|
||||
return ILOAD + getOpcodeOffset(c);
|
||||
static ClassDesc implClassDesc(Class<?> cls) {
|
||||
return cls.isHidden() ? null : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
|
||||
}
|
||||
|
||||
static int getReturnOpcode(Class<?> c) {
|
||||
if(c == Void.TYPE) {
|
||||
return RETURN;
|
||||
}
|
||||
return IRETURN + getOpcodeOffset(c);
|
||||
static ClassDesc classDesc(Class<?> cls) {
|
||||
return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
|
||||
: ReferenceClassDescImpl.ofValidated(cls.descriptorString());
|
||||
}
|
||||
|
||||
private static int getOpcodeOffset(Class<?> c) {
|
||||
if (c.isPrimitive()) {
|
||||
if (c == Long.TYPE) {
|
||||
return 1;
|
||||
} else if (c == Float.TYPE) {
|
||||
return 2;
|
||||
} else if (c == Double.TYPE) {
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return 4;
|
||||
static MethodTypeDesc methodDesc(MethodType mt) {
|
||||
var params = new ClassDesc[mt.parameterCount()];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
params[i] = classDesc(mt.parameterType(i));
|
||||
}
|
||||
return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params);
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import java.lang.classfile.TypeKind;
|
||||
import jdk.internal.perf.PerfCounter;
|
||||
import jdk.internal.vm.annotation.DontInline;
|
||||
import jdk.internal.vm.annotation.Hidden;
|
||||
@ -137,12 +138,12 @@ class LambdaForm {
|
||||
public static final int VOID_RESULT = -1, LAST_RESULT = -2;
|
||||
|
||||
enum BasicType {
|
||||
L_TYPE('L', Object.class, Wrapper.OBJECT), // all reference types
|
||||
I_TYPE('I', int.class, Wrapper.INT),
|
||||
J_TYPE('J', long.class, Wrapper.LONG),
|
||||
F_TYPE('F', float.class, Wrapper.FLOAT),
|
||||
D_TYPE('D', double.class, Wrapper.DOUBLE), // all primitive types
|
||||
V_TYPE('V', void.class, Wrapper.VOID); // not valid in all contexts
|
||||
L_TYPE('L', Object.class, Wrapper.OBJECT, TypeKind.ReferenceType), // all reference types
|
||||
I_TYPE('I', int.class, Wrapper.INT, TypeKind.IntType),
|
||||
J_TYPE('J', long.class, Wrapper.LONG, TypeKind.LongType),
|
||||
F_TYPE('F', float.class, Wrapper.FLOAT, TypeKind.FloatType),
|
||||
D_TYPE('D', double.class, Wrapper.DOUBLE, TypeKind.DoubleType), // all primitive types
|
||||
V_TYPE('V', void.class, Wrapper.VOID, TypeKind.VoidType); // not valid in all contexts
|
||||
|
||||
static final @Stable BasicType[] ALL_TYPES = BasicType.values();
|
||||
static final @Stable BasicType[] ARG_TYPES = Arrays.copyOf(ALL_TYPES, ALL_TYPES.length-1);
|
||||
@ -153,11 +154,13 @@ class LambdaForm {
|
||||
final char btChar;
|
||||
final Class<?> btClass;
|
||||
final Wrapper btWrapper;
|
||||
final TypeKind btKind;
|
||||
|
||||
private BasicType(char btChar, Class<?> btClass, Wrapper wrapper) {
|
||||
private BasicType(char btChar, Class<?> btClass, Wrapper wrapper, TypeKind typeKind) {
|
||||
this.btChar = btChar;
|
||||
this.btClass = btClass;
|
||||
this.btWrapper = wrapper;
|
||||
this.btKind = typeKind;
|
||||
}
|
||||
|
||||
char basicTypeChar() {
|
||||
@ -169,6 +172,9 @@ class LambdaForm {
|
||||
Wrapper basicTypeWrapper() {
|
||||
return btWrapper;
|
||||
}
|
||||
TypeKind basicTypeKind() {
|
||||
return btKind;
|
||||
}
|
||||
int basicTypeSlots() {
|
||||
return btWrapper.stackSlots();
|
||||
}
|
||||
|
@ -27,8 +27,9 @@ package java.lang.invoke;
|
||||
|
||||
import jdk.internal.access.JavaLangInvokeAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.constant.MethodTypeDescImpl;
|
||||
import jdk.internal.constant.ReferenceClassDescImpl;
|
||||
import jdk.internal.foreign.abi.NativeEntryPoint;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
@ -39,6 +40,8 @@ import sun.invoke.util.ValueConversions;
|
||||
import sun.invoke.util.VerifyType;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -56,13 +59,14 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import static java.lang.invoke.LambdaForm.*;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.MN_CALLER_SENSITIVE;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.MN_HIDDEN_MEMBER;
|
||||
import static java.lang.invoke.MethodHandleStatics.*;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
|
||||
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
/**
|
||||
* Trusted implementation code for MethodHandle.
|
||||
@ -1035,8 +1039,10 @@ abstract class MethodHandleImpl {
|
||||
// Put the whole mess into its own nested class.
|
||||
// That way we can lazily load the code and set up the constants.
|
||||
private static class BindCaller {
|
||||
private static MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class);
|
||||
private static MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class);
|
||||
|
||||
private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;");
|
||||
private static final MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class);
|
||||
private static final MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class);
|
||||
|
||||
static MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) {
|
||||
// Code in the boot layer should now be careful while creating method handles or
|
||||
@ -1250,8 +1256,6 @@ abstract class MethodHandleImpl {
|
||||
|
||||
/** Produces byte code for a class that is used as an injected invoker. */
|
||||
private static byte[] generateInvokerTemplate() {
|
||||
ClassWriter cw = new ClassWriter(0);
|
||||
|
||||
// private static class InjectedInvoker {
|
||||
// /* this is used to wrap DMH(s) of caller-sensitive methods */
|
||||
// @Hidden
|
||||
@ -1265,39 +1269,25 @@ abstract class MethodHandleImpl {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
cw.visit(CLASSFILE_VERSION, ACC_PRIVATE | ACC_SUPER, "InjectedInvoker", null, "java/lang/Object", null);
|
||||
{
|
||||
var mv = cw.visitMethod(ACC_STATIC, "invoke_V",
|
||||
"(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;",
|
||||
null, null);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
|
||||
"([Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(2, 2);
|
||||
mv.visitEnd();
|
||||
|
||||
cw.visitEnd();
|
||||
}
|
||||
|
||||
{
|
||||
var mv = cw.visitMethod(ACC_STATIC, "reflect_invoke_V",
|
||||
"(Ljava/lang/invoke/MethodHandle;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
|
||||
null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitMaxs(3, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
return cw.toByteArray();
|
||||
return ClassFile.of().build(ReferenceClassDescImpl.ofValidated("LInjectedInvoker;"), clb -> clb
|
||||
.withFlags(ACC_PRIVATE | ACC_SUPER)
|
||||
.withMethodBody(
|
||||
"invoke_V",
|
||||
MethodTypeDescImpl.ofValidated(CD_Object, CD_MethodHandle, CD_Object_array),
|
||||
ACC_STATIC,
|
||||
cob -> cob.aload(0)
|
||||
.aload(1)
|
||||
.invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object_array))
|
||||
.areturn())
|
||||
.withMethodBody(
|
||||
"reflect_invoke_V",
|
||||
MethodTypeDescImpl.ofValidated(CD_Object, CD_MethodHandle, CD_Object, CD_Object_array),
|
||||
ACC_STATIC,
|
||||
cob -> cob.aload(0)
|
||||
.aload(1)
|
||||
.aload(2)
|
||||
.invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Object_array))
|
||||
.areturn()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,6 @@ package java.lang.invoke;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.org.objectweb.asm.ClassReader;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.CallerSensitiveAdapter;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
@ -42,8 +39,10 @@ import sun.invoke.util.Wrapper;
|
||||
import sun.reflect.misc.ReflectUtil;
|
||||
import sun.security.util.SecurityConstants;
|
||||
|
||||
import java.lang.classfile.ClassModel;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.invoke.LambdaForm.BasicType;
|
||||
import java.lang.invoke.MethodHandleImpl.Intrinsic;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
@ -62,8 +61,8 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static java.lang.invoke.LambdaForm.BasicType.V_TYPE;
|
||||
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
|
||||
import static java.lang.invoke.MethodHandleNatives.Constants.*;
|
||||
import static java.lang.invoke.MethodHandleStatics.UNSAFE;
|
||||
import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException;
|
||||
@ -2288,27 +2287,16 @@ public class MethodHandles {
|
||||
String name;
|
||||
int accessFlags;
|
||||
try {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
// ClassReader does not check if `this_class` is CONSTANT_Class_info
|
||||
// workaround to read `this_class` using readConst and validate the value
|
||||
int thisClass = reader.readUnsignedShort(reader.header + 2);
|
||||
Object constant = reader.readConst(thisClass, new char[reader.getMaxStringLength()]);
|
||||
if (!(constant instanceof Type type)) {
|
||||
throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info");
|
||||
}
|
||||
if (!type.getDescriptor().startsWith("L")) {
|
||||
throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info");
|
||||
}
|
||||
name = type.getInternalName();
|
||||
accessFlags = reader.readUnsignedShort(reader.header);
|
||||
} catch (RuntimeException e) {
|
||||
// ASM exceptions are poorly specified
|
||||
ClassModel cm = java.lang.classfile.ClassFile.of().parse(bytes);
|
||||
name = cm.thisClass().asInternalName();
|
||||
accessFlags = cm.flags().flagsMask();
|
||||
} catch (IllegalArgumentException e) {
|
||||
ClassFormatError cfe = new ClassFormatError();
|
||||
cfe.initCause(e);
|
||||
throw cfe;
|
||||
}
|
||||
// must be a class or interface
|
||||
if ((accessFlags & Opcodes.ACC_MODULE) != 0) {
|
||||
if ((accessFlags & ACC_MODULE) != 0) {
|
||||
throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
|
||||
}
|
||||
return new ClassFile(name, accessFlags, bytes);
|
||||
|
@ -1291,15 +1291,21 @@ class MethodType
|
||||
*/
|
||||
@Override
|
||||
public Optional<MethodTypeDesc> describeConstable() {
|
||||
try {
|
||||
return Optional.of(MethodTypeDesc.of(returnType().describeConstable().orElseThrow(),
|
||||
Stream.of(parameterArray())
|
||||
.map(p -> p.describeConstable().orElseThrow())
|
||||
.toArray(ClassDesc[]::new)));
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
var retDesc = returnType().describeConstable();
|
||||
if (retDesc.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
if (parameterCount() == 0)
|
||||
return Optional.of(MethodTypeDesc.of(retDesc.get()));
|
||||
|
||||
var params = new ClassDesc[parameterCount()];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
var paramDesc = parameterType(i).describeConstable();
|
||||
if (paramDesc.isEmpty())
|
||||
return Optional.empty();
|
||||
params[i] = paramDesc.get();
|
||||
}
|
||||
return Optional.of(MethodTypeDesc.of(retDesc.get(), params));
|
||||
}
|
||||
|
||||
//--- Serialization.
|
||||
|
@ -25,176 +25,103 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import sun.invoke.util.BytecodeDescriptor;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.TypeKind;
|
||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||
import java.lang.classfile.constantpool.MethodRefEntry;
|
||||
import jdk.internal.constant.MethodTypeDescImpl;
|
||||
import jdk.internal.constant.ReferenceClassDescImpl;
|
||||
import sun.invoke.util.Wrapper;
|
||||
import static sun.invoke.util.Wrapper.*;
|
||||
|
||||
class TypeConvertingMethodAdapter extends MethodVisitor {
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
|
||||
TypeConvertingMethodAdapter(MethodVisitor mv) {
|
||||
super(Opcodes.ASM7, mv);
|
||||
}
|
||||
class TypeConvertingMethodAdapter {
|
||||
|
||||
private static final int NUM_WRAPPERS = Wrapper.COUNT;
|
||||
private static class BoxHolder {
|
||||
private static final ConstantPoolBuilder CP = ConstantPoolBuilder.of();
|
||||
|
||||
private static final String NAME_OBJECT = "java/lang/Object";
|
||||
private static final String WRAPPER_PREFIX = "Ljava/lang/";
|
||||
|
||||
// Same for all primitives; name of the boxing method
|
||||
private static final String NAME_BOX_METHOD = "valueOf";
|
||||
|
||||
// Table of opcodes for widening primitive conversions; NOP = no conversion
|
||||
private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS];
|
||||
|
||||
private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16];
|
||||
|
||||
// Table of wrappers for primitives, indexed by ASM type sorts
|
||||
private static final Wrapper[] FROM_TYPE_SORT = new Wrapper[12];
|
||||
|
||||
static {
|
||||
for (Wrapper w : Wrapper.values()) {
|
||||
if (w.basicTypeChar() != 'L') {
|
||||
int wi = hashWrapperName(w.wrapperSimpleName());
|
||||
assert (FROM_WRAPPER_NAME[wi] == null);
|
||||
FROM_WRAPPER_NAME[wi] = w;
|
||||
}
|
||||
private static MethodRefEntry box(ClassDesc primitive, ClassDesc target) {
|
||||
return CP.methodRefEntry(target, "valueOf", MethodTypeDescImpl.ofValidated(target, primitive));
|
||||
}
|
||||
|
||||
// wideningOpcodes[][] will be NOP-initialized by default
|
||||
assert(Opcodes.NOP == 0);
|
||||
private static final MethodRefEntry BOX_BOOLEAN = box(CD_boolean, CD_Boolean),
|
||||
BOX_BYTE = box(CD_byte, CD_Byte),
|
||||
BOX_SHORT = box(CD_short, CD_Short),
|
||||
BOX_CHAR = box(CD_char, CD_Character),
|
||||
BOX_INT = box(CD_int, CD_Integer),
|
||||
BOX_LONG = box(CD_long, CD_Long),
|
||||
BOX_FLOAT = box(CD_float, CD_Float),
|
||||
BOX_DOUBLE = box(CD_double, CD_Double);
|
||||
|
||||
initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(LONG, Opcodes.F2L, FLOAT);
|
||||
initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(FLOAT, Opcodes.L2F, LONG);
|
||||
initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR);
|
||||
initWidening(DOUBLE, Opcodes.F2D, FLOAT);
|
||||
initWidening(DOUBLE, Opcodes.L2D, LONG);
|
||||
|
||||
FROM_TYPE_SORT[Type.BYTE] = Wrapper.BYTE;
|
||||
FROM_TYPE_SORT[Type.SHORT] = Wrapper.SHORT;
|
||||
FROM_TYPE_SORT[Type.INT] = Wrapper.INT;
|
||||
FROM_TYPE_SORT[Type.LONG] = Wrapper.LONG;
|
||||
FROM_TYPE_SORT[Type.CHAR] = Wrapper.CHAR;
|
||||
FROM_TYPE_SORT[Type.FLOAT] = Wrapper.FLOAT;
|
||||
FROM_TYPE_SORT[Type.DOUBLE] = Wrapper.DOUBLE;
|
||||
FROM_TYPE_SORT[Type.BOOLEAN] = Wrapper.BOOLEAN;
|
||||
}
|
||||
|
||||
private static void initWidening(Wrapper to, int opcode, Wrapper... from) {
|
||||
for (Wrapper f : from) {
|
||||
wideningOpcodes[f.ordinal()][to.ordinal()] = opcode;
|
||||
private static MethodRefEntry unbox(ClassDesc owner, String methodName, ClassDesc primitiveTarget) {
|
||||
return CP.methodRefEntry(owner, methodName, MethodTypeDescImpl.ofValidated(primitiveTarget));
|
||||
}
|
||||
|
||||
private static final MethodRefEntry UNBOX_BOOLEAN = unbox(CD_Boolean, "booleanValue", CD_boolean),
|
||||
UNBOX_BYTE = unbox(CD_Number, "byteValue", CD_byte),
|
||||
UNBOX_SHORT = unbox(CD_Number, "shortValue", CD_short),
|
||||
UNBOX_CHAR = unbox(CD_Character, "charValue", CD_char),
|
||||
UNBOX_INT = unbox(CD_Number, "intValue", CD_int),
|
||||
UNBOX_LONG = unbox(CD_Number, "longValue", CD_long),
|
||||
UNBOX_FLOAT = unbox(CD_Number, "floatValue", CD_float),
|
||||
UNBOX_DOUBLE = unbox(CD_Number, "doubleValue", CD_double);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class name to Wrapper hash, derived from Wrapper.hashWrap()
|
||||
* @param xn
|
||||
* @return The hash code 0-15
|
||||
*/
|
||||
private static int hashWrapperName(String xn) {
|
||||
if (xn.length() < 3) {
|
||||
return 0;
|
||||
}
|
||||
return (3 * xn.charAt(1) + xn.charAt(2)) % 16;
|
||||
private static TypeKind primitiveTypeKindFromClass(Class<?> type) {
|
||||
if (type == int.class) return TypeKind.IntType;
|
||||
if (type == long.class) return TypeKind.LongType;
|
||||
if (type == boolean.class) return TypeKind.BooleanType;
|
||||
if (type == short.class) return TypeKind.ShortType;
|
||||
if (type == byte.class) return TypeKind.ByteType;
|
||||
if (type == char.class) return TypeKind.CharType;
|
||||
if (type == float.class) return TypeKind.FloatType;
|
||||
if (type == double.class) return TypeKind.DoubleType;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Wrapper wrapperOrNullFromDescriptor(String desc) {
|
||||
if (!desc.startsWith(WRAPPER_PREFIX)) {
|
||||
// Not a class type (array or method), so not a boxed type
|
||||
// or not in the right package
|
||||
return null;
|
||||
}
|
||||
// Pare it down to the simple class name
|
||||
String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1);
|
||||
// Hash to a Wrapper
|
||||
Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)];
|
||||
if (w == null || w.wrapperSimpleName().equals(cname)) {
|
||||
return w;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) {
|
||||
box(cob, tk);
|
||||
}
|
||||
|
||||
private static String wrapperName(Wrapper w) {
|
||||
return "java/lang/" + w.wrapperSimpleName();
|
||||
}
|
||||
|
||||
private static String unboxMethod(Wrapper w) {
|
||||
return w.primitiveSimpleName() + "Value";
|
||||
}
|
||||
|
||||
private static String boxingDescriptor(Wrapper w) {
|
||||
return "(" + w.basicTypeChar() + ")L" + wrapperName(w) + ";";
|
||||
}
|
||||
|
||||
private static String unboxingDescriptor(Wrapper w) {
|
||||
return "()" + w.basicTypeChar();
|
||||
}
|
||||
|
||||
void boxIfTypePrimitive(Type t) {
|
||||
Wrapper w = FROM_TYPE_SORT[t.getSort()];
|
||||
if (w != null) {
|
||||
box(w);
|
||||
}
|
||||
}
|
||||
|
||||
void widen(Wrapper ws, Wrapper wt) {
|
||||
static void widen(CodeBuilder cob, TypeKind ws, TypeKind wt) {
|
||||
ws = ws.asLoadable();
|
||||
wt = wt.asLoadable();
|
||||
if (ws != wt) {
|
||||
int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()];
|
||||
if (opcode != Opcodes.NOP) {
|
||||
visitInsn(opcode);
|
||||
}
|
||||
cob.conversion(ws, wt);
|
||||
}
|
||||
}
|
||||
|
||||
void box(Wrapper w) {
|
||||
visitMethodInsn(Opcodes.INVOKESTATIC,
|
||||
wrapperName(w),
|
||||
NAME_BOX_METHOD,
|
||||
boxingDescriptor(w), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert types by unboxing. The source type is known to be a primitive wrapper.
|
||||
* @param sname A primitive wrapper corresponding to wrapped reference source type
|
||||
* @param wt A primitive wrapper being converted to
|
||||
*/
|
||||
void unbox(String sname, Wrapper wt) {
|
||||
visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||
sname,
|
||||
unboxMethod(wt),
|
||||
unboxingDescriptor(wt), false);
|
||||
}
|
||||
|
||||
private String descriptorToName(String desc) {
|
||||
int last = desc.length() - 1;
|
||||
if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') {
|
||||
// In descriptor form
|
||||
return desc.substring(1, last);
|
||||
} else {
|
||||
// Already in internal name form
|
||||
return desc;
|
||||
static void box(CodeBuilder cob, TypeKind tk) {
|
||||
switch (tk) {
|
||||
case BooleanType -> cob.invokestatic(BoxHolder.BOX_BOOLEAN);
|
||||
case ByteType -> cob.invokestatic(BoxHolder.BOX_BYTE);
|
||||
case CharType -> cob.invokestatic(BoxHolder.BOX_CHAR);
|
||||
case DoubleType -> cob.invokestatic(BoxHolder.BOX_DOUBLE);
|
||||
case FloatType -> cob.invokestatic(BoxHolder.BOX_FLOAT);
|
||||
case IntType -> cob.invokestatic(BoxHolder.BOX_INT);
|
||||
case LongType -> cob.invokestatic(BoxHolder.BOX_LONG);
|
||||
case ShortType -> cob.invokestatic(BoxHolder.BOX_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
void cast(String ds, String dt) {
|
||||
String ns = descriptorToName(ds);
|
||||
String nt = descriptorToName(dt);
|
||||
if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) {
|
||||
visitTypeInsn(Opcodes.CHECKCAST, nt);
|
||||
static void unbox(CodeBuilder cob, TypeKind to) {
|
||||
switch (to) {
|
||||
case BooleanType -> cob.invokevirtual(BoxHolder.UNBOX_BOOLEAN);
|
||||
case ByteType -> cob.invokevirtual(BoxHolder.UNBOX_BYTE);
|
||||
case CharType -> cob.invokevirtual(BoxHolder.UNBOX_CHAR);
|
||||
case DoubleType -> cob.invokevirtual(BoxHolder.UNBOX_DOUBLE);
|
||||
case FloatType -> cob.invokevirtual(BoxHolder.UNBOX_FLOAT);
|
||||
case IntType -> cob.invokevirtual(BoxHolder.UNBOX_INT);
|
||||
case LongType -> cob.invokevirtual(BoxHolder.UNBOX_LONG);
|
||||
case ShortType -> cob.invokevirtual(BoxHolder.UNBOX_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
private Wrapper toWrapper(String desc) {
|
||||
char first = desc.charAt(0);
|
||||
if (first == '[' || first == '(') {
|
||||
first = 'L';
|
||||
static void cast(CodeBuilder cob, ClassDesc dt) {
|
||||
if (!dt.equals(CD_Object)) {
|
||||
cob.checkcast(dt);
|
||||
}
|
||||
return Wrapper.forBasicType(first);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,7 +131,7 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
|
||||
* @param target
|
||||
* @param functional
|
||||
*/
|
||||
void convertType(Class<?> arg, Class<?> target, Class<?> functional) {
|
||||
static void convertType(CodeBuilder cob, Class<?> arg, Class<?> target, Class<?> functional) {
|
||||
if (arg.equals(target) && arg.equals(functional)) {
|
||||
return;
|
||||
}
|
||||
@ -212,84 +139,69 @@ class TypeConvertingMethodAdapter extends MethodVisitor {
|
||||
return;
|
||||
}
|
||||
if (arg.isPrimitive()) {
|
||||
Wrapper wArg = Wrapper.forPrimitiveType(arg);
|
||||
if (target.isPrimitive()) {
|
||||
// Both primitives: widening
|
||||
widen(wArg, Wrapper.forPrimitiveType(target));
|
||||
widen(cob, TypeKind.from(arg), TypeKind.from(target));
|
||||
} else {
|
||||
// Primitive argument to reference target
|
||||
String dTarget = BytecodeDescriptor.unparse(target);
|
||||
Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget);
|
||||
if (wPrimTarget != null) {
|
||||
TypeKind wPrimTk = primitiveTypeKindFromClass(target);
|
||||
if (wPrimTk != null) {
|
||||
// The target is a boxed primitive type, widen to get there before boxing
|
||||
widen(wArg, wPrimTarget);
|
||||
box(wPrimTarget);
|
||||
widen(cob, TypeKind.from(arg), wPrimTk);
|
||||
box(cob, wPrimTk);
|
||||
} else {
|
||||
// Otherwise, box and cast
|
||||
box(wArg);
|
||||
cast(wrapperName(wArg), dTarget);
|
||||
box(cob, TypeKind.from(arg));
|
||||
cast(cob, classDesc(target));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String dArg = BytecodeDescriptor.unparse(arg);
|
||||
String dSrc;
|
||||
if (functional.isPrimitive()) {
|
||||
dSrc = dArg;
|
||||
Class<?> src;
|
||||
if (arg == functional || functional.isPrimitive()) {
|
||||
src = arg;
|
||||
} else {
|
||||
// Cast to convert to possibly more specific type, and generate CCE for invalid arg
|
||||
dSrc = BytecodeDescriptor.unparse(functional);
|
||||
cast(dArg, dSrc);
|
||||
src = functional;
|
||||
cast(cob, classDesc(functional));
|
||||
}
|
||||
String dTarget = BytecodeDescriptor.unparse(target);
|
||||
if (target.isPrimitive()) {
|
||||
Wrapper wTarget = toWrapper(dTarget);
|
||||
// Reference argument to primitive target
|
||||
Wrapper wps = wrapperOrNullFromDescriptor(dSrc);
|
||||
TypeKind wps = primitiveTypeKindFromClass(src);
|
||||
if (wps != null) {
|
||||
if (wps.isSigned() || wps.isFloating()) {
|
||||
if (src != Character.class && src != Boolean.class) {
|
||||
// Boxed number to primitive
|
||||
unbox(wrapperName(wps), wTarget);
|
||||
unbox(cob, TypeKind.from(target));
|
||||
} else {
|
||||
// Character or Boolean
|
||||
unbox(wrapperName(wps), wps);
|
||||
widen(wps, wTarget);
|
||||
unbox(cob, wps);
|
||||
widen(cob, wps, TypeKind.from(target));
|
||||
}
|
||||
} else {
|
||||
// Source type is reference type, but not boxed type,
|
||||
// assume it is super type of target type
|
||||
String intermediate;
|
||||
if (wTarget.isSigned() || wTarget.isFloating()) {
|
||||
// Boxed number to primitive
|
||||
intermediate = "java/lang/Number";
|
||||
if (target == char.class) {
|
||||
cast(cob, CD_Character);
|
||||
} else if (target == boolean.class) {
|
||||
cast(cob, CD_Boolean);
|
||||
} else {
|
||||
// Character or Boolean
|
||||
intermediate = wrapperName(wTarget);
|
||||
// Boxed number to primitive
|
||||
cast(cob, CD_Number);
|
||||
}
|
||||
cast(dSrc, intermediate);
|
||||
unbox(intermediate, wTarget);
|
||||
unbox(cob, TypeKind.from(target));
|
||||
}
|
||||
} else {
|
||||
// Both reference types: just case to target type
|
||||
cast(dSrc, dTarget);
|
||||
if (src != target) {
|
||||
cast(cob, classDesc(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following method is copied from
|
||||
* org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small
|
||||
* and fast Java bytecode manipulation framework.
|
||||
* Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved.
|
||||
*/
|
||||
void iconst(final int cst) {
|
||||
if (cst >= -1 && cst <= 5) {
|
||||
mv.visitInsn(Opcodes.ICONST_0 + cst);
|
||||
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, cst);
|
||||
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.SIPUSH, cst);
|
||||
} else {
|
||||
mv.visitLdcInsn(cst);
|
||||
}
|
||||
static ClassDesc classDesc(Class<?> cls) {
|
||||
return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
|
||||
: cls == Object.class ? CD_Object
|
||||
: cls == String.class ? CD_String
|
||||
: ReferenceClassDescImpl.ofValidated(cls.descriptorString());
|
||||
}
|
||||
}
|
||||
|
@ -25,15 +25,19 @@
|
||||
*/
|
||||
package jdk.internal.classfile.impl;
|
||||
|
||||
import java.lang.classfile.constantpool.InvokeDynamicEntry;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.classfile.Attribute;
|
||||
import java.lang.classfile.Attributes;
|
||||
import java.lang.classfile.BufWriter;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import java.lang.classfile.Label;
|
||||
import java.lang.classfile.attribute.StackMapTableAttribute;
|
||||
import java.lang.classfile.constantpool.ClassEntry;
|
||||
import java.lang.classfile.constantpool.ConstantDynamicEntry;
|
||||
import java.lang.classfile.constantpool.MemberRefEntry;
|
||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||
import java.lang.classfile.constantpool.InvokeDynamicEntry;
|
||||
import java.lang.classfile.constantpool.MemberRefEntry;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -41,15 +45,10 @@ import java.util.BitSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.lang.classfile.Attribute;
|
||||
import jdk.internal.constant.ReferenceClassDescImpl;
|
||||
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static jdk.internal.constant.ConstantUtils.binaryNameToDesc;
|
||||
|
||||
import java.lang.classfile.BufWriter;
|
||||
import java.lang.classfile.Label;
|
||||
import java.lang.classfile.attribute.StackMapTableAttribute;
|
||||
import java.lang.classfile.Attributes;
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
|
||||
/**
|
||||
* StackMapGenerator is responsible for stack map frames generation.
|
||||
@ -1249,14 +1248,14 @@ public final class StackMapGenerator {
|
||||
//frequently used types to reduce footprint
|
||||
static final Type OBJECT_TYPE = referenceType(CD_Object),
|
||||
THROWABLE_TYPE = referenceType(CD_Throwable),
|
||||
INT_ARRAY_TYPE = referenceType(CD_int.arrayType()),
|
||||
BOOLEAN_ARRAY_TYPE = referenceType(CD_boolean.arrayType()),
|
||||
BYTE_ARRAY_TYPE = referenceType(CD_byte.arrayType()),
|
||||
CHAR_ARRAY_TYPE = referenceType(CD_char.arrayType()),
|
||||
SHORT_ARRAY_TYPE = referenceType(CD_short.arrayType()),
|
||||
LONG_ARRAY_TYPE = referenceType(CD_long.arrayType()),
|
||||
DOUBLE_ARRAY_TYPE = referenceType(CD_double.arrayType()),
|
||||
FLOAT_ARRAY_TYPE = referenceType(CD_float.arrayType()),
|
||||
INT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[I")),
|
||||
BOOLEAN_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[Z")),
|
||||
BYTE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[B")),
|
||||
CHAR_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[C")),
|
||||
SHORT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[S")),
|
||||
LONG_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[J")),
|
||||
DOUBLE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[D")),
|
||||
FLOAT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[F")),
|
||||
STRING_TYPE = referenceType(CD_String),
|
||||
CLASS_TYPE = referenceType(CD_Class),
|
||||
METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle),
|
||||
@ -1321,8 +1320,8 @@ public final class StackMapGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private static final ClassDesc CD_Cloneable = binaryNameToDesc("java.lang.Cloneable");
|
||||
private static final ClassDesc CD_Serializable = binaryNameToDesc("java.io.Serializable");
|
||||
private static final ClassDesc CD_Cloneable = ReferenceClassDescImpl.ofValidated("Ljava/lang/Cloneable;");
|
||||
private static final ClassDesc CD_Serializable = ReferenceClassDescImpl.ofValidated("Ljava/io/Serializable;");
|
||||
|
||||
private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) {
|
||||
if (from == NULL_TYPE) {
|
||||
|
@ -101,6 +101,7 @@ runtime/StackGuardPages/TestStackGuardPagesNative.java 8303612 linux-all
|
||||
runtime/ErrorHandling/TestDwarf.java#checkDecoder 8305489 linux-all
|
||||
runtime/ErrorHandling/MachCodeFramesInErrorFile.java 8313315 linux-ppc64le
|
||||
runtime/cds/appcds/customLoader/HelloCustom_JFR.java 8241075 linux-all,windows-x64
|
||||
runtime/ClassInitErrors/TestStackOverflowDuringInit.java 8334545 generic-all
|
||||
|
||||
applications/jcstress/copy.java 8229852 linux-all
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user