Skip to content

Commit c091b77

Browse files
committed
add configuration for lint [mem_unsafe_functions];
finish implement lint [`mem_unsafe_functions`]; add simple description for unimplemented lints; Signed-off-by: J-ZhengLi <[email protected]>
1 parent a50f308 commit c091b77

12 files changed

+304
-47
lines changed
Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
use rustc_lint::{LateContext, LintContext};
21

3-
use super::FALLIABLE_MEMORY_ALLOCATION;
4-
5-
// TODO: Adjust the parameters as necessary
6-
pub(super) fn check(cx: &LateContext<'_>) {
7-
todo!();
8-
}
Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,45 @@
1-
use clippy_utils::diagnostics::span_lint;
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use if_chain::if_chain;
3+
use rustc_hir::def::Res;
4+
use rustc_hir::def_id::DefIdSet;
5+
use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath};
26
use rustc_lint::LateContext;
3-
use rustc_span::Span;
47

58
use super::MEM_UNSAFE_FUNCTIONS;
69

7-
// TODO: Adjust the parameters as necessary
8-
pub(super) fn check(cx: &LateContext<'_>, span: Span) {
9-
span_lint(cx, MEM_UNSAFE_FUNCTIONS, span, "it's working!");
10+
/// Check extern function definitions.
11+
///
12+
/// The main purpose of this function is to load `def_ids` of declared external functions.
13+
pub(super) fn check_foreign_item(item: &Item<'_>, blacklist: &[String], blacklist_ids: &mut DefIdSet) {
14+
if let ItemKind::ForeignMod { items, .. } = item.kind {
15+
for f_item in items {
16+
if blacklist.contains(&f_item.ident.as_str().to_string()) {
17+
let f_did = f_item.id.hir_id().owner.def_id.to_def_id();
18+
blacklist_ids.insert(f_did);
19+
}
20+
}
21+
}
22+
}
23+
24+
/// Check function call expression
25+
///
26+
/// Will lint if the name of called function was blacklisted by the configuration.
27+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, blacklist_ids: &DefIdSet) {
28+
if_chain! {
29+
if let ExprKind::Call(fn_expr, _) = &expr.kind;
30+
if let ExprKind::Path(qpath) = &fn_expr.kind;
31+
if let QPath::Resolved(_, path) = qpath;
32+
if let Res::Def(_, did) = path.res;
33+
if blacklist_ids.contains(&did);
34+
then {
35+
span_lint_and_help(
36+
cx,
37+
MEM_UNSAFE_FUNCTIONS,
38+
fn_expr.span,
39+
"use of potentially dangerous memory manipulation function",
40+
None,
41+
"consider using its safe version",
42+
);
43+
}
44+
}
1045
}

clippy_lints/src/guidelines/mod.rs

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ mod mem_unsafe_functions;
33
mod passing_string_to_c_functions;
44
mod untrusted_lib_loading;
55

6+
use clippy_utils::def_path_def_ids;
67
use rustc_hir as hir;
8+
use rustc_hir::def_id::{DefId, DefIdSet};
79
use rustc_hir::intravisit;
810
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_session::{declare_tool_lint, impl_lint_pass};
1012
use rustc_span::def_id::LocalDefId;
1113
use rustc_span::Span;
1214

1315
declare_clippy_lint! {
1416
/// ### What it does
15-
/// Check for direct usage of external functions that modify memory
17+
/// Checks for direct usage of external functions that modify memory
1618
/// without concerning about memory safety, such as `memcpy`, `strcpy`, `strcat` etc.
1719
///
1820
/// ### Why is this bad?
@@ -49,7 +51,7 @@ declare_clippy_lint! {
4951
#[clippy::version = "1.70.0"]
5052
pub UNTRUSTED_LIB_LOADING,
5153
nursery,
52-
"default lint description"
54+
"attempt to load dynamic library from untrusted source"
5355
}
5456

5557
declare_clippy_lint! {
@@ -68,7 +70,7 @@ declare_clippy_lint! {
6870
#[clippy::version = "1.70.0"]
6971
pub PASSING_STRING_TO_C_FUNCTIONS,
7072
nursery,
71-
"default lint description"
73+
"passing string or str to extern C function"
7274
}
7375

7476
declare_clippy_lint! {
@@ -87,10 +89,25 @@ declare_clippy_lint! {
8789
#[clippy::version = "1.70.0"]
8890
pub FALLIABLE_MEMORY_ALLOCATION,
8991
nursery,
90-
"default lint description"
92+
"memory allocation without checking arguments and result"
9193
}
9294

93-
declare_lint_pass!(GuidelineLints => [
95+
#[derive(Clone, Default)]
96+
pub struct GuidelineLints {
97+
mem_uns_fns: Vec<String>,
98+
mem_uns_fns_ty_ids: DefIdSet,
99+
}
100+
101+
impl GuidelineLints {
102+
pub fn new(mem_uns_fns: Vec<String>) -> Self {
103+
Self {
104+
mem_uns_fns,
105+
mem_uns_fns_ty_ids: DefIdSet::new(),
106+
}
107+
}
108+
}
109+
110+
impl_lint_pass!(GuidelineLints => [
94111
MEM_UNSAFE_FUNCTIONS,
95112
UNTRUSTED_LIB_LOADING,
96113
PASSING_STRING_TO_C_FUNCTIONS,
@@ -100,15 +117,43 @@ declare_lint_pass!(GuidelineLints => [
100117
impl<'tcx> LateLintPass<'tcx> for GuidelineLints {
101118
fn check_fn(
102119
&mut self,
103-
cx: &LateContext<'tcx>,
120+
_cx: &LateContext<'tcx>,
104121
_kind: intravisit::FnKind<'tcx>,
105122
_decl: &'tcx hir::FnDecl<'_>,
106123
_body: &'tcx hir::Body<'_>,
107-
span: Span,
124+
_span: Span,
108125
_def_id: LocalDefId,
109126
) {
110-
mem_unsafe_functions::check(cx, span);
111127
}
112128

113-
fn check_item(&mut self, _cx: &LateContext<'tcx>, _item: &'tcx hir::Item<'_>) {}
129+
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
130+
// Resolve function names to def_ids from configuration
131+
for uns_fns in &self.mem_uns_fns {
132+
// Path like function names such as `libc::foo` or `aa::bb::cc::bar`,
133+
// this only works with dependencies.
134+
if uns_fns.contains("::") {
135+
let path: Vec<&str> = uns_fns.split("::").collect();
136+
for did in def_path_def_ids(cx, path.as_slice()) {
137+
self.mem_uns_fns_ty_ids.insert(did);
138+
}
139+
}
140+
// Plain function names, then we should take its libc variant into account
141+
else if let Some(did) = libc_fn_def_id(cx, uns_fns) {
142+
self.mem_uns_fns_ty_ids.insert(did);
143+
}
144+
}
145+
}
146+
147+
fn check_item(&mut self, _cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
148+
mem_unsafe_functions::check_foreign_item(item, &self.mem_uns_fns, &mut self.mem_uns_fns_ty_ids);
149+
}
150+
151+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
152+
mem_unsafe_functions::check(cx, expr, &self.mem_uns_fns_ty_ids);
153+
}
154+
}
155+
156+
fn libc_fn_def_id(cx: &LateContext<'_>, fn_name: &str) -> Option<DefId> {
157+
let path = &["libc", fn_name];
158+
def_path_def_ids(cx, path).next()
114159
}
Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
use rustc_lint::{LateContext, LintContext};
21

3-
use super::PASSING_STRING_TO_C_FUNCTIONS;
4-
5-
// TODO: Adjust the parameters as necessary
6-
pub(super) fn check(cx: &LateContext<'_>) {
7-
todo!();
8-
}
Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
use rustc_lint::{LateContext, LintContext};
21

3-
use super::UNTRUSTED_LIB_LOADING;
4-
5-
// TODO: Adjust the parameters as necessary
6-
pub(super) fn check(cx: &LateContext<'_>) {
7-
todo!();
8-
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
960960
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
961961
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
962962
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
963-
store.register_late_pass(|_| Box::new(guidelines::GuidelineLints));
963+
let mem_unsafe_functions = conf.mem_unsafe_functions.clone();
964+
store.register_late_pass(move |_| Box::new(guidelines::GuidelineLints::new(mem_unsafe_functions.clone())));
964965
// add lints here, do not remove this comment, it's used in `new_lint`
965966
}
966967

clippy_lints/src/utils/conf.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
3232
];
3333
const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
3434

35+
#[rustfmt::skip]
36+
const DEFAULT_MEM_UNSAFE_FUNCTIONS: &[&str] = &[
37+
"memcpy", "bcopy", "wmemcpy", "memmove", "wmemmove",
38+
"strcpy", "wcscpy", "strncpy", "wcsncpy",
39+
"strcat", "wcscat", "strncat", "wcsncat",
40+
"sprintf", "swprintf", "vsprintf", "vswprintf", "snprintf", "wsnprintf",
41+
// formatted input functions
42+
"scanf", "wscanf", "vscanf", "vwscanf", "fscanf", "fwscanf", "vfscanf", "vfwscanf",
43+
"sscanf", "swscanf", "vsscanf", "vswscanf",
44+
// standard input
45+
"gets",
46+
// memory initialization
47+
"memset",
48+
];
49+
3550
/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
3651
#[derive(Clone, Debug, Deserialize)]
3752
pub struct Rename {
@@ -463,6 +478,11 @@ define_Conf! {
463478
///
464479
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
465480
(future_size_threshold: u64 = 16 * 1024),
481+
/// Lint: MEM_UNSAFE_FUNCTIONS.
482+
///
483+
/// The list of function names to lint about.
484+
/// Providing empty list has the same effect as disabling this lint.
485+
(mem_unsafe_functions: Vec<String> = super::DEFAULT_MEM_UNSAFE_FUNCTIONS.iter().map(ToString::to_string).collect()),
466486
}
467487

468488
/// Search for the configuration file.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mem-unsafe-functions = [ "not_safe", "dont_use", "del_mem", "libc::memcpy", "memcpy" ]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![warn(clippy::mem_unsafe_functions)]
2+
#![allow(unused)]
3+
4+
mod libc {
5+
pub fn safe() {}
6+
pub fn not_safe() {}
7+
pub fn memcpy() {}
8+
}
9+
10+
extern "C" {
11+
fn safe();
12+
fn safe_1();
13+
fn not_safe();
14+
fn dont_use();
15+
fn del_mem();
16+
fn memcpy();
17+
}
18+
19+
fn main() {
20+
unsafe {
21+
// Don't trigger
22+
safe();
23+
safe_1();
24+
libc::safe();
25+
libc::not_safe();
26+
libc::memcpy(); // because it's user defined
27+
28+
// should trigger
29+
not_safe();
30+
dont_use();
31+
del_mem();
32+
memcpy();
33+
}
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: use of potentially dangerous memory manipulation function
2+
--> $DIR/mem_unsafe_functions.rs:29:9
3+
|
4+
LL | not_safe();
5+
| ^^^^^^^^
6+
|
7+
= help: consider using its safe version
8+
= note: `-D clippy::mem-unsafe-functions` implied by `-D warnings`
9+
10+
error: use of potentially dangerous memory manipulation function
11+
--> $DIR/mem_unsafe_functions.rs:30:9
12+
|
13+
LL | dont_use();
14+
| ^^^^^^^^
15+
|
16+
= help: consider using its safe version
17+
18+
error: use of potentially dangerous memory manipulation function
19+
--> $DIR/mem_unsafe_functions.rs:31:9
20+
|
21+
LL | del_mem();
22+
| ^^^^^^^
23+
|
24+
= help: consider using its safe version
25+
26+
error: use of potentially dangerous memory manipulation function
27+
--> $DIR/mem_unsafe_functions.rs:32:9
28+
|
29+
LL | memcpy();
30+
| ^^^^^^
31+
|
32+
= help: consider using its safe version
33+
34+
error: aborting due to 4 previous errors
35+

tests/ui/mem_unsafe_functions.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![feature(c_size_t)]
22
#![warn(clippy::mem_unsafe_functions)]
33

4+
use core::ffi::c_size_t as size_t;
45
use core::ffi::{c_char, c_int, c_size_t, c_void};
6+
use std::ptr::{null, null_mut};
57

68
// mock libc crate
79
mod libc {
@@ -18,10 +20,42 @@ mod libc {
1820

1921
extern "C" {
2022
fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char;
21-
fn strncpy(dst: *mut c_char, src: *const c_char, n: c_size_t) -> *mut c_char;
22-
fn memcpy(dest: *mut c_void, src: *const c_void, n: c_size_t) -> *mut c_void;
23-
fn memmove(dest: *mut c_void, src: *const c_void, n: c_size_t) -> *mut c_void;
24-
fn memset(dest: *mut c_void, c: c_int, n: c_size_t) -> *mut c_void;
23+
fn strncpy(dst: *mut c_char, src: *const c_char, n: size_t) -> *mut c_char;
24+
fn memcpy(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void;
25+
fn memmove(dest: *mut c_void, src: *const c_void, n: size_t) -> *mut c_void;
26+
fn memset(dest: *mut c_void, c: c_int, n: size_t) -> *mut c_void;
2527
}
2628

27-
fn main() {}
29+
unsafe fn with_void_ptrs() {
30+
let src: *const c_void = null();
31+
let dst: *mut c_void = null_mut();
32+
let n: size_t = 1;
33+
34+
// Should lint these
35+
let _ = memcpy(dst, src, n);
36+
let _ = memmove(dst, src, n);
37+
let _ = memset(dst, 1, n);
38+
let _ = libc::memcpy(dst, src, n);
39+
let _ = libc::memmove(dst, src, n);
40+
use libc::memset;
41+
let _ = memset(dst, 1, n);
42+
}
43+
44+
unsafe fn with_char_ptrs() {
45+
let src: *const c_char = null();
46+
let dst: *mut c_char = null_mut();
47+
48+
// Should lint these
49+
let _ = strcpy(dst, src);
50+
let _ = strncpy(dst, src, 1);
51+
let _ = libc::strcpy(dst, src);
52+
use libc::strncpy;
53+
let _ = strncpy(dst, src, 1);
54+
}
55+
56+
fn main() {
57+
unsafe {
58+
with_char_ptrs();
59+
with_void_ptrs();
60+
}
61+
}

0 commit comments

Comments
 (0)