use codespan_reporting::files::Files;
use log::{debug, trace};
use serde_sarif::sarif;
use std::collections::HashSet;
use std::fmt;
use std::ops::Range;
use std::path::PathBuf;
use thiserror::Error;
use crate::report::{Report, ReportCollection, ReportLabel};
use crate::file_definition::{FileID, FileLibrary};
const SARIF_VERSION: &str = "2.1.0";
const DRIVER_NAME: &str = "Circomspect";
const ORGANIZATION: &str = "Trail of Bits";
const DOWNLOAD_URI: &str = "https://github.com/trailofbits/circomspect";
pub trait ToSarif {
type Sarif;
type Error;
fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error>;
}
impl ToSarif for ReportCollection {
type Sarif = sarif::Sarif;
type Error = SarifError;
fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error> {
debug!("converting report collection to Sarif-format");
trace!("building reporting descriptors");
let rules = self
.iter()
.map(|report| (report.name(), report.id()))
.collect::<HashSet<_>>()
.iter()
.map(|(name, id)| {
sarif::ReportingDescriptorBuilder::default().name(name).id(id).build()
})
.collect::<Result<Vec<_>, _>>()
.map_err(SarifError::from)?;
trace!("building tool");
let driver = sarif::ToolComponentBuilder::default()
.name(DRIVER_NAME)
.organization(ORGANIZATION)
.download_uri(DOWNLOAD_URI)
.rules(rules)
.build()?;
let tool = sarif::ToolBuilder::default().driver(driver).build()?;
trace!("building run");
let results =
self.iter().map(|report| report.to_sarif(files)).collect::<SarifResult<Vec<_>>>()?;
let run = sarif::RunBuilder::default().tool(tool).results(results).build()?;
trace!("building main Sarif object");
let sarif = sarif::SarifBuilder::default().runs(vec![run]).version(SARIF_VERSION).build();
sarif.map_err(SarifError::from)
}
}
impl ToSarif for Report {
type Sarif = sarif::Result;
type Error = SarifError;
fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Result> {
let level = self.category().to_level();
let rule_id = self.id();
trace!("building message");
let message = sarif::MessageBuilder::default().text(self.message()).build()?;
trace!("building locations");
let locations = self
.primary()
.iter()
.map(|label| label.to_sarif(files))
.collect::<SarifResult<Vec<_>>>()?;
let related_locations = self
.secondary()
.iter()
.map(|label| label.to_sarif(files))
.collect::<SarifResult<Vec<_>>>()?;
let rule = sarif::ReportingDescriptorReferenceBuilder::default()
.id(&rule_id)
.build()
.map_err(SarifError::from)?;
trace!("building result");
sarif::ResultBuilder::default()
.level(level)
.message(message)
.rule_id(rule_id)
.rule(rule)
.locations(locations)
.related_locations(related_locations)
.build()
.map_err(SarifError::from)
}
}
impl ToSarif for ReportLabel {
type Sarif = sarif::Location;
type Error = SarifError;
fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Location> {
trace!("building artifact location");
let file_uri = self.file_id.to_uri(files)?;
let artifact_location = sarif::ArtifactLocationBuilder::default().uri(file_uri).build()?;
trace!("building region");
assert!(self.range.start <= self.range.end);
let start = files
.to_storage()
.location(self.file_id, self.range.start)
.map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;
let end = files
.to_storage()
.location(self.file_id, self.range.end)
.map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;
let region = sarif::RegionBuilder::default()
.start_line(start.line_number as i64)
.start_column(start.column_number as i64)
.end_line(end.line_number as i64)
.end_column(end.column_number as i64)
.build()?;
trace!("building physical location");
let physical_location = sarif::PhysicalLocationBuilder::default()
.artifact_location(artifact_location)
.region(region)
.build()?;
trace!("building message");
let message = sarif::MessageBuilder::default().text(self.message.clone()).build()?;
trace!("building location");
sarif::LocationBuilder::default()
.physical_location(physical_location)
.id(0)
.message(message)
.build()
.map_err(SarifError::from)
}
}
trait ToUri {
type Error;
fn to_uri(&self, files: &FileLibrary) -> Result<String, Self::Error>;
}
impl ToUri for FileID {
type Error = SarifError;
fn to_uri(&self, files: &FileLibrary) -> Result<String, SarifError> {
let path: PathBuf = files
.to_storage()
.get(*self)
.map_err(|_| SarifError::UnknownFile(*self))?
.name()
.replace('"', "")
.into();
Ok(format!("file://{}", path.to_str().unwrap()))
}
}
#[derive(Error, Debug)]
pub enum SarifError {
InvalidReportingDescriptorReference(#[from] sarif::ReportingDescriptorReferenceBuilderError),
InvalidReportingDescriptor(#[from] sarif::ReportingDescriptorBuilderError),
InvalidPhysicalLocationError(#[from] sarif::PhysicalLocationBuilderError),
InvalidArtifactLocation(#[from] sarif::ArtifactLocationBuilderError),
InvalidToolComponent(#[from] sarif::ToolComponentBuilderError),
InvalidLocation(#[from] sarif::LocationBuilderError),
InvalidMessage(#[from] sarif::MessageBuilderError),
InvalidRegion(#[from] sarif::RegionBuilderError),
InvalidResult(#[from] sarif::ResultBuilderError),
InvalidRun(#[from] sarif::RunBuilderError),
InvalidSarif(#[from] sarif::SarifBuilderError),
InvalidTool(#[from] sarif::ToolBuilderError),
InvalidFix(#[from] sarif::FixBuilderError),
UnknownLocation(FileID, Range<usize>),
UnknownFile(FileID),
}
type SarifResult<T> = Result<T, SarifError>;
impl fmt::Display for SarifError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "failed to convert analysis results to Sarif format")
}
}