From 8d3596b3a999730c56392c5960aac26a80cc6b9a Mon Sep 17 00:00:00 2001 From: BrettMayson Date: Thu, 2 Nov 2023 04:19:45 -0600 Subject: [PATCH] Preprocessor: pragma pe23_ignore_has_include (#592) --- bin/src/modules/rapifier.rs | 7 ++ book/analysis/config.md | 11 ++ libs/common/src/reporting/processed.rs | 11 ++ libs/preprocessor/src/codes/mod.rs | 1 + .../src/codes/pe21_pragma_invalid_suppress.rs | 4 +- .../src/codes/pe22_pragma_invalid_flag.rs | 4 +- .../src/codes/pe23_if_has_include.rs | 102 ++++++++++++++++++ libs/preprocessor/src/processor/defines.rs | 9 +- libs/preprocessor/src/processor/directives.rs | 28 ++++- libs/preprocessor/src/processor/mod.rs | 6 +- libs/preprocessor/src/processor/pragma.rs | 70 +++++++++--- libs/preprocessor/tests/bootstrap.rs | 1 + .../ignore_if_has_include/expected.hpp | 3 + .../ignore_if_has_include/source.hpp | 9 ++ 14 files changed, 243 insertions(+), 23 deletions(-) create mode 100644 libs/preprocessor/src/codes/pe23_if_has_include.rs create mode 100644 libs/preprocessor/tests/bootstrap/ignore_if_has_include/expected.hpp create mode 100644 libs/preprocessor/tests/bootstrap/ignore_if_has_include/source.hpp diff --git a/bin/src/modules/rapifier.rs b/bin/src/modules/rapifier.rs index 36745207..4d76e0b6 100644 --- a/bin/src/modules/rapifier.rs +++ b/bin/src/modules/rapifier.rs @@ -171,6 +171,13 @@ pub fn rapify(path: WorkspacePath, ctx: &Context) -> RapifyResult { ))), ); } + if processed.no_rapify() { + debug!( + "skipping rapify for {}, as instructed by preprocessor", + path.as_str() + ); + return (messages, Ok(())); + } let out = if path.filename().to_lowercase() == "config.cpp" { path.parent().join("config.bin").unwrap() } else { diff --git a/book/analysis/config.md b/book/analysis/config.md index e6f1c840..fc9b27be 100644 --- a/book/analysis/config.md +++ b/book/analysis/config.md @@ -24,6 +24,17 @@ The scope can be one of the following, if not specified, the scope will be `line | file | Suppresses the warning for the remainder of the current file, not including includes | | config | Suppresses the warning for the remainder of the current config, including includes | +## Preprocessor Flags + +HEMTT provides a few preprocessor flags to control the behavior of the preprocessor. + +| Flag | Description | +| ---- | ----------- | +| pw3_ignore_arr | Ignore padded arguments in `ARR_N` macros | +| pe23_ignore_has_include| Assume any `#if __has_include` is false | + +The scope of these flags is the same as the warning suppression scope. + ## Preprocessor Warnings ### [PW1] Redefine Macro diff --git a/libs/common/src/reporting/processed.rs b/libs/common/src/reporting/processed.rs index 0e0ce8eb..15bebc2c 100644 --- a/libs/common/src/reporting/processed.rs +++ b/libs/common/src/reporting/processed.rs @@ -38,6 +38,9 @@ pub struct Processed { /// Warnings warnings: Vec>, + + /// The preprocessor was able to check the file, but it should not be rapified + no_rapify: bool, } fn append_token( @@ -175,6 +178,7 @@ impl Processed { #[cfg(feature = "lsp")] usage: HashMap>, #[cfg(feature = "lsp")] declarations: HashMap, warnings: Vec>, + no_rapify: bool, ) -> Result { let mut processed = Self { #[cfg(feature = "lsp")] @@ -182,6 +186,7 @@ impl Processed { #[cfg(feature = "lsp")] usage, warnings, + no_rapify, ..Default::default() }; let mut string_stack = Vec::new(); @@ -248,6 +253,12 @@ impl Processed { pub fn warnings(&self) -> &[Box] { &self.warnings } + + #[must_use] + /// Returns whether the file should not be rapified + pub const fn no_rapify(&self) -> bool { + self.no_rapify + } } #[derive(Debug)] diff --git a/libs/preprocessor/src/codes/mod.rs b/libs/preprocessor/src/codes/mod.rs index fbc07fa6..a61f8b84 100644 --- a/libs/preprocessor/src/codes/mod.rs +++ b/libs/preprocessor/src/codes/mod.rs @@ -18,6 +18,7 @@ pub mod pe1_unexpected_token; pub mod pe20_pragma_invalid_scope; pub mod pe21_pragma_invalid_suppress; pub mod pe22_pragma_invalid_flag; +pub mod pe23_if_has_include; pub mod pe2_unexpected_eof; pub mod pe3_expected_ident; pub mod pe4_unknown_directive; diff --git a/libs/preprocessor/src/codes/pe21_pragma_invalid_suppress.rs b/libs/preprocessor/src/codes/pe21_pragma_invalid_suppress.rs index 41c68321..e081b8c5 100644 --- a/libs/preprocessor/src/codes/pe21_pragma_invalid_suppress.rs +++ b/libs/preprocessor/src/codes/pe21_pragma_invalid_suppress.rs @@ -2,6 +2,8 @@ use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Source}; use hemtt_common::reporting::{Annotation, AnnotationLevel, Code, Token}; use tracing::error; +use crate::processor::pragma::Suppress; + use super::similar_values; #[allow(unused)] @@ -39,7 +41,7 @@ impl Code for PragmaInvalidSuppress { } fn help(&self) -> Option { - let similar = similar_values(self.token.to_string().as_str(), &["pw3_padded_arg"]); + let similar = similar_values(self.token.to_string().as_str(), Suppress::as_slice()); if similar.is_empty() { None } else { diff --git a/libs/preprocessor/src/codes/pe22_pragma_invalid_flag.rs b/libs/preprocessor/src/codes/pe22_pragma_invalid_flag.rs index 726c0cf5..34ce97b4 100644 --- a/libs/preprocessor/src/codes/pe22_pragma_invalid_flag.rs +++ b/libs/preprocessor/src/codes/pe22_pragma_invalid_flag.rs @@ -2,6 +2,8 @@ use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Source}; use hemtt_common::reporting::{Annotation, AnnotationLevel, Code, Token}; use tracing::error; +use crate::processor::pragma::Flag; + use super::similar_values; #[allow(unused)] @@ -33,7 +35,7 @@ impl Code for PragmaInvalidFlag { } fn help(&self) -> Option { - let similar = similar_values(self.token.to_string().as_str(), &["pw3_ignore_arr"]); + let similar = similar_values(self.token.to_string().as_str(), Flag::as_slice()); if similar.is_empty() { None } else { diff --git a/libs/preprocessor/src/codes/pe23_if_has_include.rs b/libs/preprocessor/src/codes/pe23_if_has_include.rs new file mode 100644 index 00000000..3fea6548 --- /dev/null +++ b/libs/preprocessor/src/codes/pe23_if_has_include.rs @@ -0,0 +1,102 @@ +use ariadne::{ColorGenerator, Fmt, Label, Report, ReportKind, Source}; +use hemtt_common::reporting::{Annotation, AnnotationLevel, Code, Token}; +use tracing::error; + +#[allow(unused)] +/// An unknown `#pragma hemtt flag` code +/// +/// ```cpp +/// #pragma hemtt flag unknown +/// ``` +pub struct IfHasInclude { + /// The [`Token`] of the code + pub(crate) token: Box, +} + +impl Code for IfHasInclude { + fn ident(&self) -> &'static str { + "PE23" + } + + fn token(&self) -> Option<&Token> { + Some(&self.token) + } + + fn message(&self) -> String { + "use of `#if __has_include`".to_string() + } + + fn label_message(&self) -> String { + "use of `#if __has_include`".to_string() + } + + fn help(&self) -> Option { + Some(String::from("use `#pragma hemtt flag pe23_ignore_has_include` to have HEMTT act as if the include was not found")) + } + + fn report_generate(&self) -> Option { + let mut colors = ColorGenerator::default(); + let color_token = colors.next(); + let mut out = Vec::new(); + let mut report = Report::build( + ReportKind::Error, + self.token.position().path().as_str(), + self.token.position().start().offset(), + ) + .with_code(self.ident()) + .with_message(self.message()) + .with_label( + Label::new(( + self.token.position().path().as_str(), + self.token.position().start().offset()..self.token.position().end().offset(), + )) + .with_color(color_token) + .with_message(format!( + "use of `{}`", + self.token.symbol().to_string().fg(color_token) + )), + ); + if let Some(help) = self.help() { + report = report.with_help(help); + } + if let Err(e) = report.finish().write_for_stdout( + ( + self.token.position().path().as_str(), + Source::from( + self.token + .position() + .path() + .read_to_string() + .unwrap_or_default(), + ), + ), + &mut out, + ) { + error!("while reporting: {e}"); + return None; + } + Some(String::from_utf8(out).unwrap_or_default()) + } + + fn ci_generate(&self) -> Vec { + vec![self.annotation( + AnnotationLevel::Error, + self.token.position().path().as_str().to_string(), + self.token.position(), + )] + } + + #[cfg(feature = "lsp")] + fn generate_lsp(&self) -> Option<(VfsPath, Diagnostic)> { + let Some(path) = self.token.position().path() else { + return None; + }; + Some(( + path.clone(), + self.diagnostic(Range { + start: self.token.position().start().to_lsp() - 1, + end: self.token.position().end().to_lsp(), + }), + )) + } +} diff --git a/libs/preprocessor/src/processor/defines.rs b/libs/preprocessor/src/processor/defines.rs index 9ff73193..9a25ca82 100644 --- a/libs/preprocessor/src/processor/defines.rs +++ b/libs/preprocessor/src/processor/defines.rs @@ -17,7 +17,10 @@ use crate::{ Error, }; -use super::{pragma::Pragma, Processor}; +use super::{ + pragma::{Flag, Pragma, Suppress}, + Processor, +}; impl Processor { /// Reads the arguments of a macro call @@ -215,8 +218,8 @@ impl Processor { } let mut arg_defines = HashMap::new(); for (arg, value) in function.args().iter().zip(args) { - if !pragma.is_suppressed("pw3_padded_arg") - && (!pragma.is_flagged("pw3_ignore_arr") + if !pragma.is_suppressed(&Suppress::Pw3PaddedArg) + && (!pragma.is_flagged(&Flag::Pw3IgnoreArr) || !ident_string.starts_with("ARR_")) { for token in [value.first(), value.last()] { diff --git a/libs/preprocessor/src/processor/directives.rs b/libs/preprocessor/src/processor/directives.rs index 612ece36..9a86e079 100644 --- a/libs/preprocessor/src/processor/directives.rs +++ b/libs/preprocessor/src/processor/directives.rs @@ -5,6 +5,7 @@ use hemtt_common::{ reporting::{Output, Symbol, Token}, }; use peekmore::{PeekMore, PeekMoreIterator}; +use tracing::debug; use crate::{ codes::{ @@ -12,14 +13,16 @@ use crate::{ pe14_include_unexpected_suffix::IncludeUnexpectedSuffix, pe15_if_invalid_operator::IfInvalidOperator, pe16_if_incompatible_types::IfIncompatibleType, pe19_pragma_unknown::PragmaUnknown, - pe20_pragma_invalid_scope::PragmaInvalidScope, pe2_unexpected_eof::UnexpectedEOF, - pe3_expected_ident::ExpectedIdent, pe4_unknown_directive::UnknownDirective, - pe6_change_builtin::ChangeBuiltin, pe7_if_unit_or_function::IfUnitOrFunction, - pe8_if_undefined::IfUndefined, pw1_redefine::RedefineMacro, + pe20_pragma_invalid_scope::PragmaInvalidScope, pe23_if_has_include::IfHasInclude, + pe2_unexpected_eof::UnexpectedEOF, pe3_expected_ident::ExpectedIdent, + pe4_unknown_directive::UnknownDirective, pe6_change_builtin::ChangeBuiltin, + pe7_if_unit_or_function::IfUnitOrFunction, pe8_if_undefined::IfUndefined, + pw1_redefine::RedefineMacro, }, defines::Defines, definition::{Definition, FunctionDefinition}, ifstate::IfState, + processor::pragma::Flag, Error, }; @@ -71,7 +74,7 @@ impl Processor { Ok(()) } ("if", true) => { - self.directive_if(command, stream)?; + self.directive_if(pragma, command, stream)?; Ok(()) } ("ifdef", true) => self.directive_ifdef(command, true, stream), @@ -289,6 +292,7 @@ impl Processor { #[allow(clippy::too_many_lines)] pub(crate) fn directive_if( &mut self, + pragma: &Pragma, command: Rc, stream: &mut PeekMoreIterator>>, ) -> Result<(), Error> { @@ -305,6 +309,20 @@ impl Processor { Ok((vec![token], false)) } let left = self.next_value(stream, None)?; + if &Symbol::Word(String::from("__has_include")) == left.symbol() { + if pragma.is_flagged(&Flag::Pe23IgnoreIfHasInclude) { + debug!( + "ignoring __has_include due to pragma flag, this config will not be rapified" + ); + self.no_rapify = true; + self.ifstates.push_if(command, false); + self.skip_to_after_newline(stream, None); + return Ok(()); + } + return Err(Error::Code(Box::new(IfHasInclude { + token: Box::new(left.as_ref().clone()), + }))); + } let (left, left_defined) = value(&mut self.defines, left)?; self.skip_whitespace(stream, None); let mut operators = Vec::with_capacity(2); diff --git a/libs/preprocessor/src/processor/mod.rs b/libs/preprocessor/src/processor/mod.rs index 5741bf31..0bf89ad1 100644 --- a/libs/preprocessor/src/processor/mod.rs +++ b/libs/preprocessor/src/processor/mod.rs @@ -19,7 +19,7 @@ use self::pragma::Pragma; mod defines; mod directives; -mod pragma; +pub mod pragma; mod whitespace; #[derive(Default)] @@ -44,6 +44,9 @@ pub struct Processor { /// Warnings pub(crate) warnings: Vec>, + + /// The preprocessor was able to run checks, but the output should not be rapified + pub(crate) no_rapify: bool, } impl Processor { @@ -88,6 +91,7 @@ impl Processor { #[cfg(feature = "lsp")] processor.declarations, processor.warnings, + processor.no_rapify, ) .map_err(Into::into) } diff --git a/libs/preprocessor/src/processor/pragma.rs b/libs/preprocessor/src/processor/pragma.rs index 9cf939e6..59ea360d 100644 --- a/libs/preprocessor/src/processor/pragma.rs +++ b/libs/preprocessor/src/processor/pragma.rs @@ -13,8 +13,8 @@ use crate::{ #[derive(Debug, Clone)] pub struct Pragma { pub(crate) root: bool, - suppress: HashMap, - flags: HashMap, + suppress: HashMap, + flags: HashMap, } impl Pragma { @@ -54,43 +54,43 @@ impl Pragma { self.suppress.retain(|_, v| *v as u8 > Scope::Line as u8); } - pub fn is_suppressed(&self, code: &str) -> bool { + pub fn is_suppressed(&self, code: &Suppress) -> bool { self.suppress.contains_key(code) } pub fn suppress(&mut self, token: &Rc, scope: Scope) -> Result<(), Error> { let code = token.symbol().to_string(); - if !["pw3_padded_arg"].contains(&code.as_str()) { + let Ok(suppress) = Suppress::try_from(code.as_str()) else { return Err(Error::Code(Box::new(PragmaInvalidSuppress { token: Box::new((**token).clone()), }))); - } - if let Some(existing) = self.suppress.get(&code) { + }; + if let Some(existing) = self.suppress.get(&suppress) { if *existing as u8 > scope as u8 { return Ok(()); } } - self.suppress.insert(code, scope); + self.suppress.insert(suppress, scope); Ok(()) } - pub fn is_flagged(&self, code: &str) -> bool { + pub fn is_flagged(&self, code: &Flag) -> bool { self.flags.contains_key(code) } pub fn flag(&mut self, token: &Rc, scope: Scope) -> Result<(), Error> { let code = token.symbol().to_string(); - if !["pw3_ignore_arr"].contains(&code.as_str()) { + let Ok(flag) = Flag::try_from(code.as_str()) else { return Err(Error::Code(Box::new(PragmaInvalidFlag { token: Box::new((**token).clone()), }))); - } - if let Some(existing) = self.flags.get(&code) { + }; + if let Some(existing) = self.flags.get(&flag) { if *existing as u8 > scope as u8 { return Ok(()); } } - self.flags.insert(code, scope); + self.flags.insert(flag, scope); Ok(()) } } @@ -114,3 +114,49 @@ impl TryFrom<&str> for Scope { } } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Flag { + Pw3IgnoreArr, + Pe23IgnoreIfHasInclude, +} + +impl Flag { + pub const fn as_slice() -> &'static [&'static str] { + &["pw3_ignore_arr", "pe23_ignore_has_include"] + } +} + +impl TryFrom<&str> for Flag { + type Error = (); + + fn try_from(value: &str) -> Result { + match value { + "pw3_ignore_arr" => Ok(Self::Pw3IgnoreArr), + "pe23_ignore_has_include" => Ok(Self::Pe23IgnoreIfHasInclude), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Suppress { + Pw3PaddedArg, +} + +impl Suppress { + pub const fn as_slice() -> &'static [&'static str] { + &["pw3_padded_arg"] + } +} + +impl TryFrom<&str> for Suppress { + type Error = (); + + fn try_from(value: &str) -> Result { + match value { + "pw3_padded_arg" => Ok(Self::Pw3PaddedArg), + _ => Err(()), + } + } +} diff --git a/libs/preprocessor/tests/bootstrap.rs b/libs/preprocessor/tests/bootstrap.rs index 708073f0..76a6442b 100644 --- a/libs/preprocessor/tests/bootstrap.rs +++ b/libs/preprocessor/tests/bootstrap.rs @@ -58,6 +58,7 @@ bootstrap!(if_operators); bootstrap!(if_pass); bootstrap!(if_read); bootstrap!(if_value); +bootstrap!(ignore_if_has_include); bootstrap!(ignore_quoted); bootstrap!(include_empty); bootstrap!(include); diff --git a/libs/preprocessor/tests/bootstrap/ignore_if_has_include/expected.hpp b/libs/preprocessor/tests/bootstrap/ignore_if_has_include/expected.hpp new file mode 100644 index 00000000..4bf24ef4 --- /dev/null +++ b/libs/preprocessor/tests/bootstrap/ignore_if_has_include/expected.hpp @@ -0,0 +1,3 @@ + + +value = "yes"; diff --git a/libs/preprocessor/tests/bootstrap/ignore_if_has_include/source.hpp b/libs/preprocessor/tests/bootstrap/ignore_if_has_include/source.hpp new file mode 100644 index 00000000..9796cdf7 --- /dev/null +++ b/libs/preprocessor/tests/bootstrap/ignore_if_has_include/source.hpp @@ -0,0 +1,9 @@ +#define THING "yes" + +#pragma hemtt flag pe23_ignore_has_include +#if __has_include("idk.hpp") +#undef THING +#define THING "no" +#endif + +value = THING;