use std::sync::Arc;
use pedant_types::{
Capability, CapabilityAnalysis, CapabilityFinding, ExecutionContext, FindingOrigin,
SourceLocation,
};
use super::attribution::{CapabilityDraft, CapabilitySourceFact, project_analysis};
use super::paths::resolve_capabilities;
use super::strings::{KEY_MATERIAL_CHECKS, STRING_LITERAL_CHECKS, truncate_evidence};
use crate::ir::FileIr;
use crate::observe::{self, Observation};
struct FindingEmitter<'a> {
draft: &'a mut CapabilityDraft,
file: &'a Arc<str>,
origin: FindingOrigin,
execution_context: Option<ExecutionContext>,
}
impl FindingEmitter<'_> {
fn emit(
&mut self,
capability: Capability,
line: usize,
column: usize,
evidence: &str,
containing_fn: Option<usize>,
) {
let finding = CapabilityFinding {
capability,
location: SourceLocation {
file: Arc::clone(self.file),
line,
column: column + 1,
},
evidence: Arc::from(evidence),
origin: Some(self.origin),
language: None,
execution_context: self.execution_context,
reachable: None,
};
self.draft.push(finding, containing_fn);
}
fn emit_from_facts<'a, T: CapabilitySourceFact + 'a>(
&mut self,
facts: &'a [T],
mut mapper: impl FnMut(&'a T) -> Option<(Capability, usize, usize, &'a str)>,
) {
for fact in facts {
if let Some((capability, line, column, evidence)) = mapper(fact) {
self.emit(capability, line, column, evidence, fact.containing_fn());
}
}
}
}
pub fn detect_capabilities(
ir: &FileIr,
execution_context: Option<ExecutionContext>,
) -> CapabilityAnalysis {
project_analysis(ir, draft_capabilities(ir, execution_context))
}
pub(crate) fn draft_capabilities(
ir: &FileIr,
execution_context: Option<ExecutionContext>,
) -> CapabilityDraft {
let file_path = &ir.file_path;
observe::record(Observation::CapabilityProjection(file_path));
let mut draft = CapabilityDraft::default();
detect_use_paths(ir, file_path, execution_context, &mut draft);
detect_unsafe_sites(ir, file_path, execution_context, &mut draft);
detect_extern_blocks(ir, file_path, execution_context, &mut draft);
detect_attributes(ir, file_path, execution_context, &mut draft);
detect_string_literals(ir, file_path, execution_context, &mut draft);
draft
}
fn detect_use_paths(
ir: &FileIr,
file_path: &Arc<str>,
execution_context: Option<ExecutionContext>,
draft: &mut CapabilityDraft,
) {
let mut emitter = FindingEmitter {
draft,
file: file_path,
origin: FindingOrigin::Import,
execution_context,
};
emitter.emit_from_facts(&ir.use_paths, |use_path| {
resolve_capabilities(&use_path.path).map(|capability| {
(
capability,
use_path.span.line,
use_path.span.column,
use_path.path.as_ref(),
)
})
});
}
fn detect_unsafe_sites(
ir: &FileIr,
file_path: &Arc<str>,
execution_context: Option<ExecutionContext>,
draft: &mut CapabilityDraft,
) {
let mut emitter = FindingEmitter {
draft,
file: file_path,
origin: FindingOrigin::CodeSite,
execution_context,
};
emitter.emit_from_facts(&ir.unsafe_sites, |site| {
Some((
Capability::UnsafeCode,
site.span.line,
site.span.column,
site.evidence.as_ref(),
))
});
}
fn detect_extern_blocks(
ir: &FileIr,
file_path: &Arc<str>,
execution_context: Option<ExecutionContext>,
draft: &mut CapabilityDraft,
) {
let mut emitter = FindingEmitter {
draft,
file: file_path,
origin: FindingOrigin::CodeSite,
execution_context,
};
emitter.emit_from_facts(&ir.extern_blocks, |block| {
Some((
Capability::Ffi,
block.span.line,
block.span.column,
"extern block",
))
});
}
fn detect_attributes(
ir: &FileIr,
file_path: &Arc<str>,
execution_context: Option<ExecutionContext>,
draft: &mut CapabilityDraft,
) {
let mut emitter = FindingEmitter {
draft,
file: file_path,
origin: FindingOrigin::Attribute,
execution_context,
};
emitter.emit_from_facts(&ir.attributes, |attribute| {
let (capability, evidence) = match &*attribute.name {
"link" => (Capability::Ffi, "#[link]"),
"proc_macro" => (Capability::ProcMacro, "#[proc_macro]"),
"proc_macro_derive" => (Capability::ProcMacro, "#[proc_macro_derive]"),
"proc_macro_attribute" => (Capability::ProcMacro, "#[proc_macro_attribute]"),
_ => return None,
};
Some((
capability,
attribute.span.line,
attribute.span.column,
evidence,
))
});
}
fn detect_string_literals(
ir: &FileIr,
file_path: &Arc<str>,
execution_context: Option<ExecutionContext>,
draft: &mut CapabilityDraft,
) {
let mut emitter = FindingEmitter {
draft,
file: file_path,
origin: FindingOrigin::StringLiteral,
execution_context,
};
for literal in &ir.string_literals {
let line = literal.span.line;
let column = literal.span.column;
let owner = literal.containing_fn();
if let Some(&(_, capability)) = STRING_LITERAL_CHECKS
.iter()
.find(|&&(checker, _)| checker(&literal.value))
{
emitter.emit(capability, line, column, &literal.value, owner);
}
if KEY_MATERIAL_CHECKS
.iter()
.any(|check| check(&literal.value))
{
let evidence = truncate_evidence(&literal.value);
emitter.emit(Capability::Crypto, line, column, &evidence, owner);
}
}
}