fix: sanitizer sweep across all generators; bound signed shifts/division/overflow at coordinate limits (L61); R31
New verify.py sanitize tier (Clang address,undefined,float-cast-overflow; GCC address,undefined; -fno-sanitize-recover) runs 19 cases per compiler at LOD0-8 across every generator, passages and the crossing: 0 runtime reports; a planted signed-overflow control is caught by both. A static scan fixed INT_MIN negation in floor division, unbounded signed LOD shifts and coordinate-times-extent overflow in VoxelClipmapDesiredTiles.h and VoxelTypes.h (plugin and standalone), and VoxelWorld.cpp, VoxelContentManager.cpp and VoxelDensityVolume.cpp (plugin only, uncompiled here). Supported-input output unchanged (version 211). fast/medium/full PASS. Research R31 (self-checked): far-LOD 4-8 cut plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
c0c204ba50
commit
135957831b
13 files changed
+760
-132
No files matched your search
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the reusable VoxelForge verification tiers: fast, medium, or full."""
|
||||
"""Run the reusable VoxelForge verification tiers: fast, medium, full, or sanitize."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
@@ -33,7 +33,7 @@ OWNER = ROOT / "Standalone/data/owner_world.json"
|
||||
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
|
||||
|
||||
PARSER = argparse.ArgumentParser(description=__doc__)
|
||||
PARSER.add_argument("tier", choices=("fast", "medium", "full"))
|
||||
PARSER.add_argument("tier", choices=("fast", "medium", "full", "sanitize"))
|
||||
PARSER.add_argument("--out", type=Path, default=OUT_DEFAULT,
|
||||
help="artifact directory (default: Saved/Linux/l41)")
|
||||
PARSER.add_argument("--baseline", default="HEAD",
|
||||
@@ -42,7 +42,11 @@ PARSER.add_argument("--overall-timeout", type=int, default=7200,
|
||||
help="maximum tier wall time in seconds (default: 7200; each child is capped at 1800)")
|
||||
PARSER.add_argument("--speed-matrix", action="store_true",
|
||||
help="run five interleaved full-view timings for every generator")
|
||||
PARSER.add_argument("--sanitize", action="store_true",
|
||||
help="in the full tier, also run the standalone short sanitizer sweep")
|
||||
ARGS = PARSER.parse_args()
|
||||
if ARGS.sanitize and ARGS.tier != "full":
|
||||
PARSER.error("--sanitize is available only with the full tier; use the sanitize tier on its own")
|
||||
OUT = ARGS.out.resolve()
|
||||
ROUND_NAME = OUT.name
|
||||
os.environ["VOXELFORGE_VERIFY_OUT"] = str(OUT)
|
||||
@@ -2548,6 +2552,253 @@ def run_gcc_supplement(runner: VerificationRunner) -> None:
|
||||
run_motif_determinism(runner, GCC, compare_reference=True)
|
||||
|
||||
|
||||
SANITIZER_STAGE_SECONDS = 1200
|
||||
# Clang's integer group diagnoses the defined unsigned left shift inside libstdc++ std::rotl.
|
||||
# Keep the Clang sweep on UBSan/ASan plus float-cast checks; GCC still runs the requested integer core.
|
||||
CLANG_SANITIZERS = "address,undefined,float-cast-overflow"
|
||||
|
||||
|
||||
def _sanitize_camera_flag(camera: tuple[float, float, float, float, float]) -> str:
|
||||
return "--camera=" + ",".join(
|
||||
[*(f"{value:.6f}" for value in camera[:3]),
|
||||
*(f"{value:.3f}" for value in camera[3:])])
|
||||
|
||||
|
||||
def _sanitize_build(runner: VerificationRunner, compiler: str, variant: str,
|
||||
sanitizer_flags: str, *, exclude_unsigned: bool,
|
||||
deadline: float) -> Path:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUT / "tmp").mkdir(parents=True, exist_ok=True)
|
||||
build_root = OUT / "sanitizer-build" / variant
|
||||
binary_dir = OUT / "bin"
|
||||
env = os.environ.copy()
|
||||
env.update({
|
||||
"TMPDIR": str(OUT / "tmp"),
|
||||
"VOXELFORGE_VERIFY_OUT": str(OUT),
|
||||
"VOXELFORGE_BUILD_ROOT": str(build_root),
|
||||
"VOXELFORGE_BIN_DIR": str(binary_dir),
|
||||
"VOXELFORGE_LIMITED_BUILD": "1",
|
||||
})
|
||||
command = ["bash", str(ROOT / "Standalone/BuildLinux.sh"),
|
||||
f"--compiler={compiler}", f"--variant={variant}",
|
||||
"--fp-contract=off", "--jobs=2", f"--sanitize={sanitizer_flags}"]
|
||||
if exclude_unsigned:
|
||||
command.append("--exclude-unsigned-integer-overflow")
|
||||
log = OUT / f"sanitizer-build-{variant}.log"
|
||||
remaining = max(1, int(deadline - time.monotonic()))
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
proc = bounded_run(command, timeout=min(600, remaining), env=env, stdout=stream)
|
||||
ok = proc is not None and proc.returncode == 0
|
||||
runner.require(f"sanitizer-build-{variant}", ok,
|
||||
f"compiler={compiler} flags={sanitizer_flags} jobs=2 fp-contract=off "
|
||||
f"limit={os.environ.get('VOXELFORGE_LIMIT_MODE', 'as')} log={log}")
|
||||
binary = binary_dir / f"VoxelForgeCoreHeadless-{variant}"
|
||||
runner.probe_binary(binary)
|
||||
return binary
|
||||
|
||||
|
||||
def _sanitizer_control(runner: VerificationRunner, compiler: str,
|
||||
binary_tag: str, sanitizer_flags: str,
|
||||
*, exclude_unsigned: bool, deadline: float) -> None:
|
||||
control_dir = OUT / "sanitizer"
|
||||
control_dir.mkdir(parents=True, exist_ok=True)
|
||||
source = control_dir / "signed-overflow-control.cpp"
|
||||
source.write_text(
|
||||
"#include <climits>\n"
|
||||
"int main() { volatile int maximum = INT_MAX; volatile int one = 1; "
|
||||
"volatile int result = maximum + one; return result == 0; }\n",
|
||||
encoding="utf-8")
|
||||
executable = control_dir / f"signed-overflow-control-{binary_tag}"
|
||||
compile_command = [compiler, "-std=c++20", "-O1", "-fno-fast-math",
|
||||
"-ffp-contract=off", f"-fsanitize={sanitizer_flags}",
|
||||
"-fno-sanitize-recover=undefined", str(source), "-o", str(executable)]
|
||||
if exclude_unsigned:
|
||||
compile_command.insert(-2, "-fno-sanitize=unsigned-integer-overflow")
|
||||
compile_log = control_dir / f"signed-overflow-control-{binary_tag}-build.log"
|
||||
compile_remaining = max(1, int(deadline - time.monotonic()))
|
||||
with compile_log.open("w", encoding="utf-8") as stream:
|
||||
built = bounded_run(compile_command, timeout=min(60, compile_remaining), stdout=stream)
|
||||
runner.require(f"sanitizer-control-build-{binary_tag}",
|
||||
built is not None and built.returncode == 0,
|
||||
f"test-only INT_MAX+1 compiled with {sanitizer_flags}")
|
||||
run_log = control_dir / f"signed-overflow-control-{binary_tag}.log"
|
||||
run_remaining = max(1, int(deadline - time.monotonic()))
|
||||
with run_log.open("w", encoding="utf-8") as stream:
|
||||
caught = bounded_run([str(executable)], timeout=min(30, run_remaining), stdout=stream)
|
||||
output = run_log.read_text(encoding="utf-8", errors="replace")
|
||||
runner.require(f"sanitizer-control-caught-{binary_tag}",
|
||||
caught is not None and caught.returncode != 0
|
||||
and "signed integer overflow" in output,
|
||||
f"signed overflow exited {caught.returncode if caught else 'TIMEOUT'}; log={run_log}")
|
||||
|
||||
|
||||
def run_sanitizer_stage(runner: VerificationRunner) -> None:
|
||||
"""Run small random-camera LOD 0..8 sweeps through both instrumented builds."""
|
||||
previous = {key: os.environ.get(key) for key in (
|
||||
"VOXELFORGE_LIMIT_MODE", "ASAN_OPTIONS", "LSAN_OPTIONS", "UBSAN_OPTIONS")}
|
||||
os.environ["VOXELFORGE_LIMIT_MODE"] = "rss"
|
||||
os.environ["ASAN_OPTIONS"] = "detect_leaks=0:abort_on_error=1:symbolize=1"
|
||||
os.environ["LSAN_OPTIONS"] = "detect_leaks=0"
|
||||
os.environ["UBSAN_OPTIONS"] = "print_stacktrace=1:halt_on_error=1"
|
||||
deadline = time.monotonic() + SANITIZER_STAGE_SECONDS
|
||||
failures: list[str] = []
|
||||
try:
|
||||
clang_binary = _sanitize_build(runner, "clang++-19",
|
||||
f"{ROUND_NAME}-clang-sanitize",
|
||||
CLANG_SANITIZERS,
|
||||
exclude_unsigned=True, deadline=deadline)
|
||||
gcc_binary = _sanitize_build(runner, "g++",
|
||||
f"{ROUND_NAME}-gcc-sanitize", "address,undefined",
|
||||
exclude_unsigned=False, deadline=deadline)
|
||||
_sanitizer_control(runner, "clang++-19", "clang",
|
||||
CLANG_SANITIZERS,
|
||||
exclude_unsigned=True, deadline=deadline)
|
||||
_sanitizer_control(runner, "g++", "gcc", "address,undefined",
|
||||
exclude_unsigned=False, deadline=deadline)
|
||||
|
||||
profile_paths = {profile: OUT / "fixtures" / f"style-override-profile-{profile}.json"
|
||||
for profile in PROFILE_NAMES}
|
||||
if not all(path.is_file() for path in profile_paths.values()):
|
||||
runner.run_stage("sanitizer-build-profile-fixtures", LINUX53 / "BuildProfileFixturesL53.py",
|
||||
["--out", str(OUT / "fixtures")], binary=CLANG, data=OWNER,
|
||||
recipe="surface-world", style=None, env=env_for(),
|
||||
script_inputs=[OWNER])
|
||||
for profile, fixture in profile_paths.items():
|
||||
manifest = OUT / "fixtures" / f"style-override-profile-{profile}-manifest.json"
|
||||
if not manifest.is_file():
|
||||
name = f"sanitizer-profile-camera-manifest-{profile}"
|
||||
runner.run_cpp(name, CLANG, fixture, "surface-world",
|
||||
[f"--world-assembler-manifest-output={manifest}",
|
||||
"--render-distance-chunks=0"], expected_style=None,
|
||||
expected_strate_styles={"0": 2, "1": 1}, workers=1,
|
||||
max_level=-1, clip_radius=0,
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
caves_fixture = OUT / "fixtures" / "caves.json"
|
||||
if not caves_fixture.is_file():
|
||||
caves_fixture = build_caves_fixture(runner, CLANG)
|
||||
lattice_fixture = OUT / "fixtures" / "lattice-l58.json"
|
||||
if not lattice_fixture.is_file():
|
||||
lattice_fixture = build_lattice_fixture(runner, CLANG)
|
||||
motif_fixture = OUT / "fixtures" / "style-override-motifs-slice1.json"
|
||||
if not motif_fixture.is_file():
|
||||
motif_fixture = build_motif_fixture(runner)
|
||||
|
||||
cave_layout = OUT / "layouts" / "native-caves-field-clang.json"
|
||||
lattice_layout = OUT / "layouts" / "native-lattice-field-clang.json"
|
||||
motif_layout = OUT / "layouts" / "motif-slice1-capsule-reachability-clang.json"
|
||||
if not cave_layout.is_file():
|
||||
_, cave_layout = run_native_caves_field_audit(runner, CLANG)
|
||||
if not lattice_layout.is_file():
|
||||
_, lattice_layout = run_native_lattice_field_audit(runner, CLANG)
|
||||
if not motif_layout.is_file():
|
||||
_, motif_layout = run_motif_fixture_audits(runner, CLANG)
|
||||
|
||||
owner_cameras = seeded_camera_positions(5)
|
||||
cases: list[tuple[str, Path, str, list[str], int | None,
|
||||
dict[str, int] | None, list[tuple[float, float, float, float, float]]]] = [
|
||||
("natural-sw", OWNER, "surface-world", [], 2, {"0": 2, "1": 1},
|
||||
[owner_cameras[0], owner_cameras[3]]),
|
||||
("floating-islands", OWNER, "floating-islands", [], 2, {"0": 2, "1": 1},
|
||||
[owner_cameras[2], owner_cameras[4]]),
|
||||
]
|
||||
for profile in PROFILE_NAMES:
|
||||
cases.append((f"profile-{profile}", profile_paths[profile], "surface-world",
|
||||
[], 2, {"0": 2, "1": 1}, profile_camera_positions(profile, count=2)))
|
||||
cases.extend([
|
||||
("caves", caves_fixture, "caves", [], 2, {"0": 2, "1": 1},
|
||||
cave_camera_positions(cave_layout, count=2)),
|
||||
("motifs", motif_fixture, "surface-world", [], None, {"0": 2, "1": 1},
|
||||
[row[1] for row in motif_camera_rows(motif_layout) if row[0].startswith("random")]),
|
||||
("lattice", lattice_fixture, "lattice", ["--lattice-strate=1"], None,
|
||||
{"0": 2, "1": 1, "2": 1}, lattice_camera_positions(lattice_layout, count=2)),
|
||||
])
|
||||
|
||||
binaries = (("clang", clang_binary), ("gcc", gcc_binary))
|
||||
for compiler_tag, binary in binaries:
|
||||
for generator, data, recipe, base_flags, style, strate_styles, cameras in cases:
|
||||
for camera_index, camera in enumerate(cameras):
|
||||
if time.monotonic() >= deadline:
|
||||
failures.append("sanitizer stage time budget expired")
|
||||
break
|
||||
name = (f"sanitize-{compiler_tag}-{generator}-camera{camera_index}-"
|
||||
f"lod0-8")
|
||||
flags = [*base_flags, "--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
_sanitize_camera_flag(camera)]
|
||||
try:
|
||||
_, output = runner.run_cpp(name, binary, data, recipe, flags,
|
||||
expected_style=style, expected_strate_styles=strate_styles,
|
||||
workers=1, max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
lod_rows = full_lod_stream_rows(output)
|
||||
lod_ok = (set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9)))
|
||||
runner.require(f"{name}:lod0-8-coverage", lod_ok,
|
||||
"positive tile sample at each of LOD0 through LOD8")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{name}: {error}")
|
||||
|
||||
# Exercise passage resolution and a live 24 m camera crossing under both runtimes.
|
||||
for recipe, flags, style in (
|
||||
("floating-islands", ["--audit-resolved-layout-passages"], 2),
|
||||
("surface-world", ["--landmark-chamber-demo", "--audit-resolved-layout-passages"], 2),
|
||||
):
|
||||
name = f"sanitize-{compiler_tag}-passages-{recipe}-lod0-8"
|
||||
camera = owner_cameras[0 if recipe == "surface-world" else 2]
|
||||
try:
|
||||
_, output = runner.run_cpp(name, binary, OWNER, recipe,
|
||||
[*flags, "--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
_sanitize_camera_flag(camera)], expected_style=style,
|
||||
expected_strate_styles={"0": 2, "1": 1}, workers=1,
|
||||
max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
lod_rows = full_lod_stream_rows(output)
|
||||
runner.require(f"{name}:lod0-8-coverage",
|
||||
set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9)),
|
||||
"passage path streamed at each of LOD0 through LOD8")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{name}: {error}")
|
||||
|
||||
crossing_name = f"sanitize-{compiler_tag}-surface-crossing-lod0-8"
|
||||
try:
|
||||
_, crossing_output = runner.run_cpp(crossing_name, binary, OWNER,
|
||||
"surface-world", ["--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
"--camera=-12,-118,-20,0,0", "--camera-step=24,0,0", "--region-crossing"],
|
||||
expected_style=2, expected_strate_styles={"0": 2, "1": 1},
|
||||
workers=1, max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{crossing_name}.json")
|
||||
lod_rows = full_lod_stream_rows(crossing_output)
|
||||
moving = re.search(r"^\[MovingTileLatency\] entering_tiles=(\d+)",
|
||||
crossing_output, re.M)
|
||||
crossing_ok = (set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9))
|
||||
and moving is not None and int(moving.group(1)) > 0)
|
||||
runner.require(f"{crossing_name}:crossing-and-lod0-8", crossing_ok,
|
||||
f"entering_tiles={moving.group(1) if moving else 'MISSING'}")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{crossing_name}: {error}")
|
||||
|
||||
edit_fixtures = [path for base in (ROOT / "Standalone/data", OUT / "fixtures")
|
||||
if base.is_dir() for path in base.rglob("*.json")
|
||||
if "edit" in path.name.lower()]
|
||||
runner.require("sanitizer-edit-fixture", True,
|
||||
(f"found {len(edit_fixtures)} edit fixture(s): "
|
||||
+ ", ".join(str(path) for path in edit_fixtures)) if edit_fixtures
|
||||
else "no edit fixture exists under Standalone/data or this run's fixtures")
|
||||
runner.require("sanitizer-stage-findings", not failures,
|
||||
"; ".join(failures) if failures else
|
||||
"all Clang/GCC ASan+UBSan generator, passage, crossing, and random-camera cases passed")
|
||||
finally:
|
||||
for key, value in previous.items():
|
||||
if value is None:
|
||||
os.environ.pop(key, None)
|
||||
else:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
def _tier_deadline(_signum: int, _frame: object) -> None:
|
||||
raise VerificationFailure(
|
||||
f"{ARGS.tier} tier exceeded its {ARGS.overall_timeout}s overall timeout")
|
||||
@@ -2566,7 +2817,9 @@ def main() -> int:
|
||||
for binary in (CLANG, GCC, RENDERER):
|
||||
if binary.is_file():
|
||||
runner.probe_binary(binary)
|
||||
if ARGS.tier == "fast":
|
||||
if ARGS.tier == "sanitize":
|
||||
run_sanitizer_stage(runner)
|
||||
elif ARGS.tier == "fast":
|
||||
owners = run_owner_passages(runner, (CLANG,))
|
||||
run_landmark_reachability(runner, (CLANG,))
|
||||
run_regression_controls(runner)
|
||||
@@ -2594,6 +2847,9 @@ def main() -> int:
|
||||
if ARGS.speed_matrix:
|
||||
run_speed_matrix(runner)
|
||||
|
||||
if ARGS.sanitize:
|
||||
run_sanitizer_stage(runner)
|
||||
|
||||
runner.assert_binaries_current()
|
||||
runner.assert_current_tree("at tier completion")
|
||||
elapsed = time.monotonic() - started
|
||||
|
||||
Reference in new issue
Block a user