Skip to content

Commit 0686f72

Browse files
shayne-fletchermeta-codesync[bot]
authored andcommitted
attrs: error informatively on deserializing an unknown key
Differential Revision: D85913786
1 parent b02b329 commit 0686f72

File tree

1 file changed

+77
-3
lines changed

1 file changed

+77
-3
lines changed

hyperactor/src/attrs.rs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,12 +627,38 @@ impl<'de> Visitor<'de> for AttrsVisitor {
627627
});
628628
let keys_by_name = &*KEYS_BY_NAME;
629629

630+
let exe_name = std::env::current_exe()
631+
.ok()
632+
.and_then(|p| p.file_name().map(|os| os.to_string_lossy().to_string()))
633+
.unwrap_or_else(|| "<unknown-exe>".to_string());
634+
630635
let mut attrs = Attrs::new();
631636
while let Some(key_name) = access.next_key::<String>()? {
632637
let Some(&key) = keys_by_name.get(key_name.as_str()) else {
633-
// Silently ignore unknown keys
634-
access.next_value::<serde::de::IgnoredAny>()?;
635-
continue;
638+
// We hit an attribute key that this binary doesn't
639+
// know about.
640+
//
641+
// In JSON we'd just deserialize that value into
642+
// `IgnoredAny` and move on. With bincode we *can't*
643+
// do that safely:
644+
//
645+
// - We don't know this key's value type.
646+
// - That means we don't know how many bytes to
647+
// consume for the value.
648+
// - If we guess wrong or try `IgnoredAny`, bincode
649+
// would need `Deserializer::deserialize_any()` to
650+
// skip it, but bincode refuses because it can't
651+
// know how many bytes to advance.
652+
//
653+
// Result: we cannot safely "skip" the unknown value
654+
// without risking desync of the remaining stream. So
655+
// we abort here and surface which key caused it, and
656+
// the caller must strip it before sending.
657+
return Err(serde::de::Error::custom(format!(
658+
"unknown attr key '{}' on binary '{}'; \
659+
this binary doesn't know this key and cannot skip its value safely under bincode",
660+
key_name, exe_name,
661+
)));
636662
};
637663

638664
// Create a seed to deserialize the value using erased_serde
@@ -1190,4 +1216,52 @@ mod tests {
11901216
attrs.set(CRATE_LOCAL_ATTR, "test".to_string());
11911217
assert_eq!(attrs[CRATE_LOCAL_ATTR], "test".to_string());
11921218
}
1219+
1220+
#[test]
1221+
fn attrs_deserialize_unknown_key_is_error() {
1222+
// Build a real Attrs, but inject a key that this binary does
1223+
// NOT know about. We do that with
1224+
// insert_value_by_name_unchecked(), which bypasses the
1225+
// declare_attrs!/inventory registration path.
1226+
//
1227+
// Then:
1228+
// 1. Serialize that Attrs with bincode (the real wire
1229+
// format).
1230+
// 2. Attempt to bincode-deserialize those bytes back into
1231+
// Attrs.
1232+
//
1233+
// During deserialization, AttrsVisitor::visit_map() will:
1234+
// - read the key string
1235+
// - fail to find it in KEYS_BY_NAME (the compiled-in
1236+
// registry)
1237+
// - immediately error instead of trying to skip the value,
1238+
// because with bincode we can't safely consume an unknown
1239+
// typed value without risking stream desync.
1240+
//
1241+
// This reproduces exactly what happens when a parent proc
1242+
// sends an Attrs containing a key the child binary wasn't
1243+
// built with.
1244+
1245+
// Definitely not declared in this crate's inventory:
1246+
let bad_key: &'static str = "monarch_hyperactor::pytokio::unawaited_pytokio_traceback";
1247+
1248+
// Make an Attrs that pretends to have that key. u32 already
1249+
// implements AttrValue -> SerializableValue, so we can just
1250+
// box a 0u32.
1251+
let mut attrs = Attrs::new();
1252+
attrs.insert_value_by_name_unchecked(bad_key, Box::new(0u32));
1253+
1254+
// Serialize this Attrs using bincode (same codec we use on
1255+
// the wire).
1256+
let wire_bytes = bincode::serialize(&attrs).unwrap();
1257+
1258+
// Now try to decode those bytes back into Attrs. This should
1259+
// hit the unknown-key branch and return Err.
1260+
let err = bincode::deserialize::<Attrs>(&wire_bytes)
1261+
.expect_err("should error on unknown attr key");
1262+
1263+
let msg = format!("{err}");
1264+
assert!(msg.contains("unknown attr key"), "got: {msg}");
1265+
assert!(msg.contains(bad_key), "got: {msg}");
1266+
}
11931267
}

0 commit comments

Comments
 (0)