Skip to content

Commit

Permalink
Refactor diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
Jake-Shadle committed Oct 4, 2020
1 parent 2c9f7f0 commit 21a5d5c
Show file tree
Hide file tree
Showing 23 changed files with 2,222 additions and 1,651 deletions.
4 changes: 1 addition & 3 deletions examples/09_bans/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ deny = [
{ name = "openssl-sys" },
]
skip = [
# rustls uses an old version of base64
{ name = "base64", version = "0.10" },
# miow unfortunately still uses the ancient 0.2 version of winapi
{ name = "winapi", version = "=0.2.8" },
]
]
218 changes: 218 additions & 0 deletions src/advisories/diags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use crate::{
diag::{Check, Diag, Diagnostic, KrateCoord, Label, Pack, Severity},
LintLevel,
};
use rustsec::advisory::{informational::Informational, metadata::Metadata, Id};

fn get_notes_from_advisory(advisory: &Metadata) -> Vec<String> {
let mut n = Vec::new();

n.push(format!("ID: {}", advisory.id));
n.push(advisory.description.clone());

if let Some(ref url) = advisory.url {
n.push(format!("URL: {}", url));
}

n
}

impl<'a> crate::CheckCtx<'a, super::cfg::ValidConfig> {
pub(crate) fn diag_for_advisory<F>(
&self,
krate: &crate::Krate,
krate_index: krates::NodeId,
advisory: &Metadata,
mut on_ignore: F,
) -> Pack
where
F: FnMut(usize),
{
#[derive(Clone, Copy)]
enum AdvisoryType {
Vulnerability,
Notice,
Unmaintained,
Unsound,
}

let (severity, ty) = {
let (lint_level, msg) = match &advisory.informational {
// Everything that isn't an informational advisory is a vulnerability
None => (self.cfg.vulnerability, AdvisoryType::Vulnerability),
Some(info) => match info {
// Security notices for a crate which are published on https://rustsec.org
// but don't represent a vulnerability in a crate itself.
Informational::Notice => (self.cfg.notice, AdvisoryType::Notice),
// Crate is unmaintained / abandoned
Informational::Unmaintained => {
(self.cfg.unmaintained, AdvisoryType::Unmaintained)
}
Informational::Unsound => (self.cfg.unsound, AdvisoryType::Unsound),
// Other types of informational advisories: left open-ended to add
// more of them in the future.
Informational::Other(_) => {
unreachable!("rustsec only returns these if we ask, and there are none at the moment to ask for");
}
_ => unreachable!("unknown advisory type encountered"),
},
};

// Ok, we found a crate whose version lies within the range of an
// advisory, but the user might have decided to ignore it
// for "reasons", but in that case we still emit it to the log
// so it doesn't just disappear into the aether
let lint_level = if let Ok(index) = self
.cfg
.ignore
.binary_search_by(|i| i.value.cmp(&advisory.id))
{
on_ignore(index);
LintLevel::Allow
} else if let Some(severity_threshold) = self.cfg.severity_threshold {
if let Some(advisory_severity) = advisory.cvss.as_ref().map(|cvss| cvss.severity())
{
if advisory_severity < severity_threshold {
LintLevel::Allow
} else {
lint_level
}
} else {
lint_level
}
} else {
lint_level
};

(
match lint_level {
LintLevel::Warn => Severity::Warning,
LintLevel::Deny => Severity::Error,
LintLevel::Allow => Severity::Help,
},
msg,
)
};

let notes = get_notes_from_advisory(&advisory);

let mut pack = Pack::with_kid(Check::Advisories, krate.id.clone());

let (message, code) = match ty {
AdvisoryType::Vulnerability => ("security vulnerability detected", "A001"),
AdvisoryType::Notice => ("notice advisory detected", "A002"),
AdvisoryType::Unmaintained => ("unmaintained advisory detected", "A003"),
AdvisoryType::Unsound => ("unsound advisory detected", "A004"),
};

let diag = pack.push(
Diagnostic::new(severity)
.with_message(advisory.title.clone())
.with_labels(vec![self
.krate_spans
.label_for_index(krate_index.index(), message)])
.with_code(code)
.with_notes(notes),
);

if self.serialize_extra {
diag.extra = serde_json::to_value(&advisory)
.ok()
.map(|v| ("advisory", v));
}

pack
}

pub(crate) fn diag_for_yanked(
&self,
krate: &crate::Krate,
krate_index: krates::NodeId,
) -> Pack {
let mut pack = Pack::with_kid(Check::Advisories, krate.id.clone());
pack.push(
Diagnostic::new(match self.cfg.yanked.value {
LintLevel::Allow => Severity::Help,
LintLevel::Deny => Severity::Error,
LintLevel::Warn => Severity::Warning,
})
.with_message("detected yanked crate")
.with_code("A005")
.with_labels(vec![self
.krate_spans
.label_for_index(krate_index.index(), "yanked version")]),
);

pack
}

pub(crate) fn diag_for_index_failure<D: std::fmt::Display>(&self, error: D) -> Pack {
(
Check::Advisories,
Diagnostic::warning()
.with_message(format!("unable to check for yanked crates: {}", error))
.with_code("A006")
.with_labels(vec![Label::primary(
self.cfg.file_id,
self.cfg.yanked.span.clone(),
)
.with_message("lint level defined here")]),
)
.into()
}

pub(crate) fn diag_for_advisory_not_encountered(
&self,
not_hit: &crate::cfg::Spanned<Id>,
) -> Pack {
(
Check::Advisories,
Diagnostic::warning()
.with_message("advisory was not encountered")
.with_code("A007")
.with_labels(vec![Label::primary(self.cfg.file_id, not_hit.span.clone())
.with_message("no crate matched advisory criteria")]),
)
.into()
}
}

pub(crate) struct NoAvailablePatches<'a> {
pub(crate) affected_krate_coord: KrateCoord,
pub(crate) advisory: &'a Metadata,
}

impl<'a> Into<Diag> for NoAvailablePatches<'a> {
fn into(self) -> Diag {
let notes = get_notes_from_advisory(self.advisory);
Diagnostic::error()
.with_message("advisory has no available patches")
.with_code("AF001")
.with_labels(vec![self
.affected_krate_coord
.into_label()
.with_message("affected crate")])
.with_notes(notes)
.into()
}
}

pub(crate) struct NoAvailablePatchedVersions<'a> {
pub(crate) affected_krate_coord: KrateCoord,
pub(crate) advisory: &'a Metadata,
}

impl<'a> Into<Diag> for NoAvailablePatchedVersions<'a> {
fn into(self) -> Diag {
let notes = get_notes_from_advisory(self.advisory);
Diagnostic::error()
.with_message("affected crate has no available patched versions")
.with_code("AF002")
.with_labels(vec![self
.affected_krate_coord
.into_label()
.with_message("affected crate")])
.with_notes(notes)
.into()
}
}
50 changes: 21 additions & 29 deletions src/advisories/fix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{diag::Pack, index::Index, Krate, Krates};
use super::diags;
use crate::{
diag::{self, Check, Diagnostic, Label, Pack},
index::Index,
Krate, Krates,
};
use anyhow::{Context, Error};
use rustsec::advisory::Metadata;
use semver::{Version, VersionReq};
Expand Down Expand Up @@ -72,6 +77,7 @@ impl super::Report {
&'a self,
krates: &'a Krates,
semv: Semver,
krate_spans: &'a diag::KrateSpans,
) -> Result<PatchSet<'a>, Error> {
use krates::petgraph as pg;
use pg::{visit::EdgeRef, Direction};
Expand All @@ -80,7 +86,7 @@ impl super::Report {
let mut index = Index::load(krates)?;

let mut candidates: Vec<(pg::graph::EdgeIndex<u32>, Vec<PatchCandidate<'_>>)> = Vec::new();
//let mut diags = Vec::new();
let mut diags = Vec::new();

#[derive(Debug)]
struct PatchCandidate<'a> {
Expand All @@ -99,18 +105,12 @@ impl super::Report {
// We could also see if there are unaffected versions, but easier to just say we only support
// fixing by using newer versions than older versions
if patchable.patched.is_empty() {
log::error!(
"advisory {} has no available patches",
patchable.advisory.id,
);
// let mut pack = Pack::with_kid(Check::Advisories, vuln_krate.id.clone());
// pack.push(
// Diagnostic::error()
// .with_message()
// .with_labels(vec![ctx.label_for_span(i.index(), message)])
// .with_code(id.as_str().to_owned())
// .with_notes(notes),
// );
let mut pack = Pack::with_kid(Check::Advisories, vuln_krate.id.clone());
pack.push(diags::NoAvailablePatches {
affected_krate_coord: krate_spans.get_coord(ind.index()),
advisory: &patchable.advisory,
});
diags.push(pack);
continue;
}

Expand Down Expand Up @@ -153,18 +153,12 @@ impl super::Report {
};

if required.is_empty() {
log::error!(
"Advisory {} has no available versions that meet the patch requirements",
patchable.advisory.id
);
// let mut pack = Pack::with_kid(Check::Advisories, vuln_krate.id.clone());
// pack.push(
// Diagnostic::error()
// .with_message()
// .with_labels(vec![ctx.label_for_span(i.index(), message)])
// .with_code(id.as_str().to_owned())
// .with_notes(notes),
// );
let mut pack = Pack::with_kid(Check::Advisories, vuln_krate.id.clone());
pack.push(diags::NoAvailablePatchedVersions {
affected_krate_coord: krate_spans.get_coord(ind.index()),
advisory: &patchable.advisory,
});
diags.push(pack);
continue;
}

Expand All @@ -176,7 +170,6 @@ impl super::Report {
// We only need to visit each unique edge once
let edge_id = edge.id();
if !visited.insert(edge_id) {
//log::warn!("Already visited {} => {}", &graph[edge.source()], dep);
continue;
}

Expand Down Expand Up @@ -375,8 +368,7 @@ impl super::Report {
}
None => {
res = Some(Err(anyhow::anyhow!(
"Unable to find registry index entry for crate '{}'",
parent.name
"Unable to find registry index entry for crate",
)));
}
}
Expand Down
Loading

0 comments on commit 21a5d5c

Please sign in to comment.