Skip to main content

path_match/
lib.rs

1//! `syn::Path` matching helpers shared by `capability-attr` (call-path
2//! detection for its allocation/IO/raw-pointer vocabulary) and
3//! `taint-check` (sink/sanitizer call-path detection).
4//!
5//! This crate is unpublished and has no design doc of its own: every
6//! function here was a private, non-`pub` helper inside `capability-attr`'s
7//! `inspector.rs` before this extraction, never reachable from outside
8//! that crate — doubly so since a `proc-macro = true` crate can only
9//! export its `#[proc_macro_attribute]` entry points to begin with. Moving
10//! them here changes no public API of either dependent crate.
11
12#![warn(missing_docs)]
13#![allow(
14    clippy::cargo_common_metadata,
15    reason = "workspace-wide dependency-graph check, not something a single-crate pass can fix or meaningfully scope"
16)]
17
18use syn::Path;
19
20/// Join every segment of `path` with `::` (e.g. `std::fs::read_to_string`).
21#[must_use]
22pub fn path_to_string(path: &Path) -> String {
23    path.segments
24        .iter()
25        .map(|s| s.ident.to_string())
26        .collect::<Vec<_>>()
27        .join("::")
28}
29
30/// Join just the trailing one or two segments of `path` with `::`, so
31/// `Vec::new()` and `std::vec::Vec::new()` both match the same
32/// `"Vec::new"` check regardless of how fully-qualified the call is
33/// written.
34#[must_use]
35pub fn path_last_two(path: &Path) -> String {
36    let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
37    if segs.len() >= 2 {
38        format!("{}::{}", segs[segs.len() - 2], segs[segs.len() - 1])
39    } else {
40        segs.last().cloned().unwrap_or_default()
41    }
42}
43
44/// `true` if any segment of `path` is exactly `marker`.
45///
46/// Used to catch a module marker (`fs`, `net`, `Command`, ...) appearing
47/// anywhere in a call path, regardless of how much of the path precedes or
48/// follows it.
49#[must_use]
50pub fn path_has_segment(path: &Path, marker: &str) -> bool {
51    path.segments.iter().any(|s| s.ident == marker)
52}
53
54/// The trailing segment's identifier, as a `String`.
55///
56/// `log_debug`, `self::log_debug`, `super::log_debug`, and
57/// `crate::auth::log_debug` all yield `"log_debug"`. Unlike
58/// [`syn::Path::get_ident`], this does not require the path be a single
59/// bare segment.
60#[must_use]
61pub fn path_last_segment(path: &Path) -> Option<String> {
62    path.segments.last().map(|s| s.ident.to_string())
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use syn::parse_quote;
69
70    #[test]
71    fn path_to_string_joins_every_segment() {
72        let path: Path = parse_quote!(std::fs::read_to_string);
73        assert_eq!(path_to_string(&path), "std::fs::read_to_string");
74    }
75
76    #[test]
77    fn path_last_two_joins_trailing_two_segments() {
78        let path: Path = parse_quote!(std::vec::Vec::new);
79        assert_eq!(path_last_two(&path), "Vec::new");
80    }
81
82    #[test]
83    fn path_last_two_handles_a_single_segment_path() {
84        let path: Path = parse_quote!(new);
85        assert_eq!(path_last_two(&path), "new");
86    }
87
88    #[test]
89    fn path_has_segment_matches_anywhere_in_the_path() {
90        let path: Path = parse_quote!(std::fs::read_to_string);
91        assert!(path_has_segment(&path, "fs"));
92        assert!(!path_has_segment(&path, "net"));
93    }
94
95    #[test]
96    fn path_last_segment_ignores_qualification() {
97        assert_eq!(
98            path_last_segment(&parse_quote!(log_debug)),
99            Some("log_debug".to_string())
100        );
101        assert_eq!(
102            path_last_segment(&parse_quote!(self::log_debug)),
103            Some("log_debug".to_string())
104        );
105        assert_eq!(
106            path_last_segment(&parse_quote!(super::log_debug)),
107            Some("log_debug".to_string())
108        );
109        assert_eq!(
110            path_last_segment(&parse_quote!(crate::auth::log_debug)),
111            Some("log_debug".to_string())
112        );
113    }
114}