Skip to content

Commit

Permalink
Merge pull request #32 from TogetherGame/custom_lints_1.74.0
Browse files Browse the repository at this point in the history
new lint [`invalid_char_range`]
  • Loading branch information
surechen authored Dec 5, 2023
2 parents d5b213b + 1c0bbb3 commit ae75e25
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5018,6 +5018,7 @@ Released 2018-09-13
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
[`invalid_char_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_char_range
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::guidelines::DANGLING_PTR_DEREFERENCE_INFO,
crate::guidelines::EXTERN_WITHOUT_REPR_INFO,
crate::guidelines::FALLIBLE_MEMORY_ALLOCATION_INFO,
crate::guidelines::INVALID_CHAR_RANGE_INFO,
crate::guidelines::MEM_UNSAFE_FUNCTIONS_INFO,
crate::guidelines::NON_REENTRANT_FUNCTIONS_INFO,
crate::guidelines::NULL_PTR_DEREFERENCE_INFO,
Expand Down
46 changes: 46 additions & 0 deletions clippy_lints/src/guidelines/invalid_char_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::{def_path_def_ids, is_lint_allowed};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::def_id::DefId;

use super::INVALID_CHAR_RANGE;

pub(super) fn check_call(cx: &LateContext<'_>, expr: &Expr<'_>, params: &[Expr<'_>], def_id: DefId) {
if is_lint_allowed(cx, INVALID_CHAR_RANGE, expr.hir_id) || !is_of_from_methods(cx, def_id) {
return;
}

let [param] = params else { return };
let param = peel_casts(param);
let tyck_res = cx.typeck_results();
// Skip checks when input cannot be evaluated at run time.
if let Some(Constant::Int(n)) = constant(cx, tyck_res, param) {
if (n > 0xD7FF && n < 0xE000) || n > 0x0010_FFFF {
span_lint_and_note(
cx,
INVALID_CHAR_RANGE,
expr.span,
"converting to char with out-of-range integer",
Some(param.span),
"this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]",
);
}
}
}

fn peel_casts<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if let ExprKind::Cast(inner, _) = expr.kind {
peel_casts(inner)
} else {
expr
}
}

/// Determine if a call's id is either `char::from_u32` or `char::from_u32_unchecked`
fn is_of_from_methods(cx: &LateContext<'_>, def_id: DefId) -> bool {
def_path_def_ids(cx, &["char", "from_u32"])
.chain(def_path_def_ids(cx, &["char", "from_u32_unchecked"]))
.any(|id| id == def_id)
}
74 changes: 54 additions & 20 deletions clippy_lints/src/guidelines/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod blocking_op_in_async;
mod extern_without_repr;
mod fallible_memory_allocation;
mod invalid_char_range;
mod passing_string_to_c_functions;
mod ptr;
mod return_stack_address;
Expand Down Expand Up @@ -334,6 +335,25 @@ declare_clippy_lint! {
"returning pointer that points to stack address"
}

// Experimental lint, may be removed in the future
declare_clippy_lint! {
/// ### What it does
/// Checks for converting to `char`` from a out-of-range unsigned int.
///
/// ### Why is this bad?
/// Conversion from an unsigned integer to a char can only be valid in a
/// certain range. User should be warned on any out-of-range convertion attempts.
///
/// ### Example
/// ```rust
/// let x = char::from_u32(0xDE01);
/// ```
#[clippy::version = "1.74.0"]
pub INVALID_CHAR_RANGE,
nursery,
"converting to char from a out-of-range unsigned int"
}

/// Helper struct with user configured path-like functions, such as `std::fs::read`,
/// and a set for `def_id`s which should be filled during checks.
///
Expand Down Expand Up @@ -409,6 +429,7 @@ impl_lint_pass!(LintGroup => [
PTR_DOUBLE_FREE,
DANGLING_PTR_DEREFERENCE,
RETURN_STACK_ADDRESS,
INVALID_CHAR_RANGE,
]);

impl<'tcx> LateLintPass<'tcx> for LintGroup {
Expand Down Expand Up @@ -453,31 +474,22 @@ impl<'tcx> LateLintPass<'tcx> for LintGroup {
if let hir::ExprKind::Call(_func, params) = &expr.kind {
if let Some(fn_did) = fn_def_id(cx, expr) {
if self.non_reentrant_fns.ids.contains(&fn_did) {
span_lint_and_help(
cx,
NON_REENTRANT_FUNCTIONS,
expr.span,
"use of non-reentrant function",
None,
"consider using its reentrant counterpart",
);
} else if self.mem_uns_fns.ids.contains(&fn_did) {
span_lint_and_help(
cx,
MEM_UNSAFE_FUNCTIONS,
expr.span,
"use of potentially dangerous memory manipulation function",
None,
"consider using its safe version",
);
} else if self.lib_loading_fns.ids.contains(&fn_did) {
lint_non_reentrant_fns(cx, expr);
}
if self.mem_uns_fns.ids.contains(&fn_did) {
lint_mem_unsafe_fns(cx, expr);
}
if self.lib_loading_fns.ids.contains(&fn_did) {
untrusted_lib_loading::check_expr(cx, expr, params, &self.io_fns.ids);
} else if self.mem_alloc_fns.ids.contains(&fn_did) {
}
if self.mem_alloc_fns.ids.contains(&fn_did) {
fallible_memory_allocation::check_expr(cx, expr, params, fn_did, &self.alloc_size_check_fns);
} else if self.mem_free_fns.ids.contains(&fn_did) {
}
if self.mem_free_fns.ids.contains(&fn_did) {
ptr::check_call(cx, expr, &self.mem_free_fns.ids);
}
passing_string_to_c_functions::check_expr(cx, expr, fn_did, params);
invalid_char_range::check_call(cx, expr, params, fn_did);
}
} else {
blocking_op_in_async::check_expr(cx, expr, &self.blocking_fns.ids);
Expand Down Expand Up @@ -544,3 +556,25 @@ pub fn peel_casts<'a, 'tcx>(maybe_cast_expr: &'a hir::Expr<'tcx>) -> &'a hir::Ex
maybe_cast_expr
}
}

// These lint logics are simple enough that don't need their own file.
fn lint_non_reentrant_fns(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
span_lint_and_help(
cx,
NON_REENTRANT_FUNCTIONS,
expr.span,
"use of non-reentrant function",
None,
"consider using its reentrant counterpart",
);
}
fn lint_mem_unsafe_fns(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
span_lint_and_help(
cx,
MEM_UNSAFE_FUNCTIONS,
expr.span,
"use of potentially dangerous memory manipulation function",
None,
"consider using its safe version",
);
}
58 changes: 58 additions & 0 deletions tests/ui/invalid_char_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![warn(clippy::invalid_char_range)]

const VALID: u32 = 0xD000;
const INVALID_BETWEEN: u32 = 0xDDDD;
const INVALID_LARGE_SIGNED: i64 = 0xFFFFFF;

pub fn valid() {
let _ = char::from(97u8);
let _ = char::from_u32(0x10FFFF);
let _ = char::from_u32(VALID);
}

pub fn invalid() {
let _ = char::from_u32(0xD800);
//~^ ERROR: converting to char with out-of-range integer
//~| NOTE: `-D clippy::invalid-char-range` implied by `-D warnings`
let _ = unsafe { char::from_u32_unchecked(0xD800) };
//~^ ERROR: converting to char with out-of-range integer
let _ = char::from_u32(INVALID_BETWEEN);
//~^ ERROR: converting to char with out-of-range integer
let _ = char::from_u32(INVALID_BETWEEN + 1);
//~^ ERROR: converting to char with out-of-range integer
let _ = unsafe { char::from_u32_unchecked(INVALID_BETWEEN + 1) };
//~^ ERROR: converting to char with out-of-range integer
let _ = char::from_u32(INVALID_LARGE_SIGNED as u32);
//~^ ERROR: converting to char with out-of-range integer
let _ = char::from_u32(INVALID_LARGE_SIGNED as usize as u32);
//~^ ERROR: converting to char with out-of-range integer
}

mod my_char {
pub fn from_u32(u: u32) -> char {
'a'
}
}

pub fn skip() {
let _ = my_char::from_u32(INVALID_BETWEEN);
}

fn get_num() -> u32 {
100
}
// Don't lint uncertain results
pub fn uncertain(n: u32) {
const INVALID_BETWEEN: u32 = 0xDDDD;
let _ = char::from_u32(INVALID_BETWEEN.saturating_sub(1));

let _ = char::from_u32(n);
let _ = char::from_u32(10 + n);
let _ = char::from_u32(10_u32.saturating_add(n));

let _ = char::from_u32(get_num());
let clo = || 0;
let _ = char::from_u32(clo());
}

fn main() {}
88 changes: 88 additions & 0 deletions tests/ui/invalid_char_range.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:14:13
|
LL | let _ = char::from_u32(0xD800);
| ^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:14:28
|
LL | let _ = char::from_u32(0xD800);
| ^^^^^^
= note: `-D clippy::invalid-char-range` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::invalid_char_range)]`

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:17:22
|
LL | let _ = unsafe { char::from_u32_unchecked(0xD800) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:17:47
|
LL | let _ = unsafe { char::from_u32_unchecked(0xD800) };
| ^^^^^^

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:19:13
|
LL | let _ = char::from_u32(INVALID_BETWEEN);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:19:28
|
LL | let _ = char::from_u32(INVALID_BETWEEN);
| ^^^^^^^^^^^^^^^

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:21:13
|
LL | let _ = char::from_u32(INVALID_BETWEEN + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:21:28
|
LL | let _ = char::from_u32(INVALID_BETWEEN + 1);
| ^^^^^^^^^^^^^^^^^^^

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:23:22
|
LL | let _ = unsafe { char::from_u32_unchecked(INVALID_BETWEEN + 1) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:23:47
|
LL | let _ = unsafe { char::from_u32_unchecked(INVALID_BETWEEN + 1) };
| ^^^^^^^^^^^^^^^^^^^

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:25:13
|
LL | let _ = char::from_u32(INVALID_LARGE_SIGNED as u32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:25:28
|
LL | let _ = char::from_u32(INVALID_LARGE_SIGNED as u32);
| ^^^^^^^^^^^^^^^^^^^^

error: converting to char with out-of-range integer
--> $DIR/invalid_char_range.rs:27:13
|
LL | let _ = char::from_u32(INVALID_LARGE_SIGNED as usize as u32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: this number should be within the range of [0, 0xD7FF] or [0xE000, 0x10FFFF]
--> $DIR/invalid_char_range.rs:27:28
|
LL | let _ = char::from_u32(INVALID_LARGE_SIGNED as usize as u32);
| ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 7 previous errors

0 comments on commit ae75e25

Please sign in to comment.