-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
include_file_outside_project
lint
- Loading branch information
1 parent
73bad36
commit a404546
Showing
6 changed files
with
136 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters