Skip to content
Open
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 @@ -4,7 +4,9 @@
import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson2.codec.FieldInfo;
import com.alibaba.fastjson2.internal.asm.ASMUtils;
import com.alibaba.fastjson2.util.BeanUtils;
import com.alibaba.fastjson2.util.Fnv;
import com.alibaba.fastjson2.util.JDKUtils;
import com.alibaba.fastjson2.util.TypeUtils;

import java.lang.reflect.*;
Expand All @@ -23,6 +25,12 @@ final class ConstructorFunction<T>
final String[] paramNames;
final boolean marker;
final long[] hashCodes;
/**
* Non-null when parameter 0 is the synthetic enclosing instance of a non-static inner class.
* Such a parameter never appears in the JSON, so it must not be left null — the constructor
* may dereference it (JDK 25 rejects a null enclosing instance outright).
*/
final Class enclosingParamType;
final List<Constructor> alternateConstructors;
Map<Set<Long>, Constructor> alternateConstructorMap;
Map<Set<Long>, String[]> alternateConstructorNames;
Expand Down Expand Up @@ -57,6 +65,8 @@ final class ConstructorFunction<T>
hashCodes[i] = Fnv.hashCode64(name);
}

this.enclosingParamType = BeanUtils.getEnclosingInstanceParamType(constructor);

this.alternateConstructors = alternateConstructors;
if (alternateConstructors != null) {
final int size = alternateConstructors.size();
Expand Down Expand Up @@ -99,6 +109,25 @@ final class ConstructorFunction<T>
}
}

/**
* Default for a parameter absent from the JSON. Parameter 0 of a non-static inner class
* constructor is the enclosing instance, which is never present in the JSON, so allocate a
* bare one rather than passing null. When the enclosing class cannot be allocated (for
* example an abstract class), fall back to null as before JDK 25; inner classes compiled
* by JDK 25+ then reject the null enclosing instance, which is unavoidable because an
* abstract enclosing class cannot be instantiated.
*/
private Object defaultArg(int index, Class<?> paramClass) {
if (index == 0 && enclosingParamType != null) {
try {
return JDKUtils.UNSAFE.allocateInstance(enclosingParamType);
} catch (InstantiationException ignored) {
// the enclosing type cannot be allocated, fall back to the default value below
}
Comment thread
wenshao marked this conversation as resolved.
Comment thread
wenshao marked this conversation as resolved.
}
return TypeUtils.getDefaultValue(paramClass);
}

@Override
public T apply(Map<Long, Object> values) {
boolean containsAll = true;
Expand Down Expand Up @@ -139,7 +168,7 @@ public T apply(Map<Long, Object> values) {
Object arg = values.get(hashCodes[0]);
Class<?> paramType = param.getType();
if (arg == null) {
arg = TypeUtils.getDefaultValue(paramType);
arg = defaultArg(0, paramType);
} else {
if (!paramType.isInstance(arg)) {
arg = TypeUtils.cast(arg, paramType);
Expand All @@ -153,7 +182,7 @@ public T apply(Map<Long, Object> values) {
Parameter param0 = parameters[0];
Class<?> param0Type = param0.getType();
if (arg0 == null) {
arg0 = TypeUtils.getDefaultValue(param0Type);
arg0 = defaultArg(0, param0Type);
} else {
if (!param0Type.isInstance(arg0)) {
arg0 = TypeUtils.cast(arg0, param0Type);
Expand Down Expand Up @@ -193,8 +222,8 @@ public T apply(Map<Long, Object> values) {
args[i] = arg;
} else {
flag |= (1 << i);
if (paramClass.isPrimitive()) {
args[i] = TypeUtils.getDefaultValue(paramClass);
if (paramClass.isPrimitive() || (i == 0 && enclosingParamType != null)) {
args[i] = defaultArg(i, paramClass);
Comment thread
wenshao marked this conversation as resolved.
}
}
n = i + 1;
Expand All @@ -210,7 +239,7 @@ public T apply(Map<Long, Object> values) {
Type paramType = parameter.getParameterizedType();
Object arg = values.get(hashCodes[i]);
if (arg == null) {
arg = TypeUtils.getDefaultValue(paramClass);
arg = defaultArg(i, paramClass);
} else {
if (!paramClass.isInstance(arg)) {
arg = TypeUtils.cast(arg, paramClass);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.util.BeanUtils;
import com.alibaba.fastjson2.util.JDKUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
Expand All @@ -11,6 +13,7 @@ final class ConstructorSupplier
final Constructor constructor;
final Class objectClass;
final boolean useClassNewInstance;
final Class paramType;

public ConstructorSupplier(Constructor constructor) {
constructor.setAccessible(true);
Expand All @@ -19,20 +22,32 @@ public ConstructorSupplier(Constructor constructor) {
this.useClassNewInstance = constructor.getParameterCount() == 0
&& Modifier.isPublic(constructor.getModifiers())
&& Modifier.isPublic(objectClass.getModifiers());
this.paramType = BeanUtils.getEnclosingInstanceParamType(constructor);
}

@Override
public Object get() {
try {
if (useClassNewInstance) {
return objectClass.newInstance();
} else {
if (constructor.getParameterCount() == 1) {
return constructor.newInstance(new Object[1]);
} else {
return constructor.newInstance();
}

if (paramType != null) {
Object dummy;
try {
dummy = JDKUtils.UNSAFE.allocateInstance(paramType);
} catch (InstantiationException ignored) {
// the enclosing class cannot be allocated (for example an abstract class),
// pass null as before JDK 25
dummy = null;
}
return constructor.newInstance(dummy);
}

if (constructor.getParameterCount() == 1) {
return constructor.newInstance(new Object[1]);
}
return constructor.newInstance();
} catch (Throwable e) {
throw new JSONException("create instance error", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.alibaba.fastjson2.schema.JSONSchema;
import com.alibaba.fastjson2.util.BeanUtils;
import com.alibaba.fastjson2.util.Fnv;
import com.alibaba.fastjson2.util.JDKUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -443,7 +444,23 @@ public T createInstance(long features) {

if (constructor != null) {
try {
T object = (T) constructor.newInstance(new Object[parameterCount]);
T object;
if (parameterCount == 0) {
object = (T) constructor.newInstance();
} else if (parameterCount == 1 && BeanUtils.getEnclosingInstanceParamType(constructor) != null) {
Class<?> paramType = constructor.getParameterTypes()[0];
Object dummy;
try {
dummy = JDKUtils.UNSAFE.allocateInstance(paramType);
} catch (InstantiationException ignored) {
// the enclosing class cannot be allocated (for example an abstract class),
// pass null as before JDK 25
dummy = null;
}
object = (T) constructor.newInstance(dummy);
} else {
object = (T) constructor.newInstance(new Object[parameterCount]);
}
Comment thread
wenshao marked this conversation as resolved.
if (hasDefaultValue) {
initDefaultValue(object);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ protected <T> ObjectReaderNoneDefaultConstructor createNoneDefaultConstructorObj
|| (objectReaderAdapter.noneDefaultConstructor != null && objectReaderAdapter.noneDefaultConstructor.getParameterCount() != paramFieldReaders.length)
|| (constructorFunction instanceof FactoryFunction && ((FactoryFunction<T>) constructorFunction).paramNames.length != paramFieldReaders.length)
|| paramFieldReaders.length > 64
// a non-static inner class constructor carries the synthetic enclosing instance
// parameter, which the generated reader cannot allocate; use the reflective
// ConstructorFunction that allocates it
|| (objectReaderAdapter.noneDefaultConstructor != null
&& BeanUtils.getEnclosingInstanceParamType(objectReaderAdapter.noneDefaultConstructor) != null)
) {
match = false;
}
Expand Down Expand Up @@ -645,7 +650,12 @@ private <T> ObjectReaderBean jitObjectReader(
mw.invokevirtual("sun/misc/Unsafe", "allocateInstance", "(Ljava/lang/Class;)Ljava/lang/Object;");
mw.areturn();
mw.visitMaxs(3, 3);
} else if (defaultConstructor != null && Modifier.isPublic(defaultConstructor.getModifiers()) && Modifier.isPublic(objectClass.getModifiers())) {
} else if (defaultConstructor != null
&& Modifier.isPublic(defaultConstructor.getModifiers())
&& Modifier.isPublic(objectClass.getModifiers())
&& enclosingTypeVisible(defaultConstructor)) {
// a non-public enclosing type cannot be referenced from the generated class,
// so skip the override and let the reflective ObjectReaderAdapter.createInstance handle it
MethodWriter mw = cw.visitMethod(
Opcodes.ACC_PUBLIC,
methodName,
Expand Down Expand Up @@ -718,11 +728,33 @@ private static void newObject(MethodWriter mw, String TYPE_OBJECT, Constructor d
mw.invokespecial(TYPE_OBJECT, "<init>", "()V");
} else {
Class paramType = defaultConstructor.getParameterTypes()[0];
mw.aconst_null();
// ldc/checkcast on the enclosing type are access-checked against the generated class,
// which lives in DynamicClassLoader; a non-public enclosing type fails with
// IllegalAccessError there. An abstract enclosing type cannot be allocated via
// Unsafe.allocateInstance (it throws InstantiationException at runtime). In both
// cases keep passing null as before JDK 25.
if (Modifier.isPublic(paramType.getModifiers()) && !Modifier.isAbstract(paramType.getModifiers())) {
mw.getstatic(TYPE_UNSAFE_UTILS, "UNSAFE", "Lsun/misc/Unsafe;");
mw.visitLdcInsn(paramType);
mw.invokevirtual("sun/misc/Unsafe", "allocateInstance", "(Ljava/lang/Class;)Ljava/lang/Object;");
mw.checkcast(ASMUtils.type(paramType));
} else {
mw.aconst_null();
}
Comment thread
wenshao marked this conversation as resolved.
mw.invokespecial(TYPE_OBJECT, "<init>", "(" + ASMUtils.desc(paramType) + ")V");
}
}

/**
* Whether the enclosing type referenced by the given inner class constructor can be loaded
* via {@code ldc}/{@code checkcast} from a generated reader class. A non-public enclosing
* type cannot, so instance creation must be delegated to the reflective creator instead.
*/
private static boolean enclosingTypeVisible(Constructor constructor) {
return constructor.getParameterCount() == 0
|| Modifier.isPublic(constructor.getParameterTypes()[0].getModifiers());
}

private void genMethodGetFieldReader(ObjectReadContext context) {
ObjectReaderAdapter objectReaderAdapter = context.objectReaderAdapter;
genMethodGetFieldReaderImpl(
Expand Down Expand Up @@ -2965,7 +2997,8 @@ private <T> void genCreateObject(
int objectModifiers = objectClass == null ? Modifier.PUBLIC : objectClass.getModifiers();
boolean publicObject = Modifier.isPublic(objectModifiers) && (objectClass == null || !classLoader.isExternalClass(objectClass));

if (defaultConstructor == null || !publicObject || !Modifier.isPublic(defaultConstructor.getModifiers())) {
if (defaultConstructor == null || !publicObject || !Modifier.isPublic(defaultConstructor.getModifiers())
|| !enclosingTypeVisible(defaultConstructor)) {
if (creator != null) {
mw.aload(THIS);
mw.getfield(classNameType, "creator", "Ljava/util/function/Supplier;");
Expand Down
17 changes: 17 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2624,6 +2624,23 @@ public static void processJacksonJsonIgnore(FieldInfo fieldInfo, Annotation anno
});
}

/**
* Returns the enclosing class if the given constructor is a non-static member class
* constructor whose first parameter is the enclosing instance ({@code this$0}),
* otherwise returns null.
*/
public static Class getEnclosingInstanceParamType(Constructor constructor) {
Class declaringClass = constructor.getDeclaringClass();
Class enclosingClass = declaringClass.getDeclaringClass();
if (enclosingClass != null
&& !Modifier.isStatic(declaringClass.getModifiers())
&& constructor.getParameterCount() > 0
&& constructor.getParameterTypes()[0] == enclosingClass) {
return enclosingClass;
}
return null;
}

public static boolean isNoneStaticMemberClass(Class objectClass, Class memberClass) {
if (memberClass == null
|| memberClass.isPrimitive()
Expand Down
Loading
Loading