use super::*;
#[test]
fn mem_reader_default_resolve_arena_type_is_none() {
struct DefaultReader;
impl MemReader for DefaultReader {
fn read_kva(&self, _: u64, _: usize) -> Option<Vec<u8>> {
None
}
}
let r = DefaultReader;
assert!(
r.resolve_arena_type(0x10_0000_1000).is_none(),
"default resolve_arena_type must return None for any address",
);
assert!(
r.resolve_arena_type(0).is_none(),
"default resolve_arena_type must return None for null too",
);
assert!(
r.resolve_arena_type(u64::MAX).is_none(),
"default resolve_arena_type must return None for u64::MAX too",
);
}
#[test]
fn mem_reader_resolve_arena_type_override_returns_configured_hit() {
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
0x10_0000_1008u64,
ArenaResolveHit {
target_type_id: 7,
header_skip: 0,
},
);
arena_types.insert(
0x10_0000_2000u64,
ArenaResolveHit {
target_type_id: 11,
header_skip: 8,
},
);
let reader = CastStubReader {
arena_type_at: arena_types,
..Default::default()
};
assert_eq!(
reader.resolve_arena_type(0x10_0000_1008),
Some(ArenaResolveHit {
target_type_id: 7,
header_skip: 0,
}),
);
assert_eq!(
reader.resolve_arena_type(0x10_0000_2000),
Some(ArenaResolveHit {
target_type_id: 11,
header_skip: 8,
}),
);
assert!(
reader.resolve_arena_type(0x10_0000_3000).is_none(),
"address not in index must return None",
);
assert!(
reader.resolve_arena_type(0).is_none(),
"null address must return None",
);
}
fn bridge_btf_outer_fwd_taskctx() -> (Vec<u8>, u32, u32, u32) {
let mut strings: Vec<u8> = vec![0];
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let n_int = push(&mut strings, "u64");
let n_outer = push(&mut strings, "outer");
let n_fwd = push(&mut strings, "sdt_data");
let n_data = push(&mut strings, "data");
let n_task = push(&mut strings, "task_ctx");
let n_weight = push(&mut strings, "weight");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Fwd {
name_off: n_fwd,
is_union: false,
},
CastSynType::Ptr { type_id: 2 },
CastSynType::Struct {
name_off: n_outer,
size: 8,
members: vec![CastSynMember {
name_off: n_data,
type_id: 3,
byte_offset: 0,
}],
},
CastSynType::Struct {
name_off: n_task,
size: 8,
members: vec![CastSynMember {
name_off: n_weight,
type_id: 1,
byte_offset: 0,
}],
},
];
(cast_build_btf(&types, &strings), 4, 2, 5)
}
#[test]
fn arena_chase_fwd_target_resolved_via_bridge() {
let (blob, outer_id, _fwd_id, task_ctx_id) = bridge_btf_outer_fwd_taskctx();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: task_ctx_id,
header_skip: 0,
},
);
let reader = CastStubReader {
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected outer Struct render, got {v:?}");
};
assert_eq!(members.len(), 1);
assert_eq!(members[0].name, "data");
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!(
"data field must render as Ptr (BTF Type::Ptr arm); got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(
deref_skipped_reason.is_none(),
"bridge resolve must not surface a skip reason; got {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("bridge resolve must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be the resolved task_ctx struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("task_ctx"),
"bridge must land on the resolved struct's name, not the Fwd's name"
);
assert_eq!(inner_members.len(), 1);
assert_eq!(inner_members[0].name, "weight");
let RenderedValue::Uint { bits, value } = inner_members[0].value else {
panic!(
"task_ctx.weight must render as Uint, got {:?}",
inner_members[0].value
);
};
assert_eq!(bits, 64);
assert_eq!(value, 0x42);
assert_eq!(
cast_annotation.as_deref(),
Some("sdt_alloc"),
"Type::Ptr arm bridge resolve must surface 'sdt_alloc' annotation",
);
}
#[test]
fn arena_chase_fwd_target_resolved_via_bridge_slot_start_skips_header() {
let (blob, outer_id, _fwd_id, task_ctx_id) = bridge_btf_outer_fwd_taskctx();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const SLOT_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = SLOT_ADDR.to_le_bytes().to_vec();
let header_sentinel = 0xDEAD_BEEF_CAFE_BABEu64.to_le_bytes();
let payload_bytes = 0x42u64.to_le_bytes();
let mut slot_bytes = Vec::with_capacity(16);
slot_bytes.extend_from_slice(&header_sentinel);
slot_bytes.extend_from_slice(&payload_bytes);
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(SLOT_ADDR, slot_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
SLOT_ADDR,
ArenaResolveHit {
target_type_id: task_ctx_id,
header_skip: 8,
},
);
let reader = CastStubReader {
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected outer Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!("data field must render as Ptr; got {:?}", members[0].value);
};
assert_eq!(value, SLOT_ADDR);
assert!(
deref_skipped_reason.is_none(),
"slot-start bridge resolve must not surface a skip reason; got {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("slot-start bridge resolve must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be the resolved task_ctx struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("task_ctx"),
"bridge must land on the resolved struct's name even with slot-start skip",
);
let RenderedValue::Uint {
bits,
value: weight,
} = inner_members[0].value
else {
panic!(
"task_ctx.weight must render as Uint, got {:?}",
inner_members[0].value
);
};
assert_eq!(bits, 64);
assert_eq!(
weight, 0x42,
"slot-start chase must skip header — weight \
must be payload value 0x42, not header sentinel \
0xDEADBEEFCAFEBABE",
);
assert_eq!(
cast_annotation.as_deref(),
Some("sdt_alloc"),
"Type::Ptr arm slot-start bridge resolve must surface 'sdt_alloc' annotation",
);
}
#[test]
fn arena_chase_fwd_target_no_bridge_entry_skips() {
let (blob, outer_id, _fwd_id, _task_ctx_id) = bridge_btf_outer_fwd_taskctx();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let reader = CastStubReader {
arena_window: Some((ARENA_LO, ARENA_HI)),
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected outer Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
..
} = members[0].value
else {
panic!("data field must render as Ptr; got {:?}", members[0].value);
};
assert!(
deref.is_none(),
"no-bridge Fwd target must not produce a deref"
);
assert!(
cast_annotation.is_none(),
"no-bridge resolve must leave cast_annotation None on the Type::Ptr arm; got {cast_annotation:?}",
);
let reason = deref_skipped_reason
.as_deref()
.expect("Fwd-no-bridge must populate skip reason");
assert!(
reason.contains("forward declaration"),
"skip reason must surface the forward-declaration cause; got: {reason}",
);
assert!(
reason.contains("sdt_data"),
"skip reason must include the Fwd type name; got: {reason}",
);
}
#[test]
fn cast_chase_arena_fwd_target_resolved_via_bridge() {
let mut strings: Vec<u8> = vec![0];
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let n_int = push(&mut strings, "u64");
let n_t = push(&mut strings, "T");
let n_fwd = push(&mut strings, "sdt_data");
let n_task = push(&mut strings, "task_ctx");
let n_f = push(&mut strings, "f");
let n_weight = push(&mut strings, "weight");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 8,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Fwd {
name_off: n_fwd,
is_union: false,
},
CastSynType::Struct {
name_off: n_task,
size: 8,
members: vec![CastSynMember {
name_off: n_weight,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let local_fwd_id: u32 = 3;
let local_task_ctx_id: u32 = 4;
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x55u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: local_task_ctx_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: local_fwd_id,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!(
"intercept must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(
deref_skipped_reason.is_none(),
"successful chase: no skip reason; got {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("bridge-resolved cast must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be the resolved task_ctx Struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("task_ctx"),
"bridge must land on the resolved struct, not the Fwd"
);
assert_eq!(inner_members.len(), 1);
let RenderedValue::Uint { value, .. } = inner_members[0].value else {
panic!(
"task_ctx.weight must render as Uint, got {:?}",
inner_members[0].value
);
};
assert_eq!(value, 0x55);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→arena (sdt_alloc)"),
"cast intercept arena bridge must extend annotation with '(sdt_alloc)'",
);
}
#[test]
fn cast_chase_arena_fwd_target_no_bridge_keeps_plain_annotation() {
let mut strings: Vec<u8> = vec![0];
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let n_int = push(&mut strings, "u64");
let n_t = push(&mut strings, "T");
let n_fwd = push(&mut strings, "sdt_data");
let n_f = push(&mut strings, "f");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 8,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Fwd {
name_off: n_fwd,
is_union: false,
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let fwd_id: u32 = 3;
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: fwd_id,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
..
} = members[0].value
else {
panic!(
"intercept must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert!(
deref.is_none(),
"no-bridge Fwd cast must not produce a deref"
);
let reason = deref_skipped_reason
.as_deref()
.expect("Fwd-no-bridge must populate skip reason");
assert!(
reason.contains("forward declaration"),
"skip reason must surface forward-declaration cause; got: {reason}",
);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→arena"),
"no-bridge cast annotation must NOT include '(sdt_alloc)'; got {cast_annotation:?}",
);
}
#[test]
fn cast_chase_kernel_fwd_target_resolved_via_bridge() {
let mut strings: Vec<u8> = vec![0];
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let n_int = push(&mut strings, "u64");
let n_t = push(&mut strings, "T");
let n_fwd = push(&mut strings, "kern_fwd");
let n_real = push(&mut strings, "kern_real");
let n_f = push(&mut strings, "f");
let n_x = push(&mut strings, "x");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_t,
size: 8,
members: vec![CastSynMember {
name_off: n_f,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Fwd {
name_off: n_fwd,
is_union: false,
},
CastSynType::Struct {
name_off: n_real,
size: 8,
members: vec![CastSynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let fwd_id: u32 = 3;
let real_id: u32 = 4;
const KVA: u64 = 0xffff_8000_0000_4000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let inner_bytes = 0x77u64.to_le_bytes().to_vec();
let mut kva_bytes = std::collections::HashMap::new();
kva_bytes.insert(KVA, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
KVA,
ArenaResolveHit {
target_type_id: real_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: fwd_id,
addr_space: AddrSpace::Kernel,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
kva_bytes_at: kva_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
..
} = members[0].value
else {
panic!("intercept must produce Ptr; got {:?}", members[0].value);
};
assert!(
deref_skipped_reason.is_none(),
"kernel arm bridge resolve must succeed; got skip reason {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("kernel arm bridge resolve must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be kern_real Struct, got {inner:?}");
};
assert_eq!(inner_name.as_deref(), Some("kern_real"));
let RenderedValue::Uint { value, .. } = inner_members[0].value else {
panic!(
"kern_real.x must render as Uint, got {:?}",
inner_members[0].value
);
};
assert_eq!(value, 0x77);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→kernel (sdt_alloc)"),
"kernel arm bridge must extend annotation with '(sdt_alloc)'",
);
}
#[test]
fn cast_chase_arena_target_type_id_zero_resolves_via_resolve_arena_type() {
let (blob, t_id, q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: q_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!(
"intercept must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(
deref_skipped_reason.is_none(),
"deferred-resolve bridge fire must not surface a skip reason; \
got {deref_skipped_reason:?}"
);
let inner = deref
.as_deref()
.expect("deferred-resolve bridge must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = *inner
else {
panic!("deref payload must be the resolved Q Struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("Q"),
"bridge must land on the resolved struct's name (Q), \
not the analyzer's deferred sentinel",
);
let RenderedValue::Uint { value, .. } = inner_members[0].value else {
panic!("Q.x must render as Uint, got {:?}", inner_members[0].value);
};
assert_eq!(value, 0x42);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→arena (sdt_alloc)"),
"deferred-resolve bridge fire must extend annotation with \
'(sdt_alloc)' since `outcome.sdt_alloc_resolved` is set; \
got {cast_annotation:?}",
);
}
#[test]
fn cast_chase_arena_target_type_id_zero_no_bridge_entry_skips() {
let (blob, t_id, _q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!(
"intercept must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(
deref.is_none(),
"no-bridge deferred-resolve must not produce a deref"
);
let reason = deref_skipped_reason
.as_deref()
.expect("no-bridge deferred-resolve must populate skip reason");
assert!(
reason.contains("STX-flow path tagged slot as Arena"),
"skip reason must surface the analyzer's STX-flow tag cause; \
got: {reason}",
);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→arena"),
"no-bridge deferred-resolve must NOT include '(sdt_alloc)' suffix; \
got {cast_annotation:?}",
);
}
#[test]
fn cast_chase_already_rendered_short_circuits() {
let (blob, t_id, q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: q_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let mut rendered = std::collections::HashSet::new();
rendered.insert(TARGET_ADDR as u32);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
rendered_slot_addrs: rendered,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!(
"dedup must still produce Ptr (only the deref is suppressed); \
got {:?}",
members[0].value
);
};
assert_eq!(value, TARGET_ADDR);
assert!(
deref.is_none(),
"dedup short-circuit must suppress the deref"
);
let reason = deref_skipped_reason
.as_deref()
.expect("dedup must populate the skip reason");
assert_eq!(
reason, "already rendered in sdt_allocations",
"dedup skip reason is wire-stable (operator reads it from \
RenderedValue::Ptr::deref_skipped_reason); the exact format \
is part of the dump's machine-checkable contract: got '{reason}'"
);
}
#[test]
fn cast_chase_already_rendered_miss_proceeds_with_normal_chase() {
let (blob, t_id, q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
const RENDERED_OTHER_ADDR: u64 = 0x10_0000_2000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: q_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let mut rendered = std::collections::HashSet::new();
rendered.insert(RENDERED_OTHER_ADDR as u32);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
rendered_slot_addrs: rendered,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr { ref deref, .. } = members[0].value else {
panic!("expected Ptr, got {:?}", members[0].value);
};
let inner = deref
.as_deref()
.expect("dedup-miss path must still produce a deref via the bridge");
let RenderedValue::Struct {
type_name: ref inner_name,
..
} = *inner
else {
panic!("deref payload must be the resolved Q Struct, got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("Q"),
"dedup-miss path must land on Q via the normal chase pipeline",
);
}
#[test]
fn cast_chase_default_is_already_rendered_returns_false() {
let (blob, t_id, q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_1000;
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let inner_bytes = 0x42u64.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, inner_bytes);
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
TARGET_ADDR,
ArenaResolveHit {
target_type_id: q_id,
header_skip: 0,
},
);
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr { ref deref, .. } = members[0].value else {
panic!("expected Ptr, got {:?}", members[0].value);
};
assert!(
deref.is_some(),
"empty rendered_slot_addrs must NOT short-circuit the chase",
);
}
#[test]
fn cast_chase_kernel_target_type_id_zero_falls_through_with_mismatch_reason() {
let (blob, t_id, _q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const KVA: u64 = 0xffff_8000_0000_4000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: 0,
addr_space: AddrSpace::Arena,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
..Default::default()
};
let v = render_value_with_mem(&btf, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
} = members[0].value
else {
panic!(
"intercept must produce Ptr (not Uint); got {:?}",
members[0].value
);
};
assert_eq!(value, KVA);
assert!(
deref.is_none(),
"kernel-arm `target_type_id == 0` special case must skip the chase",
);
let reason = deref_skipped_reason
.as_deref()
.expect("kernel-arm `target_type_id == 0` must populate skip reason");
assert!(
reason.contains("kernel cast target unresolved"),
"skip reason must mention `kernel cast target unresolved`; \
got: {reason}",
);
assert!(
reason.contains("analyzer hinted Arena with deferred resolve"),
"skip reason must surface the analyzer-hint / runtime-window \
mismatch; got: {reason}",
);
assert_eq!(
cast_annotation.as_deref(),
Some("cast→kernel"),
"kernel-arm fall-through must use `cast→kernel` annotation \
(the path actually taken); got {cast_annotation:?}",
);
}
#[test]
fn arena_chase_bridge_address_outside_window_is_no_op() {
let (blob, outer_id, _fwd_id, task_ctx_id) = bridge_btf_outer_fwd_taskctx();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const OUT_OF_WINDOW: u64 = 0x0F_0000_1000;
let outer_bytes = OUT_OF_WINDOW.to_le_bytes().to_vec();
let mut arena_types = std::collections::HashMap::new();
arena_types.insert(
OUT_OF_WINDOW,
ArenaResolveHit {
target_type_id: task_ctx_id,
header_skip: 0,
},
);
let reader = CastStubReader {
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_type_at: arena_types,
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
..
} = members[0].value
else {
panic!("data field must render as Ptr; got {:?}", members[0].value);
};
assert!(
deref.is_none(),
"out-of-window pointer must not chase via the bridge"
);
assert!(
cast_annotation.is_none(),
"BTF Type::Ptr arm must leave cast_annotation None on the kptr branch"
);
let _ = deref_skipped_reason;
}
#[test]
fn cross_btf_fwd_resolve_renders_cgx_body_through_sibling_btf() {
use std::sync::Arc;
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let mut s_a = vec![0u8];
let n_a_u64 = push(&mut s_a, "u64");
let n_a_outer = push(&mut s_a, "outer");
let n_a_field = push(&mut s_a, "cgx_raw");
let n_a_cgx = push(&mut s_a, "cgx_target");
let types_a = vec![
CastSynType::Int {
name_off: n_a_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_a_outer,
size: 8,
members: vec![CastSynMember {
name_off: n_a_field,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Fwd {
name_off: n_a_cgx,
is_union: false,
},
];
let blob_a = cast_build_btf(&types_a, &s_a);
let btf_entry = Btf::from_bytes(&blob_a).expect("entry BTF parses");
let mut s_b = vec![0u8];
let n_b_u64 = push(&mut s_b, "u64");
let n_b_cgx = push(&mut s_b, "cgx_target");
let n_b_marker = push(&mut s_b, "marker");
let types_b = vec![
CastSynType::Int {
name_off: n_b_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_b_cgx,
size: 8,
members: vec![CastSynMember {
name_off: n_b_marker,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob_b = cast_build_btf(&types_b, &s_b);
let btf_sibling = Arc::new(Btf::from_bytes(&blob_b).expect("sibling BTF parses"));
const ARENA_LO: u64 = 0x10_0000_0000;
const ARENA_HI: u64 = 0x10_0001_0000;
const TARGET_ADDR: u64 = 0x10_0000_2000;
let outer_id = 2u32;
let cgx_fwd_id = 3u32;
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(outer_id, 0),
CastHit {
alloc_size: None,
target_type_id: cgx_fwd_id,
addr_space: AddrSpace::Arena,
},
);
let outer_bytes = TARGET_ADDR.to_le_bytes().to_vec();
let mut arena_bytes = std::collections::HashMap::new();
arena_bytes.insert(TARGET_ADDR, 0xCAFEu64.to_le_bytes().to_vec());
let mut cross_btf_index = std::collections::HashMap::new();
cross_btf_index.insert("cgx_target".to_string(), (0usize, 2u32, true));
let reader = CastStubReader {
cast_map: Some(cast_map),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: arena_bytes,
cross_btf_btfs: vec![btf_sibling.clone()],
cross_btf_index,
..Default::default()
};
let v = render_value_with_mem(&btf_entry, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr { ref deref, .. } = members[0].value else {
panic!("cgx_raw must render as Ptr; got {:?}", members[0].value);
};
let inner = deref
.as_ref()
.expect("cross-BTF Fwd resolve must produce a deref (sibling BTF body), but got None");
let RenderedValue::Struct {
ref type_name,
ref members,
} = **inner
else {
panic!("inner must be Struct (cgx_target body); got {inner:?}");
};
assert_eq!(
type_name.as_deref(),
Some("cgx_target"),
"rendered subtree must carry the sibling BTF's struct name"
);
assert_eq!(members.len(), 1);
assert_eq!(members[0].name, "marker");
let RenderedValue::Uint { value: marker, .. } = members[0].value else {
panic!("marker must render as Uint; got {:?}", members[0].value);
};
assert_eq!(
marker, 0xCAFE,
"rendered marker must come from the cross-BTF body's bytes"
);
let reader_no_bridge = CastStubReader {
cast_map: Some({
let mut m = crate::monitor::cast_analysis::CastMap::new();
m.insert(
(outer_id, 0),
CastHit {
alloc_size: None,
target_type_id: cgx_fwd_id,
addr_space: AddrSpace::Arena,
},
);
m
}),
arena_window: Some((ARENA_LO, ARENA_HI)),
arena_bytes_at: {
let mut a = std::collections::HashMap::new();
a.insert(TARGET_ADDR, 0xCAFEu64.to_le_bytes().to_vec());
a
},
..Default::default()
};
let v = render_value_with_mem(&btf_entry, outer_id, &outer_bytes, &reader_no_bridge);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!("cgx_raw must render as Ptr; got {:?}", members[0].value);
};
assert!(
deref.is_none(),
"without cross-BTF bridge, Fwd target must not chase"
);
let reason = deref_skipped_reason
.as_ref()
.expect("Fwd skip must populate deref_skipped_reason");
assert!(
reason.contains("cgx_target") && reason.contains("forward declaration"),
"skip reason must name the Fwd target: {reason:?}"
);
}
#[test]
fn cast_chase_kernel_cross_btf_fwd_resolve_succeeds() {
use std::sync::Arc;
let push = |s: &mut Vec<u8>, name: &str| -> u32 {
let off = s.len() as u32;
s.extend_from_slice(name.as_bytes());
s.push(0);
off
};
let mut s_entry = vec![0u8];
let n_e_u64 = push(&mut s_entry, "u64");
let n_e_t = push(&mut s_entry, "T");
let n_e_f = push(&mut s_entry, "f");
let n_e_kern = push(&mut s_entry, "kern_target");
let types_entry = vec![
CastSynType::Int {
name_off: n_e_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_e_t,
size: 8,
members: vec![CastSynMember {
name_off: n_e_f,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Fwd {
name_off: n_e_kern,
is_union: false,
},
];
let blob_entry = cast_build_btf(&types_entry, &s_entry);
let btf_entry = Btf::from_bytes(&blob_entry).expect("entry BTF parses");
let t_id: u32 = 2;
let kern_fwd_id: u32 = 3;
let mut s_sib = vec![0u8];
let n_s_u64 = push(&mut s_sib, "u64");
let n_s_kern = push(&mut s_sib, "kern_target");
let n_s_marker = push(&mut s_sib, "marker");
let types_sib = vec![
CastSynType::Int {
name_off: n_s_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_s_kern,
size: 8,
members: vec![CastSynMember {
name_off: n_s_marker,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob_sib = cast_build_btf(&types_sib, &s_sib);
let btf_sib = Arc::new(Btf::from_bytes(&blob_sib).expect("sibling BTF parses"));
const KVA: u64 = 0xffff_8000_0001_2000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let inner_bytes = 0xBEEFu64.to_le_bytes().to_vec();
let mut kva_bytes = std::collections::HashMap::new();
kva_bytes.insert(KVA, inner_bytes);
let mut cross_btf_index = std::collections::HashMap::new();
cross_btf_index.insert("kern_target".to_string(), (0usize, 2u32, true));
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(t_id, 0),
CastHit {
alloc_size: None,
target_type_id: kern_fwd_id,
addr_space: AddrSpace::Kernel,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
kva_bytes_at: kva_bytes,
cross_btf_btfs: vec![btf_sib.clone()],
cross_btf_index,
..Default::default()
};
let v = render_value_with_mem(&btf_entry, t_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected outer Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!(
"kernel cast intercept must surface as Ptr; got {:?}",
members[0].value
);
};
assert!(
deref_skipped_reason.is_none(),
"kernel-arm cross-BTF Fwd resolve must succeed; \
got skip reason {deref_skipped_reason:?}"
);
let inner = deref
.as_ref()
.expect("kernel-arm cross-BTF Fwd resolve must produce a deref");
let RenderedValue::Struct {
type_name: ref inner_name,
members: ref inner_members,
} = **inner
else {
panic!("deref payload must be the kern_target body Struct; got {inner:?}");
};
assert_eq!(
inner_name.as_deref(),
Some("kern_target"),
"rendered subtree must carry the sibling BTF's struct name",
);
assert_eq!(inner_members.len(), 1);
assert_eq!(inner_members[0].name, "marker");
let RenderedValue::Uint { value: marker, .. } = inner_members[0].value else {
panic!(
"kern_target.marker must render as Uint; got {:?}",
inner_members[0].value
);
};
assert_eq!(
marker, 0xBEEF,
"rendered marker must come from the kva-side body bytes \
decoded against the sibling BTF",
);
}