fix(java): use REPLACE_STUB_ID for unregistered writeReplace classes to prevent cross-JVM ClassNotFoundException#3638
Open
wakilurislam wants to merge 1 commit intoapache:mainfrom
Conversation
1047281 to
3348c99
Compare
…vent cross-JVM ClassNotFoundException When Fory serializes an unregistered class with writeReplace() (e.g. a Hibernate proxy), the outer type info previously used NAMED_EXT which writes the proxy class name to the byte stream. On a different JVM where that proxy class doesn't exist, deserialization fails with ClassNotFoundException before ReplaceResolveSerializer can handle the replacement. Fix: return REPLACE_STUB_ID from buildUnregisteredTypeId() for classes detected as writeReplace candidates. This writes a compact 1-byte internal type ID instead of the class name, matching the existing pattern used for lambdas (LAMBDA_STUB_ID) and JDK proxies (JDK_PROXY_STUB_ID). Also guard addSerializer() to avoid overwriting the pre-registered typeIdToTypeInfo[REPLACE_STUB_ID] entry when adding per-class TypeInfo.
3348c99 to
fce16aa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Description
Why?
When Fory serializes an unregistered class with
writeReplace()returning a different type (e.g., a Hibernate/ByteBuddy proxy), the outer type info writes the proxy class name to the byte stream. On a different JVM where that proxy class doesn't exist, deserialization fails withClassNotFoundException.What does this PR do?
Adds an
instanceof ReplaceResolveSerializercheck inClassResolver.buildUnregisteredTypeId()to returnREPLACE_STUB_IDinstead ofNAMED_EXT. This writes a compact 1-byte internal type ID without the class name, matching the existing pattern for lambdas (LAMBDA_STUB_ID) and JDK proxies (JDK_PROXY_STUB_ID).The original
NAMED_EXTfallback forserializer == null(before serializer creation) is preserved unchanged.Changes
ClassResolver.java:buildUnregisteredTypeId()— returnREPLACE_STUB_IDwhen serializer isReplaceResolveSerializer:protected int buildUnregisteredTypeId(Class<?> cls, Serializer<?> serializer) { - if (serializer == null && !cls.isEnum() && useReplaceResolveSerializer(cls)) { - return Types.NAMED_EXT; + if (!cls.isEnum()) { + if (serializer instanceof ReplaceResolveSerializer) { + return REPLACE_STUB_ID; + } + if (serializer == null && useReplaceResolveSerializer(cls)) { + return Types.NAMED_EXT; + } }addSerializer()— guard against overwritingtypeIdToTypeInfo[REPLACE_STUB_ID]:WriteReplaceCrossJvmTest.java(new): 6 test methods × 4 configurations = 24 tests covering cross-JVM proxy, same-JVM regression, same-type replace, round-trip, and nested proxy in DTO.Related issues
AI Contribution Checklist
yesDoes this PR introduce any user-facing change?
No public API or binary protocol changes. Wire format changes only for unregistered
ReplaceResolveSerializerclasses (bug fix — previous format was broken for cross-JVM).Benchmark
N/A — one-time
buildUnregisteredTypeId()call, not per-element.review_1_with_skill.md
review_2_without_skill.md