diff --git a/Cargo.toml b/Cargo.toml index c65155c62bd..751b41e5b0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -207,8 +207,7 @@ opt-level = 2 # `opt-level = 0`. Nothing fail-fast is traded for it — `opt-level` does not # touch `debug-assertions`, which cargo still passes as `on` (checked with # `cargo build -p toyos-fat32 -v`), and rustc derives `overflow-checks` from -# that. The pure crates' hostile-input tests keep both knobs that *found* the -# two crafted-ELF kernel panics in `issues/`. +# that. # # **One member depends on this line for correctness rather than for speed, and # its own manifest says so at length — this is the other half of that @@ -227,9 +226,8 @@ opt-level = 3 # The one profile every guest binary is built with. Optimised, because an # unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. # # It is declared here rather than in `toyos-ld/Cargo.toml` because cargo diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index 7e57aadd824..d2073464a7f 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -41,9 +41,8 @@ uefi-services = { version = "0.23.0", features = ["panic_handler", "logger"] } # The one profile every guest binary is built with. Optimised, because an # unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. [profile.toyos] inherits = "dev" diff --git a/issues/kernel/a-panic-is-never-an-accident.md b/issues/kernel/a-panic-is-never-an-accident.md new file mode 100644 index 00000000000..2276a72fb21 --- /dev/null +++ b/issues/kernel/a-panic-is-never-an-accident.md @@ -0,0 +1,68 @@ +--- +status: open +kind: track +opened: 2026-10-01 +--- + +# A panic is never an accident: the owner's rule, not yet enforced + +| Tier | Rule | +|---|---| +| 1. Input boundaries | No panic at all: the set below is forbidden. Where the build allows, each parser's entry point also carries a link-time no-panic proof. | +| 2. The kernel | No implicit panic: the set is denied. A deliberate stop for a broken internal invariant stays (fail fast), as an `#[expect(…, reason = "…")]` whose reason names the invariant. A type that makes the broken state unrepresentable is preferred over any stop. | +| 3. System services | The same rule as the kernel. | +| 4. Apps, ports, test code and build tooling | Normal Rust. | + +**The set**, every name checked against clippy 0.1.98 (`48a229ceae`): +- indexing and slicing: `clippy::indexing_slicing`, and `clippy::string_slice`, + because the first does not fire on slicing a `str`; +- arithmetic: `clippy::arithmetic_side_effects`. `[profile.toyos]` sets + `overflow-checks`, so arithmetic is a panicking form; +- `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic`, + `clippy::unreachable`, `clippy::todo`, `clippy::unimplemented`, + `clippy::panic_in_result_fn`; +- truncating casts: `clippy::cast_possible_truncation`. A truncating `as` does + not panic; the owner names it, and for tiers 1 to 3 that overrides its + rejection in `issues/build/clippy-stage-two-is-lints-one-at-a-time.md`; +- standard functions: `clippy::disallowed_methods` naming `slice::split_at` and + `slice::copy_from_slice`, and `clippy::disallowed_macros` naming + `core::assert`, `core::assert_eq` and `core::assert_ne`. + +**Today.** No crate carries the set: none names `string_slice`, +`panic_in_result_fn`, `todo`, `unimplemented` or `cast_possible_truncation`. +In `kernel/`, `cargo clippy --target x86_64-unknown-none` with the set as +warnings reports 2,274 findings, 992 of them `arithmetic_side_effects`, 408 +`indexing_slicing`, 394 `cast_possible_truncation` and 208 `disallowed_macros`. + +**Constraints.** +- The set does not see a shift by a variable amount, `pow` or `abs`, though + each panics under `overflow-checks`; nor a standard function it does not + name, nor a panic inside a callee. +- `clippy::allow_attributes` checks outer attributes alone: an inner + `#![allow(…, reason = "…")]` passes it and `allow_attributes_without_reason`. +- `disallowed_methods` and `disallowed_macros` read the nearest `clippy.toml` + alone, and the root's reaches every crate beneath it, tier 4 included. +- The no-panic proof needs unwinding: its README says "The attribute is useless + in code built with `panic = "abort"`". Both kernel targets print + `"panic-strategy": "abort"` from `rustc -Z unstable-options --print + target-spec-json`, so the proof has to come from a host build. +- `--clippy` lints no userland program, because the `toyos` toolchain ships no + clippy (`src/clippy.rs:7-8`). + +**Stages, in order.** +1. Tier 1, one area at a time. The first exit is one declaration of the set + that every tier-1 crate reads in place of its own copy. An area's exit is + its crate or kernel module forbidding the set under `cargo run -- --clippy`; + `forbid` refuses any inner `#[allow]` or `#[expect]` of it, the `#[allow]` + in `issues/design-debt/elf-domain-lint-line-not-yet-added.md`'s exit too. +2. The link-time proof on the parser entry points: works or not, measured. The + exit is a step of `cargo run -- --ci host` whose host build refuses to link + an entry point with a panicking path, or a `rejected` issue with the + measurement. +3. The kernel, one module at a time. The stage ends when `kernel/src/main.rs` + denies the set and forbids `clippy::allow_attributes` and + `clippy::allow_attributes_without_reason` under `--clippy`, and a step of + `--ci host` refuses an inner `#![allow]`: then every exception is an + `#[expect(…, reason = "…")]`, and a bare `#[allow]` fails the gate. +4. The system services. The first exit is a userland shape in `src/clippy.rs`; + a service's exit is its crate root under stage 3's attributes. diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 5beca3f086b..85c4228f971 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -372,9 +372,8 @@ user-writable-gsbase = [] # The one profile every guest binary is built with. # Optimised, because an unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. [profile.toyos] inherits = "dev" diff --git a/src/build.rs b/src/build.rs index f8932e7a13d..f2ae538a9ef 100644 --- a/src/build.rs +++ b/src/build.rs @@ -405,10 +405,7 @@ fn config_crates(root: &Path, config: &SystemConfig) -> Vec { /// it in. /// /// One name, passed to every `cargo build` here and declared by every crate -/// root the image is made of. `--release` used to be a flag on `cargo run`, and -/// it silently turned `debug-assertions` and `overflow-checks` off — the two -/// knobs `issues/`'s crafted-ELF panics were *found* by. There is -/// no longer a second profile to pick, which is why there is no longer a flag. +/// root the image is made of. pub const PROFILE: &str = "toyos"; /// What every guest `cargo` and `rustc` here runs with: the toolchain directory @@ -632,9 +629,7 @@ fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool { /// [`PROFILE`] states them and `--release` is gone from this build system, so /// the way they can still be lost is somebody editing `[profile.toyos]`. This /// asks the artifact rather than the manifest, which is the only question worth -/// asking: `issues/`'s two crafted-ELF kernel panics were both -/// *found* by an overflow check, and one of them had no configuration in which -/// it was an error return. +/// asking. fn assert_overflow_checked(what: &str, image: &[u8]) { let found = contains_subslice(image, OVERFLOW_CHECK_MARKER); assert!( diff --git a/userland/Cargo.toml b/userland/Cargo.toml index 55b8b482f91..70cbde36cfa 100644 --- a/userland/Cargo.toml +++ b/userland/Cargo.toml @@ -65,9 +65,8 @@ winit = { git = "https://github.com/ToyOSOrg/winit", branch = "toyos-0.30.13" } # The one profile every guest binary is built with. # Optimised, because an unoptimised guest mismeasures everything under TCG; -# debug-assertions and overflow-checks on, because fail-fast beats speed here -# and both crafted-ELF kernel panics in `issues/` were *found* by -# an overflow check. `--release` would turn the last two off silently, so this +# debug-assertions and overflow-checks on, because fail-fast beats speed here. +# `--release` would turn the last two off silently, so this # build system no longer has the flag. # # What it replaces here: dependencies at 2 through `package."*"`, doom and the