Dmytro Shteflyuk created THRIFT-6124:
----------------------------------------

             Summary: 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


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:thrift}
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)

Reply via email to