Skip to content

[Java] 1.4.0+ fails to read 1.3.0 payloads when a registered class extends an unregistered abstract class (COMPATIBLE + refTracking=false) #3926

Description

@drse

Search before asking

  • I had searched in the issues and found no similar issues.

Version

1.x.x

Component(s)

Java

Minimal reproduce step

Payloads written by fory-core 1.3.0 can't be read back by 1.4.0, 1.5.0, or 1.6.0 when the serialized object's class extends an abstract superclass that was never registered. 1.3.0 reads the same bytes fine, and 1.4.0+ round-trips with itself fine — only reading old bytes breaks.

Full reproducer (writer pinned to 1.3.0, reader version switchable, payload file committed so you don't need to build against 1.3.0) attached

tmp-fory-repro.zip

Config, identical on writer and reader (Language.JAVA, not xlang; JDK 21):

Fory.builder()
    .withLanguage(Language.JAVA)
    .withCodegen(true)
    .requireClassRegistration(true)
    .withAsyncCompilation(false)
    .serializeEnumByName(false)
    .deserializeUnknownEnumValueAsNull(true)
    .withCompatibleMode(CompatibleMode.COMPATIBLE)
    .withDeserializeUnknownClass(false)
    .withRefTracking(false)
    .withRefCopy(false)
    .withStringCompressed(false)
    .withNumberCompressed(false)

Model:

public interface Code<T> { T value(); }

public abstract class CodeDecorator<T> implements Code<T> {
    @ForyField(id = 0) private final Code<T> delegate;
    ...
}

public final class BasicCode implements Code<String> { ... }      // registered, id 1000
public final class WrappedCode extends CodeDecorator<String> { ... } // registered, id 1001

CodeDecorator itself is not registered (it's abstract, never serialized as a root). 1.3.0 is fine with that on both ends. Serializing a WrappedCode on 1.3.0 and reading it on 1.4.0/1.5.0/1.6.0 gives:

org.apache.fory.exception.InsecureException: Class repro.model.CodeDecorator is not registered.
        at org.apache.fory.resolver.TypeResolver.loadClass(TypeResolver.java:1451)
        at org.apache.fory.resolver.ClassResolver.loadClassForMeta(ClassResolver.java:2228)
        at org.apache.fory.meta.NativeTypeDefDecoder.decodeTypeDef(NativeTypeDefDecoder.java:186)
        at org.apache.fory.meta.NativeTypeDefDecoder.decodeTypeDef(NativeTypeDefDecoder.java:106)
        at org.apache.fory.meta.TypeDef.readTypeDef(TypeDef.java:351)
        at org.apache.fory.resolver.TypeResolver.readSharedTypeDefInfo(TypeResolver.java:964)
        at org.apache.fory.resolver.TypeResolver.readSharedClassTypeInfo(TypeResolver.java:950)
        at org.apache.fory.resolver.TypeResolver.readTypeInfo(TypeResolver.java:827)
        at org.apache.fory.context.ReadContext.readRootRef(ReadContext.java:628)
        at org.apache.fory.Fory.deserialize(Fory.java:515)

Registering CodeDecorator on the reader (with an id the writer never had) makes 1.4.0+ read the old bytes correctly, so the registration check is resolving the class by name out of the encoded TypeDef. That works as a stopgap, but it means 1.4.0+ readers reject metadata that 1.3.0 legitimately produced under requireClassRegistration(true).

The break correlates with refTracking(false): the same corpus written with refTracking(true) + refCopy(true) reads fine on 1.4.0+. In a larger production corpus (~300 registered types, same config) we also see NPEs at TypeResolver.getMetaSharedTypeInfo (TypeResolver.java:1237), IllegalStateException: Serializer of class Unknown should be set in ClassResolver#getMetaSharedTypeInfo, and occasional MemoryBuffer bounds errors on 1.3.0 bytes — the reproducer isolates the registration-check variant, which is the dominant one.

Expected: a 1.4+ reader with COMPATIBLE mode and identical config/registrations should read 1.3.0 bytes.

Possibly related: #3794, #3833.

Repro steps:

gradle :writer:run                      # writes payloads with 1.3.0 (already committed)
gradle :reader:run -PforyVersion=1.3.0  # OK
gradle :reader:run -PforyVersion=1.6.0  # FAIL
gradle :reader:run -PforyVersion=1.6.0 -PregisterDecorator=true  # OK (workaround)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions