mirror of
https://github.com/SCST-project/scst.git
synced 2026-10-22 14:12:33 +00:00
Run the native checkers through the ordered SCST module build with C=2. Preserve Kbuild flags, full raw output and every attempted CHECK record. Keep full, diff and explicit input selection on the shared scope; diff selection triggers the complete configured module analysis. Read exact diagnostic exceptions from policy/sparse.json and smatch.json: two reviewed Smatch IRQ reports and the external FC flexible-array warning. Keep the existing SCST matching boundaries and reasons. Smatch uses its matching installed kernel data without engine-specific additions. Reject build, checker, frontend and truncation failures even when Kbuild returns success. Retain per-invocation records and a machine-readable run summary, and detect printed CHECK steps without a record. Require selected C inputs to occur in the build. Keep findings distinct from incompleteness. Check native compatibility before compilation, and forward external kernel capability probes to the real checker instead of skipping them as inputs outside SCST. Include the pinned Smatch typeof frontend correction and isolated fixtures for failure propagation and exact exception boundaries.
76 lines
3.3 KiB
Python
76 lines
3.3 KiB
Python
"""Shared, tracked C source scope for SCST analyzers."""
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from analyzer_policy import source_policy
|
|
|
|
|
|
def in_scope(path: Path, root: Path, tool="common") -> bool:
|
|
policy = source_policy(tool)
|
|
try:
|
|
# Neither an alias nor its destination may widen the source scope.
|
|
names = (Path(os.path.abspath(path)).relative_to(Path(os.path.abspath(root))).as_posix(),
|
|
path.resolve().relative_to(root.resolve()).as_posix())
|
|
except ValueError:
|
|
return False
|
|
return all(name.startswith(tuple(directory + "/" for directory in policy["source_roots"]))
|
|
and Path(name).suffix in policy["source_suffixes"]
|
|
and not any(name.endswith(rule["suffix"]) for rule in policy["exclude_suffixes"])
|
|
and not any(name.startswith(rule["path"] + "/")
|
|
for rule in policy["exclude_directories"])
|
|
for name in names)
|
|
|
|
|
|
def glob_literal(value: str) -> str:
|
|
"""Quote literal policy paths for Git's glob pathspecs."""
|
|
return "".join({"[": "[[]", "*": "[*]", "?": "[?]"}.get(char, char) for char in value)
|
|
|
|
|
|
def git_pathspecs(tool="common") -> list[str]:
|
|
policy = source_policy(tool)
|
|
return [*(f":(top,glob){glob_literal(directory)}/**/*{suffix}"
|
|
for directory in policy["source_roots"] for suffix in policy["source_suffixes"]),
|
|
*(f":(top,exclude,glob)**/*{rule['suffix']}" for rule in policy["exclude_suffixes"]),
|
|
*(f":(top,exclude,glob){glob_literal(rule['path'])}/**"
|
|
for rule in policy["exclude_directories"])]
|
|
|
|
|
|
def git_names(root: Path, *arguments: str) -> list[str]:
|
|
output = subprocess.check_output(["git", "-C", str(root), *arguments])
|
|
return [os.fsdecode(name) for name in output.split(b"\0") if name]
|
|
|
|
|
|
def tracked_sources(root: Path, tool="common") -> list[Path]:
|
|
roots = [f":(top,literal){directory}" for directory in source_policy(tool)["source_roots"]]
|
|
names = git_names(root, "ls-files", "--cached", "-z", "--", *roots)
|
|
return sorted({root / name for name in names
|
|
if in_scope(root / name, root, tool) and (root / name).is_file()})
|
|
|
|
|
|
def resolve_base(root: Path, base: str) -> str:
|
|
"""Require one commit before passing a user-supplied base to Git diff."""
|
|
return subprocess.check_output(
|
|
["git", "-C", str(root), "rev-parse", "--verify", "--end-of-options",
|
|
f"{base}^{{commit}}"], text=True).strip()
|
|
|
|
|
|
def changed_sources(root: Path, base: str, tool="common") -> list[Path]:
|
|
revision = resolve_base(root, base)
|
|
names = git_names(root, "diff", "--no-renames", "--name-only", "-z",
|
|
"--diff-filter=d", revision, "--", *git_pathspecs(tool))
|
|
return sorted({root / name for name in names
|
|
if in_scope(root / name, root, tool) and (root / name).is_file()})
|
|
|
|
|
|
def select_sources(root: Path, files: list[Path], base: str | None, tool="common") -> list[Path]:
|
|
if files and base:
|
|
raise ValueError("select files or a Git base, not both")
|
|
if files:
|
|
for path in files:
|
|
if not path.is_file() or not in_scope(path, root, tool):
|
|
raise ValueError(f"not a project C source/header: {path}")
|
|
return sorted({path.resolve() for path in files})
|
|
return changed_sources(root, base, tool) if base else tracked_sources(root, tool)
|