[
https://issues.apache.org/jira/browse/THRIFT-6124?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Dmytro Shteflyuk updated THRIFT-6124:
-------------------------------------
Description:
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.
was:
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.
> 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
>
> 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)