Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/chapel-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@
done
./chapel/mass-panic --repoDirectory="$WORK/corpus" --numLocales=1 --quiet --outputDir="$WORK/out"
ls "$WORK/out"/system-image-*.json >/dev/null
for report in "$WORK/out"/assemblyline-*.json "$WORK/out"/system-image-*.json; do
test -f "$report"
case "${report##*/}" in

Check failure on line 217 in .github/workflows/chapel-ci.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_panic-attacker&issues=AaG1rRMc8W9ntfedncAH&open=AaG1rRMc8W9ntfedncAH&pullRequest=268
*:*) echo "Non-portable report filename: ${report##*/}" >&2; exit 1 ;;
esac
done
echo "chapel-e2e: PASS"

chapel-cli-contract:
Expand Down
4 changes: 3 additions & 1 deletion chapel/src/MassPanic.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,9 @@ module MassPanic {
mkdir(outputDir, parents=true);
} catch { }

const timestamp = dateString();
// Keep ISO timestamps inside reports, but use portable filenames.
// Colons are forbidden in Windows checkout paths.
const timestamp = dateString().replace(":", "-");

// Write assemblyline report
const reportPath = joinPath(outputDir, "assemblyline-" + timestamp + ".json");
Expand Down
72 changes: 72 additions & 0 deletions tests/repository_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MPL-2.0
//! Repository checkout contract; run from the Git source checkout.
use std::process::Command;

fn portable_component(component: &str) -> bool {
if component.is_empty()
|| component.ends_with(['.', ' '])
|| component
.chars()
.any(|c| c.is_control() || "<>:\"\\|?*".contains(c))
{
return false;
}
let stem = component
.split('.')
.next()
.unwrap_or("")
.to_ascii_uppercase();
!matches!(stem.as_str(), "CON" | "PRN" | "AUX" | "NUL")
&& !(stem.len() == 4
&& (stem.starts_with("COM") || stem.starts_with("LPT"))
&& matches!(stem.as_bytes()[3], b'1'..=b'9'))
Comment thread
hyperpolymath marked this conversation as resolved.
Resolved
Comment thread
hyperpolymath marked this conversation as resolved.
Resolved
}

#[test]
fn path_controls_cover_windows_restrictions() {
for invalid in [
"report:12.json",
"CON",
"nul.txt",
"com1.log",
"LPT9",
"tail.",
"tail ",
"a?b",
"a\\b",
"line\nbreak",
] {
assert!(!portable_component(invalid), "accepted {invalid:?}");
}
for valid in [
"assemblyline-2026-04-12T11-23-37Z.json",
".github",
"Cargo.toml",
"com10.txt",
] {
assert!(portable_component(valid), "rejected {valid:?}");
}
}

#[test]
fn every_tracked_path_can_be_checked_out_on_windows() {
let output = Command::new("git")
.args(["ls-files", "-z"])
.output()
.expect("git is required for the source-checkout contract");
assert!(output.status.success(), "git ls-files failed");
Comment thread
hyperpolymath marked this conversation as resolved.
Resolved
let paths = std::str::from_utf8(&output.stdout).expect("tracked paths must be UTF-8");
assert!(
!paths.is_empty(),
"an empty inventory cannot establish portability"
);
let bad: Vec<_> = paths
.split('\0')
.filter(|path| !path.is_empty())
.filter(|path| !path.split('/').all(portable_component))
.collect();
Comment thread
hyperpolymath marked this conversation as resolved.
Resolved
Comment thread
hyperpolymath marked this conversation as resolved.
Resolved
assert!(
bad.is_empty(),
"Windows-incompatible tracked paths: {bad:?}"
);
}
Loading