use super::*;
#[test]
fn cast_chase_kernel_target_type_id_unresolvable() {
let (blob, t_id, _q_id) = cast_btf_t_and_q();
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
const UNRESOLVABLE: u32 = 9999;
const KVA: u64 = 0xffff_8000_0000_1000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let reader = CastStubReader {
hit: Some(CastHit {
alloc_size: None,
target_type_id: UNRESOLVABLE,
addr_space: AddrSpace::Kernel,
}),
..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!(
"unresolvable target must still surface as Ptr; got {:?}",
members[0].value
);
};
assert_eq!(value, KVA);
assert!(
deref.is_none(),
"unresolvable target must not produce a deref payload"
);
let reason = deref_skipped_reason
.as_deref()
.expect("peel_modifiers failure must populate skip reason");
assert!(
reason.contains("unresolvable"),
"skip reason must mention 'unresolvable'; got: {reason}"
);
assert!(
reason.contains(&UNRESOLVABLE.to_string()),
"skip reason must include the offending type id; got: {reason}"
);
}
#[test]
fn cast_chase_kernel_target_btf_size_zero() {
let (strings, n_int, n_t, n_q, n_f, _n_x) = cast_strings_for_t_q();
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::Struct {
name_off: n_q,
size: 0,
members: vec![],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let t_id: u32 = 2;
let q_id: u32 = 3;
const KVA: u64 = 0xffff_8000_0000_1000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let reader = CastStubReader {
hit: Some(CastHit {
alloc_size: None,
target_type_id: q_id,
addr_space: AddrSpace::Kernel,
}),
..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!(
"zero-sized target must still surface as Ptr; got {:?}",
members[0].value
);
};
assert_eq!(value, KVA);
assert!(deref.is_none());
let reason = deref_skipped_reason
.as_deref()
.expect("zero-sized target must populate skip reason");
assert!(
reason.contains("BTF size is 0"),
"skip reason must say 'BTF size is 0'; got: {reason}"
);
assert!(
reason.contains("incomplete type"),
"skip reason must mention 'incomplete type'; got: {reason}"
);
}
#[test]
fn cast_chase_kernel_target_fwd_struct() {
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 KVA: u64 = 0xffff_8000_0000_3000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let reader = CastStubReader {
hit: Some(CastHit {
alloc_size: None,
target_type_id: fwd_id,
addr_space: AddrSpace::Kernel,
}),
..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!(
"Fwd target must still surface as Ptr; got {:?}",
members[0].value
);
};
assert_eq!(value, KVA);
assert!(
deref.is_none(),
"Fwd target must not produce a deref payload"
);
let reason = deref_skipped_reason
.as_deref()
.expect("Fwd target must populate skip reason");
assert!(
reason.contains("forward declaration"),
"skip reason must mention 'forward declaration'; got: {reason}"
);
assert!(
reason.contains("body not in this BTF"),
"skip reason must mention body absence; got: {reason}"
);
assert!(
reason.contains("sdt_data"),
"skip reason must include the Fwd type's name; got: {reason}"
);
assert!(
reason.contains("struct"),
"skip reason must say 'struct' (not 'union') for is_struct() Fwd; got: {reason}"
);
assert!(
reason.contains(&fwd_id.to_string()),
"skip reason must include the type id; got: {reason}"
);
assert!(
!reason.contains("has unresolvable size"),
"Fwd targets must not surface the generic fall-through; got: {reason}"
);
}
#[test]
fn cast_chase_kernel_target_fwd_union() {
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, "my_union");
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: true,
},
];
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 KVA: u64 = 0xffff_8000_0000_4000;
let outer_bytes = KVA.to_le_bytes().to_vec();
let reader = CastStubReader {
hit: Some(CastHit {
alloc_size: None,
target_type_id: fwd_id,
addr_space: AddrSpace::Kernel,
}),
..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_skipped_reason,
..
} = members[0].value
else {
panic!(
"Fwd union target must surface as Ptr; got {:?}",
members[0].value
);
};
let reason = deref_skipped_reason
.as_deref()
.expect("Fwd union target must populate skip reason");
assert!(
reason.contains("union my_union"),
"skip reason must surface 'union my_union'; got: {reason}"
);
assert!(
!reason.contains("struct my_union"),
"Fwd union must not be labelled 'struct'; got: {reason}"
);
}
#[test]
fn arena_chase_pointee_fwd_surfaces_descriptive_reason() {
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, "sdt_chunk");
let n_fwd = push(&mut strings, "sdt_data");
let n_data = push(&mut strings, "data");
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_t,
size: 8,
members: vec![CastSynMember {
name_off: n_data,
type_id: 3,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let chunk_id: u32 = 4;
let fwd_id: u32 = 2;
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, chunk_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected 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!(
cast_annotation.is_none(),
"BTF-typed pointers must leave cast_annotation None; got {cast_annotation:?}"
);
assert!(
deref.is_none(),
"Fwd pointee chase must not produce a deref payload"
);
let reason = deref_skipped_reason
.as_deref()
.expect("Fwd pointee must populate skip reason");
assert!(
reason.starts_with("arena chase"),
"BTF Ptr arm must use 'arena chase' label; got: {reason}"
);
assert!(
reason.contains("forward declaration"),
"skip reason must mention 'forward declaration'; got: {reason}"
);
assert!(
reason.contains("body not in this BTF"),
"skip reason must mention body absence; got: {reason}"
);
assert!(
reason.contains("sdt_data"),
"skip reason must include the Fwd type's name; got: {reason}"
);
assert!(
reason.contains("struct"),
"skip reason must say 'struct' (kind_flag=0); got: {reason}"
);
assert!(
reason.contains(&fwd_id.to_string()),
"skip reason must include the Fwd type id; got: {reason}"
);
assert!(
!reason.contains("has unresolvable size"),
"Fwd targets must not surface the legacy generic message; got: {reason}"
);
}
#[test]
fn arena_chase_pointee_fwd_anonymous() {
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, "wrap");
let n_data = push(&mut strings, "data");
let types = vec![
CastSynType::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Fwd {
name_off: 0,
is_union: false,
},
CastSynType::Ptr { type_id: 2 },
CastSynType::Struct {
name_off: n_t,
size: 8,
members: vec![CastSynMember {
name_off: n_data,
type_id: 3,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let chunk_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 reader = CastStubReader {
arena_window: Some((ARENA_LO, ARENA_HI)),
..Default::default()
};
let v = render_value_with_mem(&btf, chunk_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct render, got {v:?}");
};
let RenderedValue::Ptr {
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!("data field must render as Ptr; got {:?}", members[0].value);
};
let reason = deref_skipped_reason
.as_deref()
.expect("anonymous Fwd must populate skip reason");
assert!(
reason.contains("anonymous"),
"anonymous Fwd reason must say 'anonymous'; got: {reason}"
);
assert!(
reason.contains("struct forward declaration"),
"anonymous Fwd reason must mention the aggregate kind; got: {reason}"
);
}