Skip to content

Commit 0efdff5

Browse files
committed
Implement lint to check for usages of from_raw_parts where the same expression is used for the length and the capacity
1 parent aa1fa10 commit 0efdff5

File tree

7 files changed

+132
-0
lines changed

7 files changed

+132
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6591,6 +6591,7 @@ Released 2018-09-13
65916591
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
65926592
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
65936593
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
6594+
[`same_length_and_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_length_and_capacity
65946595
[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
65956596
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
65966597
[`seek_from_current`]: https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
660660
crate::returns::LET_AND_RETURN_INFO,
661661
crate::returns::NEEDLESS_RETURN_INFO,
662662
crate::returns::NEEDLESS_RETURN_WITH_QUESTION_MARK_INFO,
663+
crate::same_length_and_capacity::SAME_LENGTH_AND_CAPACITY_INFO,
663664
crate::same_name_method::SAME_NAME_METHOD_INFO,
664665
crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO,
665666
crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ mod repeat_vec_with_capacity;
327327
mod reserve_after_initialization;
328328
mod return_self_not_must_use;
329329
mod returns;
330+
mod same_length_and_capacity;
330331
mod same_name_method;
331332
mod self_named_constructors;
332333
mod semicolon_block;
@@ -831,5 +832,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
831832
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
832833
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
833834
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
835+
store.register_late_pass(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity));
834836
// add lints here, do not remove this comment, it's used in `new_lint`
835837
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
3+
use clippy_utils::{SpanlessEq, sym};
4+
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
5+
use rustc_lint::{LateContext, LateLintPass};
6+
use rustc_session::declare_lint_pass;
7+
use rustc_span::symbol::sym as rustc_sym;
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
///
12+
/// Checks for usages of Vec::from_raw_parts and String::from_raw_parts
13+
/// where the same expression is used for the length and the capacity.
14+
///
15+
/// ### Why is this bad?
16+
///
17+
/// If the same expression is being passed for the length and
18+
/// capacity, it is most likely a semantic error. In the case of a
19+
/// Vec, for example, the only way to end up with one that has
20+
/// the same length and capacity is by going through a boxed slice,
21+
/// e.g. Box::from(some_vec), which shrinks the capacity to match
22+
/// the length.
23+
///
24+
/// ### Example
25+
///
26+
/// ```no_run
27+
/// let mut original: Vec::<i32> = Vec::with_capacity(20);
28+
/// original.extend([1, 2, 3, 4, 5]);
29+
///
30+
/// let (ptr, mut len, cap) = original.into_raw_parts();
31+
///
32+
/// // Pretend we added three more integers:
33+
/// len = 8;
34+
///
35+
/// // But I forgot the capacity was separate from the length:
36+
/// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, len) };
37+
/// ```
38+
///
39+
/// Use instead:
40+
/// ```no_run
41+
/// // Correction to the last line of the given example code:
42+
/// let reconstructed = unsafe { Vec::from_raw_parts(ptr, len, cap) };
43+
/// ```
44+
#[clippy::version = "1.91.0"]
45+
pub SAME_LENGTH_AND_CAPACITY,
46+
pedantic,
47+
"`from_raw_parts` with same length and capacity"
48+
}
49+
declare_lint_pass!(SameLengthAndCapacity => [SAME_LENGTH_AND_CAPACITY]);
50+
51+
impl<'tcx> LateLintPass<'tcx> for SameLengthAndCapacity {
52+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
53+
if let ExprKind::Call(path_expr, args) = expr.kind
54+
&& let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind
55+
&& is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), rustc_sym::Vec)
56+
&& fn_path.ident.name == sym::from_raw_parts
57+
&& SpanlessEq::new(cx).eq_expr(&args[1], &args[2])
58+
{
59+
span_lint_and_help(
60+
cx,
61+
SAME_LENGTH_AND_CAPACITY,
62+
expr.span,
63+
"usage of `Vec::from_raw_parts` with the same expression for length and capacity",
64+
None,
65+
"if the length and capacity are the same, you most likely went through a boxed slice; consider reconstructing the `Vec` using a `Box` instead, e.g. `Box::from(slice::from_raw_parts(...)).into_vec()`",
66+
);
67+
} else if let ExprKind::Call(path_expr, args) = expr.kind
68+
&& let ExprKind::Path(QPath::TypeRelative(ty, fn_path)) = path_expr.kind
69+
&& is_type_lang_item(cx, cx.typeck_results().node_type(ty.hir_id), LangItem::String)
70+
&& fn_path.ident.name == sym::from_raw_parts
71+
&& SpanlessEq::new(cx).eq_expr(&args[1], &args[2])
72+
{
73+
span_lint_and_help(
74+
cx,
75+
SAME_LENGTH_AND_CAPACITY,
76+
expr.span,
77+
"usage of `String::from_raw_parts` with the same expression for length and capacity",
78+
None,
79+
"if the length and capacity are the same, you most likely went through a boxed `str`; consider reconstructing the `String` using `String::from` instead, e.g. `String::from(str::from_utf8_unchecked(slice::from_raw_parts(...)))`",
80+
);
81+
}
82+
}
83+
}

clippy_utils/src/sym.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ generate! {
160160
from_ne_bytes,
161161
from_ptr,
162162
from_raw,
163+
from_raw_parts,
163164
from_str,
164165
from_str_radix,
165166
fs,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![feature(vec_into_raw_parts)]
2+
#![warn(clippy::same_length_and_capacity)]
3+
4+
fn main() {
5+
let mut my_vec: Vec<i32> = Vec::with_capacity(20);
6+
my_vec.extend([1, 2, 3, 4, 5]);
7+
let (ptr, mut len, cap) = my_vec.into_raw_parts();
8+
len = 8;
9+
10+
let _reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, len) };
11+
//~^ same_length_and_capacity
12+
13+
// Don't want to lint different expressions for len and cap
14+
let _properly_reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, cap) };
15+
16+
let my_string = String::from("hello");
17+
let (string_ptr, string_len, string_cap) = my_string.into_raw_parts();
18+
19+
let _reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_len) };
20+
//~^ same_length_and_capacity
21+
22+
// Don't want to lint different expressions for len and cap
23+
let _properly_reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_cap) };
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: usage of `Vec::from_raw_parts` with the same expression for length and capacity
2+
--> tests/ui/same_length_and_capacity.rs:10:39
3+
|
4+
LL | let _reconstructed_vec = unsafe { Vec::from_raw_parts(ptr, len, len) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: if the length and capacity are the same, you most likely went through a boxed slice; consider reconstructing the `Vec` using a `Box` instead, e.g. `Box::from(slice::from_raw_parts(...)).into_vec()`
8+
= note: `-D clippy::same-length-and-capacity` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::same_length_and_capacity)]`
10+
11+
error: usage of `String::from_raw_parts` with the same expression for length and capacity
12+
--> tests/ui/same_length_and_capacity.rs:19:42
13+
|
14+
LL | let _reconstructed_string = unsafe { String::from_raw_parts(string_ptr, string_len, string_len) };
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
|
17+
= help: if the length and capacity are the same, you most likely went through a boxed `str`; consider reconstructing the `String` using `String::from` instead, e.g. `String::from(str::from_utf8_unchecked(slice::from_raw_parts(...)))`
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)