Skip to content

Commit 43f1f1b

Browse files
committed
add call_missing_target_feature lint
1 parent ec37eb6 commit 43f1f1b

7 files changed

+248
-0
lines changed

CHANGELOG.md

+1
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
if let hir::Safety::Safe = fn_sig.safety() {
103+
if caller_item.vis_span.is_empty() {
104+
suggestions.push((lo_span, format!("{attr}\n{indent}unsafe ")));
105+
} else {
106+
suggestions.push((lo_span, format!("{attr}\n{indent}")));
107+
suggestions.push((caller_item.vis_span.shrink_to_hi(), " unsafe".to_string()));
108+
}
109+
}
110+
111+
diag.multipart_suggestion_verbose(
112+
"add the missing target features to the surrounding function",
113+
suggestions,
114+
rustc_errors::Applicability::MaybeIncorrect,
115+
);
116+
},
117+
);
118+
}
119+
}
120+
121+
fn callee_def_id(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<DefId> {
122+
match expr.kind {
123+
hir::ExprKind::Call(path, _) => {
124+
if let hir::ExprKind::Path(ref qpath) = path.kind
125+
&& let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
126+
{
127+
Some(did)
128+
} else {
129+
None
130+
}
131+
},
132+
hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
133+
_ => None,
134+
}
135+
}

clippy_lints/src/declared_lints.rs

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

+2
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));
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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")]
23+
unsafe fn f() -> u32 {
24+
0
25+
}
26+
27+
#[target_feature(enable = "avx2,pclmulqdq")]
28+
unsafe fn g() -> u32 {
29+
0
30+
}
31+
32+
#[target_feature(enable = "avx2")]
33+
#[target_feature(enable = "pclmulqdq")]
34+
unsafe fn h() -> u32 {
35+
0
36+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
#[target_feature(enable = "avx2")]
20+
unsafe fn f() -> u32 {
21+
0
22+
}
23+
24+
#[target_feature(enable = "avx2,pclmulqdq")]
25+
unsafe fn g() -> u32 {
26+
0
27+
}
28+
29+
#[target_feature(enable = "avx2")]
30+
#[target_feature(enable = "pclmulqdq")]
31+
unsafe fn h() -> u32 {
32+
0
33+
}
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: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: aborting due to 3 previous errors
40+

0 commit comments

Comments
 (0)