4"""Produce and validate cppcheck inputs from an isolated Git census.
6Cppcheck must receive explicit candidate-tree translation units. Directory
7operands recursively ingest ignored in-tree build output, while an inherited
8Git index/configuration or Python startup hook can silently replace a naive
9manifest. This module is the single cppcheck source-scope authority consumed
10by both the registered gate and the advisory wrapper.
13from __future__
import annotations
22from pathlib
import Path, PurePosixPath
23from typing
import BinaryIO, TextIO
28sys.path.insert(0, str(Path(__file__).resolve().parent))
29sys.path.insert(0, str(Path(__file__).resolve().parents[1] /
"dev"))
32from git_environment
import (
33 sanitized_git_environment,
34 trusted_git_executable,
37SOURCE_ROOTS = (
"libs/",
"examples/",
"tools/")
38SOURCE_SUFFIXES = (
".c",
".cc",
".cpp",
".cxx")
42class CensusError(RuntimeError):
43 """Raised when the source census or transported manifest is untrustworthy."""
46 def git_failed(cls, detail: str) -> CensusError:
47 """Build the error for a failed Git census command."""
48 return cls(f
"git ls-files failed: {detail or 'no diagnostic'}")
51 def invalid_encoding(cls) -> CensusError:
52 """Build the error for bytes outside the repository path encoding."""
53 return cls(
"source manifest contains a non-UTF-8 path")
56 def collapsed(cls, count: int, minimum: int) -> CensusError:
57 """Build the error for a census below its authenticated floor."""
58 return cls(f
"cppcheck source census has {count} unit(s); floor is {minimum}")
61 def unsafe_path(cls, rel: str, reason: str) -> CensusError:
62 """Build the error for a path outside the regular-file boundary."""
63 return cls(f
"unsafe cppcheck source path {rel!r}: {reason}")
66 def invalid_manifest(cls, reason: str) -> CensusError:
67 """Build the error for malformed transported bytes."""
68 return cls(f
"invalid cppcheck source manifest: {reason}")
71 def duplicate_manifest(cls) -> CensusError:
72 """Build the error for duplicate transported paths."""
73 return cls.invalid_manifest(
"duplicate paths")
76 def unsorted_manifest(cls) -> CensusError:
77 """Build the error for a nondeterministic transported order."""
78 return cls.invalid_manifest(
"paths are not deterministically sorted")
81 def missing_terminator(cls) -> CensusError:
82 """Build the error for a truncated NUL manifest."""
83 return cls.invalid_manifest(
"missing final NUL terminator")
86 def empty_manifest_field(cls) -> CensusError:
87 """Build the error for an empty path within a manifest."""
88 return cls.invalid_manifest(
"empty path field")
91 def unsafe_transport(cls) -> CensusError:
92 """Build the error for a non-regular manifest transport."""
93 return cls.invalid_manifest(
"transport is not a regular file")
96def trusted_python_executable() -> str:
97 """Return the fixed Python authority shared by the shell adapter."""
98 expected =
"/usr/bin/python3"
99 configured = os.environ.get(
"RA8_TRUSTED_PYTHON", expected)
100 if configured != expected:
101 raise CensusError.unsafe_path(configured,
"not the trusted Python executable")
102 path = Path(expected)
105 except OSError
as error:
106 raise CensusError.unsafe_path(expected,
"unavailable")
from error
107 resolved = path.resolve(strict=
True)
108 resolved_info = resolved.lstat()
109 if not stat.S_ISREG(resolved_info.st_mode)
or not os.access(resolved, os.X_OK):
110 raise CensusError.unsafe_path(expected,
"does not resolve to a regular executable")
114def git_paths(root: Path) -> list[str]:
115 """Return tracked and non-ignored candidate paths through sanitized Git."""
116 process = subprocess.run(
118 trusted_git_executable(),
124 "core.excludesFile=/dev/null",
129 "--exclude-standard",
132 env=sanitized_git_environment(),
136 if process.returncode != 0:
137 detail = os.fsdecode(process.stderr).strip()
138 raise CensusError.git_failed(detail)
140 decoded = process.stdout.decode(
"utf-8")
141 except UnicodeDecodeError
as error:
142 raise CensusError.invalid_encoding()
from error
143 return [rel
for rel
in decoded.split(
"\0")
if rel]
146def lexical_parts(rel: str) -> tuple[str, ...]:
147 """Return safe POSIX path components or reject lexical escape syntax."""
148 raw_parts = rel.split(
"/")
149 pure = PurePosixPath(rel)
150 if pure.is_absolute()
or not raw_parts
or any(part
in {
"",
".",
".."}
for part
in raw_parts):
151 raise CensusError.unsafe_path(rel,
"path is not a confined repository-relative name")
152 return tuple(raw_parts)
155def is_cppcheck_unit(rel: str, root: Path) -> bool:
156 """Return whether one safe repository path is an in-scope translation unit."""
158 rel.startswith(SOURCE_ROOTS)
159 and rel.endswith(SOURCE_SUFFIXES)
160 and lint_targets.language_of(rel, root) ==
"c"
164def regular_source_exists(root: Path, rel: str) -> bool:
165 """Require a non-symlink regular file under non-symlink repo directories."""
166 parts = lexical_parts(rel)
167 root_resolved = root.resolve(strict=
True)
168 current = root_resolved
169 for index, part
in enumerate(parts):
172 info = current.lstat()
173 except FileNotFoundError:
175 if stat.S_ISLNK(info.st_mode):
176 raise CensusError.unsafe_path(rel,
"symlink component is forbidden")
177 if index < len(parts) - 1
and not stat.S_ISDIR(info.st_mode):
178 raise CensusError.unsafe_path(rel,
"parent component is not a directory")
179 if index == len(parts) - 1
and not stat.S_ISREG(info.st_mode):
180 raise CensusError.unsafe_path(rel,
"source is not a regular file")
182 current.resolve(strict=
True).relative_to(root_resolved)
183 except (OSError, ValueError)
as error:
184 raise CensusError.unsafe_path(rel,
"resolved path escapes the repository")
from error
188def validate_sources(root: Path, sources: list[str], minimum: int) -> list[str]:
189 """Validate order, uniqueness, scope, confinement, and anti-vacuity."""
190 if len(sources) != len(set(sources)):
191 raise CensusError.duplicate_manifest()
192 if sources != sorted(sources):
193 raise CensusError.unsorted_manifest()
194 if len(sources) < minimum:
195 raise CensusError.collapsed(len(sources), minimum)
198 if not is_cppcheck_unit(rel, root):
199 raise CensusError.unsafe_path(rel,
"outside authenticated roots or suffixes")
200 if not regular_source_exists(root, rel):
201 raise CensusError.unsafe_path(rel,
"source disappeared before validation")
205def collect_sources(root: Path, minimum: int) -> list[str]:
206 """Collect sorted regular sources and reject unsafe candidate entries."""
207 sources: list[str] = []
208 for rel
in git_paths(root):
210 if not is_cppcheck_unit(rel, root):
212 if regular_source_exists(root, rel):
214 return validate_sources(root, sorted(sources), minimum)
217def decode_manifest(data: bytes) -> list[str]:
218 """Decode one canonical NUL-terminated path manifest."""
219 if not data
or not data.endswith(b
"\0"):
220 raise CensusError.missing_terminator()
221 fields = data[:-1].split(b
"\0")
222 if any(
not field
for field
in fields):
223 raise CensusError.empty_manifest_field()
225 return [field.decode(
"utf-8")
for field
in fields]
226 except UnicodeDecodeError
as error:
227 raise CensusError.invalid_encoding()
from error
230def emit_sources(sources: list[str], output: BinaryIO, nul_terminated: bool) ->
None:
231 """Write a path manifest without shell re-tokenization."""
232 separator = b
"\0" if nul_terminated
else b
"\n"
233 output.write(separator.join(path.encode(
"utf-8")
for path
in sources) + separator)
240 error_output: TextIO,
241 nul_terminated: bool,
243 """Run the production census and return a process-style status."""
245 sources = collect_sources(root, minimum)
246 except CensusError
as error:
247 print(f
"cppcheck_sources.py: FATAL -- {error}", file=error_output)
249 emit_sources(sources, output, nul_terminated)
253def run_manifest_validation(
257 error_output: TextIO,
258 nul_terminated: bool,
260 """Validate transported bytes independently and emit a normalized manifest."""
262 info = manifest.lstat()
263 if stat.S_ISLNK(info.st_mode)
or not stat.S_ISREG(info.st_mode):
264 raise CensusError.unsafe_transport()
265 sources = validate_sources(root, decode_manifest(manifest.read_bytes()), MIN_SOURCE_UNITS)
266 except (CensusError, OSError)
as error:
267 print(f
"cppcheck_sources.py: FATAL -- {error}", file=error_output)
269 emit_sources(sources, output, nul_terminated)
273def fixture_git(root: Path, *args: str, extra_env: dict[str, str] |
None =
None) -> bytes:
274 """Run trusted Git for a synthetic selftest repository."""
275 environment = sanitized_git_environment()
277 environment.update(extra_env)
278 process = subprocess.run(
279 [trusted_git_executable(),
"-C", str(root), *args],
285 if process.returncode != 0:
286 raise CensusError.git_failed(os.fsdecode(process.stderr).strip())
287 return process.stdout
290def write_fixture(root: Path) ->
None:
291 """Create the ordinary, ignored, generated, and SOUP fixture population."""
293 ".gitignore":
"/tools/**/build/\n",
294 "libs/ra8_core/src/tracked.c":
"int tracked_source(void) { return 0; }\n",
295 "tools/demo/src/candidate.cpp":
"int candidate_source() { return 0; }\n",
296 "tools/demo/build/generated.c":
"int ignored_output(void) { return 0; }\n",
297 "libs/ra8_fonts/generated.c":
"int generated_font(void) { return 0; }\n",
298 "libs/third_party/vendor.c":
"int vendor_source(void) { return 0; }\n",
300 for rel, content
in files.items():
302 path.parent.mkdir(parents=
True, exist_ok=
True)
303 path.write_text(content, encoding=
"ascii")
306def initialize_fixture(root: Path) ->
None:
307 """Initialize Git and stage the ordinary, generated, and SOUP fixtures."""
308 fixture_git(root,
"init",
"-q")
313 "libs/ra8_core/src/tracked.c",
314 "libs/ra8_fonts/generated.c",
315 "libs/third_party/vendor.c",
319def expect(passed: bool, label: str, failures: list[str]) ->
None:
320 """Record and print one selftest expectation."""
321 print(f
" [{'ok' if passed else 'FAIL'}] {label}")
323 failures.append(label)
326def capture_census(root: Path, minimum: int = 1) -> tuple[int, list[str], str]:
327 """Drive the production census entry point and decode successful output."""
328 output = io.BytesIO()
329 errors = io.StringIO()
330 status_code = run_census(root, minimum, output, errors, nul_terminated=
True)
331 paths = decode_manifest(output.getvalue())
if status_code == 0
else []
332 return status_code, paths, errors.getvalue()
335def basic_fixture_selftest(root: Path, failures: list[str]) ->
None:
336 """Prove ordinary inclusion and ignored/generated/SOUP exclusion."""
337 status_code, paths, errors = capture_census(root)
339 "libs/ra8_core/src/tracked.c",
340 "tools/demo/src/candidate.cpp",
342 expect(status_code == 0
and not errors,
"valid Git census succeeds", failures)
343 expect(set(paths) == expected,
"ordinary tracked and candidate sources are exact", failures)
345 "tools/demo/build/generated.c" not in paths,
346 "ignored build output is excluded",
350 not ({
"libs/ra8_fonts/generated.c",
"libs/third_party/vendor.c"} & set(paths)),
351 "generated and SOUP sources are excluded",
356def symlink_fixture_selftest(root: Path, raw_root: Path, failures: list[str]) ->
None:
357 """Prove external and internal candidate symlinks fail closed."""
359 outside = raw_root /
"outside.c"
360 outside.write_text(
"int outside(void) { return 0; }\n", encoding=
"ascii")
361 external_link = root /
"libs/ra8_core/src/untracked_external.c"
362 external_link.symlink_to(outside)
363 status_code, _, errors = capture_census(root)
365 status_code == failure_status
and "symlink" in errors,
366 "candidate external symlink fails",
369 external_link.unlink()
371 internal_link = root /
"tools/demo/src/untracked_internal.c"
372 internal_link.symlink_to(root /
"libs/ra8_core/src/tracked.c")
373 status_code, _, errors = capture_census(root)
375 status_code == failure_status
and "symlink" in errors,
376 "candidate internal symlink fails",
379 internal_link.unlink()
381 special = root /
"tools/demo/src/untracked_special.c"
384 validate_sources(root, [
"tools/demo/src/untracked_special.c"], 1)
385 except CensusError
as error:
386 expect(
"regular file" in str(error),
"special source entry fails", failures)
388 expect(passed=
False, label=
"special source entry fails", failures=failures)
392def manifest_fixture_selftest(root: Path, failures: list[str]) ->
None:
393 """Prove transported duplicates, scope drift, and low counts fail closed."""
394 valid =
"libs/ra8_core/src/tracked.c"
395 duplicate = (valid +
"\0" + valid +
"\0").encode()
397 validate_sources(root, decode_manifest(duplicate), 1)
398 except CensusError
as error:
399 expect(
"duplicate" in str(error),
"duplicate manifest path fails", failures)
401 expect(passed=
False, label=
"duplicate manifest path fails", failures=failures)
403 unexpected = root /
"tests/src/unexpected.c"
404 unexpected.parent.mkdir(parents=
True, exist_ok=
True)
405 unexpected.write_text(
"int unexpected(void) { return 0; }\n", encoding=
"ascii")
407 validate_sources(root, [
"tests/src/unexpected.c"], 1)
408 except CensusError
as error:
409 expect(
"authenticated roots" in str(error),
"unexpected scope fails", failures)
411 expect(passed=
False, label=
"unexpected scope fails", failures=failures)
414 validate_sources(root, [valid], 2)
415 except CensusError
as error:
416 expect(
"floor is 2" in str(error),
"below-floor manifest fails", failures)
418 expect(passed=
False, label=
"below-floor manifest fails", failures=failures)
421def empty_and_missing_selftest(raw_root: Path, failures: list[str]) ->
None:
422 """Prove empty and missing Git censuses fail without emitting paths."""
424 empty = raw_root /
"empty"
426 fixture_git(empty,
"init",
"-q")
427 status_code, paths, errors = capture_census(empty)
429 status_code == failure_status
and not paths
and "0 unit(s)" in errors,
430 "zero-input census fails closed",
433 missing = raw_root /
"not-a-repo"
435 status_code, paths, errors = capture_census(missing)
437 status_code == failure_status
and not paths
and "git ls-files failed" in errors,
438 "missing Git census fails closed",
443def hostile_boundary_selftest(repo_root: Path, raw_root: Path, failures: list[str]) ->
None:
444 """Drive the exact isolated interpreter boundary under hostile startup state."""
445 expected = collect_sources(repo_root, MIN_SOURCE_UNITS)
446 empty_index = raw_root /
"hostile-index"
447 fixture_git(repo_root,
"read-tree",
"--empty", extra_env={
"GIT_INDEX_FILE": str(empty_index)})
448 excludes = raw_root /
"exclude-all"
449 excludes.write_text(
"*\n", encoding=
"ascii")
450 config = raw_root /
"hostile-git-config"
451 config.write_text(f
"[core]\n\texcludesFile = {excludes}\n", encoding=
"ascii")
452 python_path = raw_root /
"hostile-python"
454 marker = raw_root /
"sitecustomize-fired"
455 (python_path /
"sitecustomize.py").write_text(
456 f
"from pathlib import Path\nPath({str(marker)!r}).write_text('fired', encoding='ascii')\n",
459 environment = os.environ.copy()
462 "GIT_CONFIG": str(config),
463 "GIT_INDEX_FILE": str(empty_index),
464 "GIT_WORK_TREE": str(raw_root),
465 "PYTHONHOME": str(raw_root /
"fake-home"),
466 "PYTHONPATH": str(python_path),
469 process = subprocess.run(
470 [trusted_python_executable(),
"-I",
"-S", str(Path(__file__).resolve()),
"--null"],
476 actual = decode_manifest(process.stdout)
if process.returncode == 0
else []
478 process.returncode == 0
and actual == expected
and not marker.exists(),
479 "hostile Git and Python startup state cannot replace the manifest",
484def run_selftest() -> int:
485 """Prove scope, transport, filesystem, and process-boundary behavior."""
486 failures: list[str] = []
487 with tempfile.TemporaryDirectory(prefix=
"cppcheck-sources-selftest-")
as raw:
489 fixture = raw_root /
"repo"
491 write_fixture(fixture)
492 initialize_fixture(fixture)
493 basic_fixture_selftest(fixture, failures)
494 symlink_fixture_selftest(fixture, raw_root, failures)
495 manifest_fixture_selftest(fixture, failures)
496 empty_and_missing_selftest(raw_root, failures)
497 repo_root = Path(__file__).resolve().parents[2]
498 hostile_boundary_selftest(repo_root, raw_root, failures)
500 print(f
"cppcheck_sources.py --selftest: {len(failures)} failure(s)", file=sys.stderr)
502 print(
"cppcheck_sources.py --selftest: all cases pass (both directions).")
506def parse_args(argv: list[str]) -> argparse.Namespace:
507 """Parse command-line options."""
508 parser = argparse.ArgumentParser(description=__doc__)
509 modes = parser.add_mutually_exclusive_group()
510 modes.add_argument(
"--selftest", action=
"store_true", help=
"run boundary selftests")
511 modes.add_argument(
"--validate-manifest", type=Path, help=
"validate a NUL manifest")
512 parser.add_argument(
"--null", action=
"store_true", help=
"NUL-terminate output paths")
513 return parser.parse_args(argv)
516def main(argv: list[str]) -> int:
517 """Run selftests, validate transport, or emit the repository manifest."""
518 args = parse_args(argv)
520 return run_selftest()
521 repo_root = Path(__file__).resolve().parents[2]
522 if args.validate_manifest
is not None:
523 return run_manifest_validation(
525 args.validate_manifest,
539if __name__ ==
"__main__":
540 sys.exit(
main(sys.argv[1:]))
void main(void)
The application entry point Reset_Handler hands control to.