1use crate::{__ToConsoleString as ToConsoleString, LightContext};
2use ansi_term::{
3 Color::{Green, Yellow},
4 Style,
5};
6use anyhow::{Result, bail};
7use bitflags::bitflags;
8use heck::ToKebabCase;
9use std::{collections::BTreeMap, io::IsTerminal, sync::Mutex};
10
11#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
14#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
15#[non_exhaustive]
16#[remain::sorted]
17pub enum Warning {
18 All,
19 DatabaseDoesNotExist,
20 DryRunFailed,
21 FilesChanged,
22 IgnoredFunctionsUnsupported,
23 IgnoredMacrosUnsupported,
24 IgnoredMethodsUnsupported,
25 InstrumentationNonbuildable,
26 ItMessageNotFound,
27 LocalFunctionAmbiguous,
28 ModulePathUnknown,
29 OptionDeprecated,
30 OutputInvalid,
31 ParsingFailed,
32 RunTestFailed,
33}
34
35impl std::fmt::Display for Warning {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", format!("{self:?}").to_kebab_case())
38 }
39}
40
41bitflags! {
42 #[derive(Clone, Copy)]
43 pub struct Flags: u8 {
44 const ONCE = 1 << 0;
45 }
46}
47
48#[allow(clippy::module_name_repetitions)]
50pub fn source_warn(
51 context: &LightContext,
52 warning: Warning,
53 source: &dyn ToConsoleString,
54 msg: &str,
55 flags: Flags,
56) -> Result<()> {
57 warn_internal(context, warning, Some(source), msg, flags)
58}
59
60pub fn warn(context: &LightContext, warning: Warning, msg: &str, flags: Flags) -> Result<()> {
73 warn_internal(context, warning, None, msg, flags)
74}
75
76const BUG_MSG: &str = "
77
78This may indicate a bug in Necessist. Consider opening an issue at: \
79https://github.com/trailofbits/necessist/issues
80";
81
82bitflags! {
83 struct State: u8 {
84 const ALLOW_MSG_EMITTED = 1 << 0;
85 const BUG_MSG_EMITTED = 1 << 1;
86 const WARNING_EMITTED = 1 << 2;
87 }
88}
89
90static WARNING_STATE_MAP: Mutex<BTreeMap<Warning, State>> = Mutex::new(BTreeMap::new());
91
92#[cfg_attr(dylint_lib = "general", allow(non_local_effect_before_error_return))]
93fn warn_internal(
94 context: &LightContext,
95 warning: Warning,
96 source: Option<&dyn ToConsoleString>,
97 msg: &str,
98 flags: Flags,
99) -> Result<()> {
100 assert_ne!(warning, Warning::All);
101
102 #[allow(clippy::unwrap_used)]
103 let mut warning_state_map = WARNING_STATE_MAP.lock().unwrap();
104
105 let state = warning_state_map
106 .entry(warning)
107 .or_insert_with(State::empty);
108
109 let msg = msg.to_owned()
111 + if may_be_bug(warning) && !state.contains(State::BUG_MSG_EMITTED) {
112 state.insert(State::BUG_MSG_EMITTED);
113 BUG_MSG
114 } else {
115 ""
116 };
117
118 if context.opts.deny.contains(&Warning::All) || context.opts.deny.contains(&warning) {
119 bail!(msg);
120 }
121
122 if context.opts.quiet
123 || context.opts.allow.contains(&Warning::All)
124 || context.opts.allow.contains(&warning)
125 || (flags.contains(Flags::ONCE) && state.contains(State::WARNING_EMITTED))
126 {
127 return Ok(());
128 }
129
130 let allow_msg = if state.contains(State::ALLOW_MSG_EMITTED) {
131 String::new()
132 } else {
133 state.insert(State::ALLOW_MSG_EMITTED);
134 format!(
135 "
136Silence this warning with: --allow {warning}"
137 )
138 };
139
140 (context.println)(&format!(
141 "{}{}: {}{}",
142 source.map_or(String::new(), |source| format!(
143 "{}: ",
144 source.to_console_string()
145 )),
146 if std::io::stdout().is_terminal() {
147 Yellow.bold()
148 } else {
149 Style::default()
150 }
151 .paint("Warning"),
152 msg,
153 allow_msg
154 ));
155
156 state.insert(State::WARNING_EMITTED);
157
158 Ok(())
159}
160
161pub(crate) fn note(context: &LightContext, msg: &str) {
162 if context.opts.quiet {
163 return;
164 }
165
166 (context.println)(&format!(
167 "{}: {}",
168 if std::io::stdout().is_terminal() {
169 Green.bold()
170 } else {
171 Style::default()
172 }
173 .paint("Note"),
174 msg
175 ));
176}
177
178fn may_be_bug(warning: Warning) -> bool {
179 match warning {
180 Warning::All => unreachable!(),
181 Warning::DatabaseDoesNotExist
182 | Warning::DryRunFailed
183 | Warning::FilesChanged
184 | Warning::IgnoredFunctionsUnsupported
185 | Warning::IgnoredMacrosUnsupported
186 | Warning::IgnoredMethodsUnsupported
187 | Warning::ItMessageNotFound
188 | Warning::LocalFunctionAmbiguous
189 | Warning::OptionDeprecated
190 | Warning::OutputInvalid
191 | Warning::ParsingFailed => false,
192 Warning::InstrumentationNonbuildable
193 | Warning::ModulePathUnknown
194 | Warning::RunTestFailed => true,
195 }
196}