Skip to content

Commit de11ecb

Browse files
committed
add call_missing_target_feature lint
1 parent ffc391c commit de11ecb

7 files changed

+251
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5281,6 +5281,7 @@ Released 2018-09-13
52815281
[`byte_char_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices
52825282
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
52835283
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
5284+
[`call_missing_target_feature`]: https://rust-lang.github.io/rust-clippy/master/index.html#call_missing_target_feature
52845285
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
52855286
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
52865287
[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::sugg::DiagExt;
3+
use rustc_hir as hir;
4+
use rustc_hir::def::Res;
5+
use rustc_hir::def_id::DefId;
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_middle::lint::in_external_macro;
8+
use rustc_session::declare_lint_pass;
9+
use rustc_span::{sym, Symbol};
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Checks that the caller enables the target features that the callee requires
14+
///
15+
/// ### Why is this bad?
16+
/// Not enabling target features can cause UB and limits optimization opportunities.
17+
///
18+
/// ### Example
19+
/// ```no_run
20+
/// #[target_feature(enable = "avx2")]
21+
/// unsafe fn f() -> u32 {
22+
/// 0
23+
/// }
24+
///
25+
/// fn g() {
26+
/// unsafe { f() };
27+
/// // g does not enable the target features f requires
28+
/// }
29+
/// ```
30+
#[clippy::version = "CURRENT_CLIPPY_VERSION"]
31+
pub CALL_MISSING_TARGET_FEATURE,
32+
suspicious,
33+
"call requires target features that the surrounding function does not enable"
34+
}
35+
36+
declare_lint_pass!(CallMissingTargetFeature => [CALL_MISSING_TARGET_FEATURE]);
37+
38+
impl<'tcx> LateLintPass<'tcx> for CallMissingTargetFeature {
39+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
40+
if !in_external_macro(cx.tcx.sess, expr.span) {
41+
let Some(caller_def_id) = caller_def_id(cx, expr) else {
42+
return;
43+
};
44+
let Some(callee_def_id) = callee_def_id(cx, expr) else {
45+
return;
46+
};
47+
48+
let caller_target_features = def_id_target_features(cx, caller_def_id);
49+
let callee_target_features = def_id_target_features(cx, callee_def_id);
50+
51+
let missing: Vec<_> = callee_target_features
52+
.iter()
53+
.filter(|target_feature| !caller_target_features.contains(target_feature))
54+
.map(|target_feature| target_feature.as_str())
55+
.collect();
56+
57+
if missing.is_empty() {
58+
return;
59+
}
60+
61+
let hint = format!("#[target_feature(enable = \"{}\")]", missing.join(", "));
62+
63+
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
64+
span_lint_and_then(
65+
cx,
66+
CALL_MISSING_TARGET_FEATURE,
67+
expr.span,
68+
"this call requires target features that the surrounding function does not enable",
69+
|diag| {
70+
diag.span_label(
71+
expr.span,
72+
format!("this function call requires target features to be enabled"),
73+
);
74+
75+
if let Some(caller_item) = caller_item(cx, expr) {
76+
diag.suggest_item_with_attr(
77+
cx,
78+
caller_item.span,
79+
"add the missing target features to the surrounding function",
80+
hint.as_str(),
81+
rustc_errors::Applicability::MaybeIncorrect,
82+
);
83+
}
84+
},
85+
);
86+
}
87+
}
88+
}
89+
90+
fn callee_def_id(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<DefId> {
91+
match expr.kind {
92+
hir::ExprKind::Call(path, _) => {
93+
if let hir::ExprKind::Path(ref qpath) = path.kind
94+
&& let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
95+
{
96+
Some(did)
97+
} else {
98+
None
99+
}
100+
},
101+
hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
102+
_ => None,
103+
}
104+
}
105+
106+
fn caller_def_id<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> Option<DefId> {
107+
let item = caller_item(cx, expr)?;
108+
Some(item.owner_id.to_def_id())
109+
}
110+
111+
fn caller_item<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> Option<&'tcx hir::Item<'tcx>> {
112+
for (_hir_id, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
113+
if let hir::Node::Item(
114+
item @ hir::Item {
115+
kind: hir::ItemKind::Fn(..),
116+
..
117+
},
118+
) = node
119+
{
120+
return Some(item);
121+
}
122+
}
123+
124+
None
125+
}
126+
127+
// return the target features that the called function depends on
128+
fn def_id_target_features(cx: &LateContext<'_>, did: DefId) -> Vec<Symbol> {
129+
let mut added_target_features = Vec::new();
130+
131+
for attr in cx.tcx.get_attrs(did, sym::target_feature) {
132+
let Some(list) = attr.meta_item_list() else {
133+
return vec![];
134+
};
135+
136+
for item in list {
137+
// Only `enable = ...` is accepted in the meta-item list.
138+
if !item.has_name(sym::enable) {
139+
continue;
140+
}
141+
142+
// Must be of the form `enable = "..."` (a string).
143+
let Some(value) = item.value_str() else {
144+
continue;
145+
};
146+
147+
added_target_features.extend(value.as_str().split(',').map(|feature| Symbol::intern(feature)));
148+
}
149+
}
150+
151+
added_target_features
152+
}

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
7070
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
7171
crate::box_default::BOX_DEFAULT_INFO,
7272
crate::byte_char_slices::BYTE_CHAR_SLICES_INFO,
73+
crate::call_missing_target_feature::CALL_MISSING_TARGET_FEATURE_INFO,
7374
crate::cargo::CARGO_COMMON_METADATA_INFO,
7475
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
7576
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ mod booleans;
9090
mod borrow_deref_ref;
9191
mod box_default;
9292
mod byte_char_slices;
93+
mod call_missing_target_feature;
9394
mod cargo;
9495
mod casts;
9596
mod cfg_not_test;
@@ -742,6 +743,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
742743
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
743744
store.register_late_pass(|_| Box::new(as_conversions::AsConversions));
744745
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
746+
store.register_late_pass(|_| Box::new(call_missing_target_feature::CallMissingTargetFeature));
745747
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
746748
store.register_late_pass(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf)));
747749
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//@only-target-x86_64
2+
3+
#[target_feature(enable = "avx2, pclmulqdq")]
4+
#[target_feature(enable = "avx2, pclmulqdq")]
5+
#[target_feature(enable = "avx2")]
6+
pub fn test() {
7+
unsafe { f() };
8+
//~^ ERROR: this call requires target features
9+
unsafe { g() };
10+
//~^ ERROR: this call requires target features
11+
unsafe { h() };
12+
//~^ ERROR: this call requires target features
13+
}
14+
15+
#[target_feature(enable = "avx2")]
16+
unsafe fn f() -> u32 {
17+
0
18+
}
19+
20+
#[target_feature(enable = "avx2,pclmulqdq")]
21+
unsafe fn g() -> u32 {
22+
0
23+
}
24+
25+
#[target_feature(enable = "avx2")]
26+
#[target_feature(enable = "pclmulqdq")]
27+
unsafe fn h() -> u32 {
28+
0
29+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@only-target-x86_64
2+
3+
pub fn test() {
4+
unsafe { f() };
5+
//~^ ERROR: this call requires target features
6+
unsafe { g() };
7+
//~^ ERROR: this call requires target features
8+
unsafe { h() };
9+
//~^ ERROR: this call requires target features
10+
}
11+
12+
#[target_feature(enable = "avx2")]
13+
unsafe fn f() -> u32 {
14+
0
15+
}
16+
17+
#[target_feature(enable = "avx2,pclmulqdq")]
18+
unsafe fn g() -> u32 {
19+
0
20+
}
21+
22+
#[target_feature(enable = "avx2")]
23+
#[target_feature(enable = "pclmulqdq")]
24+
unsafe fn h() -> u32 {
25+
0
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: this call requires target features that the surrounding function does not enable
2+
--> tests/ui/call_missing_target_feature.rs:4:14
3+
|
4+
LL | unsafe { f() };
5+
| ^^^ this function call requires target features to be enabled
6+
|
7+
= note: `-D clippy::call-missing-target-feature` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::call_missing_target_feature)]`
9+
help: add the missing target features to the surrounding function
10+
|
11+
LL + #[target_feature(enable = "avx2")]
12+
LL | pub fn test() {
13+
|
14+
15+
error: this call requires target features that the surrounding function does not enable
16+
--> tests/ui/call_missing_target_feature.rs:6:14
17+
|
18+
LL | unsafe { g() };
19+
| ^^^ this function call requires target features to be enabled
20+
|
21+
help: add the missing target features to the surrounding function
22+
|
23+
LL + #[target_feature(enable = "avx2, pclmulqdq")]
24+
LL | pub fn test() {
25+
|
26+
27+
error: this call requires target features that the surrounding function does not enable
28+
--> tests/ui/call_missing_target_feature.rs:8:14
29+
|
30+
LL | unsafe { h() };
31+
| ^^^ this function call requires target features to be enabled
32+
|
33+
help: add the missing target features to the surrounding function
34+
|
35+
LL + #[target_feature(enable = "avx2, pclmulqdq")]
36+
LL | pub fn test() {
37+
|
38+
39+
error: aborting due to 3 previous errors
40+

0 commit comments

Comments
 (0)