1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
//@ ignore-cross-compile
//@ ignore-wasm (`object` can't handle wasm object files)
//@ ignore-windows
// This test should be replaced with one in tests/debuginfo once GDB or LLDB support 128-bit enums.
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use gimli::read::DebuggingInformationEntry;
use gimli::{AttributeValue, EndianRcSlice, Reader, RunTimeEndian};
use object::{Object, ObjectSection};
use run_make_support::{gimli, object, rfs, rustc};
fn main() {
// Before LLVM 20, 128-bit enums with variants didn't emit debuginfo correctly.
// This check can be removed once Rust no longer supports LLVM 18 and 19.
let llvm_version = rustc()
.verbose()
.arg("--version")
.run()
.stdout_utf8()
.lines()
.filter_map(|line| line.strip_prefix("LLVM version: "))
.map(|version| version.split(".").next().unwrap().parse::<u32>().unwrap())
.next()
.unwrap();
let is_old_llvm = llvm_version < 20;
let output = PathBuf::from("repr128");
let mut rustc = rustc();
if is_old_llvm {
rustc.cfg("old_llvm");
}
rustc.input("main.rs").output(&output).arg("-Cdebuginfo=2").run();
// Mach-O uses packed debug info
let dsym_location = output
.with_extension("dSYM")
.join("Contents")
.join("Resources")
.join("DWARF")
.join("repr128");
let output =
rfs::read(if dsym_location.try_exists().unwrap() { dsym_location } else { output });
let obj = object::File::parse(output.as_slice()).unwrap();
let endian = if obj.is_little_endian() { RunTimeEndian::Little } else { RunTimeEndian::Big };
let dwarf = gimli::Dwarf::load(|section| -> Result<_, ()> {
let data = obj.section_by_name(section.name()).map(|s| s.uncompressed_data().unwrap());
Ok(EndianRcSlice::new(Rc::from(data.unwrap_or_default().as_ref()), endian))
})
.unwrap();
let mut iter = dwarf.units();
let mut enumerators_to_find = HashMap::from([
("U128A", 0_u128),
("U128B", 1_u128),
("U128C", u64::MAX as u128 + 1),
("U128D", u128::MAX),
("I128A", 0_i128 as u128),
("I128B", (-1_i128) as u128),
("I128C", i128::MIN as u128),
("I128D", i128::MAX as u128),
]);
let mut variants_to_find = HashMap::from([
("VariantU128A", 0_u128),
("VariantU128B", 1_u128),
("VariantU128C", u64::MAX as u128 + 1),
("VariantU128D", u128::MAX),
("VariantI128A", 0_i128 as u128),
("VariantI128B", (-1_i128) as u128),
("VariantI128C", i128::MIN as u128),
("VariantI128D", i128::MAX as u128),
]);
while let Some(header) = iter.next().unwrap() {
let unit = dwarf.unit(header).unwrap();
let mut cursor = unit.entries();
let get_name = |entry: &DebuggingInformationEntry<'_, '_, _>| {
let name = dwarf
.attr_string(
&unit,
entry.attr(gimli::constants::DW_AT_name).unwrap().unwrap().value(),
)
.unwrap();
name.to_string().unwrap().to_string()
};
while let Some((_, entry)) = cursor.next_dfs().unwrap() {
match entry.tag() {
gimli::constants::DW_TAG_variant if !is_old_llvm => {
let Some(value) = entry.attr(gimli::constants::DW_AT_discr_value).unwrap()
else {
// `std` enums might have variants without `DW_AT_discr_value`.
continue;
};
let value = match value.value() {
AttributeValue::Block(value) => value.to_slice().unwrap().to_vec(),
// `std` has non-repr128 enums which don't use `AttributeValue::Block`.
value => continue,
};
// The `DW_TAG_member` that is a child of `DW_TAG_variant` will contain the
// variant's name.
let Some((1, child_entry)) = cursor.next_dfs().unwrap() else {
panic!("Missing child of DW_TAG_variant");
};
assert_eq!(child_entry.tag(), gimli::constants::DW_TAG_member);
let name = get_name(child_entry);
if let Some(expected) = variants_to_find.remove(name.as_str()) {
// This test uses LE byte order is used for consistent values across
// architectures.
assert_eq!(value.as_slice(), expected.to_le_bytes().as_slice(), "{name}");
}
}
gimli::constants::DW_TAG_enumerator => {
let name = get_name(entry);
if let Some(expected) = enumerators_to_find.remove(name.as_str()) {
match entry
.attr(gimli::constants::DW_AT_const_value)
.unwrap()
.unwrap()
.value()
{
AttributeValue::Block(value) => {
// This test uses LE byte order is used for consistent values across
// architectures.
assert_eq!(
value.to_slice().unwrap(),
expected.to_le_bytes().as_slice(),
"{name}"
);
}
value => panic!("{name}: unexpected DW_AT_const_value of {value:?}"),
}
}
}
_ => {}
}
}
}
if !enumerators_to_find.is_empty() {
panic!("Didn't find debug enumerator entries for {enumerators_to_find:?}");
}
if !is_old_llvm && !variants_to_find.is_empty() {
panic!("Didn't find debug variant entries for {variants_to_find:?}");
}
}
|