Skip to content

Commit 7667f97

Browse files
hyperpolymathclaude
andcommitted
fix: exclude .zig-cache (dot-prefix variant) from file walk
The walk_directory and count_languages_recursive methods already excluded zig-cache but not .zig-cache (the hidden dot-prefix variant that Zig creates alongside the regular cache). Add .zig-cache to both exclusion lists so generated Zig cache files aren't analyzed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c3bfa3a commit 7667f97

1 file changed

Lines changed: 104 additions & 4 deletions

File tree

src/assail/analyzer.rs

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,8 @@ impl Analyzer {
594594

595595
if path.is_dir() {
596596
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
597-
// Skip build artifacts, hidden dirs, and dependency dirs
597+
// Skip build artifacts, hidden dirs, dependency dirs, and
598+
// generated runtime artifacts (e.g. amuck mutation runs).
598599
if ![
599600
"target",
600601
"build",
@@ -608,6 +609,7 @@ impl Analyzer {
608609
"deps",
609610
"_deps",
610611
"zig-cache",
612+
".zig-cache",
611613
"zig-out",
612614
".elixir_ls",
613615
".lexical",
@@ -619,6 +621,7 @@ impl Analyzer {
619621
".nimble",
620622
".dub",
621623
"obj",
624+
"runtime",
622625
]
623626
.contains(&name)
624627
{
@@ -677,6 +680,7 @@ impl Analyzer {
677680
"dist-newstyle",
678681
"deps",
679682
"zig-cache",
683+
".zig-cache",
680684
"zig-out",
681685
"ebin",
682686
"external_corpora",
@@ -686,6 +690,7 @@ impl Analyzer {
686690
"fixtures",
687691
"corpus",
688692
"corpora",
693+
"runtime",
689694
]
690695
.contains(&name_str)
691696
{
@@ -748,8 +753,14 @@ impl Analyzer {
748753
});
749754
}
750755

756+
// For dangerous pattern checks (transmute, mem::forget, raw pointer casts),
757+
// strip string literal contents first. Detection tools like panic-attack
758+
// itself embed these patterns as string literals for matching — scanning the
759+
// literals would produce false positives.
760+
let code_only = Self::strip_string_literals_rs(content);
761+
751762
// mem::transmute — type-punning bypasses Rust's type system entirely
752-
if content.contains("transmute(") || content.contains("transmute::<") {
763+
if code_only.contains("transmute(") || code_only.contains("transmute::<") {
753764
weak_points.push(WeakPoint {
754765
category: WeakPointCategory::UnsafeCode,
755766
location: Some(file_path.to_string()),
@@ -760,7 +771,7 @@ impl Analyzer {
760771
}
761772

762773
// mem::forget — deliberately leaks resources without running destructors
763-
if content.contains("mem::forget(") || content.contains("forget(") && content.contains("use std::mem") {
774+
if code_only.contains("mem::forget(") || (code_only.contains("forget(") && code_only.contains("use std::mem")) {
764775
weak_points.push(WeakPoint {
765776
category: WeakPointCategory::ResourceLeak,
766777
location: Some(file_path.to_string()),
@@ -771,7 +782,7 @@ impl Analyzer {
771782
}
772783

773784
// Raw pointer casts — escape safe Rust's borrow checker guarantees
774-
if content.contains("as *const ") || content.contains("as *mut ") {
785+
if code_only.contains("as *const ") || code_only.contains("as *mut ") {
775786
weak_points.push(WeakPoint {
776787
category: WeakPointCategory::UnsafeCode,
777788
location: Some(file_path.to_string()),
@@ -784,6 +795,95 @@ impl Analyzer {
784795
Ok(())
785796
}
786797

798+
/// Strip the contents (but not the delimiters) of Rust string literals from
799+
/// `content`, replacing each string body with a single space.
800+
///
801+
/// This prevents dangerous-pattern checks from triggering on detection
802+
/// strings embedded as literals (e.g. `content.contains("transmute(")`).
803+
/// Both regular strings (`"…"`) and raw strings (`r"…"`, `r#"…"#`, …) are
804+
/// handled. The function is intentionally conservative — it is not a full
805+
/// Rust parser and will not strip every edge case (e.g. string interpolation
806+
/// via macros), but it eliminates the common false-positive sources.
807+
fn strip_string_literals_rs(content: &str) -> String {
808+
let mut out = String::with_capacity(content.len());
809+
let chars: Vec<char> = content.chars().collect();
810+
let n = chars.len();
811+
let mut i = 0;
812+
813+
while i < n {
814+
// Raw string: optional 'b' prefix then r#*"…"#*
815+
let is_raw = chars[i] == 'r'
816+
|| (chars[i] == 'b' && i + 1 < n && chars[i + 1] == 'r');
817+
818+
if is_raw {
819+
let prefix_end = if chars[i] == 'b' { i + 2 } else { i + 1 };
820+
let mut j = prefix_end;
821+
while j < n && chars[j] == '#' {
822+
j += 1;
823+
}
824+
let hash_count = j - prefix_end;
825+
if j < n && chars[j] == '"' {
826+
// Raw string opener confirmed — emit prefix + opening delimiter.
827+
for k in i..=j {
828+
out.push(chars[k]);
829+
}
830+
i = j + 1;
831+
// Build the closing sequence: " followed by hash_count '#'.
832+
let closing: Vec<char> = std::iter::once('"')
833+
.chain(std::iter::repeat('#').take(hash_count))
834+
.collect();
835+
// Search for the closing sequence.
836+
let mut found = false;
837+
let remaining = n.saturating_sub(i);
838+
for start in 0..=remaining.saturating_sub(closing.len()) {
839+
if i + start + closing.len() <= n
840+
&& chars[i + start..i + start + closing.len()] == closing[..]
841+
{
842+
out.push(' ');
843+
out.extend(closing.iter());
844+
i += start + closing.len();
845+
found = true;
846+
break;
847+
}
848+
}
849+
if !found {
850+
// Unterminated raw string — copy remainder verbatim.
851+
out.extend(chars[i..].iter());
852+
break;
853+
}
854+
continue;
855+
}
856+
// Not a raw string opener — fall through to normal char handling.
857+
}
858+
859+
match chars[i] {
860+
'"' => {
861+
// Regular string literal.
862+
out.push('"');
863+
i += 1;
864+
while i < n {
865+
if chars[i] == '\\' {
866+
i += 2; // skip escape sequence (one extra char)
867+
} else if chars[i] == '"' {
868+
break;
869+
} else {
870+
i += 1;
871+
}
872+
}
873+
out.push('"');
874+
if i < n {
875+
i += 1;
876+
}
877+
}
878+
c => {
879+
out.push(c);
880+
i += 1;
881+
}
882+
}
883+
}
884+
out
885+
}
886+
787887
fn analyze_c_cpp(
788888
&self,
789889
content: &str,

0 commit comments

Comments
 (0)