This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new d25481825 fix(java): configure type checker during build (#3550)
d25481825 is described below
commit d2548182555a062bcbc76449903dce70ae4de5c0
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Apr 10 22:17:41 2026 +0800
fix(java): configure type checker during build (#3550)
## Why?
## What does this PR do?
- add `ForyBuilder.withTypeChecker` so type checkers can be installed
before build
- wire builder-configured checkers into `Fory` creation and centralize
`AllowListChecker` listener setup
- update the Java type-registration docs and tests to use the supported
allow-list flow
## Related issues
Closes #3530.
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence of the final clean AI review results
from both fresh reviewers on the current PR diff or current HEAD after
the latest code changes.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
---
docs/guide/java/type-registration.md | 20 +++++---
.../org/apache/fory/AbstractThreadSafeFory.java | 11 +---
.../src/main/java/org/apache/fory/Fory.java | 5 ++
.../java/org/apache/fory/config/ForyBuilder.java | 35 ++++++++++---
.../org/apache/fory/resolver/TypeResolver.java | 5 +-
.../apache/fory/resolver/AllowListCheckerTest.java | 59 +++++++++++++++++++---
6 files changed, 103 insertions(+), 32 deletions(-)
diff --git a/docs/guide/java/type-registration.md
b/docs/guide/java/type-registration.md
index 7e82572da..2e63a35e1 100644
--- a/docs/guide/java/type-registration.md
+++ b/docs/guide/java/type-registration.md
@@ -60,14 +60,15 @@ If there are no duplicate names for types, `namespace` can
be left as empty to r
### Type Checker
-If you invoke `ForyBuilder#requireClassRegistration(false)` to disable class
registration check, you can set `org.apache.fory.resolver.TypeChecker` by
`TypeResolver#setTypeChecker` to control which classes are allowed for
serialization.
+If you invoke `ForyBuilder#requireClassRegistration(false)` to disable class
registration check, you can configure `org.apache.fory.resolver.TypeChecker` by
`ForyBuilder#withTypeChecker` or `TypeResolver#setTypeChecker` to control which
classes are allowed for serialization.
For example, you can allow classes started with `org.example.*`:
```java
-Fory fory = xxx;
-fory.getTypeResolver().setTypeChecker(
- (typeResolver, className) -> className.startsWith("org.example."));
+Fory fory = Fory.builder()
+ .requireClassRegistration(false)
+ .withTypeChecker((typeResolver, className) ->
className.startsWith("org.example."))
+ .build();
```
### AllowListChecker
@@ -76,12 +77,17 @@ Fory provides a `org.apache.fory.resolver.AllowListChecker`
which is an allowed/
```java
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
-ThreadSafeFory fory =
Fory.builder().requireClassRegistration(false).buildThreadSafeFory();
-fory.setTypeChecker(checker);
checker.allowClass("org.example.*");
+ThreadSafeFory fory = Fory.builder()
+ .requireClassRegistration(false)
+ .withTypeChecker(checker)
+ .buildThreadSafeFory();
```
-You can use this checker or implement a more sophisticated checker by yourself.
+`withTypeChecker` installs the checker on every created runtime immediately,
which also avoids the
+generic startup warning emitted when class registration is disabled without
any checker. You can
+still use `TypeResolver#setTypeChecker` or `ThreadSafeFory#setTypeChecker`
later if you need to
+replace the checker after build time.
## Limit Max Deserialization Depth
diff --git
a/java/fory-core/src/main/java/org/apache/fory/AbstractThreadSafeFory.java
b/java/fory-core/src/main/java/org/apache/fory/AbstractThreadSafeFory.java
index 19d1474bd..33ac6af9e 100644
--- a/java/fory-core/src/main/java/org/apache/fory/AbstractThreadSafeFory.java
+++ b/java/fory-core/src/main/java/org/apache/fory/AbstractThreadSafeFory.java
@@ -20,8 +20,6 @@
package org.apache.fory;
import java.util.function.Function;
-import org.apache.fory.resolver.AllowListChecker;
-import org.apache.fory.resolver.ClassResolver;
import org.apache.fory.resolver.TypeChecker;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.Serializer;
@@ -121,14 +119,7 @@ public abstract class AbstractThreadSafeFory implements
ThreadSafeFory {
@Override
public void setTypeChecker(TypeChecker typeChecker) {
- registerCallback(
- fory -> {
- TypeResolver typeResolver = fory.getTypeResolver();
- typeResolver.setTypeChecker(typeChecker);
- if (typeChecker instanceof AllowListChecker && typeResolver
instanceof ClassResolver) {
- ((AllowListChecker) typeChecker).addListener((ClassResolver)
typeResolver);
- }
- });
+ registerCallback(fory ->
fory.getTypeResolver().setTypeChecker(typeChecker));
}
@Override
diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java
b/java/fory-core/src/main/java/org/apache/fory/Fory.java
index 1222f4145..adf1e8c6d 100644
--- a/java/fory-core/src/main/java/org/apache/fory/Fory.java
+++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java
@@ -52,6 +52,7 @@ import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
import org.apache.fory.resolver.ClassResolver;
import org.apache.fory.resolver.SharedRegistry;
+import org.apache.fory.resolver.TypeChecker;
import org.apache.fory.resolver.TypeInfo;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.resolver.XtypeResolver;
@@ -133,6 +134,10 @@ public final class Fory implements BaseFory {
? new XtypeResolver(config, classLoader, sharedRegistry,
jitContext)
: new ClassResolver(config, classLoader, sharedRegistry,
jitContext);
typeResolver.initialize();
+ TypeChecker configuredTypeChecker = builder.getTypeChecker();
+ if (configuredTypeChecker != null) {
+ typeResolver.setTypeChecker(configuredTypeChecker);
+ }
MetaStringWriter metaStringWriter = new MetaStringWriter();
MetaStringReader metaStringReader = new MetaStringReader(sharedRegistry);
writeContext =
diff --git
a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
index feb7f4565..044a9ac25 100644
--- a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
+++ b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java
@@ -35,6 +35,7 @@ import org.apache.fory.meta.MetaCompressor;
import org.apache.fory.pool.ThreadPoolFory;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.resolver.SharedRegistry;
+import org.apache.fory.resolver.TypeChecker;
import org.apache.fory.serializer.JavaSerializer;
import org.apache.fory.serializer.ObjectStreamSerializer;
import org.apache.fory.serializer.Serializer;
@@ -97,6 +98,7 @@ public final class ForyBuilder {
int maxDepth = 50;
float mapRefLoadFactor = 0.51f;
boolean forVirtualThread = false;
+ TypeChecker typeChecker;
private List<Consumer<ForyBuilder>> actions = new ArrayList<>();
private boolean replayingActions = false;
@@ -349,9 +351,9 @@ public final class ForyBuilder {
* attack if the classes `constructor`/`equals`/`hashCode` method contain
malicious code. Do not
* disable class registration if you can't ensure your environment are
*indeed secure*. We are not
* responsible for security risks if you disable this option. If you disable
this option, you can
- * configure {@link org.apache.fory.resolver.TypeChecker} by {@link
- * org.apache.fory.resolver.TypeResolver#setTypeChecker} to control which
classes are allowed
- * being serialized.
+ * configure {@link org.apache.fory.resolver.TypeChecker} by {@link
#withTypeChecker(TypeChecker)}
+ * or {@link org.apache.fory.resolver.TypeResolver#setTypeChecker} to
control which classes are
+ * allowed being serialized.
*/
public ForyBuilder requireClassRegistration(boolean
requireClassRegistration) {
this.requireClassRegistration = requireClassRegistration;
@@ -359,6 +361,21 @@ public final class ForyBuilder {
return this;
}
+ /**
+ * Configure a {@link TypeChecker} during build time so it is installed on
every created runtime.
+ * This checker is only consulted for unknown class names when class
registration checks are
+ * disabled.
+ */
+ public ForyBuilder withTypeChecker(TypeChecker typeChecker) {
+ this.typeChecker = typeChecker;
+ recordAction(b -> b.withTypeChecker(typeChecker));
+ return this;
+ }
+
+ public TypeChecker getTypeChecker() {
+ return typeChecker;
+ }
+
/**
* Whether suppress class registration warnings. The warnings can be used
for security audit, but
* may be annoying, this suppression will be enabled by default.
@@ -572,11 +589,13 @@ public final class ForyBuilder {
}
}
if (!requireClassRegistration) {
- LOG.warn(
- "Class registration isn't forced, unknown classes can be
deserialized. "
- + "If the environment isn't secure, please enable class
registration by "
- + "`ForyBuilder#requireClassRegistration(true)` or configure
TypeChecker by "
- + "`TypeResolver#setTypeChecker`");
+ if (typeChecker == null) {
+ LOG.warn(
+ "Class registration isn't forced, unknown classes can be
deserialized. "
+ + "If the environment isn't secure, please enable class
registration by "
+ + "`ForyBuilder#requireClassRegistration(true)` or configure
TypeChecker by "
+ + "`ForyBuilder#withTypeChecker` or
`TypeResolver#setTypeChecker`");
+ }
}
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE && asyncCompilationEnabled) {
LOG.info("Use sync compilation for graalvm native image since it doesn't
support JIT.");
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index ff7cecb50..f092cf52e 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -1580,7 +1580,10 @@ public abstract class TypeResolver {
}
public void setTypeChecker(TypeChecker typeChecker) {
- extRegistry.typeChecker = typeChecker;
+ extRegistry.typeChecker = typeChecker == null ? DEFAULT_TYPE_CHECKER :
typeChecker;
+ if (extRegistry.typeChecker instanceof AllowListChecker && this instanceof
ClassResolver) {
+ ((AllowListChecker) extRegistry.typeChecker).addListener((ClassResolver)
this);
+ }
}
public void setSerializerFactory(SerializerFactory serializerFactory) {
diff --git
a/java/fory-core/src/test/java/org/apache/fory/resolver/AllowListCheckerTest.java
b/java/fory-core/src/test/java/org/apache/fory/resolver/AllowListCheckerTest.java
index 6ab489754..db5b7a280 100644
---
a/java/fory-core/src/test/java/org/apache/fory/resolver/AllowListCheckerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/resolver/AllowListCheckerTest.java
@@ -21,9 +21,14 @@ package org.apache.fory.resolver;
import static org.testng.Assert.*;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;
import org.apache.fory.exception.InsecureException;
+import org.apache.fory.logging.LogLevel;
+import org.apache.fory.logging.LoggerFactory;
import org.testng.annotations.Test;
public class AllowListCheckerTest {
@@ -37,7 +42,6 @@ public class AllowListCheckerTest {
assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
checker.allowClass(AllowListCheckerTest.class.getName());
byte[] bytes = fory.serialize(new AllowListCheckerTest());
- checker.addListener((ClassResolver) fory.getTypeResolver());
checker.disallowClass(AllowListCheckerTest.class.getName());
assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
assertThrows(InsecureException.class, () -> fory.deserialize(bytes));
@@ -46,7 +50,6 @@ public class AllowListCheckerTest {
Fory fory = Fory.builder().requireClassRegistration(false).build();
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.WARN);
fory.getTypeResolver().setTypeChecker(checker);
- checker.addListener((ClassResolver) fory.getTypeResolver());
byte[] bytes = fory.serialize(new AllowListCheckerTest());
checker.disallowClass(AllowListCheckerTest.class.getName());
assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
@@ -60,7 +63,6 @@ public class AllowListCheckerTest {
Fory fory = Fory.builder().requireClassRegistration(false).build();
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
fory.getTypeResolver().setTypeChecker(checker);
- checker.addListener((ClassResolver) fory.getTypeResolver());
assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
checker.allowClass("org.apache.fory.*");
byte[] bytes = fory.serialize(new AllowListCheckerTest());
@@ -72,7 +74,6 @@ public class AllowListCheckerTest {
Fory fory = Fory.builder().requireClassRegistration(false).build();
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.WARN);
fory.getTypeResolver().setTypeChecker(checker);
- checker.addListener((ClassResolver) fory.getTypeResolver());
byte[] bytes = fory.serialize(new AllowListCheckerTest());
checker.disallowClass("org.apache.fory.*");
assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
@@ -80,11 +81,57 @@ public class AllowListCheckerTest {
}
}
+ @Test
+ public void testBuilderConfiguredChecker() {
+ AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
+ checker.allowClass("org.apache.fory.*");
+ Fory fory =
Fory.builder().requireClassRegistration(false).withTypeChecker(checker).build();
+ byte[] bytes = fory.serialize(new AllowListCheckerTest());
+ checker.disallowClass("org.apache.fory.*");
+ assertThrows(InsecureException.class, () -> fory.serialize(new
AllowListCheckerTest()));
+ assertThrows(InsecureException.class, () -> fory.deserialize(bytes));
+ }
+
+ @Test
+ public void testBuilderConfiguredCheckerSuppressesStartupWarning() {
+ synchronized (AllowListCheckerTest.class) {
+ PrintStream previousOut = System.out;
+ int previousLogLevel = LoggerFactory.getLogLevel();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try (PrintStream capture = new PrintStream(output, true,
StandardCharsets.UTF_8.name())) {
+ System.setOut(capture);
+ LoggerFactory.setLogLevel(LogLevel.WARN_LEVEL);
+
+ Fory.builder().requireClassRegistration(false).build();
+ assertTrue(
+ new String(output.toByteArray(), StandardCharsets.UTF_8)
+ .contains("Class registration isn't forced"));
+
+ output.reset();
+ Fory.builder()
+ .requireClassRegistration(false)
+ .withTypeChecker((resolver, className) -> true)
+ .build();
+ assertFalse(
+ new String(output.toByteArray(), StandardCharsets.UTF_8)
+ .contains("Class registration isn't forced"));
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ } finally {
+ LoggerFactory.setLogLevel(previousLogLevel);
+ System.setOut(previousOut);
+ }
+ }
+ }
+
@Test
public void testThreadSafeFory() {
AllowListChecker checker = new
AllowListChecker(AllowListChecker.CheckLevel.STRICT);
- ThreadSafeFory fory =
Fory.builder().requireClassRegistration(false).buildThreadSafeFory();
- fory.setTypeChecker(checker);
+ ThreadSafeFory fory =
+ Fory.builder()
+ .requireClassRegistration(false)
+ .withTypeChecker(checker)
+ .buildThreadSafeFory();
checker.allowClass("org.apache.fory.*");
byte[] bytes = fory.serialize(new AllowListCheckerTest());
checker.disallowClass("org.apache.fory.*");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]