diff --git a/integration_tests/jpms_tests/src/test/java/org/apache/fory/integration_tests/JpmsFieldAccessorTest.java b/integration_tests/jpms_tests/src/test/java/org/apache/fory/integration_tests/JpmsFieldAccessorTest.java index 5abb72c254..03fb9539ce 100644 --- a/integration_tests/jpms_tests/src/test/java/org/apache/fory/integration_tests/JpmsFieldAccessorTest.java +++ b/integration_tests/jpms_tests/src/test/java/org/apache/fory/integration_tests/JpmsFieldAccessorTest.java @@ -130,9 +130,8 @@ public void testCodegenFinalFieldAccess() throws Exception { Assert.assertEquals(result.value(), 17); Class serializerClass = serializerClass(fory, PrivateFieldBean.class); - Assert.assertTrue((Boolean) Class.class.getMethod("isHidden").invoke(serializerClass)); - Assert.assertSame( - Class.class.getMethod("getNestHost").invoke(serializerClass), PrivateFieldBean.class); + Assert.assertFalse((Boolean) Class.class.getMethod("isHidden").invoke(serializerClass)); + Assert.assertSame(serializerClass.getClassLoader(), PrivateFieldBean.class.getClassLoader()); assertVarHandleField(serializerClass, "value"); } diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java b/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java index 2361179ddb..c23ed1e580 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java @@ -24,6 +24,7 @@ import org.apache.fory.Fory; import org.apache.fory.codegen.CodeGenerator; import org.apache.fory.codegen.CompileUnit; +import org.apache.fory.codegen.CompileUnit.DefinitionMode; import org.apache.fory.collection.Tuple3; import org.apache.fory.meta.TypeDef; import org.apache.fory.platform.AndroidSupport; @@ -138,7 +139,8 @@ static Class loadOrGenCodecClass( CodeGenerator.getPackage(beanClass), codecBuilder.codecClassName(beanClass), codecBuilder::genCode, - neighborClass); + neighborClass, + DefinitionMode.NORMAL); return (Class) codeGenerator.compileAndLoad(compileUnit, compileState -> compileState.lock.lock()); } diff --git a/java/fory-core/src/main/java/org/apache/fory/codegen/CodeGenerator.java b/java/fory-core/src/main/java/org/apache/fory/codegen/CodeGenerator.java index f63e4151c9..47ed6aae34 100644 --- a/java/fory-core/src/main/java/org/apache/fory/codegen/CodeGenerator.java +++ b/java/fory-core/src/main/java/org/apache/fory/codegen/CodeGenerator.java @@ -34,12 +34,14 @@ import java.util.concurrent.TimeUnit; import org.apache.fory.builder.AccessorHelper; import org.apache.fory.builder.Generated; +import org.apache.fory.codegen.CompileUnit.DefinitionMode; import org.apache.fory.collection.Collections; import org.apache.fory.collection.MultiKeyWeakMap; import org.apache.fory.logging.Logger; import org.apache.fory.logging.LoggerFactory; import org.apache.fory.platform.AndroidSupport; import org.apache.fory.platform.GraalvmSupport; +import org.apache.fory.platform.JdkVersion; import org.apache.fory.platform.internal.DefineClass; import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.util.ClassLoaderUtils; @@ -167,7 +169,18 @@ public Class compileAndLoad(CompileUnit unit, CompileCallback callback) { throw new IllegalStateException( "Compiler did not produce bytecode for " + unit.getQualifiedClassName()); } - Class definedClass = DefineClass.defineHiddenNestmate(neighborClass, bytecodes); + Class definedClass; + if (unit.getDefinitionMode() == DefinitionMode.NORMAL || JdkVersion.MAJOR_VERSION < 15) { + definedClass = + DefineClass.defineClass( + unit.getQualifiedClassName(), + neighborClass, + parentClassLoader, + neighborClass.getProtectionDomain(), + bytecodes); + } else { + definedClass = DefineClass.defineHiddenNestmate(neighborClass, bytecodes); + } defineState.definedClass = definedClass; defineState.defined = true; return definedClass; diff --git a/java/fory-core/src/main/java/org/apache/fory/codegen/CompileUnit.java b/java/fory-core/src/main/java/org/apache/fory/codegen/CompileUnit.java index d90b6fdae4..e81ca7a8af 100644 --- a/java/fory-core/src/main/java/org/apache/fory/codegen/CompileUnit.java +++ b/java/fory-core/src/main/java/org/apache/fory/codegen/CompileUnit.java @@ -29,11 +29,16 @@ public class CompileUnit { private static final Logger LOG = LoggerFactory.getLogger(CompileUnit.class); + public enum DefinitionMode { + HIDDEN_NESTMATE, + NORMAL + } + String pkg; String mainClassName; - // Non-null only when the compiled class must be defined as a JDK25+ hidden nestmate of this - // neighbor. Ordinary generated classes still use the CodeGenerator classloader path. + // Non-null when the compiled class should be defined with this class as the lookup neighbor. private final Class neighborClass; + private final DefinitionMode definitionMode; private String code; private Supplier genCodeFunc; @@ -42,10 +47,20 @@ public CompileUnit(String pkg, String mainClassName, String code) { } public CompileUnit(String pkg, String mainClassName, String code, Class neighborClass) { + this(pkg, mainClassName, code, neighborClass, DefinitionMode.HIDDEN_NESTMATE); + } + + public CompileUnit( + String pkg, + String mainClassName, + String code, + Class neighborClass, + DefinitionMode definitionMode) { this.pkg = pkg; this.mainClassName = mainClassName; this.code = code; this.neighborClass = neighborClass; + this.definitionMode = definitionMode; } public CompileUnit(String pkg, String mainClassName, Supplier genCodeFunc) { @@ -54,10 +69,20 @@ public CompileUnit(String pkg, String mainClassName, Supplier genCodeFun public CompileUnit( String pkg, String mainClassName, Supplier genCodeFunc, Class neighborClass) { + this(pkg, mainClassName, genCodeFunc, neighborClass, DefinitionMode.HIDDEN_NESTMATE); + } + + public CompileUnit( + String pkg, + String mainClassName, + Supplier genCodeFunc, + Class neighborClass, + DefinitionMode definitionMode) { this.pkg = pkg; this.mainClassName = mainClassName; this.genCodeFunc = genCodeFunc; this.neighborClass = neighborClass; + this.definitionMode = definitionMode; } public String getCode() { @@ -83,6 +108,10 @@ public Class getNeighborClass() { return neighborClass; } + public DefinitionMode getDefinitionMode() { + return definitionMode; + } + @Override public String toString() { return "CompileUnit{" + "pkg='" + pkg + '\'' + ", mainClassName='" + mainClassName + '\'' + '}'; diff --git a/java/fory-core/src/test/java/org/apache/fory/builder/CodecUtilsTest.java b/java/fory-core/src/test/java/org/apache/fory/builder/CodecUtilsTest.java index 41702a4425..db2b3f521b 100644 --- a/java/fory-core/src/test/java/org/apache/fory/builder/CodecUtilsTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/builder/CodecUtilsTest.java @@ -20,8 +20,8 @@ package org.apache.fory.builder; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; import org.apache.fory.Fory; import org.apache.fory.ForyTestBase; @@ -62,8 +62,8 @@ public void loadOrGenObjectCodecClass() throws Exception { private static void assertGeneratedClassShape(Class serializerClass, Class beanClass) throws Exception { if (JdkVersion.MAJOR_VERSION >= 25) { - assertTrue((Boolean) Class.class.getMethod("isHidden").invoke(serializerClass)); - assertSame(Class.class.getMethod("getNestHost").invoke(serializerClass), beanClass); + assertFalse((Boolean) Class.class.getMethod("isHidden").invoke(serializerClass)); + assertSame(serializerClass.getClassLoader(), beanClass.getClassLoader()); } else { assertSame( serializerClass.getClassLoader().loadClass(serializerClass.getName()), serializerClass); diff --git a/java/fory-core/src/test/java/org/apache/fory/builder/JITContextTest.java b/java/fory-core/src/test/java/org/apache/fory/builder/JITContextTest.java index efe5781a74..1009763c4a 100644 --- a/java/fory-core/src/test/java/org/apache/fory/builder/JITContextTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/builder/JITContextTest.java @@ -20,23 +20,31 @@ package org.apache.fory.builder; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; -import lombok.Data; import org.apache.fory.Fory; import org.apache.fory.ForyTestBase; import org.apache.fory.ThreadSafeFory; +import org.apache.fory.codegen.CodeGenerator; +import org.apache.fory.codegen.CompileUnit; +import org.apache.fory.codegen.CompileUnit.DefinitionMode; +import org.apache.fory.config.ForyBuilder; import org.apache.fory.context.MetaReadContext; import org.apache.fory.context.MetaWriteContext; import org.apache.fory.logging.Logger; import org.apache.fory.logging.LoggerFactory; +import org.apache.fory.platform.JdkVersion; import org.apache.fory.reflect.ReflectionUtils; +import org.apache.fory.resolver.TypeChecker; import org.apache.fory.serializer.Serializer; import org.apache.fory.test.bean.BeanA; import org.apache.fory.test.bean.BeanB; @@ -46,6 +54,7 @@ public class JITContextTest extends ForyTestBase { private static final Logger LOG = LoggerFactory.getLogger(JITContextTest.class); + private static final TypeChecker ALLOW_ALL_TYPES = (resolver, className) -> true; @DataProvider public static Object[][] config1() { @@ -149,35 +158,109 @@ public void testAsyncCompilationMetaShare( @Test(timeOut = 60000) public void testAsyncCompilationSwitch() throws InterruptedException { - final Fory fory = + testAsyncCompilationSwitch(false); + } + + @Test(timeOut = 60000) + public void testAsyncCompilationSwitchAllowAllTypes() throws InterruptedException { + testAsyncCompilationSwitch(true); + } + + private void testAsyncCompilationSwitch(boolean allowAllTypes) throws InterruptedException { + ForyBuilder builder = Fory.builder() .withXlang(false) .requireClassRegistration(false) .withRefTracking(true) .withAsyncCompilation(true) - .withCompatible(false) - .build(); + .withCompatible(false); + if (allowAllTypes) { + builder.withTypeChecker(ALLOW_ALL_TYPES); + } + final Fory fory = builder.build(); - TestAccessLevel o = new TestAccessLevel(new PkgAccessLevel(1), new PrivateAccessLevel(2)); - serDeCheck(fory, o); - Class[] classes = {PkgAccessLevel.class, PrivateAccessLevel.class}; + ContainerPayload o = + new ContainerPayload(new NestedPayload(1, "name"), new PayloadDetails("category", true)); + assertContainerPayloadRoundTrip(fory, o); + Class[] classes = {NestedPayload.class, PayloadDetails.class}; for (Class cls : classes) { while (!(fory.getTypeResolver().getSerializer(cls) instanceof Generated)) { Thread.sleep(1000); LOG.warn("Wait async compilation finish for {}", cls); } } - while (fory.getJITContext().hasJITResult(PkgAccessLevel.class)) { + while (fory.getJITContext().hasJITResult(NestedPayload.class)) { Thread.sleep(10); // allow serializer be switched to generated version } - while (fory.getJITContext().hasJITResult(PrivateAccessLevel.class)) { + while (fory.getJITContext().hasJITResult(PayloadDetails.class)) { Thread.sleep(10); // allow serializer be switched to generated version } - Serializer serializer = - fory.getTypeResolver().getSerializer(TestAccessLevel.class); + Serializer serializer = + fory.getTypeResolver().getSerializer(ContainerPayload.class); assertTrue(ReflectionUtils.getObjectFieldValue(serializer, "serializer") instanceof Generated); assertTrue(ReflectionUtils.getObjectFieldValue(serializer, "serializer1") instanceof Generated); - serDeCheck(fory, o); + assertContainerPayloadRoundTrip(fory, o); + } + + @Test(timeOut = 60000) + public void testGeneratedSerializerFieldAccessAllowAllTypes() throws Exception { + Fory fory = + Fory.builder() + .withXlang(false) + .requireClassRegistration(false) + .withRefTracking(true) + .withAsyncCompilation(true) + .withCompatible(false) + .withTypeChecker(ALLOW_ALL_TYPES) + .build(); + ContainerPayload value = + new ContainerPayload(new NestedPayload(1, "name"), new PayloadDetails("category", true)); + assertContainerPayloadRoundTrip(fory, value); + + Class serializerClass = compileGeneratedSerializerClass(); + assertGeneratedSerializerClassShape(serializerClass); + Object generatedSerializer = serializerClass.getConstructor().newInstance(); + Field field = serializerClass.getDeclaredField("serializer1"); + Serializer nestedSerializer = fory.getTypeResolver().getSerializer(NestedPayload.class); + + ReflectionUtils.setObjectFieldValue(generatedSerializer, field, nestedSerializer); + + assertSame(ReflectionUtils.getObjectFieldValue(generatedSerializer, field), nestedSerializer); + } + + private static Class compileGeneratedSerializerClass() { + String pkg = JITContextTest.class.getPackage().getName(); + CompileUnit unit = + new CompileUnit( + pkg, + "ContainerPayloadForyCodec_0", + ("package " + + pkg + + ";\n" + + "import org.apache.fory.serializer.Serializer;\n" + + "public class ContainerPayloadForyCodec_0 {\n" + + " public Serializer serializer1;\n" + + "}"), + JITContextTest.class, + DefinitionMode.NORMAL); + return new CodeGenerator(JITContextTest.class.getClassLoader()) + .compileAndLoad(unit, compileState -> compileState.lock.lock()); + } + + private static void assertGeneratedSerializerClassShape(Class serializerClass) + throws ReflectiveOperationException { + assertSame(serializerClass.getClassLoader(), JITContextTest.class.getClassLoader()); + if (JdkVersion.MAJOR_VERSION >= 15) { + assertFalse((Boolean) Class.class.getMethod("isHidden").invoke(serializerClass)); + } + } + + private static void assertContainerPayloadRoundTrip(Fory fory, ContainerPayload value) { + ContainerPayload roundTrip = (ContainerPayload) fory.deserialize(fory.serialize(value)); + assertEquals(roundTrip.nestedPayload.id, value.nestedPayload.id); + assertEquals(roundTrip.nestedPayload.name, value.nestedPayload.name); + assertEquals(roundTrip.details.category, value.details.category); + assertEquals(roundTrip.details.enabled, value.details.enabled); } @Test(timeOut = 60000) @@ -215,34 +298,39 @@ public void testThreadSafetyWithManyForyInstances() throws Exception { assertTrue(errors.isEmpty(), "No exceptions should be thrown: " + errors); } - @Data - public static final class TestAccessLevel { - PkgAccessLevel f1; - PrivateAccessLevel f2; + public static final class ContainerPayload { + public NestedPayload nestedPayload; + public PayloadDetails details; + + public ContainerPayload() {} - public TestAccessLevel(PkgAccessLevel f1, PrivateAccessLevel f2) { - this.f1 = f1; - this.f2 = f2; + public ContainerPayload(NestedPayload nestedPayload, PayloadDetails details) { + this.nestedPayload = nestedPayload; + this.details = details; } } - // test pkg level class - @Data - private static final class PkgAccessLevel { - private final int f1; + public static final class NestedPayload { + public int id; + public String name; - public PkgAccessLevel(int f1) { - this.f1 = f1; + public NestedPayload() {} + + public NestedPayload(int id, String name) { + this.id = id; + this.name = name; } } - // test private class, class should be final for switch - @Data - private static final class PrivateAccessLevel { - private final int f1; + public static final class PayloadDetails { + public String category; + public boolean enabled; + + public PayloadDetails() {} - public PrivateAccessLevel(int f1) { - this.f1 = f1; + public PayloadDetails(String category, boolean enabled) { + this.category = category; + this.enabled = enabled; } } }