From ab5a6da519f91faa1ba5aa281112e547fcd08b88 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Thu, 23 Jan 2020 20:58:59 +0100 Subject: clang-parser: centralize diagnostic building/reporting --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 8 +- src/parser/clang/diagnostics.rs | 313 ++++++++++++++++++++++++++++++++++++++++ src/parser/clang/mod.rs | 1 + src/parser/clang/parsing.rs | 102 ++----------- 6 files changed, 336 insertions(+), 90 deletions(-) create mode 100644 src/parser/clang/diagnostics.rs diff --git a/Cargo.lock b/Cargo.lock index 4a11c70..c36fa9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "clang 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "codemap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "codemap-diagnostic 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "pandoc_types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index fe4118d..a2aaa92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1" clang = { version = "0.23", features = ["clang_8_0"] } codemap = "0.1" codemap-diagnostic = "0.1" +lazy_static = "1" log = "0.4" num_cpus = "1" pandoc_types = "0.2" diff --git a/src/main.rs b/src/main.rs index 5a0b175..254d8d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,9 +89,9 @@ fn start(codemap: &mut CodeMap) -> Result<()> { let config = load_effective_config(cli, codemap)?; let entities = if let Some(file) = file { - parser::clang::parse_file(file, &config.clang, codemap)? + parser::clang::parse_file(file, &config.clang)? } else { - parser::clang::parse_compile_commands(&config.clang, codemap)? + parser::clang::parse_compile_commands(&config.clang)? }; let base_output_dir = std::path::Path::new("doc"); @@ -101,9 +101,9 @@ fn start(codemap: &mut CodeMap) -> Result<()> { let file = file.clone(); let config = load_effective_config(cli, codemap)?; let entities = if let Some(file) = file { - parser::clang::parse_file(file, &config.clang, codemap)? + parser::clang::parse_file(file, &config.clang)? } else { - parser::clang::parse_compile_commands(&config.clang, codemap)? + parser::clang::parse_compile_commands(&config.clang)? }; serde_json::to_writer_pretty(std::io::stdout().lock(), &entities)?; } diff --git a/src/parser/clang/diagnostics.rs b/src/parser/clang/diagnostics.rs new file mode 100644 index 0000000..7aa43be --- /dev/null +++ b/src/parser/clang/diagnostics.rs @@ -0,0 +1,313 @@ +use anyhow::{anyhow, Context, Result}; +use clang::{ + source::{SourceLocation as ClangSourceLocation, SourceRange as ClangSourceRange}, + Entity as ClangEntity, +}; +use codemap::File; +use codemap_diagnostic::{Diagnostic, Level, SpanLabel}; +use lazy_static::lazy_static; + +use std::collections::{hash_map, HashMap}; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; + +lazy_static! { + pub(super) static ref CODEMAP: RwLock = RwLock::new(CodeMap::new()); +} + +#[derive(Default)] +pub(super) struct CodeMap { + files: HashMap>, + raw_codemap: codemap::CodeMap, +} + +impl CodeMap { + pub fn new() -> Self { + Default::default() + } + + pub fn span_from_entity(&mut self, entity: ClangEntity) -> Result { + self.span_from_libclang_range(entity.get_range().ok_or_else(|| { + anyhow!("Trying to build a diagnostic from an entity that doesn't have a range") + })?) + } + + pub fn span_from_libclang_range(&mut self, range: ClangSourceRange) -> Result { + let start_file_location = range.get_start().get_file_location(); + let file_name = start_file_location + .file + .ok_or_else(|| { + anyhow!("Trying to build a diagnostic from an entity that isn't located in a file") + })? + .get_path(); + + let file = self.get_file(file_name)?; + + let begin = start_file_location.offset as u64; + let end = range.get_end().get_file_location().offset as u64; + + Ok(file.span.subspan(begin, end)) + } + + pub fn span_from_libclang_location( + &mut self, + location: ClangSourceLocation, + ) -> Result { + let file_location = location.get_file_location(); + let file_name = file_location + .file + .ok_or_else(|| { + anyhow!("Trying to build a diagnostic from an entity that isn't located in a file") + })? + .get_path(); + + let file = self.get_file(file_name)?; + + let offset = file_location.offset as u64; + + Ok(file.span.subspan(offset, offset)) + } + + pub fn get_file(&mut self, file_name: PathBuf) -> Result<&mut Arc> { + Ok(match self.files.entry(file_name.clone()) { + hash_map::Entry::Occupied(file) => file.into_mut(), + hash_map::Entry::Vacant(entry) => { + let file = self.raw_codemap.add_file( + file_name + .to_str() + .context("Filename is not valid UTF-8")? + .to_owned(), + std::fs::read_to_string(&file_name) + .with_context(|| format!("Cannot readfile: {:?}", file_name))?, + ); + entry.insert(file) + } + }) + } + + pub fn emitter(&self) -> codemap_diagnostic::Emitter { + use codemap_diagnostic::{ColorConfig, Emitter}; + Emitter::stderr(ColorConfig::Auto, Some(&self.raw_codemap)) + } +} + +#[derive(Debug, Clone)] +pub(super) struct DiagnosticBuilder { + level: Level, + message: String, + code: Option, + spans: Vec, +} + +impl DiagnosticBuilder { + pub fn new(level: Level, message: String) -> Self { + DiagnosticBuilder { + level, + message, + code: None, + spans: vec![], + } + } + + /* + pub fn error(message: String) -> Self { + Self::new(Level::Error, message) + } + + pub fn warning(message: String) -> Self { + Self::new(Level::Warning, message) + } + + pub fn note(message: String) -> Self { + Self::new(Level::Note, message) + } + + pub fn help(message: String) -> Self { + Self::new(Level::Help, message) + } + + pub fn bug(message: String) -> Self { + Self::new(Level::Bug, message) + } + + pub fn level(&mut self, level: Level) -> &mut Self { + self.level = level; + self + } + + pub fn message(&mut self, message: String) -> &mut Self { + self.message = message; + self + } + + pub fn code(&mut self, code: String) -> &mut Self { + self.code = Some(code); + self + } + */ + + pub fn span(&mut self, span: SpanLabel) -> &mut Self { + self.spans.push(span); + self + } + + pub fn build(self) -> Diagnostic { + Diagnostic { + level: self.level, + code: self.code, + message: self.message, + spans: self.spans, + } + } +} + +fn clang_fixit_to_label( + fixit: clang::diagnostic::FixIt, +) -> Result { + use clang::diagnostic::FixIt; + use codemap_diagnostic::SpanStyle; + + let mut codemap = CODEMAP + .write() + .expect("Failed to lock the codemap for writing"); + + Ok(match fixit { + FixIt::Deletion(range) => { + let span = codemap.span_from_libclang_range(range)?; + SpanLabel { + span, + label: Some(String::from("help: try deleting this")), + style: SpanStyle::Secondary, + } + } + FixIt::Insertion(location, text) => { + let span = codemap.span_from_libclang_location(location)?; + SpanLabel { + span, + label: Some(format!("help: try inserting {:?}", text)), + style: SpanStyle::Secondary, + } + } + FixIt::Replacement(range, text) => { + let span = codemap.span_from_libclang_range(range)?; + SpanLabel { + span, + label: Some(format!("help: try replacing this with {:?}", text)), + style: SpanStyle::Secondary, + } + } + }) +} + +pub(super) fn clang_diagnostic_to_diagnostics( + diagnostic: clang::diagnostic::Diagnostic, +) -> Vec { + use clang::diagnostic::Severity; + use codemap_diagnostic::SpanStyle; + + let level = match diagnostic.get_severity() { + Severity::Error | Severity::Fatal => Level::Error, + Severity::Warning => Level::Warning, + Severity::Note => Level::Note, + Severity::Ignored => return vec![], + }; + + let mut diag = DiagnosticBuilder::new(level, diagnostic.get_text()); + + { + let mut codemap = CODEMAP + .write() + .expect("Failed to lock the codemap for writing"); + if let Ok(span) = codemap.span_from_libclang_location(diagnostic.get_location()) { + diag.span(SpanLabel { + span, + label: None, + style: SpanStyle::Primary, + }); + }; + } + + for range in diagnostic.get_ranges() { + let mut codemap = CODEMAP + .write() + .expect("Failed to lock the codemap for writing"); + let span = match codemap.span_from_libclang_range(range) { + Ok(span) => span, + Err(_) => continue, + }; + + diag.span(SpanLabel { + span, + label: None, + style: SpanStyle::Primary, + }); + } + + let mut child_diags = diagnostic + .get_children() + .into_iter() + .flat_map(|diag| clang_diagnostic_to_diagnostics(diag)) + .collect(); + + for fixit_label in diagnostic + .get_fix_its() + .into_iter() + .filter_map(|fixit| clang_fixit_to_label(fixit).ok()) + { + diag.span(fixit_label); + } + + let mut diags = vec![diag.build()]; + diags.append(&mut child_diags); + diags +} + +pub(super) fn emit(diagnostics: &[Diagnostic]) { + CODEMAP + .read() + .expect("Failed to lock the codemap for reading") + .emitter() + .emit(diagnostics) +} + +pub(super) fn log(level: Level, message: String, entity: ClangEntity) { + use codemap_diagnostic::SpanStyle; + + let mut diag = DiagnosticBuilder::new(level, message); + + if let Ok(span) = CODEMAP + .write() + .expect("Failed to lock the codemap for writing") + .span_from_entity(entity) + { + diag.span(SpanLabel { + span, + label: None, + style: SpanStyle::Primary, + }); + }; + + emit(&[diag.build()]); +} + +pub(super) fn error(message: String, entity: ClangEntity) { + log(Level::Error, message, entity) +} + +/* +pub(super) fn warn(message: String, entity: ClangEntity) { + log(Level::Warning, message, entity) +} + +pub(super) fn note(message: String, entity: ClangEntity) { + log(Level::Note, message, entity) +} + +pub(super) fn help(message: String, entity: ClangEntity) { + log(Level::Help, message, entity) +} + +pub(super) fn bug(message: String, entity: ClangEntity) { + log(Level::Bug, message, entity) +} +*/ diff --git a/src/parser/clang/mod.rs b/src/parser/clang/mod.rs index da49462..a65beed 100644 --- a/src/parser/clang/mod.rs +++ b/src/parser/clang/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod config; +mod diagnostics; mod entities; mod parsing; diff --git a/src/parser/clang/parsing.rs b/src/parser/clang/parsing.rs index 6883d06..50ec5d9 100644 --- a/src/parser/clang/parsing.rs +++ b/src/parser/clang/parsing.rs @@ -1,10 +1,10 @@ use super::config::Config; +use super::diagnostics; use super::entities::*; use crate::types::*; use anyhow::{anyhow, Context, Error, Result}; use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr}; -use codemap::CodeMap; use thiserror::Error; use std::collections::BTreeMap; @@ -234,10 +234,7 @@ impl FromTopLevel for Struct { } */ -pub(crate) fn parse_compile_commands( - config: &Config, - codemap: &mut CodeMap, -) -> Result> { +pub(crate) fn parse_compile_commands(config: &Config) -> Result> { let clang = Clang::new().unwrap(); let index = Index::new( &clang, /* exclude from pch = */ false, /* print diagnostics = */ false, @@ -268,15 +265,6 @@ pub(crate) fn parse_compile_commands( let filename = command.get_filename(); - let file_map = codemap.add_file( - filename - .to_str() - .context("File is not valid UTF-8")? - .to_owned(), - std::fs::read_to_string(&filename) - .with_context(|| format!("Cannot readfile: {:?}", filename))?, - ); - trace!("Parsing file: {:?}", filename); // The file name is passed as an argument in the compile commands let mut parser = index.parser(""); @@ -293,8 +281,6 @@ pub(crate) fn parse_compile_commands( .with_context(|| format!("Could not parse file: {:?}", filename))?, &mut entities, &toplevel_directory, - file_map.span, - &codemap, )?; trace!("Changing directory to: {:?}", toplevel_directory); @@ -309,11 +295,7 @@ pub(crate) fn parse_compile_commands( Ok(entities.into()) } -pub(crate) fn parse_file( - path: T, - config: &Config, - codemap: &mut CodeMap, -) -> Result> +pub(crate) fn parse_file(path: T, config: &Config) -> Result> where T: Into, T: AsRef, @@ -324,7 +306,6 @@ where // as provided in the command line let filename = path.to_string(); - let file_map = codemap.add_file(filename.clone(), std::fs::read_to_string(&path)?); let path = path.as_ref().canonicalize()?; let toplevel_directory = std::env::current_dir().context("Cannot read current directory")?; @@ -341,14 +322,18 @@ where .unwrap_or_default(); clang_arguments.extend_from_slice(&config.extra_args); - if let Some(command) = maybe_command { + let mut parser = if let Some(command) = maybe_command { let directory = command.get_directory(); trace!("Changing directory to: {:?}", directory); std::env::set_current_dir(&directory) .with_context(|| format!("Cannot change current directory to: {:?}", directory))?; - } + // If it has the parameters provided by the compilation database, + // then it knows which file to parse + index.parser("") + } else { + index.parser(path) + }; - let mut parser = index.parser(""); parser.skip_function_bodies(true); parser.arguments(&clang_arguments); @@ -363,8 +348,6 @@ where .with_context(|| format!("Could not parse file: {:?}", filename))?, &mut entities, &toplevel_directory, - file_map.span, - &codemap, )?; trace!("Changing directory to: {:?}", toplevel_directory); @@ -382,8 +365,6 @@ fn parse_unit( trans_unit: &TranslationUnit, entities: &mut TopLevel, base_dir: impl AsRef, - file_span: codemap::Span, - codemap: &CodeMap, ) -> Result<()> { trans_unit.get_entity().visit_children(|entity, _parent| { if is_in_system_header(entity, &base_dir) { @@ -400,36 +381,18 @@ fn parse_unit( trace!("Entity with USR = {:?}", usr); debug!("Parsing toplevel entity: {:?}", entity); - add_entity(entity, entities, file_span, codemap) + add_entity(entity, entities) }); - use codemap_diagnostic::{ColorConfig, Emitter}; - - let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); - - for diagnostic in trans_unit.get_diagnostics().iter() { - warn!("{}", diagnostic); + for diagnostic in trans_unit.get_diagnostics() { + let diags = diagnostics::clang_diagnostic_to_diagnostics(diagnostic); + diagnostics::emit(&diags); /* - let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { - Some(diag) => diag, - None => continue, - }; - let sub_diags = diagnostic .get_children() .into_iter() .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); - let fix_it_diags = diagnostic - .get_fix_its() - .into_iter() - .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); - - emitter.emit( - &std::iter::once(main_diag) - .chain(sub_diags) - .chain(fix_it_diags) - .collect::>(), ); */ } @@ -462,8 +425,6 @@ fn is_in_system_header(entity: clang::Entity, base_dir: impl AsRef) -> boo fn add_entity( libclang_entity: clang::Entity, toplevel: &mut TopLevel, - file_span: codemap::Span, - codemap: &CodeMap, ) -> clang::EntityVisitResult { if libclang_entity.get_usr().is_none() { return clang::EntityVisitResult::Continue; @@ -472,38 +433,7 @@ fn add_entity( let kind = match ClangEntityKind::try_from(libclang_entity.get_kind()) { Ok(kind) => kind, Err(err) => { - use codemap_diagnostic::{ - ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle, - }; - let spans = if let Some(range) = libclang_entity.get_range() { - // TODO: add every file parsed in this translation unit to the - // codemap, so we can properly report errors - if !range.is_in_main_file() { - vec![] - } else { - let begin = range.get_start().get_file_location().offset as u64; - let end = range.get_end().get_file_location().offset as u64; - - vec![SpanLabel { - span: file_span.subspan(begin, end), - label: None, - style: SpanStyle::Primary, - }] - } - } else { - vec![] - }; - - let diag = Diagnostic { - level: Level::Warning, - message: format!("{}", err), - code: None, - spans, - }; - - let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(codemap)); - emitter.emit(&[diag]); - + diagnostics::error(format!("{}", err), libclang_entity); return clang::EntityVisitResult::Continue; } }; @@ -547,7 +477,7 @@ fn add_entity( // TODO: check result if let Err(err) = result { - error!("{}: {:?}", err, libclang_entity); + diagnostics::error(format!("{}", err), libclang_entity); return ::clang::EntityVisitResult::Continue; } } -- cgit v1.2.3