Skip to content

Commit

Permalink
Add new include_file_outside_project lint
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Oct 31, 2024
1 parent 73bad36 commit a404546
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5555,6 +5555,7 @@ Released 2018-09-13
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
[`include_file_outside_project`]: https://rust-lang.github.io/rust-clippy/master/index.html#include_file_outside_project
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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

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

Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
Expand Down
2 changes: 1 addition & 1 deletion book/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.

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

Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
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 @@ -222,6 +222,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
crate::implicit_saturating_sub::INVERTED_SATURATING_SUB_INFO,
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
crate::include_file_outside_project::INCLUDE_FILE_OUTSIDE_PROJECT_INFO,
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
Expand Down
130 changes: 130 additions & 0 deletions clippy_lints/src/include_file_outside_project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{Expr, ExprKind, HirId, Item};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{FileName, Span, sym};

use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;

use std::path::PathBuf;

declare_clippy_lint! {
/// ### What it does
/// Check if file included with one of the `include` macros (ie, `include!`, `include_bytes!`
/// and `include_str!`) is actually part of the project.
///
/// ### Why is this bad?
/// If the included file is outside of the project folder, it will not be part of the releases,
/// prevent project to work when others use it.
///
/// ### Example
/// ```ignore
/// let x = include_str!("/etc/passwd");
/// ```
/// Use instead:
/// ```ignore
/// let x = include_str!("./passwd");
/// ```
#[clippy::version = "1.84.0"]
pub INCLUDE_FILE_OUTSIDE_PROJECT,
suspicious,
"checks that all included files are inside the project folder"
}

pub(crate) struct IncludeFileOutsideProject {
cargo_manifest_dir: Option<PathBuf>,
warned_spans: FxHashSet<PathBuf>,
}

impl_lint_pass!(IncludeFileOutsideProject => [INCLUDE_FILE_OUTSIDE_PROJECT]);

impl IncludeFileOutsideProject {
pub(crate) fn new() -> Self {
Self {
cargo_manifest_dir: std::env::var("CARGO_MANIFEST_DIR").ok().map(|dir| PathBuf::from(dir)),
warned_spans: FxHashSet::default(),
}
}

fn check_file_path(&mut self, cx: &LateContext<'_>, span: Span) {
let source_map = cx.tcx.sess.source_map();
let file = source_map.lookup_char_pos(span.lo()).file;
if let FileName::Real(real_filename) = file.name.clone()
&& let Some(file_path) = real_filename.into_local_path()
&& let Ok(file_path) = file_path.canonicalize()
// Only lint once per path.
&& !self.warned_spans.contains(&file_path)
&& let Some(ref cargo_manifest_dir) = self.cargo_manifest_dir
{
// Check if both paths start with the same thing.
let mut file_iter = file_path.iter();

for cargo_item in cargo_manifest_dir.iter() {
match file_iter.next() {
Some(file_path) if file_path == cargo_item => {},
_ => {
// If we enter this arm, it means that the included file path is not
// into the cargo manifest folder.
self.emit_error(cx, span, file_path);
return;
},
}
}
}
}

fn emit_error(&mut self, cx: &LateContext<'_>, span: Span, file_path: PathBuf) {
let span = span.source_callsite();
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
INCLUDE_FILE_OUTSIDE_PROJECT,
span.with_hi(span.lo()),
"attempted to include a file outside of the project",
|diag| {
diag.note(format!(
"file is located at `{}` which is outside of project folder (`{}`)",
file_path.display(),
self.cargo_manifest_dir.as_ref().unwrap().display(),
));
},
);
self.warned_spans.insert(file_path);
}

fn check_hir_id(&mut self, cx: &LateContext<'_>, span: Span, hir_id: HirId) {
if self.cargo_manifest_dir.is_some()
&& let hir = cx.tcx.hir()
&& let Some(parent_hir_id) = hir.parent_id_iter(hir_id).next()
&& let parent_span = hir.span(parent_hir_id)
&& !parent_span.contains(span)
{
self.check_file_path(cx, span);
}
}
}

impl LateLintPass<'_> for IncludeFileOutsideProject {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if !expr.span.from_expansion() {
self.check_hir_id(cx, expr.span, expr.hir_id);
} else if let ExprKind::Lit(lit) = &expr.kind
&& matches!(lit.node, LitKind::ByteStr(..) | LitKind::Str(..))
&& let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
{
self.check_hir_id(cx, expr.span, expr.hir_id);
}
}

fn check_item(&mut self, cx: &LateContext<'_>, item: &'_ Item<'_>) {
// Interestingly enough, `include!` content is not considered expanded. Which allows us
// to easily filter out items we're not interested into.
if !item.span.from_expansion() {
self.check_hir_id(cx, item.span, item.hir_id());
}
}
}
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ mod implicit_return;
mod implicit_saturating_add;
mod implicit_saturating_sub;
mod implied_bounds_in_impls;
mod include_file_outside_project;
mod incompatible_msrv;
mod inconsistent_struct_constructor;
mod index_refutable_slice;
Expand Down Expand Up @@ -949,5 +950,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(|_| Box::new(include_file_outside_project::IncludeFileOutsideProject::new()));
// add lints here, do not remove this comment, it's used in `new_lint`
}

0 comments on commit a404546

Please sign in to comment.