[
https://issues.apache.org/jira/browse/THRIFT-6124?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Dmytro Shteflyuk resolved THRIFT-6124.
--------------------------------------
Fix Version/s: 0.25.0
Resolution: Fixed
> Ruby deserializer retains stale values when reusing target objects
> ------------------------------------------------------------------
>
> Key: THRIFT-6124
> URL: https://issues.apache.org/jira/browse/THRIFT-6124
> Project: Thrift
> Issue Type: Bug
> Components: Ruby - Library
> Reporter: Dmytro Shteflyuk
> Assignee: Dmytro Shteflyuk
> Priority: Major
> Fix For: 0.25.0
>
> Time Spent: 20m
> Remaining Estimate: 0h
>
> h3. Problem
> The Ruby {{Deserializer}} API accepts an existing generated object as its
> target. Struct and union readers currently assign only fields that are
> present in the incoming payload; they do not clear values left by an earlier
> deserialization.
> For structs, an optional field omitted from the next payload therefore keeps
> its previous value. A field whose incoming wire type does not match the
> generated definition can have the same result. Generated defaults also retain
> caller-modified values instead of being restored before the next message is
> read.
> For unions, an unknown or mismatched field is skipped. If the target already
> contains a variant, the old discriminator and value remain in place, so
> validation can succeed even though the new payload did not contain that
> variant.
> h3. Client impact
> Applications that reuse generated Ruby objects to reduce allocations can
> observe values that were not present in the latest message. Required-field
> validation can also be satisfied by a value retained from an earlier message.
> A reused union can represent an unknown newer field as an unrelated variant
> from the preceding message.
> The behavior is present in both the pure-Ruby and native readers.
> h3. Reproduction
> Given these generated types:
> {code}
> struct Record {
> 1: optional string note
> }
> union Choice {
> 1: string text
> }
> {code}
> Reusing one {{Record}} instance retains the first message's optional value:
> {code:ruby}
> serializer = Thrift::Serializer.new
> deserializer = Thrift::Deserializer.new
> target = Record.new
> deserializer.deserialize(
> target,
> serializer.serialize(Record.new(note: "old"))
> )
> puts target.note
> # old
> deserializer.deserialize(
> target,
> serializer.serialize(Record.new)
> )
> puts target.note.inspect
> # "old", although the second payload omitted the field
> {code}
> The equivalent union case occurs when a reused {{Choice}} containing {{text}}
> reads a payload containing only an unknown field: the previous {{text}}
> discriminator and value remain set.
> h3. Expected behavior
> Every read should begin from the same field state as a newly initialized
> target: declared defaults restored, other struct fields unset, and the union
> discriminator and value cleared.
> Absent, unknown, or type-mismatched fields must not expose values from an
> earlier message. Required-field validation must evaluate only the current
> message. If reading fails partway through, values that existed before that
> read must not survive; the target should not be treated as a successfully
> deserialized value.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)