Skip to content

Commit a7edf7d

Browse files
committed
Validate paths in disallowed_* configurations
1 parent bb0d09b commit a7edf7d

File tree

12 files changed

+261
-63
lines changed

12 files changed

+261
-63
lines changed

clippy.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ lint-commented-code = true
77
[[disallowed-methods]]
88
path = "rustc_lint::context::LintContext::lint"
99
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
10+
allow-invalid = true
1011

1112
[[disallowed-methods]]
1213
path = "rustc_lint::context::LintContext::span_lint"
1314
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
15+
allow-invalid = true
1416

1517
[[disallowed-methods]]
1618
path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
1719
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"
20+
allow-invalid = true

clippy_config/src/conf.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,7 @@ impl ConfError {
120120
Self {
121121
message: message.into(),
122122
suggestion,
123-
span: Span::new(
124-
file.start_pos + BytePos::from_usize(span.start),
125-
file.start_pos + BytePos::from_usize(span.end),
126-
SyntaxContext::root(),
127-
None,
128-
),
123+
span: span_from_toml_range(file, span),
129124
}
130125
}
131126
}
@@ -176,11 +171,61 @@ macro_rules! default_text {
176171
};
177172
}
178173

174+
macro_rules! deserialize {
175+
($map:expr, $ty:ty, $errors:expr, $file:expr) => {{
176+
let raw_value = $map.next_value::<toml::Spanned<toml::Value>>()?;
177+
let value_span = raw_value.span();
178+
let value = match <$ty>::deserialize(raw_value.into_inner()) {
179+
Err(e) => {
180+
$errors.push(ConfError::spanned(
181+
$file,
182+
e.to_string().replace('\n', " ").trim(),
183+
None,
184+
value_span,
185+
));
186+
continue;
187+
},
188+
Ok(value) => value,
189+
};
190+
(value, value_span)
191+
}};
192+
193+
($map:expr, $ty:ty, $errors:expr, $file:expr, $replacements_allowed:expr) => {{
194+
let array = $map.next_value::<Vec<toml::Spanned<toml::Value>>>()?;
195+
let mut disallowed_paths_span = Range {
196+
start: usize::MAX,
197+
end: usize::MIN,
198+
};
199+
let mut disallowed_paths = Vec::new();
200+
for raw_value in array {
201+
let value_span = raw_value.span();
202+
let mut disallowed_path = match DisallowedPath::<$replacements_allowed>::deserialize(raw_value.into_inner())
203+
{
204+
Err(e) => {
205+
$errors.push(ConfError::spanned(
206+
$file,
207+
e.to_string().replace('\n', " ").trim(),
208+
None,
209+
value_span,
210+
));
211+
continue;
212+
},
213+
Ok(disallowed_path) => disallowed_path,
214+
};
215+
disallowed_paths_span = union(&disallowed_paths_span, &value_span);
216+
disallowed_path.set_span(span_from_toml_range($file, value_span));
217+
disallowed_paths.push(disallowed_path);
218+
}
219+
(disallowed_paths, disallowed_paths_span)
220+
}};
221+
}
222+
179223
macro_rules! define_Conf {
180224
($(
181225
$(#[doc = $doc:literal])+
182226
$(#[conf_deprecated($dep:literal, $new_conf:ident)])?
183227
$(#[default_text = $default_text:expr])?
228+
$(#[disallowed_path_replacements_allowed = $replacements_allowed:expr])?
184229
$(#[lints($($for_lints:ident),* $(,)?)])?
185230
$name:ident: $ty:ty = $default:expr,
186231
)*) => {
@@ -219,7 +264,7 @@ macro_rules! define_Conf {
219264
let mut errors = Vec::new();
220265
let mut warnings = Vec::new();
221266

222-
// Declare a local variable for each field field available to a configuration file.
267+
// Declare a local variable for each field available to a configuration file.
223268
$(let mut $name = None;)*
224269

225270
// could get `Field` here directly, but get `String` first for diagnostics
@@ -237,15 +282,8 @@ macro_rules! define_Conf {
237282
$(Field::$name => {
238283
// Is this a deprecated field, i.e., is `$dep` set? If so, push a warning.
239284
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
240-
let raw_value = map.next_value::<toml::Spanned<toml::Value>>()?;
241-
let value_span = raw_value.span();
242-
let value = match <$ty>::deserialize(raw_value.into_inner()) {
243-
Err(e) => {
244-
errors.push(ConfError::spanned(self.0, e.to_string().replace('\n', " ").trim(), None, value_span));
245-
continue;
246-
}
247-
Ok(value) => value
248-
};
285+
let (value, value_span) =
286+
deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
249287
// Was this field set previously?
250288
if $name.is_some() {
251289
errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span()));
@@ -286,6 +324,22 @@ macro_rules! define_Conf {
286324
};
287325
}
288326

327+
fn union(x: &Range<usize>, y: &Range<usize>) -> Range<usize> {
328+
Range {
329+
start: cmp::min(x.start, y.start),
330+
end: cmp::max(x.end, y.end),
331+
}
332+
}
333+
334+
fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
335+
Span::new(
336+
file.start_pos + BytePos::from_usize(span.start),
337+
file.start_pos + BytePos::from_usize(span.end),
338+
SyntaxContext::root(),
339+
None,
340+
)
341+
}
342+
289343
define_Conf! {
290344
/// Which crates to allow absolute paths from
291345
#[lints(absolute_paths)]
@@ -472,6 +526,7 @@ define_Conf! {
472526
)]
473527
avoid_breaking_exported_api: bool = true,
474528
/// The list of types which may not be held across an await point.
529+
#[disallowed_path_replacements_allowed = false]
475530
#[lints(await_holding_invalid_type)]
476531
await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
477532
/// DEPRECATED LINT: BLACKLISTED_NAME.
@@ -517,9 +572,11 @@ define_Conf! {
517572
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
518573
cyclomatic_complexity_threshold: u64 = 25,
519574
/// The list of disallowed macros, written as fully qualified paths.
575+
#[disallowed_path_replacements_allowed = true]
520576
#[lints(disallowed_macros)]
521577
disallowed_macros: Vec<DisallowedPath> = Vec::new(),
522578
/// The list of disallowed methods, written as fully qualified paths.
579+
#[disallowed_path_replacements_allowed = true]
523580
#[lints(disallowed_methods)]
524581
disallowed_methods: Vec<DisallowedPath> = Vec::new(),
525582
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
@@ -528,6 +585,7 @@ define_Conf! {
528585
#[lints(disallowed_names)]
529586
disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
530587
/// The list of disallowed types, written as fully qualified paths.
588+
#[disallowed_path_replacements_allowed = true]
531589
#[lints(disallowed_types)]
532590
disallowed_types: Vec<DisallowedPath> = Vec::new(),
533591
/// The list of words this lint should not consider as identifiers needing ticks. The value

clippy_config/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
rustc::untranslatable_diagnostic
1414
)]
1515

16+
extern crate rustc_data_structures;
1617
extern crate rustc_errors;
1718
extern crate rustc_hir;
1819
extern crate rustc_middle;

clippy_config/src/types.rs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
use clippy_utils::def_path_def_ids;
1+
use rustc_data_structures::fx::FxHashMap;
22
use rustc_errors::{Applicability, Diag};
3+
use rustc_hir::PrimTy;
4+
use rustc_hir::def::{DefKind, Res};
35
use rustc_hir::def_id::DefIdMap;
46
use rustc_middle::ty::TyCtxt;
57
use rustc_span::Span;
@@ -21,6 +23,16 @@ pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
2123
path: String,
2224
reason: Option<String>,
2325
replacement: Option<String>,
26+
/// Setting `allow_invalid` to true suppresses a warning if `path` is invalid.
27+
///
28+
/// This could be useful when conditional compilation is used, or when a clippy.toml file is
29+
/// shared among multiple projects.
30+
allow_invalid: bool,
31+
/// The span of the `DisallowedPath`.
32+
///
33+
/// Used for diagnostics.
34+
#[serde(skip_serializing)]
35+
span: Span,
2436
}
2537

2638
impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
@@ -36,6 +48,8 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<R
3648
path: enum_.path().to_owned(),
3749
reason: enum_.reason().map(ToOwned::to_owned),
3850
replacement: enum_.replacement().map(ToOwned::to_owned),
51+
allow_invalid: enum_.allow_invalid(),
52+
span: Span::default(),
3953
})
4054
}
4155
}
@@ -50,6 +64,8 @@ enum DisallowedPathEnum {
5064
path: String,
5165
reason: Option<String>,
5266
replacement: Option<String>,
67+
#[serde(rename = "allow-invalid")]
68+
allow_invalid: Option<bool>,
5369
},
5470
}
5571

@@ -72,6 +88,14 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
7288
}
7389
}
7490
}
91+
92+
pub fn span(&self) -> Span {
93+
self.span
94+
}
95+
96+
pub fn set_span(&mut self, span: Span) {
97+
self.span = span;
98+
}
7599
}
76100

77101
impl DisallowedPathEnum {
@@ -94,20 +118,77 @@ impl DisallowedPathEnum {
94118
Self::Simple(_) => None,
95119
}
96120
}
121+
122+
fn allow_invalid(&self) -> bool {
123+
match &self {
124+
Self::WithReason { allow_invalid, .. } => allow_invalid.unwrap_or_default(),
125+
Self::Simple(_) => false,
126+
}
127+
}
97128
}
98129

99130
/// Creates a map of disallowed items to the reason they were disallowed.
131+
#[allow(clippy::type_complexity)]
100132
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
101133
tcx: TyCtxt<'_>,
102-
disallowed: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
103-
) -> DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> {
104-
disallowed
105-
.iter()
106-
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x))
107-
.flat_map(|(name, path, disallowed_path)| {
108-
def_path_def_ids(tcx, &path).map(move |id| (id, (name, disallowed_path)))
109-
})
110-
.collect()
134+
disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
135+
def_kind_predicate: impl Fn(DefKind) -> bool,
136+
allow_prim_tys: bool,
137+
) -> (
138+
DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
139+
FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
140+
) {
141+
let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
142+
let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
143+
FxHashMap::default();
144+
for disallowed_path in disallowed_paths {
145+
let path = disallowed_path.path();
146+
let mut reses = clippy_utils::def_path_res(tcx, &path.split("::").collect::<Vec<_>>());
147+
148+
let mut found_def_id = false;
149+
let mut found_prim_ty = false;
150+
reses.retain(|res| match res {
151+
Res::Def(def_kind, _) => {
152+
found_def_id = true;
153+
def_kind_predicate(*def_kind)
154+
},
155+
Res::PrimTy(_) => {
156+
found_prim_ty = true;
157+
allow_prim_tys
158+
},
159+
_ => false,
160+
});
161+
162+
if reses.is_empty() {
163+
let span = disallowed_path.span();
164+
165+
if found_def_id {
166+
tcx.sess
167+
.dcx()
168+
.span_warn(span, format!("`{path}` is not of an appropriate kind"));
169+
} else if found_prim_ty {
170+
tcx.sess
171+
.dcx()
172+
.span_warn(span, format!("primitive types are not allowed: {path}"));
173+
} else if !disallowed_path.allow_invalid {
174+
tcx.sess.dcx().span_warn(span, format!("`{path}` is invalid"));
175+
}
176+
}
177+
178+
for res in reses {
179+
match res {
180+
Res::Def(_, def_id) => {
181+
def_ids.insert(def_id, (path, disallowed_path));
182+
},
183+
Res::PrimTy(ty) => {
184+
prim_tys.insert(ty, (path, disallowed_path));
185+
},
186+
_ => unreachable!(),
187+
}
188+
}
189+
}
190+
191+
(def_ids, prim_tys)
111192
}
112193

113194
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]

clippy_lints/src/await_holding_invalid.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,13 @@ pub struct AwaitHolding {
179179

180180
impl AwaitHolding {
181181
pub(crate) fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
182-
Self {
183-
def_ids: create_disallowed_map(tcx, &conf.await_holding_invalid_types),
184-
}
182+
let (def_ids, _) = create_disallowed_map(
183+
tcx,
184+
&conf.await_holding_invalid_types,
185+
crate::disallowed_types::def_kind_predicate,
186+
false,
187+
);
188+
Self { def_ids }
185189
}
186190
}
187191

clippy_lints/src/disallowed_macros.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use clippy_config::types::{DisallowedPath, create_disallowed_map};
33
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
44
use clippy_utils::macros::macro_backtrace;
55
use rustc_data_structures::fx::FxHashSet;
6+
use rustc_hir::def::DefKind;
67
use rustc_hir::def_id::DefIdMap;
78
use rustc_hir::{
89
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
@@ -71,8 +72,14 @@ pub struct DisallowedMacros {
7172

7273
impl DisallowedMacros {
7374
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
75+
let (disallowed, _) = create_disallowed_map(
76+
tcx,
77+
&conf.disallowed_macros,
78+
|def_kind| matches!(def_kind, DefKind::Macro(_)),
79+
false,
80+
);
7481
Self {
75-
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
82+
disallowed,
7683
seen: FxHashSet::default(),
7784
derive_src: None,
7885
earlies,

0 commit comments

Comments
 (0)