Skip to content

Commit 4bbdba4

Browse files
Add new include_file_outside_project lint
1 parent 73bad36 commit 4bbdba4

File tree

6 files changed

+132
-2
lines changed

6 files changed

+132
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5555,6 +5555,7 @@ Released 2018-09-13
55555555
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
55565556
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
55575557
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
5558+
[`include_file_outside_project`]: https://rust-lang.github.io/rust-clippy/master/index.html#include_file_outside_project
55585559
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
55595560
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
55605561
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
1111
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

book/src/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
A collection of lints to catch common mistakes and improve your
77
[Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
Lints are divided into categories, each with a default [lint
1212
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
222222
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
223223
crate::implicit_saturating_sub::INVERTED_SATURATING_SUB_INFO,
224224
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
225+
crate::include_file_outside_project::INCLUDE_FILE_OUTSIDE_PROJECT_INFO,
225226
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
226227
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
227228
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use rustc_ast::LitKind;
2+
use rustc_data_structures::fx::FxHashSet;
3+
use rustc_hir::{Expr, ExprKind, HirId, Item};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::impl_lint_pass;
6+
use rustc_span::{FileName, Span, sym};
7+
8+
use clippy_utils::diagnostics::span_lint_and_then;
9+
use clippy_utils::macros::root_macro_call_first_node;
10+
11+
use std::path::PathBuf;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Check if file included with one of the `include` macros (ie, `include!`, `include_bytes!`
16+
/// and `include_str!`) is actually part of the project.
17+
///
18+
/// ### Why is this bad?
19+
/// If the included file is outside of the project folder, it will not be part of the releases,
20+
/// prevent project to work when others use it.
21+
///
22+
/// ### Example
23+
/// ```ignore
24+
/// let x = include_str!("/etc/passwd");
25+
/// ```
26+
/// Use instead:
27+
/// ```ignore
28+
/// let x = include_str!("./passwd");
29+
/// ```
30+
#[clippy::version = "1.84.0"]
31+
pub INCLUDE_FILE_OUTSIDE_PROJECT,
32+
suspicious,
33+
"checks that all included files are inside the project folder"
34+
}
35+
36+
pub(crate) struct IncludeFileOutsideProject {
37+
cargo_manifest_dir: Option<PathBuf>,
38+
warned_spans: FxHashSet<PathBuf>,
39+
}
40+
41+
impl_lint_pass!(IncludeFileOutsideProject => [INCLUDE_FILE_OUTSIDE_PROJECT]);
42+
43+
impl IncludeFileOutsideProject {
44+
pub(crate) fn new() -> Self {
45+
Self {
46+
cargo_manifest_dir: std::env::var("CARGO_MANIFEST_DIR").ok().map(|dir| PathBuf::from(dir)),
47+
warned_spans: FxHashSet::default(),
48+
}
49+
}
50+
51+
fn check_file_path(&mut self, cx: &LateContext<'_>, span: Span) {
52+
let source_map = cx.tcx.sess.source_map();
53+
let file = source_map.lookup_char_pos(span.lo()).file;
54+
if let FileName::Real(real_filename) = file.name.clone()
55+
&& let Some(file_path) = real_filename.into_local_path()
56+
&& let Ok(file_path) = file_path.canonicalize()
57+
// Only lint once per path.
58+
&& !self.warned_spans.contains(&file_path)
59+
&& let Some(ref cargo_manifest_dir) = self.cargo_manifest_dir
60+
{
61+
// Check if both paths start with the same thing.
62+
let mut file_iter = file_path.iter();
63+
64+
for cargo_item in cargo_manifest_dir.iter() {
65+
match file_iter.next() {
66+
Some(file_path) if file_path == cargo_item => {},
67+
_ => {
68+
// If we enter this arm, it means that the included file path is not
69+
// into the cargo manifest folder.
70+
self.emit_error(cx, span, file_path);
71+
return;
72+
},
73+
}
74+
}
75+
}
76+
}
77+
78+
fn emit_error(&mut self, cx: &LateContext<'_>, span: Span, file_path: PathBuf) {
79+
let span = span.source_callsite();
80+
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
81+
span_lint_and_then(
82+
cx,
83+
INCLUDE_FILE_OUTSIDE_PROJECT,
84+
span.with_hi(span.lo()),
85+
"attempted to include a file outside of the project",
86+
|diag| {
87+
diag.note(format!(
88+
"file is located at `{}` which is outside of project folder (`{}`)",
89+
file_path.display(),
90+
self.cargo_manifest_dir.as_ref().unwrap().display(),
91+
));
92+
},
93+
);
94+
self.warned_spans.insert(file_path);
95+
}
96+
97+
fn check_hir_id(&mut self, cx: &LateContext<'_>, span: Span, hir_id: HirId) {
98+
if self.cargo_manifest_dir.is_some()
99+
&& let hir = cx.tcx.hir()
100+
&& let Some(parent_hir_id) = hir.parent_id_iter(hir_id).next()
101+
&& let parent_span = hir.span(parent_hir_id)
102+
&& !parent_span.contains(span)
103+
{
104+
self.check_file_path(cx, span);
105+
}
106+
}
107+
}
108+
109+
impl LateLintPass<'_> for IncludeFileOutsideProject {
110+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
111+
if !expr.span.from_expansion() {
112+
self.check_hir_id(cx, expr.span, expr.hir_id);
113+
} else if let ExprKind::Lit(lit) = &expr.kind
114+
&& matches!(lit.node, LitKind::ByteStr(..) | LitKind::Str(..))
115+
&& let Some(macro_call) = root_macro_call_first_node(cx, expr)
116+
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
117+
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
118+
{
119+
self.check_hir_id(cx, expr.span, expr.hir_id);
120+
}
121+
}
122+
123+
fn check_item(&mut self, cx: &LateContext<'_>, item: &'_ Item<'_>) {
124+
self.check_hir_id(cx, item.span, item.hir_id());
125+
}
126+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ mod implicit_return;
160160
mod implicit_saturating_add;
161161
mod implicit_saturating_sub;
162162
mod implied_bounds_in_impls;
163+
mod include_file_outside_project;
163164
mod incompatible_msrv;
164165
mod inconsistent_struct_constructor;
165166
mod index_refutable_slice;
@@ -949,5 +950,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
949950
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
950951
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
951952
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
953+
store.register_late_pass(|_| Box::new(include_file_outside_project::IncludeFileOutsideProject::new()));
952954
// add lints here, do not remove this comment, it's used in `new_lint`
953955
}

0 commit comments

Comments
 (0)