use super::*;
fn cpumask_kptr_outer_btf(pointee_name: &str) -> (Btf, u32) {
let mut strings: Vec<u8> = vec![0];
let mut push = |name: &str| -> u32 {
let off = strings.len() as u32;
strings.extend_from_slice(name.as_bytes());
strings.push(0);
off
};
let n_u64 = push("u64");
let n_pointee = push(pointee_name);
let n_bits = push("bits");
let n_outer = push("outer");
let n_mask = push("mask");
let types = vec![
CastSynType::Int {
name_off: n_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_pointee,
size: 8,
members: vec![CastSynMember {
name_off: n_bits,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Ptr { type_id: 2 },
CastSynType::Struct {
name_off: n_outer,
size: 8,
members: vec![CastSynMember {
name_off: n_mask,
type_id: 3,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic cpumask BTF parses");
(btf, 4)
}
fn cpumask_read_bytes(word0: u64) -> Vec<u8> {
let mut b = vec![0u8; 1024];
b[..8].copy_from_slice(&word0.to_le_bytes());
b
}
#[test]
fn cpumask_kptr_member_chases_to_cpu_list() {
for name in ["cpumask", "bpf_cpumask"] {
let (btf, outer_id) = cpumask_kptr_outer_btf(name);
let val: u64 = 0xFFFF_8000_0010_0000; let outer_bytes = val.to_le_bytes().to_vec();
let mut kva = std::collections::HashMap::new();
kva.insert(val, cpumask_read_bytes(0b0000_1011));
let reader = CastStubReader {
kva_bytes_at: kva,
..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 for {name}, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
ref cast_annotation,
..
} = members[0].value
else {
panic!(
"mask field must render as Ptr for {name}; got {:?}",
members[0].value
);
};
assert_eq!(value, val, "raw pointer retained alongside deref ({name})");
assert!(
deref_skipped_reason.is_none(),
"valid cpumask must not skip ({name}): {deref_skipped_reason:?}"
);
assert!(
cast_annotation.is_none(),
"kptr branch leaves cast_annotation None ({name})"
);
match deref.as_deref() {
Some(RenderedValue::CpuList { cpus }) => {
assert_eq!(cpus, "0-1,3", "decoded cpu set ({name})")
}
other => panic!("expected deref Some(CpuList) for {name}, got {other:?}"),
}
}
}
#[test]
fn cpumask_kptr_null_pointer_no_chase() {
let (btf, outer_id) = cpumask_kptr_outer_btf("bpf_cpumask");
let outer_bytes = 0u64.to_le_bytes().to_vec(); let reader = CastStubReader::default();
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
let RenderedValue::Ptr {
value,
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!("mask field must render as Ptr; got {:?}", members[0].value);
};
assert_eq!(value, 0);
assert!(deref.is_none(), "NULL kptr must not chase");
assert!(
deref_skipped_reason.is_none(),
"NULL kptr is a clean skip with no reason, got {deref_skipped_reason:?}"
);
}
#[test]
fn cpumask_kptr_plausibility_gate_rejects_freed_slab_pattern() {
let (btf, outer_id) = cpumask_kptr_outer_btf("cpumask");
let val: u64 = 0xFFFF_8000_0010_0000;
let outer_bytes = val.to_le_bytes().to_vec();
let mut kva = std::collections::HashMap::new();
kva.insert(val, cpumask_read_bytes(0xFF00_0000_0000_0000));
let reader = CastStubReader {
kva_bytes_at: kva,
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!("mask field must render as Ptr; got {:?}", members[0].value);
};
assert!(
deref.is_none(),
"freed-slab pattern must not decode to a CpuList"
);
assert!(
deref_skipped_reason
.as_deref()
.is_some_and(|r| r.contains("plausibility")),
"skip reason must cite the plausibility gate, got {deref_skipped_reason:?}"
);
}
#[test]
fn llc_cpumask_struct_renders_as_cpu_list() {
let mut strings: Vec<u8> = vec![0];
let mut push = |name: &str| -> u32 {
let off = strings.len() as u32;
strings.extend_from_slice(name.as_bytes());
strings.push(0);
off
};
let n_u64 = push("u64");
let n_llc = push("llc_cpumask");
let n_bits = push("bits");
let types = vec![
CastSynType::Int {
name_off: n_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_llc,
size: 8,
members: vec![CastSynMember {
name_off: n_bits,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic llc_cpumask BTF parses");
let bytes = 0b10_0101u64.to_le_bytes();
match render_value(&btf, 2, &bytes) {
RenderedValue::CpuList { cpus } => assert_eq!(cpus, "0,2,5"),
other => panic!("expected CpuList for llc_cpumask, got {other:?}"),
}
}
#[test]
fn cpumask_kptr_all_ones_word_decodes_not_rejected() {
let (btf, outer_id) = cpumask_kptr_outer_btf("bpf_cpumask");
let val: u64 = 0xFFFF_8000_0010_0000;
let outer_bytes = val.to_le_bytes().to_vec();
let mut kva = std::collections::HashMap::new();
kva.insert(val, cpumask_read_bytes(u64::MAX));
let reader = CastStubReader {
kva_bytes_at: kva,
..Default::default()
};
let v = render_value_with_mem(&btf, outer_id, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!("mask field must render as Ptr; got {:?}", members[0].value);
};
match deref.as_deref() {
Some(RenderedValue::CpuList { cpus }) => assert_eq!(cpus, "0-63"),
other => panic!(
"all-ones mask must decode to cpus 0-63, got {other:?} \
(skip reason {deref_skipped_reason:?})"
),
}
}
#[test]
fn cpumask_kptr_through_kptr_typetag_chases_to_cpu_list() {
let mut strings: Vec<u8> = vec![0];
let mut push = |name: &str| -> u32 {
let off = strings.len() as u32;
strings.extend_from_slice(name.as_bytes());
strings.push(0);
off
};
let n_u64 = push("u64");
let n_cpumask = push("bpf_cpumask");
let n_bits = push("bits");
let n_kptr = push("kptr");
let n_outer = push("outer");
let n_mask = push("mask");
let types = vec![
CastSynType::Int {
name_off: n_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Struct {
name_off: n_cpumask,
size: 8,
members: vec![CastSynMember {
name_off: n_bits,
type_id: 1,
byte_offset: 0,
}],
},
CastSynType::Ptr { type_id: 2 },
CastSynType::TypeTag {
name_off: n_kptr,
type_id: 3,
},
CastSynType::Struct {
name_off: n_outer,
size: 8,
members: vec![CastSynMember {
name_off: n_mask,
type_id: 4,
byte_offset: 0,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic kptr-typetag BTF parses");
let val: u64 = 0xFFFF_8000_0010_0000;
let outer_bytes = val.to_le_bytes().to_vec();
let mut kva = std::collections::HashMap::new();
kva.insert(val, cpumask_read_bytes(0b0000_1011)); let reader = CastStubReader {
kva_bytes_at: kva,
..Default::default()
};
let v = render_value_with_mem(&btf, 5, &outer_bytes, &reader);
let RenderedValue::Struct { ref members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
let RenderedValue::Ptr {
ref deref,
ref deref_skipped_reason,
..
} = members[0].value
else {
panic!(
"kptr-tagged mask field must render as Ptr; got {:?}",
members[0].value
);
};
match deref.as_deref() {
Some(RenderedValue::CpuList { cpus }) => assert_eq!(cpus, "0-1,3"),
other => panic!(
"kptr TypeTag must peel to the Ptr cpumask chase; got {other:?} \
(skip reason {deref_skipped_reason:?})"
),
}
}
#[test]
fn rendered_value_as_bool_coerces_ptr_as_non_null() {
let non_null = RenderedValue::Ptr {
value: 0xffff_8000_0000_0000,
deref: None,
deref_skipped_reason: None,
cast_annotation: None,
};
let null = RenderedValue::Ptr {
value: 0,
deref: None,
deref_skipped_reason: None,
cast_annotation: None,
};
assert_eq!(non_null.as_bool(), Some(true), "non-null pointer is true");
assert_eq!(null.as_bool(), Some(false), "null pointer is false");
assert_eq!(non_null.as_u64(), Some(0xffff_8000_0000_0000));
assert_eq!(null.as_u64(), Some(0));
}
#[test]
fn rendered_value_as_bool_array_accepts_pointer_elements() {
let arr = RenderedValue::Array {
len: 3,
elements: vec![
RenderedValue::Ptr {
value: 0x1000,
deref: None,
deref_skipped_reason: None,
cast_annotation: None,
},
RenderedValue::Ptr {
value: 0,
deref: None,
deref_skipped_reason: None,
cast_annotation: None,
},
RenderedValue::Ptr {
value: 0xdead_beef,
deref: None,
deref_skipped_reason: None,
cast_annotation: None,
},
],
};
assert_eq!(
arr.as_bool_array(),
Some(vec![true, false, true]),
"an array of pointers coerces to a per-element non-null mask",
);
}