use std::path::{Path, PathBuf};
use rllvm_core::{
catalog::{
CatalogOrigin, CatalogScope, ModuleCatalog, ModuleRecord, ModuleStatus, hash_bytes,
write_catalog,
},
config::LlvmBindir,
};
use crate::{
bind::{BindingCandidate, BindingStatus, SymbolBinding},
facts::{
CallSiteFact, CallSiteId, CallTarget, FieldBasis, FieldEvidence, FieldRef, FunctionFact,
FunctionId, Language, LanguageBasis, Linkage, ModuleAnalysis, ModuleReport, ProgramFacts,
SourceLanguage, SourceLocation, SourceStatus, UseFact, UseKind,
},
index::Session,
};
pub(crate) fn function(
module: &str,
symbol: &str,
is_definition: bool,
linkage: Linkage,
) -> FunctionFact {
FunctionFact {
id: FunctionId {
module_id: module.into(),
symbol: symbol.into(),
},
is_definition,
linkage,
language: None,
signature: "i32 (i32, i32)".into(),
location: None,
mapped_lines: Default::default(),
alias_of: None,
}
}
pub(crate) fn attributed(
symbol: &str,
linkage: Linkage,
language: Option<Language>,
) -> FunctionFact {
let mut fact = function("m", symbol, true, linkage);
fact.language = language.map(|name| SourceLanguage {
name,
basis: LanguageBasis::Producer,
});
fact
}
pub(crate) fn source_location(file: &str, line: u32) -> SourceLocation {
SourceLocation {
file: file.into(),
directory: None,
line,
column: 1,
source_status: SourceStatus::Current,
status_basis: None,
inlined_at: Vec::new(),
}
}
pub(crate) fn direct_call(
caller: &FunctionFact,
callee: &FunctionFact,
index: u32,
) -> CallSiteFact {
CallSiteFact {
id: CallSiteId {
function: caller.id.clone(),
block_index: 0,
instruction_index: index,
},
location: None,
target: CallTarget::Direct {
callee: callee.id.clone(),
},
}
}
pub(crate) fn indirect_call(
caller: &FunctionFact,
index: u32,
location: Option<SourceLocation>,
llvm_target_bound: Option<Vec<FunctionId>>,
) -> CallSiteFact {
CallSiteFact {
id: CallSiteId {
function: caller.id.clone(),
block_index: 0,
instruction_index: index,
},
location,
target: CallTarget::Indirect {
signature: "i32 (i32, i32)".into(),
llvm_target_bound,
via_field: None,
via_slot: None,
},
}
}
pub(crate) fn indirect_call_through(
caller: &FunctionFact,
line: u32,
field: Option<FieldRef>,
) -> CallSiteFact {
let mut site = indirect_call(caller, line, Some(source_location("t.c", line)), None);
if let CallTarget::Indirect { via_field, .. } = &mut site.target {
*via_field = field.map(|field| FieldEvidence {
field,
basis: FieldBasis::StructGep,
name: None,
});
}
site
}
pub(crate) fn report(id: &str, status: ModuleAnalysis) -> ModuleReport {
ModuleReport {
id: id.into(),
status,
ir_stage: None,
debug_info: None,
compiler: None,
configuration_id: None,
content_sha256: None,
target_triple: None,
producers: Vec::new(),
diagnostic: None,
}
}
pub(crate) fn ambiguous_binding(
symbol: &str,
declared_in: &[&str],
candidates: &[(&str, Option<&str>)],
) -> SymbolBinding {
SymbolBinding {
symbol: symbol.into(),
declared_in: declared_in.iter().map(|id| (*id).to_string()).collect(),
candidates: candidates
.iter()
.map(|(module, configuration)| BindingCandidate {
function: FunctionId {
module_id: (*module).into(),
symbol: symbol.into(),
},
configuration_id: configuration.map(str::to_string),
})
.collect(),
status: BindingStatus::Ambiguous,
}
}
pub(crate) fn facts(functions: Vec<FunctionFact>, call_sites: Vec<CallSiteFact>) -> ProgramFacts {
ProgramFacts {
functions,
call_sites,
uses: Vec::new(),
slot_flows: Vec::new(),
field_flows: Vec::new(),
returned_addresses: Vec::new(),
scope: CatalogScope {
kind: "test".into(),
total_entries: 1,
selected_entries: 1,
selection: Default::default(),
selection_history: Vec::new(),
analysis_arguments: Vec::new(),
whole_program_complete: None,
limitations: Vec::new(),
},
origin: CatalogOrigin {
kind: "test".into(),
input: PathBuf::from("test"),
sha256: None,
},
modules: Vec::new(),
}
}
pub(crate) fn compiled_session(
scratch: &Path,
file_name: &str,
source: &str,
flags: &[&str],
) -> Session {
let source_path = scratch.join(file_name);
std::fs::write(&source_path, source).unwrap();
let module = rllvm_testkit::compile_bitcode_file(&source_path, flags);
module_session(scratch, &module)
}
const STALE_DEBUG_INFO_IR: &str = r#"
declare void @sink(i32)
define void @f(i32 %x) !dbg !5 {
call void @sink(i32 %x), !dbg !8
ret void, !dbg !8
}
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3}
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", emissionKind: FullDebug)
!1 = !DIFile(filename: "t.c", directory: "/tmp")
!3 = !{i32 2, !"Debug Info Version", i32 2}
!5 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !9, unit: !0, spFlags: DISPFlagDefinition)
!8 = !DILocation(line: 1, column: 1, scope: !5)
!9 = !DISubroutineType(types: !{})
"#;
pub(crate) fn stale_debug_info_module(scratch: &Path) -> PathBuf {
let source = scratch.join("stale.ll");
std::fs::write(&source, STALE_DEBUG_INFO_IR).unwrap();
let module = scratch.join("stale.bc");
let status = std::process::Command::new(rllvm_testkit::llvm_bin("llvm-as"))
.arg("-disable-auto-upgrade-debug-info")
.arg(&source)
.arg("-o")
.arg(&module)
.status()
.unwrap();
assert!(status.success(), "llvm-as failed on {}", source.display());
module
}
pub(crate) fn module_session(scratch: &Path, module: &Path) -> Session {
let mut record = ModuleRecord::new("m");
record.path = Some(module.file_name().unwrap().into());
record.content_sha256 = Some(hash_bytes(&std::fs::read(module).unwrap()));
record.status = ModuleStatus::Available;
let catalog = ModuleCatalog::new(
CatalogOrigin {
kind: "test".into(),
input: PathBuf::from("test"),
sha256: None,
},
"test",
vec![record],
);
let path = scratch.join("catalog.json");
write_catalog(&path, &catalog).unwrap();
let mut session = crate::open(&path).unwrap();
let opt = rllvm_testkit::llvm_bin("opt");
session.set_llvm_bindir(Ok(LlvmBindir::configured(opt.parent().unwrap())));
session
}
pub(crate) fn session_from(edges: &[(&str, &str)]) -> Session {
let mut functions: Vec<FunctionFact> = Vec::new();
let lookup = |functions: &mut Vec<FunctionFact>, name: &str| -> FunctionFact {
if let Some(existing) = functions.iter().find(|f| f.id.symbol == name) {
return existing.clone();
}
let created = function("m", name, true, Linkage::Internal);
functions.push(created.clone());
created
};
let mut call_sites = Vec::new();
for (index, (from, to)) in edges.iter().enumerate() {
let caller = lookup(&mut functions, from);
let callee = lookup(&mut functions, to);
call_sites.push(direct_call(&caller, &callee, index as u32));
}
Session::new(facts(functions, call_sites), Vec::new())
}
pub(crate) fn session_with_bounded_indirect() -> Session {
let a = function("m", "a", true, Linkage::Internal);
let target = function("m", "target", true, Linkage::Internal);
let other = function("m", "other", true, Linkage::Internal);
let site = indirect_call(
&a,
0,
Some(source_location("t.c", 9)),
Some(vec![target.id.clone(), other.id.clone()]),
);
Session::new(facts(vec![a, target, other], vec![site]), Vec::new())
}
pub(crate) fn session_with_indirect_gap() -> Session {
let a = function("m", "a", true, Linkage::Internal);
let b = function("m", "b", true, Linkage::Internal);
let c = function("m", "c", true, Linkage::Internal);
let call = direct_call(&a, &b, 0);
let indirect = indirect_call(&b, 1, None, None);
Session::new(facts(vec![a, b, c], vec![call, indirect]), Vec::new())
}
pub(crate) fn session_with_address_taken_function() -> Session {
let add = function("m", "add", true, Linkage::Internal);
let log = FunctionFact {
signature: "void (ptr)".into(),
..function("m", "log", true, Linkage::Internal)
};
let caller = function("m", "caller", true, Linkage::Internal);
let mut base = facts(
vec![add.clone(), log.clone(), caller.clone()],
vec![indirect_call(
&caller,
0,
Some(SourceLocation {
file: "t.c".into(),
directory: None,
line: 4,
column: 3,
source_status: SourceStatus::Unknown,
status_basis: None,
inlined_at: Vec::new(),
}),
None,
)],
);
base.uses = [add.id, log.id]
.into_iter()
.map(|used| UseFact {
used,
in_function: Some(caller.id.clone()),
in_global: None,
location: None,
kind: UseKind::StoredToMemory,
into_slot: None,
field: None,
})
.collect();
Session::new(base, Vec::new())
}
pub(crate) fn facts_with_one_failed_module() -> ProgramFacts {
let mut base = facts(vec![], vec![]);
base.scope.total_entries = 2;
base.scope.selected_entries = 2;
base.modules = vec![
ModuleReport {
debug_info: Some(true),
..report("a", ModuleAnalysis::Analyzed)
},
ModuleReport {
diagnostic: Some("truncated".into()),
..report("b", ModuleAnalysis::Failed)
},
];
base
}
pub(crate) fn session_from_source_lines(lines: &[(&str, u32)]) -> Session {
let mut only = function("m", "only", true, Linkage::Internal);
only.mapped_lines = lines
.iter()
.map(|(file, line)| (PathBuf::from(file), *line))
.collect();
Session::new(facts(vec![only], Vec::new()), Vec::new())
}
pub(crate) fn session_with_ambiguous_bindings() -> Session {
let mut functions = Vec::new();
let mut call_sites = Vec::new();
for (index, module) in ["c1", "c2"].into_iter().enumerate() {
let caller = function(module, "caller", true, Linkage::External);
let declaration = function(module, "target", false, Linkage::External);
call_sites.push(direct_call(&caller, &declaration, index as u32));
functions.push(caller);
functions.push(declaration);
}
functions.push(function("a", "target", true, Linkage::External));
functions.push(function("b", "target", true, Linkage::External));
let target = ambiguous_binding(
"target",
&["c1", "c2"],
&[("a", Some("debug")), ("b", Some("release"))],
);
let unused = ambiguous_binding("unused", &["z"], &[("a", None), ("b", None)]);
Session::new(facts(functions, call_sites), vec![target, unused])
}
pub(crate) fn session_with_cxx_symbols() -> Session {
let functions = [
"_Z5twiceIiET_S0_", "_Z5twiceIdET_S0_", "_Z12twice_helperv", "_ZN2ns5twiceEv", "main",
]
.into_iter()
.map(|symbol| function("m", symbol, true, Linkage::External))
.collect();
Session::new(facts(functions, Vec::new()), Vec::new())
}
pub(crate) fn facts_with_overlay_sites() -> ProgramFacts {
let dispatch = function("mod_a", "dispatch", true, Linkage::External);
let call_plain = function("mod_a", "call_plain", true, Linkage::External);
let bounded = function("mod_a", "bounded", true, Linkage::External);
let init = function("mod_a", "init", true, Linkage::External);
let h1 = function("mod_a", "h1", true, Linkage::Internal);
let h3 = function("mod_a", "h3", true, Linkage::Internal);
let call_sites = vec![
indirect_call_through(
&dispatch,
4,
Some(FieldRef {
record: "ops".into(),
offset: 8,
}),
),
indirect_call_through(&call_plain, 5, None),
indirect_call(
&bounded,
6,
Some(source_location("t.c", 6)),
Some(vec![h1.id.clone()]),
),
direct_call(&init, &h3, 7),
];
let functions = vec![
dispatch,
call_plain,
bounded,
init,
h1,
h3,
function("mod_a", "helper", true, Linkage::Internal),
function("mod_b", "helper", true, Linkage::Internal),
];
let mut base = facts(functions, call_sites);
base.modules = [("mod_a", "aa"), ("mod_b", "bb")]
.into_iter()
.map(|(id, hash)| ModuleReport {
content_sha256: Some(hash.into()),
..report(id, ModuleAnalysis::Analyzed)
})
.collect();
base
}
pub(crate) fn facts_with_one_verified_module() -> ProgramFacts {
let mut base = facts(vec![], vec![]);
base.scope.total_entries = 1;
base.scope.selected_entries = 1;
base.modules = vec![report("v", ModuleAnalysis::Verified)];
base
}
pub(crate) fn overlay_header(session: &Session) -> String {
format!(
"{{\"v\":{},\"fingerprint\":\"{}\",\"facts\":{}}}\n",
crate::overlay::OVERLAY_VERSION,
crate::overlay::fingerprint(session).unwrap(),
crate::cache::FACTS_FORMAT
)
}