Skip to content
Draft
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 @@ -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");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,7 +139,8 @@ static <T> Class<? extends Serializer> loadOrGenCodecClass(
CodeGenerator.getPackage(beanClass),
codecBuilder.codecClassName(beanClass),
codecBuilder::genCode,
neighborClass);
neighborClass,
DefinitionMode.NORMAL);
return (Class<? extends Serializer>)
codeGenerator.compileAndLoad(compileUnit, compileState -> compileState.lock.lock());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> genCodeFunc;

Expand All @@ -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<String> genCodeFunc) {
Expand All @@ -54,10 +69,20 @@ public CompileUnit(String pkg, String mainClassName, Supplier<String> genCodeFun

public CompileUnit(
String pkg, String mainClassName, Supplier<String> genCodeFunc, Class<?> neighborClass) {
this(pkg, mainClassName, genCodeFunc, neighborClass, DefinitionMode.HIDDEN_NESTMATE);
}

public CompileUnit(
String pkg,
String mainClassName,
Supplier<String> genCodeFunc,
Class<?> neighborClass,
DefinitionMode definitionMode) {
this.pkg = pkg;
this.mainClassName = mainClassName;
this.genCodeFunc = genCodeFunc;
this.neighborClass = neighborClass;
this.definitionMode = definitionMode;
}

public String getCode() {
Expand All @@ -83,6 +108,10 @@ public Class<?> getNeighborClass() {
return neighborClass;
}

public DefinitionMode getDefinitionMode() {
return definitionMode;
}

@Override
public String toString() {
return "CompileUnit{" + "pkg='" + pkg + '\'' + ", mainClassName='" + mainClassName + '\'' + '}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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<TestAccessLevel> serializer =
fory.getTypeResolver().getSerializer(TestAccessLevel.class);
Serializer<ContainerPayload> 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)
Expand Down Expand Up @@ -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;
}
}
}
Loading