circomspect_program_structure/utils/
sarif_conversion.rs

1use codespan_reporting::files::Files;
2use log::{debug, trace};
3use serde_sarif::sarif;
4use std::collections::HashSet;
5use std::fmt;
6use std::ops::Range;
7use std::path::PathBuf;
8use thiserror::Error;
9
10use crate::report::{Report, ReportCollection, ReportLabel};
11use crate::file_definition::{FileID, FileLibrary};
12
13// This is the Sarif file format version, not the tool version.
14const SARIF_VERSION: &str = "2.1.0";
15const DRIVER_NAME: &str = "Circomspect";
16const ORGANIZATION: &str = "Trail of Bits";
17const DOWNLOAD_URI: &str = "https://github.com/trailofbits/circomspect";
18
19/// A trait for objects that can be converted into a Sarif artifact.
20pub trait ToSarif {
21    type Sarif;
22    type Error;
23
24    /// Converts the object to the corresponding Sarif artifact.
25    fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error>;
26}
27
28impl ToSarif for ReportCollection {
29    type Sarif = sarif::Sarif;
30    type Error = SarifError;
31
32    fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error> {
33        debug!("converting report collection to Sarif-format");
34        // Build reporting descriptors.
35        trace!("building reporting descriptors");
36        let rules = self
37            .iter()
38            .map(|report| (report.name(), report.id()))
39            .collect::<HashSet<_>>()
40            .iter()
41            .map(|(name, id)| {
42                sarif::ReportingDescriptorBuilder::default().name(name).id(id).build()
43            })
44            .collect::<Result<Vec<_>, _>>()
45            .map_err(SarifError::from)?;
46        // Build tool.
47        //
48        // TODO: Should include primary package version.
49        trace!("building tool");
50        let driver = sarif::ToolComponentBuilder::default()
51            .name(DRIVER_NAME)
52            .organization(ORGANIZATION)
53            .download_uri(DOWNLOAD_URI)
54            .rules(rules)
55            .build()?;
56        let tool = sarif::ToolBuilder::default().driver(driver).build()?;
57        // Build run.
58        trace!("building run");
59        let results =
60            self.iter().map(|report| report.to_sarif(files)).collect::<SarifResult<Vec<_>>>()?;
61        let run = sarif::RunBuilder::default().tool(tool).results(results).build()?;
62        // Build main object.
63        trace!("building main Sarif object");
64        let sarif = sarif::SarifBuilder::default().runs(vec![run]).version(SARIF_VERSION).build();
65        sarif.map_err(SarifError::from)
66    }
67}
68
69impl ToSarif for Report {
70    type Sarif = sarif::Result;
71    type Error = SarifError;
72
73    fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Result> {
74        let level = self.category().to_level();
75        let rule_id = self.id();
76        // Build message.
77        trace!("building message");
78        let message = sarif::MessageBuilder::default().text(self.message()).build()?;
79        // Build primary and secondary locations.
80        trace!("building locations");
81        let locations = self
82            .primary()
83            .iter()
84            .map(|label| label.to_sarif(files))
85            .collect::<SarifResult<Vec<_>>>()?;
86        let related_locations = self
87            .secondary()
88            .iter()
89            .map(|label| label.to_sarif(files))
90            .collect::<SarifResult<Vec<_>>>()?;
91        // Build reporting descriptor reference.
92        let rule = sarif::ReportingDescriptorReferenceBuilder::default()
93            .id(&rule_id)
94            .build()
95            .map_err(SarifError::from)?;
96        // Build result.
97        trace!("building result");
98        sarif::ResultBuilder::default()
99            .level(level)
100            .message(message)
101            .rule_id(rule_id)
102            .rule(rule)
103            .locations(locations)
104            .related_locations(related_locations)
105            .build()
106            .map_err(SarifError::from)
107    }
108}
109
110impl ToSarif for ReportLabel {
111    type Sarif = sarif::Location;
112    type Error = SarifError;
113
114    fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Location> {
115        // Build artifact location.
116        trace!("building artifact location");
117        let file_uri = self.file_id.to_uri(files)?;
118        let artifact_location = sarif::ArtifactLocationBuilder::default().uri(file_uri).build()?;
119        // Build region.
120        trace!("building region");
121        assert!(self.range.start <= self.range.end);
122        let start = files
123            .to_storage()
124            .location(self.file_id, self.range.start)
125            .map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;
126        let end = files
127            .to_storage()
128            .location(self.file_id, self.range.end)
129            .map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;
130        let region = sarif::RegionBuilder::default()
131            .start_line(start.line_number as i64)
132            .start_column(start.column_number as i64)
133            .end_line(end.line_number as i64)
134            .end_column(end.column_number as i64)
135            .build()?;
136        // Build physical location.
137        trace!("building physical location");
138        let physical_location = sarif::PhysicalLocationBuilder::default()
139            .artifact_location(artifact_location)
140            .region(region)
141            .build()?;
142        // Build message.
143        trace!("building message");
144        let message = sarif::MessageBuilder::default().text(self.message.clone()).build()?;
145        // Build location.
146        trace!("building location");
147        sarif::LocationBuilder::default()
148            .physical_location(physical_location)
149            .id(0)
150            .message(message)
151            .build()
152            .map_err(SarifError::from)
153    }
154}
155
156trait ToUri {
157    type Error;
158    fn to_uri(&self, files: &FileLibrary) -> Result<String, Self::Error>;
159}
160
161impl ToUri for FileID {
162    type Error = SarifError;
163
164    fn to_uri(&self, files: &FileLibrary) -> Result<String, SarifError> {
165        let path: PathBuf = files
166            .to_storage()
167            .get(*self)
168            .map_err(|_| SarifError::UnknownFile(*self))?
169            .name()
170            .replace('"', "")
171            .into();
172        // This path already comes from an UTF-8 string so it is ok to unwrap here.
173        Ok(format!("file://{}", path.to_str().unwrap()))
174    }
175}
176
177#[derive(Error, Debug)]
178pub enum SarifError {
179    InvalidReportingDescriptorReference(#[from] sarif::ReportingDescriptorReferenceBuilderError),
180    InvalidReportingDescriptor(#[from] sarif::ReportingDescriptorBuilderError),
181    InvalidPhysicalLocationError(#[from] sarif::PhysicalLocationBuilderError),
182    InvalidArtifactLocation(#[from] sarif::ArtifactLocationBuilderError),
183    InvalidToolComponent(#[from] sarif::ToolComponentBuilderError),
184    InvalidLocation(#[from] sarif::LocationBuilderError),
185    InvalidMessage(#[from] sarif::MessageBuilderError),
186    InvalidRegion(#[from] sarif::RegionBuilderError),
187    InvalidResult(#[from] sarif::ResultBuilderError),
188    InvalidRun(#[from] sarif::RunBuilderError),
189    InvalidSarif(#[from] sarif::SarifBuilderError),
190    InvalidTool(#[from] sarif::ToolBuilderError),
191    InvalidFix(#[from] sarif::FixBuilderError),
192    UnknownLocation(FileID, Range<usize>),
193    UnknownFile(FileID),
194}
195
196type SarifResult<T> = Result<T, SarifError>;
197
198impl fmt::Display for SarifError {
199    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
200        write!(f, "failed to convert analysis results to Sarif format")
201    }
202}