use std::{
collections::{BTreeSet, HashMap, VecDeque},
ffi::{CStr, c_char, c_int, c_void},
path::PathBuf,
};
use llvm_sys::{
LLVMDiagnosticSeverity, LLVMLinkage, LLVMOpcode,
bit_reader::LLVMParseBitcodeInContext2,
core::*,
debuginfo::{
LLVMDIFileGetDirectory, LLVMDIFileGetFilename, LLVMDILocationGetColumn,
LLVMDILocationGetInlinedAt, LLVMDILocationGetLine, LLVMDILocationGetScope,
LLVMDIScopeGetFile, LLVMGetMetadataKind, LLVMGetSubprogram, LLVMInstructionGetDebugLoc,
LLVMMetadataKind,
},
error::{LLVMDisposeErrorMessage, LLVMErrorRef, LLVMGetErrorMessage},
prelude::*,
transforms::pass_builder::{
LLVMCreatePassBuilderOptions, LLVMDisposePassBuilderOptions, LLVMRunPasses,
},
};
use rllvm_core::error::Error;
use crate::{
facts::*,
load::{LoadedModule, SourceState},
};
unsafe extern "C" {
fn __cxa_demangle(
name: *const c_char,
output: *mut c_char,
length: *mut usize,
status: *mut c_int,
) -> *mut c_char;
fn free(pointer: *mut c_void);
}
pub fn demangle(symbol: &str) -> Option<String> {
if let Some(rust) = symbol
.starts_with("_R")
.then(|| rustc_demangle::try_demangle(symbol).ok())
.flatten()
{
return Some(format!("{rust:#}"));
}
if !symbol.starts_with("_Z") {
return None;
}
let input = std::ffi::CString::new(symbol).ok()?;
let mut status: c_int = 0;
let output = unsafe {
__cxa_demangle(
input.as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut status,
)
};
if output.is_null() {
return None;
}
let text = unsafe { CStr::from_ptr(output) }
.to_string_lossy()
.into_owned();
unsafe { free(output.cast()) };
(status == 0).then_some(text)
}
pub fn llvm_version() -> String {
let (mut major, mut minor, mut patch) = (0, 0, 0);
unsafe { llvm_sys::core::LLVMGetVersion(&mut major, &mut minor, &mut patch) };
format!("{major}.{minor}.{patch}")
}
const NEUTRAL_MODULE_ID: &str = "";
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ModuleFacts {
pub functions: Vec<FunctionFact>,
pub call_sites: Vec<CallSiteFact>,
pub uses: Vec<UseFact>,
pub producers: Vec<String>,
pub diagnostics: Vec<String>,
}
impl ModuleFacts {
pub fn bind_to_catalog(
&mut self,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) {
let stamp = |id: &mut FunctionId| id.module_id = module_id.to_string();
let resolve = |location: &mut Option<SourceLocation>| {
if let Some(location) = location {
resolve_status(location, module_id, source_status);
}
};
for function in &mut self.functions {
stamp(&mut function.id);
if let Some(target) = &mut function.alias_of {
stamp(target);
}
resolve(&mut function.location);
}
for site in &mut self.call_sites {
stamp(&mut site.id.function);
resolve(&mut site.location);
match &mut site.target {
CallTarget::Direct { callee } => stamp(callee),
CallTarget::Indirect {
llvm_target_bound: Some(bound),
..
} => bound.iter_mut().for_each(stamp),
CallTarget::Indirect { .. }
| CallTarget::Intrinsic { .. }
| CallTarget::InlineAsm => {}
}
}
for use_fact in &mut self.uses {
stamp(&mut use_fact.used);
if let Some(function) = &mut use_fact.in_function {
stamp(function);
}
resolve(&mut use_fact.location);
}
}
}
fn resolve_status(
location: &mut SourceLocation,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) {
let full = match &location.directory {
Some(directory) => directory.join(&location.file),
None => location.file.clone(),
};
let state = source_status.get(&(module_id.to_string(), full)).copied();
location.source_status = state.map_or(SourceStatus::Unknown, |state| state.status);
location.status_basis = state.and_then(|state| state.basis);
for frame in &mut location.inlined_at {
resolve_status(frame, module_id, source_status);
}
}
struct Context(LLVMContextRef);
impl Drop for Context {
fn drop(&mut self) {
unsafe { LLVMContextDispose(self.0) };
}
}
struct ParsedModule(LLVMModuleRef);
impl Drop for ParsedModule {
fn drop(&mut self) {
unsafe { LLVMDisposeModule(self.0) };
}
}
struct Buffer(LLVMMemoryBufferRef);
impl Drop for Buffer {
fn drop(&mut self) {
unsafe { LLVMDisposeMemoryBuffer(self.0) };
}
}
struct DiagnosticSink(*mut Vec<String>);
impl DiagnosticSink {
fn new() -> Self {
Self(Box::into_raw(Box::new(Vec::new())))
}
fn take(&self) -> Vec<String> {
unsafe { std::mem::take(&mut *self.0) }
}
}
impl Drop for DiagnosticSink {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.0) });
}
}
extern "C" fn collect_diagnostic(info: LLVMDiagnosticInfoRef, context: *mut c_void) {
if info.is_null() || context.is_null() {
return;
}
let sink = unsafe { &mut *context.cast::<Vec<String>>() };
let description = unsafe { owned_message(LLVMGetDiagInfoDescription(info)) };
let severity = match unsafe { LLVMGetDiagInfoSeverity(info) } {
LLVMDiagnosticSeverity::LLVMDSError => "error",
LLVMDiagnosticSeverity::LLVMDSWarning => "warning",
LLVMDiagnosticSeverity::LLVMDSRemark => "remark",
LLVMDiagnosticSeverity::LLVMDSNote => "note",
};
sink.push(format!("{severity}: {description}"));
}
unsafe fn owned(pointer: *const c_char, length: usize) -> String {
if pointer.is_null() || length == 0 {
return String::new();
}
let bytes = unsafe { std::slice::from_raw_parts(pointer as *const u8, length) };
String::from_utf8_lossy(bytes).into_owned()
}
unsafe fn owned_message(pointer: *mut c_char) -> String {
if pointer.is_null() {
return String::new();
}
let text = unsafe { CStr::from_ptr(pointer) }
.to_string_lossy()
.into_owned();
unsafe { LLVMDisposeMessage(pointer) };
text
}
unsafe fn owned_error_message(error: LLVMErrorRef) -> String {
if error.is_null() {
return String::new();
}
let message = unsafe { LLVMGetErrorMessage(error) };
if message.is_null() {
return String::new();
}
let text = unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned();
unsafe { LLVMDisposeErrorMessage(message) };
text
}
unsafe fn value_name(value: LLVMValueRef) -> String {
let mut length = 0usize;
unsafe { owned(LLVMGetValueName2(value, &mut length), length) }
}
unsafe fn location_of(value: LLVMValueRef) -> Option<SourceLocation> {
let line = unsafe { LLVMGetDebugLocLine(value) };
if line == 0 {
return None;
}
let mut length = 0;
let file = unsafe { owned(LLVMGetDebugLocFilename(value, &mut length), length as usize) };
let mut length = 0;
let directory = unsafe {
owned(
LLVMGetDebugLocDirectory(value, &mut length),
length as usize,
)
};
let mut inlined_at = Vec::new();
if !unsafe { LLVMIsAInstruction(value) }.is_null() {
let mut metadata = unsafe { LLVMInstructionGetDebugLoc(value) };
while !metadata.is_null() {
let outer = unsafe { LLVMDILocationGetInlinedAt(metadata) };
if outer.is_null() {
break;
}
inlined_at.push(unsafe { location_of_metadata(outer) });
metadata = outer;
}
}
Some(unsafe {
build_location(
file,
directory,
line,
LLVMGetDebugLocColumn(value),
inlined_at,
)
})
}
unsafe fn location_of_metadata(location: LLVMMetadataRef) -> SourceLocation {
let scope = unsafe { LLVMDILocationGetScope(location) };
let scope_file = unsafe { LLVMDIScopeGetFile(scope) };
let mut length = 0;
let file = unsafe {
owned(
LLVMDIFileGetFilename(scope_file, &mut length),
length as usize,
)
};
let mut length = 0;
let directory = unsafe {
owned(
LLVMDIFileGetDirectory(scope_file, &mut length),
length as usize,
)
};
unsafe {
build_location(
file,
directory,
LLVMDILocationGetLine(location),
LLVMDILocationGetColumn(location),
Vec::new(),
)
}
}
fn build_location(
file: String,
directory: String,
line: u32,
column: u32,
inlined_at: Vec<SourceLocation>,
) -> SourceLocation {
SourceLocation {
file: PathBuf::from(file),
directory: (!directory.is_empty()).then(|| PathBuf::from(directory)),
line,
column,
source_status: SourceStatus::Unknown,
status_basis: None,
inlined_at,
}
}
fn linkage_of(linkage: LLVMLinkage) -> Linkage {
match linkage {
LLVMLinkage::LLVMExternalLinkage => Linkage::External,
LLVMLinkage::LLVMInternalLinkage | LLVMLinkage::LLVMPrivateLinkage => Linkage::Internal,
LLVMLinkage::LLVMWeakODRLinkage | LLVMLinkage::LLVMLinkOnceODRLinkage => Linkage::Odr,
LLVMLinkage::LLVMWeakAnyLinkage | LLVMLinkage::LLVMLinkOnceAnyLinkage => Linkage::Weak,
LLVMLinkage::LLVMAvailableExternallyLinkage => Linkage::AvailableExternally,
_ => Linkage::Other,
}
}
const RUSTC_PRODUCER_PREFIX: &str = "rustc version ";
const SUBPROGRAM_UNIT_OPERAND: usize = 5;
const DWARF_LANGUAGE_KEYS: [(&str, &str); 2] = [
("language: DW_LANG_", "Rust"),
("sourceLanguageName: DW_LNAME_", "Rust"),
];
fn producer_language(producers: &[String]) -> Option<Language> {
let rustc = producers
.iter()
.filter(|producer| producer.starts_with(RUSTC_PRODUCER_PREFIX))
.count();
if producers.is_empty() || (rustc != 0 && rustc != producers.len()) {
None
} else if rustc == 0 {
Some(Language::Other)
} else {
Some(Language::Rust)
}
}
fn dwarf_language(unit: &str) -> Option<Language> {
DWARF_LANGUAGE_KEYS.iter().find_map(|(key, rust)| {
let start = unit.find(key)? + key.len();
let name = unit[start..].split([',', ')']).next()?.trim();
if name.is_empty() {
None
} else if name == *rust {
Some(Language::Rust)
} else {
Some(Language::Other)
}
})
}
unsafe fn md_node_operands(node: LLVMValueRef) -> Vec<LLVMValueRef> {
let count = unsafe { LLVMGetMDNodeNumOperands(node) } as usize;
let mut operands = vec![std::ptr::null_mut(); count];
unsafe { LLVMGetMDNodeOperands(node, operands.as_mut_ptr()) };
operands
}
unsafe fn read_producers(module: LLVMModuleRef) -> Vec<String> {
let name = c"llvm.ident";
let count = unsafe { LLVMGetNamedMetadataNumOperands(module, name.as_ptr()) } as usize;
let mut nodes = vec![std::ptr::null_mut(); count];
unsafe { LLVMGetNamedMetadataOperands(module, name.as_ptr(), nodes.as_mut_ptr()) };
nodes
.into_iter()
.filter(|node| !node.is_null())
.filter_map(|node| {
let first = *unsafe { md_node_operands(node) }.first()?;
if first.is_null() {
return None;
}
let mut length = 0;
let text = unsafe { LLVMGetMDString(first, &mut length) };
(!text.is_null()).then(|| unsafe { owned(text, length as usize) })
})
.collect()
}
const MAX_ALIAS_DEPTH: usize = 16;
unsafe fn alias_target(alias: LLVMValueRef) -> Option<LLVMValueRef> {
let mut value = alias;
for _ in 0..MAX_ALIAS_DEPTH {
if !unsafe { LLVMIsAFunction(value) }.is_null() {
return Some(value);
}
if unsafe { LLVMIsAGlobalAlias(value) }.is_null() {
return None;
}
value = unsafe { LLVMAliasGetAliasee(value) };
if value.is_null() {
return None;
}
}
None
}
unsafe fn definition_language(
context: LLVMContextRef,
function: LLVMValueRef,
units: &mut HashMap<LLVMMetadataRef, Option<Language>>,
module_language: Option<Language>,
) -> Option<SourceLanguage> {
unsafe { debug_info_language(context, function, units) }
.map(|name| SourceLanguage {
name,
basis: LanguageBasis::DebugInfo,
})
.or(module_language.map(|name| SourceLanguage {
name,
basis: LanguageBasis::Producer,
}))
}
unsafe fn debug_info_language(
context: LLVMContextRef,
function: LLVMValueRef,
units: &mut HashMap<LLVMMetadataRef, Option<Language>>,
) -> Option<Language> {
let subprogram = unsafe { LLVMGetSubprogram(function) };
if subprogram.is_null() {
return None;
}
let node = unsafe { LLVMMetadataAsValue(context, subprogram) };
let unit = *unsafe { md_node_operands(node) }.get(SUBPROGRAM_UNIT_OPERAND)?;
if unit.is_null() {
return None;
}
let metadata = unsafe { LLVMValueAsMetadata(unit) };
let kind = unsafe { LLVMGetMetadataKind(metadata) };
if !matches!(kind, LLVMMetadataKind::LLVMDICompileUnitMetadataKind) {
return None;
}
*units.entry(metadata).or_insert_with(|| {
dwarf_language(&unsafe { owned_message(LLVMPrintValueToString(unit)) })
})
}
unsafe fn indirect_target_bound(
instruction: LLVMValueRef,
callees_kind: u32,
module_id: &str,
) -> Option<Vec<FunctionId>> {
let node = unsafe { LLVMGetMetadata(instruction, callees_kind) };
if node.is_null() {
return None;
}
let operands = unsafe { md_node_operands(node) };
Some(
operands
.into_iter()
.filter(|operand| !operand.is_null())
.map(|operand| FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(operand) },
})
.collect(),
)
}
unsafe fn call_target(instruction: LLVMValueRef, module_id: &str, callees_kind: u32) -> CallTarget {
let called = unsafe { LLVMGetCalledValue(instruction) };
if !called.is_null() {
if !unsafe { LLVMIsAFunction(called) }.is_null() {
let symbol = unsafe { value_name(called) };
return if symbol.starts_with("llvm.") {
CallTarget::Intrinsic { name: symbol }
} else {
CallTarget::Direct {
callee: FunctionId {
module_id: module_id.to_string(),
symbol,
},
}
};
}
if !unsafe { LLVMIsAGlobalAlias(called) }.is_null()
&& unsafe { alias_target(called) }.is_some()
{
return CallTarget::Direct {
callee: FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(called) },
},
};
}
if !unsafe { LLVMIsAInlineAsm(called) }.is_null() {
return CallTarget::InlineAsm;
}
}
CallTarget::Indirect {
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGetCalledFunctionType(
instruction,
)))
},
llvm_target_bound: unsafe { indirect_target_bound(instruction, callees_kind, module_id) },
}
}
unsafe fn enclosing_function(instruction: LLVMValueRef, module_id: &str) -> Option<FunctionId> {
let block = unsafe { LLVMGetInstructionParent(instruction) };
if block.is_null() {
return None;
}
let function = unsafe { LLVMGetBasicBlockParent(block) };
if function.is_null() {
return None;
}
Some(FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(function) },
})
}
const WRAPPING_CONSTANTS: [unsafe extern "C" fn(LLVMValueRef) -> LLVMValueRef; 4] = [
LLVMIsAConstantExpr,
LLVMIsAConstantStruct,
LLVMIsAConstantArray,
LLVMIsAConstantVector,
];
unsafe fn users_of(value: LLVMValueRef) -> Vec<(LLVMValueRef, LLVMValueRef)> {
let mut users = Vec::new();
let mut current = unsafe { LLVMGetFirstUse(value) };
while !current.is_null() {
let user = unsafe { LLVMGetUser(current) };
current = unsafe { LLVMGetNextUse(current) };
if !user.is_null() {
users.push((value, user));
}
}
users
}
unsafe fn collect_uses(
function: LLVMValueRef,
id: &FunctionId,
module_id: &str,
uses: &mut Vec<UseFact>,
) {
let mut pending: VecDeque<_> = unsafe { users_of(function) }.into();
while let Some((used, user)) = pending.pop_front() {
let is_instruction = !unsafe { LLVMIsAInstruction(user) }.is_null();
let opcode = is_instruction.then(|| unsafe { LLVMGetInstructionOpcode(user) });
let is_call = matches!(opcode, Some(LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke));
if is_call && unsafe { LLVMGetCalledValue(user) } == used {
continue;
}
let is_global = !unsafe { LLVMIsAGlobalVariable(user) }.is_null();
let wraps = !is_instruction
&& !is_global
&& WRAPPING_CONSTANTS
.iter()
.any(|is_a| !unsafe { is_a(user) }.is_null());
if wraps {
pending.extend(unsafe { users_of(user) });
continue;
}
let kind = match opcode {
Some(LLVMOpcode::LLVMStore) => UseKind::StoredToMemory,
Some(LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke) => UseKind::PassedAsArgument,
Some(LLVMOpcode::LLVMRet) => UseKind::ReturnedValue,
Some(_) => UseKind::Other,
None if is_global => UseKind::GlobalInitializer,
None => UseKind::Other,
};
uses.push(UseFact {
used: id.clone(),
in_function: is_instruction
.then(|| unsafe { enclosing_function(user, module_id) })
.flatten(),
in_global: is_global.then(|| unsafe { value_name(user) }),
location: is_instruction
.then(|| unsafe { location_of(user) })
.flatten(),
kind,
});
}
}
fn parse_error(module: &LoadedModule, diagnostics: &[String]) -> Error {
let reader = llvm_version();
let detail = if diagnostics.is_empty() {
String::new()
} else {
format!(" ({})", diagnostics.join("; "))
};
match module.record.compiler.as_ref() {
Some(compiler) => Error::InvalidArguments(format!(
"module {} was produced by {} and cannot be read by LLVM {reader}{detail}",
module.id, compiler.version
)),
None => Error::InvalidArguments(format!(
"module {} cannot be read by LLVM {reader}{detail}",
module.id
)),
}
}
pub fn extract_neutral(module: &LoadedModule) -> Result<ModuleFacts, Error> {
unsafe { extract_inner(module) }
}
pub fn extract(
module: &LoadedModule,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> Result<ModuleFacts, Error> {
let mut facts = extract_neutral(module)?;
facts.bind_to_catalog(&module.id, source_status);
Ok(facts)
}
unsafe fn extract_inner(module: &LoadedModule) -> Result<ModuleFacts, Error> {
let sink = DiagnosticSink::new();
let context = Context(unsafe { LLVMContextCreate() });
unsafe { LLVMContextSetDiagnosticHandler(context.0, Some(collect_diagnostic), sink.0.cast()) };
let bytes = &module.bytes;
let buffer = Buffer(unsafe {
LLVMCreateMemoryBufferWithMemoryRange(
bytes.as_ptr().cast::<c_char>(),
bytes.len(),
c"rllvm-module".as_ptr(),
0,
)
});
let mut parsed: LLVMModuleRef = std::ptr::null_mut();
let failed = unsafe { LLVMParseBitcodeInContext2(context.0, buffer.0, &mut parsed) } != 0;
if failed || parsed.is_null() {
return Err(parse_error(module, &sink.take()));
}
let parsed = ParsedModule(parsed);
let mut pass_diagnostics = Vec::new();
unsafe {
let options = LLVMCreatePassBuilderOptions();
let error = LLVMRunPasses(
parsed.0,
c"called-value-propagation".as_ptr(),
std::ptr::null_mut(),
options,
);
LLVMDisposePassBuilderOptions(options);
if !error.is_null() {
let message = owned_error_message(error);
pass_diagnostics.push(format!("called-value-propagation did not run: {message}"));
}
}
let callees_kind = unsafe { LLVMGetMDKindIDInContext(context.0, c"callees".as_ptr(), 7) };
let producers = unsafe { read_producers(parsed.0) };
let module_language = producer_language(&producers);
let mut units = HashMap::new();
let mut functions = Vec::new();
let mut call_sites = Vec::new();
let mut uses = Vec::new();
let mut function = unsafe { LLVMGetFirstFunction(parsed.0) };
while !function.is_null() {
let id = FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(function) },
};
let mut mapped_lines = BTreeSet::new();
let mut block_index = 0u32;
let mut block = unsafe { LLVMGetFirstBasicBlock(function) };
while !block.is_null() {
let mut instruction_index = 0u32;
let mut instruction = unsafe { LLVMGetFirstInstruction(block) };
while !instruction.is_null() {
let location = unsafe { location_of(instruction) };
if let Some(location) = &location {
mapped_lines.insert((location.file.clone(), location.line));
}
let opcode = unsafe { LLVMGetInstructionOpcode(instruction) };
if matches!(opcode, LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke) {
call_sites.push(CallSiteFact {
id: CallSiteId {
function: id.clone(),
block_index,
instruction_index,
},
location,
target: unsafe {
call_target(instruction, NEUTRAL_MODULE_ID, callees_kind)
},
});
}
instruction = unsafe { LLVMGetNextInstruction(instruction) };
instruction_index += 1;
}
block = unsafe { LLVMGetNextBasicBlock(block) };
block_index += 1;
}
let is_definition = unsafe { LLVMIsDeclaration(function) } == 0;
functions.push(FunctionFact {
id: id.clone(),
is_definition,
linkage: linkage_of(unsafe { LLVMGetLinkage(function) }),
language: if is_definition {
unsafe { definition_language(context.0, function, &mut units, module_language) }
} else {
None
},
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGlobalGetValueType(function)))
},
location: unsafe { location_of(function) },
mapped_lines,
alias_of: None,
});
unsafe { collect_uses(function, &id, NEUTRAL_MODULE_ID, &mut uses) };
function = unsafe { LLVMGetNextFunction(function) };
}
let mut alias = unsafe { LLVMGetFirstGlobalAlias(parsed.0) };
while !alias.is_null() {
if let Some(target) = unsafe { alias_target(alias) } {
functions.push(FunctionFact {
id: FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(alias) },
},
is_definition: true,
linkage: linkage_of(unsafe { LLVMGetLinkage(alias) }),
language: unsafe {
definition_language(context.0, target, &mut units, module_language)
},
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGlobalGetValueType(alias)))
},
location: unsafe { location_of(target) },
mapped_lines: BTreeSet::new(),
alias_of: Some(FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(target) },
}),
});
}
alias = unsafe { LLVMGetNextGlobalAlias(alias) };
}
drop(parsed);
drop(buffer);
drop(context);
let mut diagnostics = pass_diagnostics;
diagnostics.extend(sink.take());
Ok(ModuleFacts {
functions,
call_sites,
uses,
producers,
diagnostics,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn producers_decide_a_module_only_when_they_agree() {
let rustc = "rustc version 1.98.0".to_string();
let clang = "clang version 23.1.1".to_string();
assert_eq!(
producer_language(std::slice::from_ref(&rustc)),
Some(Language::Rust)
);
assert_eq!(
producer_language(std::slice::from_ref(&clang)),
Some(Language::Other)
);
assert_eq!(producer_language(&[rustc, clang]), None, "merged");
assert_eq!(producer_language(&[]), None, "no producer named");
}
#[test]
fn a_printed_unit_names_its_language_under_either_key() {
let unit = |text: &str| format!("distinct !DICompileUnit({text}, file: !1)");
assert_eq!(
dwarf_language(&unit("language: DW_LANG_Rust")),
Some(Language::Rust)
);
assert_eq!(
dwarf_language(&unit("language: DW_LANG_C11")),
Some(Language::Other)
);
assert_eq!(
dwarf_language(&unit("sourceLanguageName: DW_LNAME_Rust")),
Some(Language::Rust)
);
assert_eq!(dwarf_language("distinct !DICompileUnit(file: !1)"), None);
}
#[test]
fn demangling_reads_cxx_names_and_refuses_everything_else() {
assert_eq!(
demangle("_Z5twiceIiET_S0_").as_deref(),
Some("int twice<int>(int)"),
"the template instantiation from the ODR repro in #184"
);
assert_eq!(demangle("_ZN3FooC1Ev").as_deref(), Some("Foo::Foo()"));
assert_eq!(demangle("main"), None, "a C name is not mangled");
assert_eq!(demangle(""), None);
assert_eq!(
demangle("_Znotreallymangled"),
None,
"a name that only looks mangled must not produce a guess"
);
assert_eq!(
demangle("_RNvC6foo3bar"),
None,
"malformed v0: a crate root carries a disambiguator"
);
}
#[test]
fn demangling_reads_rust_v0_names() {
assert_eq!(
demangle("_RNvMs2_NtCs8f0ESrtUyjS_6quiche3tlsNtB5_9Handshake12do_handshake").as_deref(),
Some("<quiche::tls::Handshake>::do_handshake"),
);
assert!(
demangle("_ZN4core3fmt5Debug3fmt17h0123456789abcdefE")
.is_some_and(|name| name.contains("core::fmt")),
);
}
#[test]
fn a_legacy_rust_symbol_reads_back_with_its_hash() {
assert_eq!(
demangle("_ZN4core3fmt5write17h1234567890abcdefE").as_deref(),
Some("core::fmt::write::h1234567890abcdef")
);
}
}