From 58846c92ecd63ed5c797c8eeeb1f5efb1f03a022 Mon Sep 17 00:00:00 2001 From: nightcityblade Date: Sat, 12 Sep 2026 11:08:42 +0800 Subject: [PATCH] fix: reject non-launchable wavec paths Signed-off-by: nightcityblade --- tools/check_wave_corpus.py | 14 ++++++++++---- tools/test_check_wave_corpus.py | 23 ++++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/tools/check_wave_corpus.py b/tools/check_wave_corpus.py index 8428fd30..eea1c19e 100755 --- a/tools/check_wave_corpus.py +++ b/tools/check_wave_corpus.py @@ -83,18 +83,24 @@ def _resolve_compiler_path(candidate: Path | str) -> Path | None: return None +def _require_launchable(path: Path) -> Path: + if os.name != "nt" and not os.access(path, os.X_OK): + raise PermissionError(f"wavec executable is not launchable: {path}") + return path + + def resolve_wavec(explicit: Path | None) -> Path: if explicit is not None: resolved = _resolve_compiler_path(explicit) if resolved is not None: - return resolved + return _require_launchable(resolved) raise FileNotFoundError(f"wavec executable not found at {explicit}") env_wavec = os.environ.get("WAVEC") if env_wavec and env_wavec.strip(): resolved = _resolve_compiler_path(env_wavec) if resolved is not None: - return resolved + return _require_launchable(resolved) raise FileNotFoundError(f"wavec executable specified by WAVEC not found: {env_wavec}") candidates = [ @@ -108,7 +114,7 @@ def resolve_wavec(explicit: Path | None) -> Path: for candidate in candidates: if candidate.is_file(): - return candidate + return _require_launchable(candidate) raise FileNotFoundError("wavec not found; build it or pass --wavec") @@ -168,7 +174,7 @@ def main(argv: list[str] | None = None) -> int: args = parse_args(argv) try: wavec = resolve_wavec(args.wavec) - except FileNotFoundError as error: + except OSError as error: print(error, file=sys.stderr) return 2 diff --git a/tools/test_check_wave_corpus.py b/tools/test_check_wave_corpus.py index c2139bcf..8d6cd5aa 100644 --- a/tools/test_check_wave_corpus.py +++ b/tools/test_check_wave_corpus.py @@ -129,7 +129,7 @@ def test_valid_explicit_path_selected(self): root = Path(td) custom = root / "bin" / "custom_wavec" custom.parent.mkdir(parents=True) - custom.touch() + custom.touch(mode=0o700) fallback = root / "target" / "release" / "wavec" fallback.parent.mkdir(parents=True) @@ -147,11 +147,11 @@ def test_valid_wavec_env_selected(self): root = Path(td) custom = root / "bin" / "custom_wavec" custom.parent.mkdir(parents=True) - custom.touch() + custom.touch(mode=0o700) fallback = root / "target" / "release" / "wavec" fallback.parent.mkdir(parents=True) - fallback.touch() + fallback.touch(mode=0o700) with patch.object(check_wave_corpus, "ROOT", root): with patch.dict(os.environ, {"WAVEC": str(custom)}, clear=False): @@ -162,7 +162,7 @@ def test_no_override_falls_back_to_discovery(self): root = Path(td) fallback = root / "target" / "release" / "wavec" fallback.parent.mkdir(parents=True) - fallback.touch() + fallback.touch(mode=0o700) with patch.object(check_wave_corpus, "ROOT", root): env = os.environ.copy() @@ -175,7 +175,7 @@ def test_empty_wavec_env_falls_back_to_discovery(self): root = Path(td) fallback = root / "target" / "release" / "wavec" fallback.parent.mkdir(parents=True) - fallback.touch() + fallback.touch(mode=0o700) with patch.object(check_wave_corpus, "ROOT", root): with patch.dict(os.environ, {"WAVEC": " "}, clear=False): @@ -193,6 +193,19 @@ def test_no_override_and_no_binary_raises_file_not_found(self): self.assertIn("wavec not found; build it or pass --wavec", str(cm.exception)) + @unittest.skipIf(os.name == "nt", "POSIX executable bits do not apply") + def test_main_rejects_non_executable_compiler_without_traceback(self): + with tempfile.TemporaryDirectory() as td: + compiler = Path(td) / "wavec" + compiler.touch(mode=0o600) + stderr = io.StringIO() + with patch("sys.stderr", stderr): + self.assertEqual(main(["--wavec", str(compiler)]), 2) + + self.assertIn(str(compiler), stderr.getvalue()) + self.assertIn("not launchable", stderr.getvalue()) + self.assertNotIn("Traceback", stderr.getvalue()) + if __name__ == "__main__": unittest.main()