fix(diagnostics): bound formatting and release ingress before callbacks
Bound UTF-8 formatting and release diagnostic ingress before invoking callbacks. Check error-ID collisions at Rust/C++ release final link. Validation: diagnostic tests, strict Clippy, Miri and final-link cases.
This commit is contained in:
1 parent
3226ef97fe
commit
84807dde4e
12 files changed
+547
-48
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# Cross-crate error-ID final-link checks
|
||||
|
||||
From the repository root, run `nix develop -c make -C firmware native-test`.
|
||||
For a separate artifact directory:
|
||||
|
||||
```sh
|
||||
nix develop -c env CARGO_BUILD_JOBS=2 python3 firmware/libs/kaptan-err-reportable-macros/tests/final_link.py \
|
||||
--work-dir /tmp/kaptan-final-link
|
||||
```
|
||||
|
||||
The fixture builds two crates using the production `KaptanError` derive as
|
||||
rlibs and staticlibs. Rust and C++ executables cover unique and duplicate IDs,
|
||||
used and unused error values, and an unused second crate. Successful executables
|
||||
must run. Expected duplicate failures must name `__kaptan_error_id_65401`.
|
||||
|
||||
The helper seeds its fixture lock from `firmware/Cargo.lock`, verifies registry
|
||||
versions, sources and checksums, then builds with Rust 1.90.0 and `--locked
|
||||
--offline`. It retains source, manifests, locks, binaries, linker output and
|
||||
`results.txt` with the Rust compiler identity and all twelve outcomes. Native linker flags
|
||||
are Linux-specific.
|
||||
|
||||
The exported `#[used]` marker detects collisions when both defining objects
|
||||
participate in the link. It cannot force extraction of an unreferenced staticlib
|
||||
member. The native case that calls only the first crate can therefore link despite
|
||||
the duplicate ID. Rust's release-LTO fixture explicitly names the second crate
|
||||
and rejects that collision. These cases do not establish catalog-wide uniqueness
|
||||
across every supplied archive or every embedded linker/LTO combination. Stable
|
||||
error-ID allocation still requires review.
|
||||
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise the actual derive across crates at the host release-LTO final link.
|
||||
|
||||
Run with Python 3.11+. Build output is retained under the supplied directory.
|
||||
This is a link experiment, not a trybuild or cargo-check substitute.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tomllib
|
||||
|
||||
|
||||
def run(arguments, cwd):
|
||||
return subprocess.run(arguments, cwd=cwd, text=True, capture_output=True)
|
||||
|
||||
|
||||
def require_success(result):
|
||||
if result.returncode:
|
||||
raise RuntimeError(result.stdout + result.stderr)
|
||||
|
||||
|
||||
def registry_packages(lock):
|
||||
return {
|
||||
(package["source"], package["name"], package["version"], package["checksum"])
|
||||
for package in tomllib.loads(lock.decode())["package"]
|
||||
if package.get("source", "").startswith("registry+")
|
||||
}
|
||||
|
||||
|
||||
def verify_registry_packages(firmware_lock, fixture_lock):
|
||||
packages = registry_packages(fixture_lock)
|
||||
drift = packages - registry_packages(firmware_lock)
|
||||
if drift:
|
||||
raise RuntimeError(
|
||||
f"fixture registry dependencies differ from firmware lock: {sorted(drift)!r}"
|
||||
)
|
||||
return sorted(packages)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--work-dir", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
work = args.work_dir.resolve()
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
runtime = Path(__file__).resolve().parents[2] / "kaptan-err-reportable"
|
||||
version = run(["rustc", "+1.90.0", "-vV"], work)
|
||||
require_success(version)
|
||||
host = next(
|
||||
line.removeprefix("host: ")
|
||||
for line in version.stdout.splitlines()
|
||||
if line.startswith("host: ")
|
||||
)
|
||||
print(version.stdout, end="")
|
||||
for name in ["a", "b", "app"]:
|
||||
(work / name / "src").mkdir(parents=True, exist_ok=True)
|
||||
(work / "Cargo.toml").write_text("""
|
||||
[workspace]
|
||||
members = ["a", "b", "app"]
|
||||
resolver = "2"
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
panic = "abort"
|
||||
strip = "symbols"
|
||||
""")
|
||||
for name in ["a", "b"]:
|
||||
(work / name / "Cargo.toml").write_text(f"""
|
||||
[package]
|
||||
name = "error-id-{name}"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
[lib]
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
[features]
|
||||
duplicate = []
|
||||
unused = []
|
||||
[dependencies]
|
||||
kaptan-err-reportable = {{ path = {json.dumps(str(runtime))} }}
|
||||
""")
|
||||
unique = 65401 if name == "a" else 65402
|
||||
(work / name / "src/lib.rs").write_text(f"""
|
||||
#[derive(Debug, kaptan_err_reportable::KaptanError)]
|
||||
pub enum Error {{
|
||||
#[cfg_attr(feature = "duplicate", kaptan_error(id = 65401))]
|
||||
#[cfg_attr(not(feature = "duplicate"), kaptan_error(id = {unique}))]
|
||||
Fault,
|
||||
}}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn fixture_{name}() -> u16 {{
|
||||
#[cfg(not(feature = "unused"))]
|
||||
{{ kaptan_err_reportable::ReportableError::error_id(&Error::Fault).get() }}
|
||||
#[cfg(feature = "unused")]
|
||||
{{ {unique} }}
|
||||
}}
|
||||
""")
|
||||
(work / "app/Cargo.toml").write_text("""
|
||||
[package]
|
||||
name = "error-id-app"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
[dependencies]
|
||||
error-id-a = { path = "../a" }
|
||||
error-id-b = { path = "../b" }
|
||||
""")
|
||||
(work / "app/src/main.rs").write_text("fn main() {}\n")
|
||||
firmware_lock = (runtime.parents[1] / "Cargo.lock").read_bytes()
|
||||
(work / "firmware.Cargo.lock").write_bytes(firmware_lock)
|
||||
fixture_lock_path = work / "Cargo.lock"
|
||||
fixture_lock_path.write_bytes(firmware_lock)
|
||||
# Resolve the fixture's local workspace entries while retaining locked
|
||||
# registry versions. generate-lockfile would instead select newer versions.
|
||||
require_success(
|
||||
run(
|
||||
[
|
||||
"cargo",
|
||||
"+1.90.0",
|
||||
"metadata",
|
||||
"--offline",
|
||||
"--all-features",
|
||||
"--format-version",
|
||||
"1",
|
||||
],
|
||||
work,
|
||||
)
|
||||
)
|
||||
fixture_lock = fixture_lock_path.read_bytes()
|
||||
packages = verify_registry_packages(firmware_lock, fixture_lock)
|
||||
provenance = [
|
||||
f"firmware Cargo.lock SHA-256: {hashlib.sha256(firmware_lock).hexdigest()}",
|
||||
f"fixture Cargo.lock SHA-256: {hashlib.sha256(fixture_lock).hexdigest()}",
|
||||
"Fixture registry dependencies verified as an exact subset of the firmware lock:",
|
||||
*[
|
||||
f" {name} {version} source={source} checksum={checksum}"
|
||||
for source, name, version, checksum in packages
|
||||
],
|
||||
]
|
||||
print("\n".join(provenance))
|
||||
results = []
|
||||
for mode in ["used", "unused", "unextracted"]:
|
||||
use_b = mode != "unextracted"
|
||||
rust_call = (
|
||||
"assert!(std::hint::black_box(error_id_b::fixture_b()) >= 65401);"
|
||||
if use_b
|
||||
else ""
|
||||
)
|
||||
native_call = "|| fixture_b() < 65401" if use_b else ""
|
||||
(work / "app/src/main.rs").write_text(f"""
|
||||
extern crate error_id_b;
|
||||
fn main() {{
|
||||
assert_eq!(std::hint::black_box(error_id_a::fixture_a()), 65401);
|
||||
{rust_call}
|
||||
}}
|
||||
""")
|
||||
(work / "main.cpp").write_text(f"""
|
||||
#include <cstdint>
|
||||
extern "C" uint16_t fixture_a();
|
||||
extern "C" uint16_t fixture_b();
|
||||
int main() {{ return fixture_a() != 65401 {native_call}; }}
|
||||
""")
|
||||
for duplicate in [False, True]:
|
||||
label = f"{mode}-{'duplicate' if duplicate else 'unique'}"
|
||||
features = ["error-id-b/duplicate"] if duplicate else []
|
||||
if mode != "used":
|
||||
features += ["error-id-a/unused", "error-id-b/unused"]
|
||||
target = work / "target" / label
|
||||
cargo = [
|
||||
"cargo",
|
||||
"+1.90.0",
|
||||
"build",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"--release",
|
||||
"--target",
|
||||
host,
|
||||
"--target-dir",
|
||||
str(target),
|
||||
]
|
||||
if features:
|
||||
cargo += ["--features", ",".join(features)]
|
||||
# Each archive must build independently before linking them together.
|
||||
archives = run(cargo + ["-p", "error-id-a", "-p", "error-id-b"], work)
|
||||
require_success(archives)
|
||||
release = target / host / "release"
|
||||
native = run(
|
||||
[
|
||||
"c++",
|
||||
str(work / "main.cpp"),
|
||||
str(release / "liberror_id_a.a"),
|
||||
str(release / "liberror_id_b.a"),
|
||||
"-ldl",
|
||||
"-lpthread",
|
||||
"-lm",
|
||||
"-o",
|
||||
str(release / "native-app"),
|
||||
],
|
||||
work,
|
||||
)
|
||||
rust = run(cargo + ["-p", "error-id-app"], work)
|
||||
for kind, result, binary in [
|
||||
("staticlib", native, release / "native-app"),
|
||||
("rlib", rust, release / "error-id-app"),
|
||||
]:
|
||||
(work / f"{label}-{kind}.log").write_text(result.stdout + result.stderr)
|
||||
if not duplicate:
|
||||
require_success(result)
|
||||
elif (use_b or kind == "rlib") and result.returncode == 0:
|
||||
raise RuntimeError(f"{label} {kind}: duplicate unexpectedly linked")
|
||||
if result.returncode == 0:
|
||||
require_success(run([str(binary)], work))
|
||||
outcome = "linked and ran"
|
||||
else:
|
||||
if "__kaptan_error_id_65401" not in result.stderr:
|
||||
raise RuntimeError(result.stderr)
|
||||
outcome = "duplicate ID rejected"
|
||||
results.append(f"{label} {kind}: {outcome}")
|
||||
print("\n".join(results))
|
||||
(work / "results.txt").write_text(
|
||||
version.stdout + "\n".join(provenance + results) + "\n"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user