pub mod cfg;
pub(crate) mod diags;
mod helpers;
pub mod model;
use crate::{LintLevel, diag};
pub use diags::Code;
pub use helpers::{
db::{self, AdvisoryDb, DbSet, Fetch, Report},
index::{Entry, Indices},
};
pub trait AuditReporter {
fn report(&mut self, report: serde_json::Value);
}
pub struct NoneReporter;
impl AuditReporter for NoneReporter {
fn report(&mut self, _report: serde_json::Value) {}
}
impl<F> AuditReporter for F
where
F: FnMut(serde_json::Value),
{
fn report(&mut self, report: serde_json::Value) {
self(report);
}
}
pub fn check<R, S>(
ctx: crate::CheckCtx<'_, cfg::ValidConfig>,
advisory_dbs: &DbSet,
audit_compatible_reporter: Option<R>,
serialize_advisories: crate::SerializeAdvisory,
indices: Option<Indices<'_>>,
sink: S,
) where
R: AuditReporter,
S: Into<diag::ErrorSink>,
{
let mut sink = sink.into();
let emit_audit_compatible_reports = audit_compatible_reporter.is_some();
let (report, yanked) = rayon::join(
|| {
Report::generate(
&ctx.cfg,
advisory_dbs,
ctx.krates,
emit_audit_compatible_reports,
)
},
|| {
if let Some(indices) = indices {
let yanked: Vec<_> = ctx
.krates
.krates()
.filter_map(|package| match indices.is_yanked(package) {
Ok(is_yanked) => {
if is_yanked {
Some((package, None))
} else {
None
}
}
Err(err) => Some((package, Some(err))),
})
.collect();
yanked
} else {
Vec::new()
}
},
);
use bitvec::prelude::*;
let mut ignore_hits: BitVec = BitVec::repeat(false, ctx.cfg.ignore.len());
let mut ignore_yanked_hits: BitVec = BitVec::repeat(false, ctx.cfg.ignore_yanked.len());
use crate::cfg::Scope;
let ws_set = if matches!(
ctx.cfg.unmaintained.value,
Scope::Workspace | Scope::Transitive
) || matches!(ctx.cfg.unsound.value, Scope::Workspace | Scope::Transitive)
{
ctx.krates
.workspace_members()
.filter_map(|wm| {
if let krates::Node::Krate { id, .. } = wm {
Some(id.clone())
} else {
None
}
})
.collect::<std::collections::BTreeSet<_>>()
} else {
Default::default()
};
'lup: for (krate, advisory) in &report.advisories {
'block: {
let Some(scope) =
advisory
.advisory
.informational
.as_ref()
.and_then(|info| match info {
model::Informational::Unmaintained => Some(&ctx.cfg.unmaintained),
model::Informational::Unsound => Some(&ctx.cfg.unsound),
_ => None,
})
else {
break 'block;
};
match scope.value {
Scope::All => break 'block,
Scope::None => continue 'lup,
Scope::Workspace | Scope::Transitive => {
let nid = ctx.krates.nid_for_kid(&krate.id).unwrap();
let dds = ctx.krates.direct_dependents(nid);
let transitive = scope.value == Scope::Transitive;
if dds
.iter()
.any(|dd| ws_set.contains(&dd.krate.id) ^ transitive)
{
break 'block;
}
continue 'lup;
}
}
}
let diag = ctx.diag_for_advisory(krate, serialize_advisories, advisory, |index| {
ignore_hits.as_mut_bitslice().set(index, true);
});
sink.push(diag);
}
for (krate, status) in yanked {
if let Some(e) = status {
if ctx.cfg.yanked.value != LintLevel::Allow {
sink.push(ctx.diag_for_index_failure(krate, e));
}
} else {
if let Some(i) = ctx
.cfg
.ignore_yanked
.iter()
.position(|iy| crate::match_krate(krate, &iy.spec))
{
sink.push(ctx.diag_for_yanked_ignore(krate, i));
ignore_yanked_hits.as_mut_bitslice().set(i, true);
} else {
sink.push(ctx.diag_for_yanked(krate));
}
}
}
for ignored in &ctx.cfg.ignore {
if !advisory_dbs.has_advisory(&ignored.id.value) {
sink.push(ctx.diag_for_unknown_advisory(ignored));
}
}
for ignore in ignore_hits
.into_iter()
.zip(ctx.cfg.ignore.iter())
.filter_map(|(hit, ignore)| if !hit { Some(ignore) } else { None })
{
sink.push(
ctx.diag_for_advisory_not_encountered(ignore, ctx.cfg.unused_ignored_advisory.into()),
);
}
for ignore in ignore_yanked_hits
.into_iter()
.zip(ctx.cfg.ignore_yanked.iter())
.filter_map(|(hit, ignore)| if !hit { Some(ignore) } else { None })
{
sink.push(ctx.diag_for_ignored_yanked_not_encountered(
ignore,
ctx.cfg.unused_ignored_advisory.into(),
));
}
if let Some(mut reporter) = audit_compatible_reporter {
for ser_report in report.serialized_reports {
reporter.report(ser_report);
}
}
}