/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.lang.invoke;
import jdk.internal.org.objectweb.asm.*;
import sun.invoke.util.
BytecodeDescriptor;
import sun.misc.
Unsafe;
import sun.security.action.
GetPropertyAction;
import java.io.
FilePermission;
import java.io.
Serializable;
import java.lang.reflect.
Constructor;
import java.security.
AccessController;
import java.security.
PrivilegedAction;
import java.util.
LinkedHashSet;
import java.util.concurrent.atomic.
AtomicInteger;
import java.util.
PropertyPermission;
import java.util.
Set;
import static jdk.internal.org.objectweb.asm.
Opcodes.*;
/**
* Lambda metafactory implementation which dynamically creates an
* inner-class-like class per lambda callsite.
*
* @see LambdaMetafactory
*/
/* package */ final class
InnerClassLambdaMetafactory extends
AbstractValidatingLambdaMetafactory {
private static final
Unsafe UNSAFE =
Unsafe.
getUnsafe();
private static final int
CLASSFILE_VERSION = 52;
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 NAME_FACTORY = "get$Lambda";
//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_CTOR_SERIALIZED_LAMBDA
=
MethodType.
methodType(void.class,
Class.class,
String.class,
String.class,
String.class,
int.class,
String.class,
String.class,
String.class,
String.class,
Object[].class).
toMethodDescriptorString();
private static final
String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION
=
MethodType.
methodType(void.class,
String.class).
toMethodDescriptorString();
private static final
String[]
SER_HOSTILE_EXCEPTIONS = new
String[] {
NAME_NOT_SERIALIZABLE_EXCEPTION};
private static final
String[]
EMPTY_STRING_ARRAY = new
String[0];
// Used to ensure that each spun class name is unique
private static final
AtomicInteger counter = new
AtomicInteger(0);
// For dumping generated classes to disk, for debugging purposes
private static final
ProxyClassesDumper dumper;
static {
final
String key = "jdk.internal.lambda.dumpProxyClasses";
String path =
AccessController.
doPrivileged(
new
GetPropertyAction(
key), null,
new
PropertyPermission(
key , "read"));
dumper = (null ==
path) ? null :
ProxyClassesDumper.
getInstance(
path);
}
// See context values in AbstractValidatingLambdaMetafactory
private final
String implMethodClassName; // 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
Class<?>
implMethodReturnClass; // class for implementaion method return type "Ljava/lang/String;"
private final
MethodType constructorType; // Generated class constructor type "(CC)void"
private final
ClassWriter cw; // ASM class writer
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$1"
/**
* General meta-factory constructor, supporting both standard cases and
* allowing for uncommon options such as serialization or bridging.
*
* @param caller Stacked automatically by VM; represents a lookup context
* with the accessibility privileges of the caller.
* @param invokedType Stacked automatically by VM; the signature of the
* invoked method, which includes the expected static
* type of the returned lambda object, and the static
* types of the captured arguments for the lambda. In
* the event that the implementation method is an
* instance method, the first argument in the invocation
* signature will correspond to the receiver.
* @param samMethodName Name of the method in the functional interface to
* which the lambda or method reference is being
* converted, represented as a String.
* @param samMethodType Type of the method in the functional interface to
* which the lambda or method reference is being
* converted, represented as a MethodType.
* @param implMethod The implementation method which should be called (with
* suitable adaptation of argument types, return types,
* and adjustment for captured arguments) when methods of
* the resulting functional interface instance are invoked.
* @param instantiatedMethodType The signature of the primary functional
* interface method after type variables are
* substituted with their instantiation from
* the capture site
* @param isSerializable Should the lambda be made serializable? If set,
* either the target type or one of the additional SAM
* types must extend {@code Serializable}.
* @param markerInterfaces Additional interfaces which the lambda object
* should implement.
* @param additionalBridges Method types for additional signatures to be
* bridged to the implementation method
* @throws LambdaConversionException If any of the meta-factory protocol
* invariants are violated
*/
public
InnerClassLambdaMetafactory(
MethodHandles.
Lookup caller,
MethodType invokedType,
String samMethodName,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType,
boolean
isSerializable,
Class<?>[]
markerInterfaces,
MethodType[]
additionalBridges)
throws
LambdaConversionException {
super(
caller,
invokedType,
samMethodName,
samMethodType,
implMethod,
instantiatedMethodType,
isSerializable,
markerInterfaces,
additionalBridges);
implMethodClassName =
implDefiningClass.
getName().
replace('.', '/');
implMethodName =
implInfo.
getName();
implMethodDesc =
implMethodType.
toMethodDescriptorString();
implMethodReturnClass = (
implKind ==
MethodHandleInfo.
REF_newInvokeSpecial)
?
implDefiningClass
:
implMethodType.
returnType();
constructorType =
invokedType.
changeReturnType(
Void.
TYPE);
lambdaClassName =
targetClass.
getName().
replace('.', '/') + "$$Lambda$" +
counter.
incrementAndGet();
cw = new
ClassWriter(
ClassWriter.
COMPUTE_MAXS);
int
parameterCount =
invokedType.
parameterCount();
if (
parameterCount > 0) {
argNames = new
String[
parameterCount];
argDescs = new
String[
parameterCount];
for (int
i = 0;
i <
parameterCount;
i++) {
argNames[
i] = "arg$" + (
i + 1);
argDescs[
i] =
BytecodeDescriptor.
unparse(
invokedType.
parameterType(
i));
}
} else {
argNames =
argDescs =
EMPTY_STRING_ARRAY;
}
}
/**
* Build the CallSite. Generate a class file which implements the functional
* interface, define the class, if there are no parameters create an instance
* of the class which the CallSite will return, otherwise, generate handles
* which will call the class' constructor.
*
* @return a CallSite, which, when invoked, will return an instance of the
* functional interface
* @throws ReflectiveOperationException
* @throws LambdaConversionException If properly formed functional interface
* is not found
*/
@
Override
CallSite buildCallSite() throws
LambdaConversionException {
final
Class<?>
innerClass =
spinInnerClass();
if (
invokedType.
parameterCount() == 0) {
final
Constructor<?>[]
ctrs =
AccessController.
doPrivileged(
new
PrivilegedAction<
Constructor<?>[]>() {
@
Override
public
Constructor<?>[]
run() {
Constructor<?>[]
ctrs =
innerClass.
getDeclaredConstructors();
if (
ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].
setAccessible(true);
}
return
ctrs;
}
});
if (
ctrs.length != 1) {
throw new
LambdaConversionException("Expected one lambda constructor for "
+
innerClass.
getCanonicalName() + ", got " +
ctrs.length);
}
try {
Object inst =
ctrs[0].
newInstance();
return new
ConstantCallSite(
MethodHandles.
constant(
samBase,
inst));
}
catch (
ReflectiveOperationException e) {
throw new
LambdaConversionException("Exception instantiating lambda object",
e);
}
} else {
try {
UNSAFE.
ensureClassInitialized(
innerClass);
return new
ConstantCallSite(
MethodHandles.
Lookup.
IMPL_LOOKUP
.
findStatic(
innerClass,
NAME_FACTORY,
invokedType));
}
catch (
ReflectiveOperationException e) {
throw new
LambdaConversionException("Exception finding constructor",
e);
}
}
}
/**
* Generate a class file which implements the functional
* interface, define and return the class.
*
* @implNote The class that is generated does not include signature
* information for exceptions that may be present on the SAM method.
* This is to reduce classfile size, and is harmless as checked exceptions
* are erased anyway, no one will ever compile against this classfile,
* and we make no guarantees about the reflective properties of lambda
* objects.
*
* @return a Class which implements the functional interface
* @throws LambdaConversionException If properly formed functional interface
* is not found
*/
private
Class<?>
spinInnerClass() throws
LambdaConversionException {
String[]
interfaces;
String samIntf =
samBase.
getName().
replace('.', '/');
boolean
accidentallySerializable = !
isSerializable &&
Serializable.class.
isAssignableFrom(
samBase);
if (
markerInterfaces.length == 0) {
interfaces = new
String[]{
samIntf};
} else {
// Assure no duplicate interfaces (ClassFormatError)
Set<
String>
itfs = new
LinkedHashSet<>(
markerInterfaces.length + 1);
itfs.
add(
samIntf);
for (
Class<?>
markerInterface :
markerInterfaces) {
itfs.
add(
markerInterface.
getName().
replace('.', '/'));
accidentallySerializable |= !
isSerializable &&
Serializable.class.
isAssignableFrom(
markerInterface);
}
interfaces =
itfs.
toArray(new
String[
itfs.
size()]);
}
cw.
visit(
CLASSFILE_VERSION,
ACC_SUPER +
ACC_FINAL +
ACC_SYNTHETIC,
lambdaClassName, null,
JAVA_LANG_OBJECT,
interfaces);
// 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();
}
generateConstructor();
if (
invokedType.
parameterCount() != 0) {
generateFactory();
}
// Forward the SAM method
MethodVisitor mv =
cw.
visitMethod(
ACC_PUBLIC,
samMethodName,
samMethodType.
toMethodDescriptorString(), null, null);
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
new
ForwardingMethodGenerator(
mv).
generate(
samMethodType);
// Forward the bridges
if (
additionalBridges != null) {
for (
MethodType mt :
additionalBridges) {
mv =
cw.
visitMethod(
ACC_PUBLIC|
ACC_BRIDGE,
samMethodName,
mt.
toMethodDescriptorString(), null, null);
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
new
ForwardingMethodGenerator(
mv).
generate(
mt);
}
}
if (
isSerializable)
generateSerializationFriendlyMethods();
else if (
accidentallySerializable)
generateSerializationHostileMethods();
cw.
visitEnd();
// Define the generated class in this VM.
final byte[]
classBytes =
cw.
toByteArray();
// If requested, dump out to a file for debugging purposes
if (
dumper != null) {
AccessController.
doPrivileged(new
PrivilegedAction<
Void>() {
@
Override
public
Void run() {
dumper.
dumpClass(
lambdaClassName,
classBytes);
return null;
}
}, null,
new
FilePermission("<<ALL FILES>>", "read, write"),
// createDirectories may need it
new
PropertyPermission("user.dir", "read"));
}
return
UNSAFE.
defineAnonymousClass(
targetClass,
classBytes, null);
}
/**
* Generate the factory method for the class
*/
private void
generateFactory() {
MethodVisitor m =
cw.
visitMethod(
ACC_PRIVATE |
ACC_STATIC,
NAME_FACTORY,
invokedType.
toMethodDescriptorString(), null, null);
m.
visitCode();
m.
visitTypeInsn(
NEW,
lambdaClassName);
m.
visitInsn(
Opcodes.
DUP);
int
parameterCount =
invokedType.
parameterCount();
for (int
typeIndex = 0,
varIndex = 0;
typeIndex <
parameterCount;
typeIndex++) {
Class<?>
argType =
invokedType.
parameterType(
typeIndex);
m.
visitVarInsn(
getLoadOpcode(
argType),
varIndex);
varIndex +=
getParameterSize(
argType);
}
m.
visitMethodInsn(
INVOKESPECIAL,
lambdaClassName,
NAME_CTOR,
constructorType.
toMethodDescriptorString(), false);
m.
visitInsn(
ARETURN);
m.
visitMaxs(-1, -1);
m.
visitEnd();
}
/**
* Generate the constructor for the class
*/
private void
generateConstructor() {
// 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 =
invokedType.
parameterCount();
for (int
i = 0,
lvIndex = 0;
i <
parameterCount;
i++) {
ctor.
visitVarInsn(
ALOAD, 0);
Class<?>
argType =
invokedType.
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();
}
/**
* 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(
invokedType.
returnType().
getName().
replace('.', '/'));
mv.
visitLdcInsn(
samMethodName);
mv.
visitLdcInsn(
samMethodType.
toMethodDescriptorString());
mv.
visitLdcInsn(
implInfo.
getReferenceKind());
mv.
visitLdcInsn(
implInfo.
getDeclaringClass().
getName().
replace('.', '/'));
mv.
visitLdcInsn(
implInfo.
getName());
mv.
visitLdcInsn(
implInfo.
getMethodType().
toMethodDescriptorString());
mv.
visitLdcInsn(
instantiatedMethodType.
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();
}
/**
* 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();
}
/**
* This class 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);
}
for (int
i = 0;
i <
argNames.length;
i++) {
visitVarInsn(
ALOAD, 0);
visitFieldInsn(
GETFIELD,
lambdaClassName,
argNames[
i],
argDescs[
i]);
}
convertArgumentTypes(
methodType);
// Invoke the method we want to forward to
visitMethodInsn(
invocationOpcode(),
implMethodClassName,
implMethodName,
implMethodDesc,
implDefiningClass.
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<?>
samReturnClass =
methodType.
returnType();
convertType(
implMethodReturnClass,
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;
boolean
samIncludesReceiver =
implIsInstanceMethod &&
invokedType.
parameterCount() == 0;
int
samReceiverLength =
samIncludesReceiver ? 1 : 0;
if (
samIncludesReceiver) {
// push receiver
Class<?>
rcvrType =
samType.
parameterType(0);
visitVarInsn(
getLoadOpcode(
rcvrType),
lvIndex + 1);
lvIndex +=
getParameterSize(
rcvrType);
convertType(
rcvrType,
implDefiningClass,
instantiatedMethodType.
parameterType(0));
}
int
samParametersLength =
samType.
parameterCount();
int
argOffset =
implMethodType.
parameterCount() -
samParametersLength;
for (int
i =
samReceiverLength;
i <
samParametersLength;
i++) {
Class<?>
argType =
samType.
parameterType(
i);
visitVarInsn(
getLoadOpcode(
argType),
lvIndex + 1);
lvIndex +=
getParameterSize(
argType);
convertType(
argType,
implMethodType.
parameterType(
argOffset +
i),
instantiatedMethodType.
parameterType(
i));
}
}
private int
invocationOpcode() throws
InternalError {
switch (
implKind) {
case
MethodHandleInfo.
REF_invokeStatic:
return
INVOKESTATIC;
case
MethodHandleInfo.
REF_newInvokeSpecial:
return
INVOKESPECIAL;
case
MethodHandleInfo.
REF_invokeVirtual:
return
INVOKEVIRTUAL;
case
MethodHandleInfo.
REF_invokeInterface:
return
INVOKEINTERFACE;
case
MethodHandleInfo.
REF_invokeSpecial:
return
INVOKESPECIAL;
default:
throw new
InternalError("Unexpected invocation kind: " +
implKind);
}
}
}
static int
getParameterSize(
Class<?>
c) {
if (
c ==
Void.
TYPE) {
return 0;
} else if (
c ==
Long.
TYPE ||
c ==
Double.
TYPE) {
return 2;
}
return 1;
}
static int
getLoadOpcode(
Class<?>
c) {
if(
c ==
Void.
TYPE) {
throw new
InternalError("Unexpected void type of load opcode");
}
return
ILOAD +
getOpcodeOffset(
c);
}
static int
getReturnOpcode(
Class<?>
c) {
if(
c ==
Void.
TYPE) {
return
RETURN;
}
return
IRETURN +
getOpcodeOffset(
c);
}
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;
}
}
}