Skip to content

Commit e975563

Browse files
Validate paths in disallowed_* configurations (rust-lang#14397)
This PR resolves rust-lang#11432 by checking that paths resolve in `disallowed_*` configurations. It also does some lightweight validation of definition kinds. For example, only paths that resolve to `DefKind::Macro` definitions are allowed in `disallowed_macro` configurations. ~The PR is currently two commits. The first is rust-lang#14376 (cc: @Jarcho), which I submitted just a few days ago. The second commit is everything else.~ Side note: For me, the most difficult part of this PR was getting the spans of the individual `DisallowedPath` configurations. There is some discussion at toml-rs/toml#840, if interested. changelog: validate paths in `disallowed_*` configurations
2 parents e429bde + dc7d9ec commit e975563

File tree

12 files changed

+273
-64
lines changed

12 files changed

+273
-64
lines changed

clippy.toml

+3
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

+74-16
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_paths_allow_replacements = $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_paths_allow_replacements = 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_paths_allow_replacements = 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_paths_allow_replacements = 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_paths_allow_replacements = 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

+1
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

+103-11
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,17 @@ 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` does not refer to an existing
27+
/// definition.
28+
///
29+
/// This could be useful when conditional compilation is used, or when a clippy.toml file is
30+
/// shared among multiple projects.
31+
allow_invalid: bool,
32+
/// The span of the `DisallowedPath`.
33+
///
34+
/// Used for diagnostics.
35+
#[serde(skip_serializing)]
36+
span: Span,
2437
}
2538

2639
impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
@@ -36,6 +49,8 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<R
3649
path: enum_.path().to_owned(),
3750
reason: enum_.reason().map(ToOwned::to_owned),
3851
replacement: enum_.replacement().map(ToOwned::to_owned),
52+
allow_invalid: enum_.allow_invalid(),
53+
span: Span::default(),
3954
})
4055
}
4156
}
@@ -50,6 +65,8 @@ enum DisallowedPathEnum {
5065
path: String,
5166
reason: Option<String>,
5267
replacement: Option<String>,
68+
#[serde(rename = "allow-invalid")]
69+
allow_invalid: Option<bool>,
5370
},
5471
}
5572

@@ -58,7 +75,7 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
5875
&self.path
5976
}
6077

61-
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) + use<'_, REPLACEMENT_ALLOWED> {
78+
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) {
6279
move |diag| {
6380
if let Some(replacement) = &self.replacement {
6481
diag.span_suggestion(
@@ -72,6 +89,14 @@ impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
7289
}
7390
}
7491
}
92+
93+
pub fn span(&self) -> Span {
94+
self.span
95+
}
96+
97+
pub fn set_span(&mut self, span: Span) {
98+
self.span = span;
99+
}
75100
}
76101

77102
impl DisallowedPathEnum {
@@ -94,20 +119,87 @@ impl DisallowedPathEnum {
94119
Self::Simple(_) => None,
95120
}
96121
}
122+
123+
fn allow_invalid(&self) -> bool {
124+
match &self {
125+
Self::WithReason { allow_invalid, .. } => allow_invalid.unwrap_or_default(),
126+
Self::Simple(_) => false,
127+
}
128+
}
97129
}
98130

99131
/// Creates a map of disallowed items to the reason they were disallowed.
132+
#[allow(clippy::type_complexity)]
100133
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
101134
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()
135+
disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
136+
def_kind_predicate: impl Fn(DefKind) -> bool,
137+
predicate_description: &str,
138+
allow_prim_tys: bool,
139+
) -> (
140+
DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
141+
FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
142+
) {
143+
let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
144+
let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
145+
FxHashMap::default();
146+
for disallowed_path in disallowed_paths {
147+
let path = disallowed_path.path();
148+
let mut resolutions = clippy_utils::def_path_res(tcx, &path.split("::").collect::<Vec<_>>());
149+
150+
let mut found_def_id = None;
151+
let mut found_prim_ty = false;
152+
resolutions.retain(|res| match res {
153+
Res::Def(def_kind, def_id) => {
154+
found_def_id = Some(*def_id);
155+
def_kind_predicate(*def_kind)
156+
},
157+
Res::PrimTy(_) => {
158+
found_prim_ty = true;
159+
allow_prim_tys
160+
},
161+
_ => false,
162+
});
163+
164+
if resolutions.is_empty() {
165+
let span = disallowed_path.span();
166+
167+
if let Some(def_id) = found_def_id {
168+
tcx.sess.dcx().span_warn(
169+
span,
170+
format!(
171+
"expected a {predicate_description}, found {} {}",
172+
tcx.def_descr_article(def_id),
173+
tcx.def_descr(def_id)
174+
),
175+
);
176+
} else if found_prim_ty {
177+
tcx.sess.dcx().span_warn(
178+
span,
179+
format!("expected a {predicate_description}, found a primitive type",),
180+
);
181+
} else if !disallowed_path.allow_invalid {
182+
tcx.sess.dcx().span_warn(
183+
span,
184+
format!("`{path}` does not refer to an existing {predicate_description}"),
185+
);
186+
}
187+
}
188+
189+
for res in resolutions {
190+
match res {
191+
Res::Def(_, def_id) => {
192+
def_ids.insert(def_id, (path, disallowed_path));
193+
},
194+
Res::PrimTy(ty) => {
195+
prim_tys.insert(ty, (path, disallowed_path));
196+
},
197+
_ => unreachable!(),
198+
}
199+
}
200+
}
201+
202+
(def_ids, prim_tys)
111203
}
112204

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

clippy_lints/src/await_holding_invalid.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,14 @@ 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+
"type",
187+
false,
188+
);
189+
Self { def_ids }
185190
}
186191
}
187192

clippy_lints/src/disallowed_macros.rs

+9-1
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,15 @@ 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+
"macro",
80+
false,
81+
);
7482
Self {
75-
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
83+
disallowed,
7684
seen: FxHashSet::default(),
7785
derive_src: None,
7886
earlies,

0 commit comments

Comments
 (0)