Skip to content

Commit cf88b49

Browse files
committed
add call_missing_target_feature lint
1 parent ec37eb6 commit cf88b49

File tree

7 files changed

+277
-0
lines changed

7 files changed

+277
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5417,6 +5417,7 @@ Released 2018-09-13
54175417
[`byte_char_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices
54185418
[`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len
54195419
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
5420+
[`call_missing_target_feature`]: https://rust-lang.github.io/rust-clippy/master/index.html#call_missing_target_feature
54205421
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
54215422
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
54225423
[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#![allow(clippy::similar_names)]
2+
use clippy_utils::diagnostics::span_lint_and_then;
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+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks that the caller enables the target features that the callee requires
13+
///
14+
/// ### Why is this bad?
15+
/// Not enabling target features can cause UB and limits optimization opportunities.
16+
///
17+
/// ### Example
18+
/// ```no_run
19+
/// #[target_feature(enable = "avx2")]
20+
/// unsafe fn f() -> u32 {
21+
/// 0
22+
/// }
23+
///
24+
/// fn g() {
25+
/// unsafe { f() };
26+
/// // g does not enable the target features f requires
27+
/// }
28+
/// ```
29+
#[clippy::version = "1.82.0"]
30+
pub CALL_MISSING_TARGET_FEATURE,
31+
suspicious,
32+
"call requires target features that the surrounding function does not enable"
33+
}
34+
35+
declare_lint_pass!(CallMissingTargetFeature => [CALL_MISSING_TARGET_FEATURE]);
36+
37+
impl<'tcx> LateLintPass<'tcx> for CallMissingTargetFeature {
38+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) {
39+
let Some(callee_def_id) = callee_def_id(cx, expr) else {
40+
return;
41+
};
42+
let callee_target_features = &cx.tcx.codegen_fn_attrs(callee_def_id).target_features;
43+
44+
if callee_target_features.is_empty() {
45+
return;
46+
}
47+
48+
let Some(caller_body_id) = cx.enclosing_body else {
49+
return;
50+
};
51+
let caller_def_id = cx.tcx.hir().body_owner_def_id(caller_body_id);
52+
let caller_target_features = &cx.tcx.codegen_fn_attrs(caller_def_id).target_features;
53+
54+
if in_external_macro(cx.tcx.sess, expr.span) {
55+
return;
56+
}
57+
58+
// target features can imply other target features (e.g. avx2 implies sse4.2). We can safely skip
59+
// implied target features and only warn for the more general missing target feature.
60+
let missing: Vec<_> = callee_target_features
61+
.iter()
62+
.filter_map(|target_feature| {
63+
if target_feature.implied || caller_target_features.iter().any(|tf| tf.name == target_feature.name) {
64+
None
65+
} else {
66+
Some(target_feature.name.as_str())
67+
}
68+
})
69+
.collect();
70+
71+
if missing.is_empty() {
72+
return;
73+
}
74+
75+
let attr = format!("#[target_feature(enable = \"{}\")]", missing.join(","));
76+
77+
span_lint_and_then(
78+
cx,
79+
CALL_MISSING_TARGET_FEATURE,
80+
expr.span,
81+
"this call requires target features that the surrounding function does not enable",
82+
|diag| {
83+
diag.span_label(
84+
expr.span,
85+
"this function call requires target features to be enabled".to_string(),
86+
);
87+
88+
let fn_sig = cx.tcx.fn_sig(caller_def_id).skip_binder();
89+
90+
let mut suggestions = Vec::with_capacity(2);
91+
92+
let hir::Node::Item(caller_item) = cx.tcx.hir_node_by_def_id(caller_def_id) else {
93+
return;
94+
};
95+
96+
let Some(indent) = clippy_utils::source::snippet_indent(cx, caller_item.span) else {
97+
return;
98+
};
99+
100+
let lo_span = caller_item.span.with_hi(caller_item.span.lo());
101+
102+
match fn_sig.safety() {
103+
hir::Safety::Safe => {
104+
// the `target_feature` attribute can only be applied to unsafe functions
105+
if caller_item.vis_span.is_empty() {
106+
suggestions.push((lo_span, format!("{attr}\n{indent}unsafe ")));
107+
} else {
108+
suggestions.push((lo_span, format!("{attr}\n{indent}")));
109+
suggestions.push((caller_item.vis_span.shrink_to_hi(), " unsafe".to_string()));
110+
}
111+
},
112+
hir::Safety::Unsafe => {
113+
suggestions.push((lo_span, format!("{attr}\n{indent}")));
114+
},
115+
}
116+
117+
diag.multipart_suggestion_verbose(
118+
"add the missing target features to the surrounding function",
119+
suggestions,
120+
rustc_errors::Applicability::MaybeIncorrect,
121+
);
122+
},
123+
);
124+
}
125+
}
126+
127+
fn callee_def_id(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<DefId> {
128+
match expr.kind {
129+
hir::ExprKind::Call(path, _) => {
130+
if let hir::ExprKind::Path(ref qpath) = path.kind
131+
&& let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
132+
{
133+
Some(did)
134+
} else {
135+
None
136+
}
137+
},
138+
hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
139+
_ => None,
140+
}
141+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
6969
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
7070
crate::box_default::BOX_DEFAULT_INFO,
7171
crate::byte_char_slices::BYTE_CHAR_SLICES_INFO,
72+
crate::call_missing_target_feature::CALL_MISSING_TARGET_FEATURE_INFO,
7273
crate::cargo::CARGO_COMMON_METADATA_INFO,
7374
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
7475
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ mod booleans;
9292
mod borrow_deref_ref;
9393
mod box_default;
9494
mod byte_char_slices;
95+
mod call_missing_target_feature;
9596
mod cargo;
9697
mod casts;
9798
mod cfg_not_test;
@@ -784,6 +785,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
784785
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
785786
store.register_late_pass(|_| Box::new(as_conversions::AsConversions));
786787
store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore));
788+
store.register_late_pass(|_| Box::new(call_missing_target_feature::CallMissingTargetFeature));
787789
store.register_early_pass(|| Box::<single_component_path_imports::SingleComponentPathImports>::default());
788790
store.register_late_pass(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf)));
789791
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//@only-target: x86_64
2+
#![allow(clippy::missing_safety_doc)]
3+
4+
#[target_feature(enable = "avx2")]
5+
pub unsafe fn test_f() {
6+
unsafe { f() };
7+
//~^ ERROR: this call requires target features
8+
}
9+
10+
#[target_feature(enable = "avx2,pclmulqdq")]
11+
pub(crate) unsafe fn test_g() {
12+
unsafe { g() };
13+
//~^ ERROR: this call requires target features
14+
}
15+
16+
#[target_feature(enable = "avx2,pclmulqdq")]
17+
unsafe fn test_h() {
18+
unsafe { h() };
19+
//~^ ERROR: this call requires target features
20+
}
21+
22+
#[target_feature(enable = "avx2,pclmulqdq")]
23+
unsafe fn test_h_unsafe() {
24+
unsafe { h() };
25+
//~^ ERROR: this call requires target features
26+
}
27+
28+
#[target_feature(enable = "avx2")]
29+
unsafe fn f() -> u32 {
30+
0
31+
}
32+
33+
#[target_feature(enable = "avx2,pclmulqdq")]
34+
unsafe fn g() -> u32 {
35+
0
36+
}
37+
38+
#[target_feature(enable = "avx2")]
39+
#[target_feature(enable = "pclmulqdq")]
40+
unsafe fn h() -> u32 {
41+
0
42+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//@only-target: x86_64
2+
#![allow(clippy::missing_safety_doc)]
3+
4+
pub fn test_f() {
5+
unsafe { f() };
6+
//~^ ERROR: this call requires target features
7+
}
8+
9+
pub(crate) fn test_g() {
10+
unsafe { g() };
11+
//~^ ERROR: this call requires target features
12+
}
13+
14+
fn test_h() {
15+
unsafe { h() };
16+
//~^ ERROR: this call requires target features
17+
}
18+
19+
unsafe fn test_h_unsafe() {
20+
unsafe { h() };
21+
//~^ ERROR: this call requires target features
22+
}
23+
24+
#[target_feature(enable = "avx2")]
25+
unsafe fn f() -> u32 {
26+
0
27+
}
28+
29+
#[target_feature(enable = "avx2,pclmulqdq")]
30+
unsafe fn g() -> u32 {
31+
0
32+
}
33+
34+
#[target_feature(enable = "avx2")]
35+
#[target_feature(enable = "pclmulqdq")]
36+
unsafe fn h() -> u32 {
37+
0
38+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: this call requires target features that the surrounding function does not enable
2+
--> tests/ui/call_missing_target_feature.rs:5: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 unsafe fn test_f() {
13+
|
14+
15+
error: this call requires target features that the surrounding function does not enable
16+
--> tests/ui/call_missing_target_feature.rs:10: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(crate) unsafe fn test_g() {
25+
|
26+
27+
error: this call requires target features that the surrounding function does not enable
28+
--> tests/ui/call_missing_target_feature.rs:15: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 ~ unsafe fn test_h() {
37+
|
38+
39+
error: this call requires target features that the surrounding function does not enable
40+
--> tests/ui/call_missing_target_feature.rs:20:14
41+
|
42+
LL | unsafe { h() };
43+
| ^^^ this function call requires target features to be enabled
44+
|
45+
help: add the missing target features to the surrounding function
46+
|
47+
LL + #[target_feature(enable = "avx2,pclmulqdq")]
48+
LL | unsafe fn test_h_unsafe() {
49+
|
50+
51+
error: aborting due to 4 previous errors
52+

0 commit comments

Comments
 (0)