Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ protected MethodHandles.Lookup lookup(Class<?> declaringClass) {
}
}

/**
* LambdaMetafactory requires the caller Lookup to have private access, otherwise it rejects the
* call with {@code LambdaConversionException: Invalid caller}. When {@link #lookup(Class)} cannot
* produce a trusted lookup (for example {@code JDKUtils.trustedLookup} degraded to
* {@code IMPL_LOOKUP.in(declaringClass)}, which drops the PRIVATE bit), the lambda path can never
* succeed, so the reflection based accessors are used directly instead of throwing per property.
*
* @param declaringClass the class declaring the getter/setter
* @return true if lambda accessors can be created for the given class
*/
private boolean lambdaSupported(Class<?> declaringClass) {
MethodHandles.Lookup lookup = lookup(declaringClass);
return lookup != null && (lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) != 0;
}

/**
* Creates a Supplier that can instantiate objects using the given constructor
* via MethodHandle and LambdaMetafactory for better performance than reflection.
Expand Down Expand Up @@ -267,51 +282,77 @@ public PropertyAccessor create(
}
boolean lambda = declaringClass.getName().contains("$$Lambda");

if (!lambda && (setter == null || !isChainableSetter(setter))) {
if (propertyClass == boolean.class) {
return create(name, getBoolean(getter), setBoolean(setter));
}
if (JDKUtils.JVM_VERSION == 8) {
if (propertyClass == byte.class) {
return create(name, getByte(getter), setByte(setter));
}
if (propertyClass == short.class) {
return create(name, getShort(getter), setShort(setter));
}
if (propertyClass == char.class) {
return create(name, getChar(getter), setChar(setter));
if (!lambda && (setter == null || !isChainableSetter(setter)) && lambdaSupported(declaringClass)) {
try {
PropertyAccessor accessor = createLambdaAccessor(
name, propertyClass, propertyType, getter, setter, exceptionHandler);
if (accessor != null) {
return accessor;
}
} catch (VirtualMachineError e) {
throw e;
} catch (Throwable ignored) {
// the lambda path is not usable for this property, fall back to reflection
}
if (propertyClass == int.class) {
return create(name, getInt(getter), setInt(setter));
}
if (propertyClass == long.class) {
return create(name, getLong(getter), setLong(setter));
}

return super.create(name, propertyClass, propertyType, getter, setter, exceptionHandler);
}

/**
* Creates a LambdaMetafactory based accessor for the given property.
*
* @return the accessor, or null when the property type has no lambda based accessor
*/
private PropertyAccessor createLambdaAccessor(
String name, Class<?> propertyClass,
Type propertyType,
Method getter,
Method setter,
BiFunction<PropertyAccessor, Throwable, RuntimeException> exceptionHandler
) {
if (propertyClass == boolean.class) {
return create(name, getBoolean(getter), setBoolean(setter));
}
if (JDKUtils.JVM_VERSION == 8) {
if (propertyClass == byte.class) {
return create(name, getByte(getter), setByte(setter));
}
if (propertyClass == float.class) {
return create(name, getFloat(getter), setFloat(setter));
if (propertyClass == short.class) {
return create(name, getShort(getter), setShort(setter));
}
if (propertyClass == double.class) {
return create(name, getDouble(getter), setDouble(setter));
if (propertyClass == char.class) {
return create(name, getChar(getter), setChar(setter));
}
if (!propertyClass.isPrimitive()) {
if (propertyType == null) {
if (getter != null) {
propertyType = getter.getGenericReturnType();
} else {
Type[] parameterTypes = setter.getGenericParameterTypes();
if (parameterTypes.length == 1) {
propertyType = parameterTypes[0];
} else if (parameterTypes.length == 2 && String.class.equals(parameterTypes[0])) {
propertyType = parameterTypes[1];
}
}
if (propertyClass == int.class) {
return create(name, getInt(getter), setInt(setter));
}
if (propertyClass == long.class) {
return create(name, getLong(getter), setLong(setter));
}
if (propertyClass == float.class) {
return create(name, getFloat(getter), setFloat(setter));
}
if (propertyClass == double.class) {
return create(name, getDouble(getter), setDouble(setter));
}
if (!propertyClass.isPrimitive()) {
if (propertyType == null) {
if (getter != null) {
propertyType = getter.getGenericReturnType();
} else {
Type[] parameterTypes = setter.getGenericParameterTypes();
if (parameterTypes.length == 1) {
propertyType = parameterTypes[0];
} else if (parameterTypes.length == 2 && String.class.equals(parameterTypes[0])) {
propertyType = parameterTypes[1];
}
}
return create(name, propertyClass, propertyType, getObject(getter), setObject(name, setter), exceptionHandler);
}
return create(name, propertyClass, propertyType, getObject(getter), setObject(name, setter), exceptionHandler);
}

return super.create(name, propertyClass, propertyType, getter, setter, exceptionHandler);
return null;
}

/**
Expand Down Expand Up @@ -500,8 +541,7 @@ public Function<Object, Object> getObject(Method method) {
if (method == null) {
return null;
}
Class<?> declaringClass = method.getDeclaringClass();
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
MethodHandles.Lookup lookup = lookup(method);
try {
MethodHandle handle = lookup.unreflect(method);
return (Function<Object, Object>) LambdaMetafactory.metafactory(
Expand Down
9 changes: 7 additions & 2 deletions core/src/main/java/com/alibaba/fastjson2/util/JDKUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,21 @@ public class JDKUtils {

MethodHandles.Lookup trustedLookup = null;
if (!ANDROID) {
// MethodHandles.Lookup.IMPL_LOOKUP is read straight from memory, which bypasses class
// initialization. Calling MethodHandles.lookup() first forces MethodHandles.Lookup to be
// initialized, otherwise the field is still null and every trustedLookup() call degrades
// to a lookup without private access, breaking LambdaMetafactory. see issue #7691
MethodHandles.Lookup callerLookup = MethodHandles.lookup();
try {
Class lookupClass = MethodHandles.Lookup.class;
Field implLookup = lookupClass.getDeclaredField("IMPL_LOOKUP");
long fieldOffset = UNSAFE.staticFieldOffset(implLookup);
trustedLookup = (MethodHandles.Lookup) UNSAFE.getObject(lookupClass, fieldOffset);
trustedLookup = (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookup), fieldOffset);
} catch (Throwable ignored) {
// ignored
}
if (trustedLookup == null) {
trustedLookup = MethodHandles.lookup();
trustedLookup = callerLookup;
}
}
IMPL_LOOKUP = trustedLookup;
Expand Down
Loading
Loading