use super::*;
#[test]
fn rendered_value_as_i64_per_variant_arms() {
assert_eq!(
RenderedValue::Int {
bits: 32,
value: -7,
}
.as_i64(),
Some(-7),
"Int passes through unchanged",
);
assert_eq!(uint(42).as_i64(), Some(42), "Uint <= i64::MAX coerces");
assert_eq!(
uint(i64::MAX as u64 + 1).as_i64(),
None,
"Uint above i64::MAX rejects (no silent wrap to negative)",
);
assert_eq!(RenderedValue::Bool { value: true }.as_i64(), Some(1));
assert_eq!(RenderedValue::Bool { value: false }.as_i64(), Some(0));
assert_eq!(RenderedValue::Char { value: b'Z' }.as_i64(), Some(90));
assert_eq!(
enum_v(32, -5, Some("X"), true).as_i64(),
Some(-5),
"Enum surfaces its stored i64 verbatim",
);
assert_eq!(
RenderedValue::Float {
bits: 64,
value: 1.5,
}
.as_i64(),
None,
"Float has no lossless i64 coercion",
);
}
#[test]
fn rendered_value_as_f64_per_variant_arms() {
assert_eq!(
RenderedValue::Float {
bits: 32,
value: 2.5,
}
.as_f64(),
Some(2.5),
);
assert_eq!(
RenderedValue::Int {
bits: 32,
value: -3,
}
.as_f64(),
Some(-3.0),
"Int widens to f64",
);
assert_eq!(uint(8).as_f64(), Some(8.0), "Uint widens to f64");
assert_eq!(
enum_v(32, 4, None, true).as_f64(),
Some(4.0),
"Enum widens to f64",
);
assert_eq!(
RenderedValue::Bool { value: true }.as_f64(),
None,
"Bool has no f64 arm",
);
assert_eq!(struct_with(vec![]).as_f64(), None);
}
#[test]
fn rendered_value_as_bool_non_ptr_arms() {
assert_eq!(RenderedValue::Bool { value: false }.as_bool(), Some(false));
assert_eq!(uint(0).as_bool(), Some(false), "zero Uint is false");
assert_eq!(uint(9).as_bool(), Some(true), "non-zero Uint is true");
assert_eq!(
RenderedValue::Int {
bits: 32,
value: -1,
}
.as_bool(),
Some(true),
"non-zero Int is true",
);
assert_eq!(RenderedValue::Char { value: 0 }.as_bool(), Some(false));
assert_eq!(RenderedValue::Char { value: b'q' }.as_bool(), Some(true));
assert_eq!(enum_v(32, 0, None, false).as_bool(), Some(false));
assert_eq!(enum_v(32, 3, None, false).as_bool(), Some(true));
assert_eq!(
RenderedValue::Float {
bits: 64,
value: 0.0,
}
.as_bool(),
None,
"Float has no bool coercion",
);
}
#[test]
fn rendered_value_as_i64_and_f64_array_collect_and_reject() {
let i64_arr = RenderedValue::Array {
len: 3,
elements: vec![
RenderedValue::Int {
bits: 32,
value: -1,
},
RenderedValue::Int { bits: 32, value: 0 },
RenderedValue::Int { bits: 32, value: 5 },
],
};
assert_eq!(i64_arr.as_i64_array(), Some(vec![-1, 0, 5]));
let f64_arr = RenderedValue::Array {
len: 2,
elements: vec![
RenderedValue::Float {
bits: 64,
value: 1.0,
},
RenderedValue::Float {
bits: 64,
value: 2.0,
},
],
};
assert_eq!(f64_arr.as_f64_array(), Some(vec![1.0, 2.0]));
let mixed = RenderedValue::Array {
len: 2,
elements: vec![
RenderedValue::Float {
bits: 64,
value: 1.0,
},
RenderedValue::Bool { value: true },
],
};
assert_eq!(
mixed.as_f64_array(),
None,
"a non-coercible element rejects the whole collection",
);
assert_eq!(uint(1).as_i64_array(), None);
}
#[test]
fn rendered_value_array_accessor_peels_ptr_deref() {
let inner = RenderedValue::Array {
len: 2,
elements: vec![uint(11), uint(22)],
};
let ptr = RenderedValue::Ptr {
value: 0x4000,
deref: Some(Box::new(inner)),
deref_skipped_reason: None,
cast_annotation: None,
};
assert_eq!(
ptr.as_u64_array(),
Some(vec![11, 22]),
"as_u64_array peels Ptr{{deref: Some(Array)}}",
);
assert!(matches!(
ptr.index(1),
Some(RenderedValue::Uint { value: 22, .. })
));
}
#[test]
fn display_unsigned_enum_renders_wire_value_not_negative() {
let unsigned_max = enum_v(64, -1_i64, None, false);
assert_eq!(
format!("{unsigned_max}"),
"18446744073709551615",
"unsigned enum displays its u64 wire value",
);
let signed_neg = enum_v(64, -1_i64, None, true);
assert_eq!(format!("{signed_neg}"), "-1");
let named = enum_v(64, -1_i64, Some("ALL"), false);
assert_eq!(format!("{named}"), "ALL (18446744073709551615)");
}
#[test]
fn display_ptr_with_cast_annotation_surfaces_tag() {
let v = RenderedValue::Ptr {
value: 0xdead_beef,
deref: None,
deref_skipped_reason: None,
cast_annotation: Some(std::borrow::Cow::Borrowed("cast→arena")),
};
assert_eq!(format!("{v}"), "0xdeadbeef (cast→arena)");
}
#[test]
fn display_ptr_cycle_reason_collapses_to_dense_marker() {
let v = RenderedValue::Ptr {
value: 0xabcd,
deref: None,
deref_skipped_reason: Some("cycle → 0xabcd".to_string()),
cast_annotation: None,
};
assert_eq!(format!("{v}"), "0xabcd [cycle]");
}
#[test]
fn display_multiline_struct_column_alignment_pads_equals() {
let mk = |name: &str, value: u64| RenderedMember {
name: name.into(),
value: RenderedValue::Uint { bits: 64, value },
};
let v = RenderedValue::Struct {
type_name: Some("wide".into()),
members: vec![
mk("a", 111_111_111_111),
mk("bb", 222_222_222_222),
mk("cc", 333_333_333_333),
mk("a_long_name", 444_444_444_444),
mk("dd", 555_555_555_555),
mk("ee", 666_666_666_666),
mk("ff", 777_777_777_777),
mk("gg", 888_888_888_888),
mk("hh", 999_999_999_999),
],
};
let out = format!("{v}");
assert!(
out.starts_with("wide:"),
"multi-line breadcrumb form: {out}"
);
assert!(
out.contains("a_long_name = 444444444444"),
"long column-0 name uses the ` = ` aligned form: {out}",
);
let padded = format!("a{} = 111111111111", " ".repeat(11 - 1));
assert!(
out.contains(&padded),
"short column-0 name must be padded to width 11 before the aligned \
` = `; looked for {padded:?} in: {out}",
);
}
#[test]
fn display_inline_array_wraps_past_budget() {
let elements: Vec<RenderedValue> = (0..12)
.map(|_| RenderedValue::Uint {
bits: 64,
value: 0x1111_2222_3333_4444,
})
.collect();
let v = RenderedValue::Array { len: 12, elements };
let out = format!("{v}");
assert!(
out.contains('\n'),
"over-budget contiguous array must wrap to multiple lines: {out}",
);
assert!(
out.starts_with('['),
"wrap output still opens with `[`: {out}"
);
assert!(
out.ends_with(']'),
"wrap output still closes with `]`: {out}"
);
assert_eq!(
out.matches("0x1111222233334444").count(),
12,
"all 12 elements present after wrap: {out}",
);
assert!(out.contains(",\n"), "wrap breaks after the comma: {out}");
}
#[test]
fn display_8bit_int_uint_arrays_render_as_quoted_strings() {
let int_str = RenderedValue::Array {
len: 3,
elements: vec![
RenderedValue::Int {
bits: 8,
value: b'h' as i64,
},
RenderedValue::Int {
bits: 8,
value: b'i' as i64,
},
RenderedValue::Int { bits: 8, value: 0 },
],
};
assert_eq!(
format!("{int_str}"),
"\"hi\"",
"8-bit Int array reads as a C string"
);
let uint_str = RenderedValue::Array {
len: 3,
elements: vec![
RenderedValue::Uint {
bits: 8,
value: b'o' as u64,
},
RenderedValue::Uint {
bits: 8,
value: b'k' as u64,
},
RenderedValue::Uint { bits: 8, value: 0 },
],
};
assert_eq!(
format!("{uint_str}"),
"\"ok\"",
"8-bit Uint array reads as a C string"
);
}
#[test]
fn display_8bit_uint_multiline_string_uses_pipe_block() {
let v = RenderedValue::Array {
len: 5,
elements: vec![
RenderedValue::Uint {
bits: 8,
value: b'x' as u64,
},
RenderedValue::Uint {
bits: 8,
value: b'\n' as u64,
},
RenderedValue::Uint {
bits: 8,
value: b'y' as u64,
},
RenderedValue::Uint { bits: 8, value: 0 },
RenderedValue::Uint { bits: 8, value: 0 },
],
};
let out = format!("{v}");
assert!(
out.starts_with("|\n"),
"multiline string uses pipe block: {out}"
);
assert!(out.contains("x"), "first segment present: {out}");
assert!(out.contains("y"), "second segment present: {out}");
}
#[test]
fn display_block_array_truncation_note_when_fewer_shown() {
let mk = |x: u64| RenderedValue::Struct {
type_name: Some("s".into()),
members: vec![RenderedMember {
name: "x".into(),
value: RenderedValue::Uint { bits: 32, value: x },
}],
};
let v = RenderedValue::Array {
len: 5,
elements: vec![mk(1), mk(2)],
};
let out = format!("{v}");
assert!(
out.contains("/* 2 of 5 shown */"),
"block render must note partial element coverage: {out}",
);
}
#[test]
fn display_block_array_struct_run_breaks_on_member_count_mismatch() {
let two_field = |a: u64| RenderedValue::Struct {
type_name: Some("s".into()),
members: vec![
RenderedMember {
name: "a".into(),
value: RenderedValue::Uint { bits: 32, value: a },
},
RenderedMember {
name: "b".into(),
value: RenderedValue::Uint { bits: 32, value: 2 },
},
],
};
let one_field = RenderedValue::Struct {
type_name: Some("s".into()),
members: vec![RenderedMember {
name: "a".into(),
value: RenderedValue::Uint { bits: 32, value: 9 },
}],
};
let v = RenderedValue::Array {
len: 3,
elements: vec![two_field(1), two_field(7), one_field],
};
let out = format!("{v}");
assert!(
out.contains("[2] "),
"differing-shape struct renders standalone: {out}"
);
assert!(
out.contains("a=9"),
"the one-field struct's value surfaces: {out}"
);
assert!(out.contains("a=1"), "first struct's value surfaces: {out}");
assert!(out.contains("a=7"), "second struct's value surfaces: {out}");
}
#[test]
fn display_anonymous_struct_template_omits_name_and_suppresses_zero_common_field() {
let mk = |v: u64| RenderedValue::Struct {
type_name: None,
members: vec![
RenderedMember {
name: "pad".into(),
value: RenderedValue::Uint { bits: 32, value: 0 },
},
RenderedMember {
name: "v".into(),
value: RenderedValue::Uint { bits: 32, value: v },
},
],
};
let arr = RenderedValue::Array {
len: 3,
elements: vec![mk(1), mk(2), mk(3)],
};
let out = format!("{arr}");
assert!(
out.contains("[0-2]:"),
"anonymous template uses nameless `[range]:` header: {out}",
);
assert!(
!out.contains("pad="),
"all-zero common field must be suppressed from the template: {out}",
);
assert!(out.contains("v: "), "varying field present: {out}");
assert!(out.contains("[0]="), "per-index marker present: {out}");
}
#[test]
fn render_char_int_renders_as_char_variant() {
let mut strings: Vec<u8> = vec![0];
let n = strings.len() as u32;
strings.extend_from_slice(b"char\0");
let types = vec![CastSynType::Int {
name_off: n,
size: 1,
encoding: 2,
offset: 0,
bits: 8,
}];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let v = render_value(&btf, 1, b"Q");
assert_eq!(v, RenderedValue::Char { value: b'Q' });
}
#[test]
fn render_int_wider_than_8_bytes_falls_back_to_bytes() {
let mut strings: Vec<u8> = vec![0];
let n = strings.len() as u32;
strings.extend_from_slice(b"__int128\0");
let types = vec![CastSynType::Int {
name_off: n,
size: 16,
encoding: 0,
offset: 0,
bits: 128,
}];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let bytes: Vec<u8> = (0u8..16).collect();
let v = render_value(&btf, 1, &bytes);
match v {
RenderedValue::Bytes { hex } => {
assert_eq!(
hex, "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f",
"wide int dumps every byte as lowercase hex",
);
}
other => panic!("expected Bytes hex fallback for a 16-byte int, got {other:?}"),
}
}
#[test]
fn render_enum_truncated_and_variant_resolution() {
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_e = push(&mut strings, "E");
let n_a = push(&mut strings, "A");
let n_b = push(&mut strings, "B");
let types = vec![CastSynType::Enum {
name_off: n_e,
size: 4,
signed: false,
members: vec![(n_a, 1), (n_b, 2)],
}];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let trunc = render_value(&btf, 1, &[0x01, 0x00]);
match trunc {
RenderedValue::Truncated {
needed,
had,
partial,
} => {
assert_eq!(needed, 4);
assert_eq!(had, 2);
assert_eq!(
*partial,
RenderedValue::Bytes {
hex: "01 00".into()
}
);
}
other => panic!("expected Truncated enum, got {other:?}"),
}
let v = render_value(&btf, 1, &[0x02, 0x00, 0x00, 0x00]);
assert_eq!(v, enum_v(32, 2, Some("B"), false));
}
#[test]
fn render_enum64_truncated_and_variant_resolution() {
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_e = push(&mut strings, "E64");
let n_hi = push(&mut strings, "HI");
let types = vec![CastSynType::Enum64 {
name_off: n_e,
size: 8,
signed: false,
members: vec![(n_hi, 0x1_0000_0000u64)],
}];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let trunc = render_value(&btf, 1, &[0, 0, 0, 0]);
match trunc {
RenderedValue::Truncated { needed, had, .. } => {
assert_eq!(needed, 8);
assert_eq!(had, 4);
}
other => panic!("expected Truncated enum64, got {other:?}"),
}
let bytes = 0x1_0000_0000u64.to_le_bytes();
let v = render_value(&btf, 1, &bytes);
assert_eq!(v, enum_v(64, 0x1_0000_0000, Some("HI"), false));
}
#[test]
fn render_standalone_var_forwards_to_underlying_type() {
const BTF_KIND_VAR: u32 = 14;
let mut strings: Vec<u8> = vec![0];
let n_u32 = strings.len() as u32;
strings.extend_from_slice(b"u32\0");
let n_g = strings.len() as u32;
strings.extend_from_slice(b"g\0");
let mut type_section = Vec::new();
type_section.extend_from_slice(&n_u32.to_le_bytes());
type_section.extend_from_slice(&((1u32 << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&4u32.to_le_bytes());
type_section.extend_from_slice(&32u32.to_le_bytes());
type_section.extend_from_slice(&n_g.to_le_bytes());
type_section.extend_from_slice(&((BTF_KIND_VAR << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&1u32.to_le_bytes()); type_section.extend_from_slice(&1u32.to_le_bytes());
let type_len = type_section.len() as u32;
let str_len = strings.len() as u32;
let mut blob = Vec::new();
blob.extend_from_slice(&0xEB9Fu16.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&24u32.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&str_len.to_le_bytes());
blob.extend_from_slice(&type_section);
blob.extend_from_slice(&strings);
let btf = Btf::from_bytes(&blob).expect("synthetic Var BTF parses");
let v = render_value(&btf, 2, &0xDEADu32.to_le_bytes());
assert_eq!(
v,
RenderedValue::Uint {
bits: 32,
value: 0xDEAD,
},
"standalone Var renders its underlying u32 against the bytes",
);
}
#[test]
fn render_fwd_type_is_unsupported_forward_declaration() {
let mut strings: Vec<u8> = vec![0];
let n = strings.len() as u32;
strings.extend_from_slice(b"opaque\0");
let types = vec![CastSynType::Fwd {
name_off: n,
is_union: false,
}];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let v = render_value(&btf, 1, &[0u8; 8]);
match v {
RenderedValue::Unsupported { reason } => {
assert!(
reason.contains("forward declaration"),
"Fwd render reason names the forward declaration: {reason}",
);
}
other => panic!("expected Unsupported for Fwd, got {other:?}"),
}
}
#[test]
fn render_bitfield_non_int_non_enum_base_is_unsigned() {
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_u64 = push(&mut strings, "u64");
let n_b = push(&mut strings, "B");
let n_f = push(&mut strings, "f");
let types = vec![
CastSynType::Int {
name_off: n_u64,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
CastSynType::Ptr { type_id: 1 },
CastSynType::BitfieldStruct {
name_off: n_b,
size: 8,
members: vec![CastSynBitMember {
name_off: n_f,
type_id: 2,
bit_offset: 0,
bitfield_size: 4,
}],
},
];
let blob = cast_build_btf(&types, &strings);
let btf = Btf::from_bytes(&blob).expect("synthetic BTF parses");
let mut bytes = [0u8; 8];
bytes[0] = 0x0F;
let v = render_value(&btf, 3, &bytes);
assert_eq!(
sole_member_value(&v),
&RenderedValue::Uint {
bits: 4,
value: 0xF
},
"a non-Int / non-Enum bitfield base is rendered unsigned",
);
}
#[test]
fn render_struct_sizes_array_and_const_members_via_type_size() {
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_u32 = push(&mut strings, "u32");
let n_s = push(&mut strings, "S");
let n_arr = push(&mut strings, "arr");
let n_c = push(&mut strings, "c");
const BTF_KIND_ARRAY: u32 = 3;
let mut type_section = Vec::new();
type_section.extend_from_slice(&n_u32.to_le_bytes());
type_section.extend_from_slice(&((1u32 << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&4u32.to_le_bytes());
type_section.extend_from_slice(&32u32.to_le_bytes());
type_section.extend_from_slice(&0u32.to_le_bytes()); type_section.extend_from_slice(&((BTF_KIND_ARRAY << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&0u32.to_le_bytes()); type_section.extend_from_slice(&1u32.to_le_bytes()); type_section.extend_from_slice(&1u32.to_le_bytes()); type_section.extend_from_slice(&3u32.to_le_bytes()); const BTF_KIND_CONST: u32 = 10;
type_section.extend_from_slice(&0u32.to_le_bytes());
type_section.extend_from_slice(&((BTF_KIND_CONST << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&1u32.to_le_bytes());
type_section.extend_from_slice(&n_s.to_le_bytes());
let vlen = 2u32;
type_section.extend_from_slice(&(((4u32 << 24) & 0x1f00_0000) | (vlen & 0xffff)).to_le_bytes());
type_section.extend_from_slice(&16u32.to_le_bytes());
type_section.extend_from_slice(&n_arr.to_le_bytes());
type_section.extend_from_slice(&2u32.to_le_bytes());
type_section.extend_from_slice(&0u32.to_le_bytes());
type_section.extend_from_slice(&n_c.to_le_bytes());
type_section.extend_from_slice(&3u32.to_le_bytes());
type_section.extend_from_slice(&(96u32).to_le_bytes());
let type_len = type_section.len() as u32;
let str_len = strings.len() as u32;
let mut blob = Vec::new();
blob.extend_from_slice(&0xEB9Fu16.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&24u32.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&str_len.to_le_bytes());
blob.extend_from_slice(&type_section);
blob.extend_from_slice(&strings);
let btf = Btf::from_bytes(&blob).expect("synthetic array/const BTF parses");
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&1u32.to_le_bytes());
bytes[4..8].copy_from_slice(&2u32.to_le_bytes());
bytes[8..12].copy_from_slice(&3u32.to_le_bytes());
bytes[12..16].copy_from_slice(&7u32.to_le_bytes());
let v = render_value(&btf, 4, &bytes);
let RenderedValue::Struct { members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
assert_eq!(members.len(), 2);
assert_eq!(members[0].name, "arr");
assert_eq!(
members[0].value,
RenderedValue::Array {
len: 3,
elements: vec![uint32(1), uint32(2), uint32(3)],
},
"array member sized as len*elem via type_size",
);
assert_eq!(members[1].name, "c");
assert_eq!(
members[1].value,
uint32(7),
"const-wrapped member peeled and sized via type_size",
);
}
fn uint32(value: u64) -> RenderedValue {
RenderedValue::Uint { bits: 32, value }
}
#[test]
fn cast_chase_kernel_func_target_surfaces_func_reason() {
const BTF_KIND_FUNC_PROTO: u32 = 13;
const BTF_KIND_FUNC: u32 = 12;
let mut strings: Vec<u8> = vec![0];
let n_u64 = strings.len() as u32;
strings.extend_from_slice(b"u64\0");
let n_fn = strings.len() as u32;
strings.extend_from_slice(b"fn\0");
let mut type_section = Vec::new();
type_section.extend_from_slice(&n_u64.to_le_bytes());
type_section.extend_from_slice(&((1u32 << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&8u32.to_le_bytes());
type_section.extend_from_slice(&64u32.to_le_bytes());
type_section.extend_from_slice(&0u32.to_le_bytes());
type_section.extend_from_slice(&((BTF_KIND_FUNC_PROTO << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&1u32.to_le_bytes()); type_section.extend_from_slice(&n_fn.to_le_bytes());
type_section.extend_from_slice(&((BTF_KIND_FUNC << 24) & 0x1f00_0000).to_le_bytes());
type_section.extend_from_slice(&2u32.to_le_bytes()); let n_t = strings.len() as u32;
strings.extend_from_slice(b"T\0");
let n_f = strings.len() as u32;
strings.extend_from_slice(b"f\0");
type_section.extend_from_slice(&n_t.to_le_bytes());
type_section.extend_from_slice(&(((4u32 << 24) & 0x1f00_0000) | 1).to_le_bytes());
type_section.extend_from_slice(&8u32.to_le_bytes());
type_section.extend_from_slice(&n_f.to_le_bytes());
type_section.extend_from_slice(&1u32.to_le_bytes());
type_section.extend_from_slice(&0u32.to_le_bytes());
let type_len = type_section.len() as u32;
let str_len = strings.len() as u32;
let mut blob = Vec::new();
blob.extend_from_slice(&0xEB9Fu16.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&24u32.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&str_len.to_le_bytes());
blob.extend_from_slice(&type_section);
blob.extend_from_slice(&strings);
let btf = Btf::from_bytes(&blob).expect("synthetic Func BTF parses");
const KVA: u64 = 0xffff_9000_0000_1000;
let mut cast_map = crate::monitor::cast_analysis::CastMap::new();
cast_map.insert(
(4, 0),
CastHit {
alloc_size: None,
target_type_id: 3,
addr_space: AddrSpace::Kernel,
},
);
let reader = CastStubReader {
cast_map: Some(cast_map),
..Default::default()
};
let outer = KVA.to_le_bytes().to_vec();
let v = render_value_with_mem(&btf, 4, &outer, &reader);
let RenderedValue::Struct { members, .. } = v else {
panic!("expected Struct, got {v:?}");
};
let RenderedValue::Ptr {
deref,
deref_skipped_reason,
..
} = &members[0].value
else {
panic!("cast member must render as Ptr, got {:?}", members[0].value);
};
assert!(deref.is_none(), "func target cannot be chased: deref None");
let reason = deref_skipped_reason
.as_deref()
.expect("a Func chase target must record a skip reason");
assert!(
reason.contains("function") && reason.contains("no storage size"),
"Func target surfaces the function-specific unsizable reason: {reason}",
);
}