You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is, I think, not a bug in Fressian. It might be considered a documentation bug? Or maybe a lack of implementation safeguards? Regardless, I read the documentation and fucked this up badly for several years, so I'm writing this up as a resource for others.
I've been hitting EOF errors when trying to deserialize Fressian records written by Jepsen. It looked like if a custom handler is used to write two objects, the second object somehow gets the component-count of the first. For instance:
:read-tag "persistent-hash-set":component-count2
:read-object :foo
Execution error (EOFException) at org.fressian.impl.RawInput/readRawByte (RawInput.java:40).
As you can imagine, this can sometimes work. It will, notably, pass round-trip tests, as long as those tests aren't testing collections with multiple objects of the same tag with different lengths. This can also lead to all kinds of silent data corruption issues--objects being read back in to higher nested data structures than where they originally belonged, coerced to different types, etc etc. It's a real mess.
The byte representation of this data structure [#{5 6} #{:foo}] is:
The problem here is that I misunderstood (what I think is) a fundamental invariant assumed by Fressian: structs with the same tag always have the same component-count. Unpacked structs are, I think, fine, since they encode their component counts explicitly--which means tests for complex datatypes might (nervous laughter) happen to pass as well, until a custom handler of this type happens to fall in the first 16 structs used in a Fressian record. Packed structs, however, re-use the component count from the original struct's declaration, which will cause silent, or not-so-silent, data corruption.
This is, I think, not a bug in Fressian. It might be considered a documentation bug? Or maybe a lack of implementation safeguards? Regardless, I read the documentation and fucked this up badly for several years, so I'm writing this up as a resource for others.
I've been hitting EOF errors when trying to deserialize Fressian records written by Jepsen. It looked like if a custom handler is used to write two objects, the second object somehow gets the component-count of the first. For instance:
This writes the first set, with component-count 2, and the second set, with component-count 1...
... and we read the first set correctly:
But somehow the second set gets component-count 2
As you can imagine, this can sometimes work. It will, notably, pass round-trip tests, as long as those tests aren't testing collections with multiple objects of the same tag with different lengths. This can also lead to all kinds of silent data corruption issues--objects being read back in to higher nested data structures than where they originally belonged, coerced to different types, etc etc. It's a real mess.
The byte representation of this data structure
[#{5 6} #{:foo}]is:So we have:
"persistent-hash-set"
The problem here is that I misunderstood (what I think is) a fundamental invariant assumed by Fressian: structs with the same tag always have the same component-count. Unpacked structs are, I think, fine, since they encode their component counts explicitly--which means tests for complex datatypes might (nervous laughter) happen to pass as well, until a custom handler of this type happens to fall in the first 16 structs used in a Fressian record. Packed structs, however, re-use the component count from the original struct's declaration, which will cause silent, or not-so-silent, data corruption.
You might want to consider documenting this on the https://github.com/clojure/data.fressian/wiki/Creating-custom-handlers page, or offering guardrails to users--perhaps by throwing exceptions when struct handlers write the same struct tag twice with different component counts.