Repository navigation
reflect: add DynamicMessage::try_from_message and BridgeError - #414
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
try_from_message_with_index is presented as the fallible bridge, but it still inherits MessageIndex's cross-pool hazard. An index obtained from another pool can panic when its ordinal is out of range, or silently select the wrong descriptor when that ordinal exists in both pools. Since this API accepts the pool and index separately, could we validate/associate them or at least document this remaining panic/silent-mismatch case and cover it with two pools?
|
Thanks — confirmed. MessageIndex does not carry pool identity, so the same-ordinal mismatch cannot be validated without changing its representation. I documented that invariant and both the panic and silent-mismatch cases on the explicit-index bridge (and its panicking sibling), corrected the referenced DescriptorPool::message panic contract, and added two-pool regressions for out-of-range, deferred empty-message, and same-ordinal behavior. The name-resolving try_from_message path remains the recommended option when MessageName is available. |
Fallible, name-resolving bridge from a generated message: resolves the descriptor via MessageName::FULL_NAME and returns BridgeError instead of panicking when the pool lacks the type or rejects the encoded bytes. try_from_message_with_index is the fallible index-taking form; from_message keeps its signature and panics and shares the decode step, which now lifts the untrusted-input element-memory and unknown-field budgets for the process's own bytes. Refs anthropics#413 (items 1 and 2).
… BridgeError::Decode, scale to_message too The 128 element-bytes-per-encoded-byte factor was duplicated between this crate and buffa-codegen's view conversion with nothing pinning it to size_of::<Value>(); it is now a named constant with a const assertion. An over-2 GiB message surfaced as "failed to round-trip through the pool's descriptor: message too large", blaming the descriptor for an encode-side failure; BridgeError::MessageTooLarge names it. to_message re-decoded with the fixed untrusted-input budgets, the mirror of the bug this PR fixes inbound, so a snapshot the bridge admitted could fail to convert back; it uses the same scaled budgets. The large-message test builds one budget's worth of values plus one rather than two. Fragments in house style; the anthropics#341 fragment carries its PR number.
918e105 to
c2f9b39
Compare
|
[claude code] This is a well-built bridge:
One thing for @iainmcgin rather than you: bridge-mode |
Addresses items 1 and 2 of #413. Item 3, a reflection path that does not re-encode, remains a separate design topic.
Summary
DynamicMessage::try_from_message(msg, pool), which resolves the descriptor throughM::FULL_NAMEand returns a structuredBridgeErrorinstead of panicking when the type is missing or the encoded bytes do not match the pool schema.DynamicMessage::try_from_message_with_index(msg, pool, idx)for callers that already hold aMessageIndex, returning the underlyingDecodeErrorand mapping over-limit encoding toDecodeError::MessageTooLarge.from_messagesource-compatible while sharing one bridge decode path across the fallible and panicking entry points.BridgeErrorfrombuffa_descriptor::reflectand the crate root, withDisplayandstd::error::Error::source()support.MessageIndexcarries no pool identity, so a foreign index may panic when out of range or silently select a different descriptor at the same position.Value
The new name-resolving entry point gives runtime-schema users a practical, diagnosable bridge from generated messages to
DynamicMessage. Missing types and incompatible descriptors become ordinary errors that can be propagated or logged with the generated type name, while existing generated reflection bridges keep their current API.The bridge uses encoded-length-scaled memory bounds for bytes the process has just encoded from an already-materialized message, matching the existing generated
ReflectMessage::to_dynamicpolicy. This prevents valid large repeated, packed, or map fields from failing against the fixed default while keeping the second representation bounded; the recursion limit and protobuf 2 GiB size limit remain in force.Compatibility and boundaries
from_messageis not deprecated; generated reflection code and checked-in WKT output still use it. Its signature and decode-failure panic contract remain, while valid large self-encoded messages no longer hit the fixed default memory budget.try_from_messageis recommended wheneverMessageNameis available because the index is obtained from the same pool.MessageIndexrepresentation cannot validate that association without a broader API change.encoded_len * 128for element memory andencoded_lenfor unknown fields, each floored at its default); nousize::MAXescape hatch remains.Tests
Ten colocated bridge tests cover:
MessageNotFound,Decode,Display, andError::source()behavior;from_messagedecode-failure panic behavior;Verification
Windows 11, pinned Rust 1.95.0, protoc 33.5.
cargo fmt --all --check: clean.cargo clippy --workspace --all-targets -- -D warnings: clean.RUSTDOCFLAGS=-D warnings cargo doc --workspace --all-features --no-deps: clean.cargo test --workspace -- --skip systemtime_roundtrip_post_epoch: clean.SystemTimerounds123456789 nsto123456700 nsat 100 ns resolution.10 passed.docs/guide.mdpasses markdownlint. Repository-wide markdownlint has one unchanged baseline finding:AGENTS.mdlacks a trailing newline.Changelog
Includes separate
AddedandFixedfragments under.changes/unreleased/, updates the reflection guide with the fallible bridge example and bounded-memory policy, and corrects the existing #341 fragment's wording from "lifted" to the finite scaled-and-floored behavior its code already implements.