Skip to content

Exception when serializing nested Externalizable objects #4035

Description

@NotLebedev

Search before asking

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

Version

Fory 1.7.1, linux, openjdk 25

Component(s)

Java

Minimal reproduce step

Compile and run this example with fory 1.7.1

package org.example;

import org.apache.fory.Fory;
import org.apache.fory.ThreadLocalFory;
import org.apache.fory.config.Language;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class MainPure {
    public static void main() {
        ThreadLocalFory fory = Fory.builder()
                .withLanguage(Language.JAVA)
                .requireClassRegistration(false)
                .withRefTracking(true)
                .withXlang(false)
                .withCompatible(false)
                .buildThreadLocalFory();

        Container<Container<Integer>> one = new Container<>(new Container<>(1, 2, 3), new Container<>(4, 5, 6));

        try {
            Object o = fory.deserialize(fory.serialize(one));
            System.out.println(o);
        } catch (Throwable e) {
            System.out.println("failed");
            e.printStackTrace(System.out);
        }
    }


    static class Container<T> implements Externalizable {
        Object[] item;

        public Container(T... t) {
            item = t;
        }

        public Container() {
            item = new Object[]{};
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(item.length);

            for (Object t : item) {
                out.writeObject(t);
            }
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            int length = in.readInt();

            item = new Object[length];
            for (int i = 0; i < length; i++) {
                item[i] = in.readObject();
            }
        }
    }
}

What did you expect to see?

No exception should be emitted

What did you see instead?

`java.lang.NullPointerException: Cannot invoke "org.apache.fory.context.WriteContext.writeRef(Object)" because "this.writeContext" is null`
org.apache.fory.exception.SerializationException: java.lang.NullPointerException: Cannot invoke "org.apache.fory.context.WriteContext.writeRef(Object)" because "this.writeContext" is null
	at org.apache.fory.Fory.processSerializationError(Fory.java:391)
	at org.apache.fory.Fory.serialize(Fory.java:358)
	at org.apache.fory.Fory.serialize(Fory.java:319)
	at org.apache.fory.ThreadLocalFory.serialize(ThreadLocalFory.java:96)
	at org.example.MainPure.main(MainPure.java:25)
Caused by: java.lang.NullPointerException: Cannot invoke "org.apache.fory.context.WriteContext.writeRef(Object)" because "this.writeContext" is null
	at org.apache.fory.io.MemoryBufferObjectOutput.writeObject(MemoryBufferObjectOutput.java:60)
	at org.example.MainPure$Container.writeExternal(MainPure.java:50)
	at org.apache.fory.serializer.ExternalizableSerializer.write(ExternalizableSerializer.java:50)
	at org.apache.fory.serializer.ExternalizableSerializer.write(ExternalizableSerializer.java:32)
	at org.apache.fory.context.WriteContext.writeData(WriteContext.java:663)
	at org.apache.fory.context.WriteContext.writeRef(WriteContext.java:480)
	at org.apache.fory.context.WriteContext.writeRootRef(WriteContext.java:537)
	at org.apache.fory.Fory.serialize(Fory.java:355)
	... 3 more

Anything Else?

This happens due to handling of objectOutput in ExternalizableSerializer.write

When value.writeExternal(objectOutput); is done the finally block calls objectOutput.clearWriteContext(); which sets writeContext = null. The following chain of events happens in this example:

  1. Container<Container<Integer>> (outer container) starts serializing it's first element
  2. Inner Container<Integer> finishes writeExternal and objectOutput.clearWriteContext(); is called
  3. Container<Container<Integer>> (outer container) starts serializing it's second element
  4. Because objectOutput is the same in poth calls of ExternalizableSerializer.write when returning to Container<Container<Integer>>.writeExternal the field writeContext is now null and exception happens when writeObject is called and gets to serializing second internal container

I also expect the same thing to happen with ExternalizableSerializer.read and readContext.

I'm not sure what intended logic here is, but it seems like MemoryBufferObjectOutput objects should be created for each value.writeExternal call instead of this attempted reuse (so each call to writeExternal gets it's own object)

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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