lazycodex-ai 5.0.0-beta.85 → 5.0.0-beta.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.js +68 -41
- package/dist/cli-node/index.js +68 -41
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -0
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +63 -89
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +63 -89
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +2 -3
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +8 -2
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/ATTRIBUTION.md +26 -14
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +65 -52
- package/packages/omo-codex/plugin/skills/browser/references/commands.md +81 -66
- package/packages/omo-codex/plugin/skills/browser/references/install.md +31 -34
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/README.md +41 -21
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/ladder.md +24 -22
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/network.md +35 -15
- package/packages/omo-codex/plugin/skills/browser/references/recipes/1password.md +13 -11
- package/packages/omo-codex/plugin/skills/browser/references/remote.md +5 -4
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +1534 -0
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +9 -0
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/page-bundle.js +1395 -0
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +37 -32
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +31 -44
- package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +25 -0
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/06-fix.md +3 -3
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/08-qa.md +1 -1
- package/packages/omo-codex/plugin/skills/debugging/references/tools/browser-qa.md +104 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -18
- package/packages/omo-codex/plugin/skills/programming/references/rust/README.md +43 -15
- package/packages/omo-codex/plugin/skills/programming/references/rust/api-design.md +81 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/async-tokio.md +60 -28
- package/packages/omo-codex/plugin/skills/programming/references/rust/axum-stack.md +1 -13
- package/packages/omo-codex/plugin/skills/programming/references/rust/cargo-strict.md +44 -8
- package/packages/omo-codex/plugin/skills/programming/references/rust/clap-stack.md +8 -3
- package/packages/omo-codex/plugin/skills/programming/references/rust/concurrency.md +66 -52
- package/packages/omo-codex/plugin/skills/programming/references/rust/libraries.md +35 -25
- package/packages/omo-codex/plugin/skills/programming/references/rust/macros.md +63 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/one-liners.md +5 -3
- package/packages/omo-codex/plugin/skills/programming/references/rust/proptest-insta.md +8 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/type-state.md +50 -12
- package/packages/omo-codex/plugin/skills/programming/references/rust/unsafe-discipline.md +34 -6
- package/packages/omo-codex/plugin/skills/programming/references/rust/zero-cost-safety.md +62 -52
- package/packages/omo-codex/plugin/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/new-project.py +31 -28
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +27 -20
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +5 -11
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +63 -89
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +4 -4
- package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +2 -3
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +46 -46
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +4 -2
- package/packages/prompts-core/prompts/ultrawork/codex.md +63 -89
- package/packages/shared-skills/skills/browser/ATTRIBUTION.md +26 -14
- package/packages/shared-skills/skills/browser/SKILL.md +65 -52
- package/packages/shared-skills/skills/browser/references/commands.md +81 -66
- package/packages/shared-skills/skills/browser/references/install.md +31 -34
- package/packages/shared-skills/skills/browser/references/owned-engine/README.md +41 -21
- package/packages/shared-skills/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
- package/packages/shared-skills/skills/browser/references/owned-engine/ladder.md +24 -22
- package/packages/shared-skills/skills/browser/references/owned-engine/network.md +35 -15
- package/packages/shared-skills/skills/browser/references/recipes/1password.md +13 -11
- package/packages/shared-skills/skills/browser/references/remote.md +5 -4
- package/packages/shared-skills/skills/browser/runtime/omowright/index.js +1534 -0
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +9 -0
- package/packages/shared-skills/skills/browser/runtime/omowright/page-bundle.js +1395 -0
- package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +37 -32
- package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +31 -44
- package/packages/shared-skills/skills/browser/scripts/omowright.mjs +25 -0
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -2
- package/packages/shared-skills/skills/debugging/references/methodology/06-fix.md +3 -3
- package/packages/shared-skills/skills/debugging/references/methodology/08-qa.md +1 -1
- package/packages/shared-skills/skills/debugging/references/tools/browser-qa.md +104 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/shared-skills/skills/programming/SKILL.md +12 -18
- package/packages/shared-skills/skills/programming/references/rust/README.md +43 -15
- package/packages/shared-skills/skills/programming/references/rust/api-design.md +81 -0
- package/packages/shared-skills/skills/programming/references/rust/async-tokio.md +60 -28
- package/packages/shared-skills/skills/programming/references/rust/axum-stack.md +1 -13
- package/packages/shared-skills/skills/programming/references/rust/cargo-strict.md +44 -8
- package/packages/shared-skills/skills/programming/references/rust/clap-stack.md +8 -3
- package/packages/shared-skills/skills/programming/references/rust/concurrency.md +66 -52
- package/packages/shared-skills/skills/programming/references/rust/libraries.md +35 -25
- package/packages/shared-skills/skills/programming/references/rust/macros.md +63 -0
- package/packages/shared-skills/skills/programming/references/rust/one-liners.md +5 -3
- package/packages/shared-skills/skills/programming/references/rust/proptest-insta.md +8 -0
- package/packages/shared-skills/skills/programming/references/rust/type-state.md +50 -12
- package/packages/shared-skills/skills/programming/references/rust/unsafe-discipline.md +34 -6
- package/packages/shared-skills/skills/programming/references/rust/zero-cost-safety.md +62 -52
- package/packages/shared-skills/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
- package/packages/shared-skills/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.test.ts +83 -0
- package/packages/shared-skills/skills/programming/scripts/rust/new-project.py +31 -28
- package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +27 -20
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +5 -11
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +4 -4
- package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
- package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +46 -46
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-env.mjs +0 -41
- package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +0 -112
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
- package/packages/shared-skills/skills/browser/scripts/browser-env.mjs +0 -41
- package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +0 -112
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
|
@@ -146,7 +146,7 @@ impl Arena {
|
|
|
146
146
|
// bump. `aligned` is aligned to `layout.align()` by `align_up`. No
|
|
147
147
|
// other writer can have observed this offset because the CAS above
|
|
148
148
|
// returned `Ok`.
|
|
149
|
-
let ptr = unsafe { self.map.as_mut_ptr().add(aligned)
|
|
149
|
+
let ptr = unsafe { self.map.as_mut_ptr().add(aligned) }.cast::<T>();
|
|
150
150
|
// SAFETY: `ptr` is non-null (mmap base + offset), aligned (above),
|
|
151
151
|
// exclusively owned (CAS), and we are about to initialize it.
|
|
152
152
|
unsafe { ptr::write(ptr, value) };
|
|
@@ -187,7 +187,35 @@ What miri catches that the borrow checker cannot:
|
|
|
187
187
|
- Stacked borrows / Tree borrows aliasing violations
|
|
188
188
|
- Data races (single-threaded model, but catches concurrent access through `UnsafeCell` misuse)
|
|
189
189
|
- Atomic ordering bugs in some patterns
|
|
190
|
-
- Memory leaks (
|
|
190
|
+
- Memory leaks (reported by default at exit; `-Zmiri-ignore-leaks` turns it off)
|
|
191
|
+
|
|
192
|
+
## FFI Declarations (Rust 2024)
|
|
193
|
+
|
|
194
|
+
The 2024 edition makes the unsafety of FFI items visible at the declaration:
|
|
195
|
+
|
|
196
|
+
```rust
|
|
197
|
+
use core::ffi::{c_char, c_int};
|
|
198
|
+
|
|
199
|
+
// SAFETY: these signatures match <string.h>/<stdlib.h> for every target we build.
|
|
200
|
+
unsafe extern "C" {
|
|
201
|
+
/// No preconditions: callable from safe code.
|
|
202
|
+
pub safe fn abs(x: c_int) -> c_int;
|
|
203
|
+
/// Caller must pass a valid NUL-terminated string.
|
|
204
|
+
pub unsafe fn strlen(s: *const c_char) -> usize;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Exported symbol: the attribute itself is unsafe because a name clash is UB.
|
|
208
|
+
#[unsafe(no_mangle)]
|
|
209
|
+
pub extern "C" fn mylib_version() -> u32 { 3 }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- A bare `extern "C" { ... }` block is a hard error in the 2024 edition; write `unsafe extern` and mark each item `safe fn` (no caller obligations) or `unsafe fn`. Marking an item `safe` is a promise you make for every caller; when in doubt, it is `unsafe`.
|
|
213
|
+
- `#[no_mangle]`, `#[export_name]`, and `#[link_section]` are written `#[unsafe(...)]`.
|
|
214
|
+
- A panic that unwinds out of an `extern "C"` function aborts the process (1.81+). Catch it with `std::panic::catch_unwind` at the boundary and return an error code; use `extern "C-unwind"` only when the foreign side is built to unwind.
|
|
215
|
+
|
|
216
|
+
## Uninitialized Memory
|
|
217
|
+
|
|
218
|
+
`MaybeUninit<T>` is the only way to hold memory that is not yet a valid `T`. `mem::uninitialized()` is always wrong, and `mem::zeroed()` is UB for any type whose all-zero bit pattern is invalid (references, `NonZero*`, `bool`-bearing enums, most `repr(Rust)` structs). An uninitialized array is `[const { MaybeUninit::<T>::uninit() }; N]`; write every element through `MaybeUninit::write`, then convert once, with a SAFETY comment stating that all `N` elements were written.
|
|
191
219
|
|
|
192
220
|
## When Miri Cannot Run
|
|
193
221
|
|
|
@@ -218,8 +246,8 @@ mod tests {
|
|
|
218
246
|
fn concurrent_push_pop() {
|
|
219
247
|
loom::model(|| {
|
|
220
248
|
let queue = std::sync::Arc::new(MyQueue::new());
|
|
221
|
-
let q1 =
|
|
222
|
-
let q2 =
|
|
249
|
+
let q1 = std::sync::Arc::clone(&queue);
|
|
250
|
+
let q2 = std::sync::Arc::clone(&queue);
|
|
223
251
|
let h1 = loom::thread::spawn(move || q1.push(1));
|
|
224
252
|
let h2 = loom::thread::spawn(move || q2.pop());
|
|
225
253
|
h1.join().unwrap();
|
|
@@ -229,7 +257,7 @@ mod tests {
|
|
|
229
257
|
}
|
|
230
258
|
```
|
|
231
259
|
|
|
232
|
-
Run: `RUSTFLAGS="--cfg loom" cargo test --release`. Loom
|
|
260
|
+
Run: `RUSTFLAGS="--cfg loom" cargo test --release`. Loom systematically explores the interleavings its bounded model allows for the test scope; combined with miri on the single-thread paths, that is strong machine-checked evidence, not a proof over every execution.
|
|
233
261
|
|
|
234
262
|
## The Forbidden List
|
|
235
263
|
|
|
@@ -241,7 +269,7 @@ Reject in code review, automatic CI fail:
|
|
|
241
269
|
- `std::mem::transmute` for anything but lifetime extension on the same layout (and that should usually be `core::mem::transmute_copy` or `bytemuck::cast` if the relayout is well-defined).
|
|
242
270
|
- `std::ptr::read_unaligned` / `write_unaligned` without a comment explaining why aligned access is impossible.
|
|
243
271
|
- `from_raw_parts` / `from_raw_parts_mut` without proving the source pointer's provenance covers the entire slice.
|
|
244
|
-
- `Arc::get_mut_unchecked`, `Box::leak` to bypass ownership, `MaybeUninit::assume_init` on partially-initialized data.
|
|
272
|
+
- `Arc::get_mut_unchecked`, `Box::leak` to bypass ownership, `MaybeUninit::assume_init` on partially-initialized data, `mem::uninitialized`, `mem::zeroed` for a type whose all-zero pattern is not valid.
|
|
245
273
|
- `unsafe impl Send`, `unsafe impl Sync` on types containing raw pointers, without a comment naming exactly which interior-mutability rule is upheld.
|
|
246
274
|
- Any `unsafe` block whose justification depends on "in practice this never happens".
|
|
247
275
|
|
|
@@ -100,18 +100,20 @@ const fn fibonacci(n: usize) -> usize {
|
|
|
100
100
|
const FIB_20: usize = fibonacci(20); // computed at compile time: 6765
|
|
101
101
|
|
|
102
102
|
// Use in array sizes
|
|
103
|
+
#[expect(clippy::indexing_slicing, reason = "const evaluation: an out-of-bounds index is a compile error")]
|
|
103
104
|
const LOOKUP: [u8; 256] = {
|
|
104
105
|
let mut table = [0u8; 256];
|
|
105
|
-
let mut i = 0;
|
|
106
|
-
|
|
107
|
-
table[i] =
|
|
106
|
+
let mut i: u8 = 0;
|
|
107
|
+
loop {
|
|
108
|
+
table[i as usize] = i.wrapping_mul(7); // u8 -> usize widens: lossless
|
|
109
|
+
if i == u8::MAX { break; }
|
|
108
110
|
i += 1;
|
|
109
111
|
}
|
|
110
112
|
table
|
|
111
113
|
};
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
`const fn` bodies may use `if`, `match`, `loop`/`while`, and mutable locals; trait methods (`From`, `TryFrom`, `Iterator`) are not callable in `const` on stable. That makes a **lossless widening `as` inside `const`** the one accepted numeric cast: iterate in the narrow type and widen to index, as above, so no value can truncate. Use `const { }` blocks (Rust 1.79+) for inline compile-time assertions.
|
|
115
117
|
|
|
116
118
|
```rust
|
|
117
119
|
fn process<const N: usize>(data: &[u8; N]) {
|
|
@@ -123,6 +125,7 @@ fn process<const N: usize>(data: &[u8; N]) {
|
|
|
123
125
|
### const generics — Type-Level Values
|
|
124
126
|
|
|
125
127
|
```rust
|
|
128
|
+
#[derive(Debug)]
|
|
126
129
|
struct Buffer<const N: usize> {
|
|
127
130
|
data: [u8; N],
|
|
128
131
|
len: usize,
|
|
@@ -134,13 +137,17 @@ impl<const N: usize> Buffer<N> {
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
fn push(&mut self, byte: u8) -> Result<(), BufferFullError> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self.len
|
|
140
|
+
let slot = self.data.get_mut(self.len).ok_or(BufferFullError)?;
|
|
141
|
+
*slot = byte;
|
|
142
|
+
self.len = self.len.saturating_add(1); // < N after the get_mut above
|
|
140
143
|
Ok(())
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
|
|
147
|
+
impl<const N: usize> Default for Buffer<N> {
|
|
148
|
+
fn default() -> Self { Self::new() }
|
|
149
|
+
}
|
|
150
|
+
|
|
144
151
|
// Compiler enforces: Buffer<16> and Buffer<32> are distinct types.
|
|
145
152
|
let small: Buffer<16> = Buffer::new();
|
|
146
153
|
let large: Buffer<1024> = Buffer::new();
|
|
@@ -148,26 +155,7 @@ let large: Buffer<1024> = Buffer::new();
|
|
|
148
155
|
|
|
149
156
|
### proc macros — Code Generation (Zig comptime type creation)
|
|
150
157
|
|
|
151
|
-
When `const fn` is not enough (generating struct fields, impl blocks, or derive logic), proc macros fill the gap.
|
|
152
|
-
|
|
153
|
-
```rust
|
|
154
|
-
// In a proc-macro crate:
|
|
155
|
-
use proc_macro::TokenStream;
|
|
156
|
-
use quote::quote;
|
|
157
|
-
use syn::{parse_macro_input, DeriveInput};
|
|
158
|
-
|
|
159
|
-
#[proc_macro_derive(Builder)]
|
|
160
|
-
pub fn derive_builder(input: TokenStream) -> TokenStream {
|
|
161
|
-
let input = parse_macro_input!(input as DeriveInput);
|
|
162
|
-
let name = &input.ident;
|
|
163
|
-
// ... generate builder struct and impl
|
|
164
|
-
TokenStream::from(quote! {
|
|
165
|
-
impl #name {
|
|
166
|
-
pub fn builder() -> #name##Builder { /* ... */ }
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
```
|
|
158
|
+
When `const fn` is not enough (generating struct fields, impl blocks, or derive logic), proc macros fill the gap. Crate layout, `syn`/`quote`, identifier construction, and spanned errors → [macros.md](macros.md).
|
|
171
159
|
|
|
172
160
|
**Decision tree:**
|
|
173
161
|
|
|
@@ -195,15 +183,14 @@ fn process(input: &str) -> String {
|
|
|
195
183
|
|
|
196
184
|
// GOOD: caller provides output buffer, zero allocation
|
|
197
185
|
fn process(input: &[u8], output: &mut [u8]) -> usize {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
output[i] = input[i].to_ascii_uppercase();
|
|
186
|
+
for (dst, src) in output.iter_mut().zip(input) {
|
|
187
|
+
*dst = src.to_ascii_uppercase();
|
|
201
188
|
}
|
|
202
|
-
len //
|
|
189
|
+
input.len().min(output.len()) // bytes written; zip stopped at the shorter slice
|
|
203
190
|
}
|
|
204
191
|
|
|
205
192
|
// GOOD: return borrowed data when possible
|
|
206
|
-
fn find_token
|
|
193
|
+
fn find_token(input: &str) -> Option<&str> {
|
|
207
194
|
input.split_whitespace().next() // no allocation — borrows from input
|
|
208
195
|
}
|
|
209
196
|
```
|
|
@@ -231,9 +218,28 @@ tags.push(1); // on stack if <= 8 elements
|
|
|
231
218
|
|
|
232
219
|
// ArrayVec: purely stack, fixed capacity, no heap ever
|
|
233
220
|
let mut buf: ArrayVec<u8, 64> = ArrayVec::new();
|
|
234
|
-
buf.try_push(42).map_err(|_|
|
|
221
|
+
buf.try_push(42).map_err(|_| BufferFullError)?; // typed error instead of panic
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Move Out of `&mut` Without Cloning
|
|
225
|
+
|
|
226
|
+
```rust
|
|
227
|
+
// Take the value, leave Default behind: no clone, no Option dance.
|
|
228
|
+
let pending = std::mem::take(&mut self.pending); // Vec/String/HashMap
|
|
229
|
+
let old = std::mem::replace(&mut self.state, State::Idle);
|
|
230
|
+
let conn = self.conn.take(); // Option<T> field
|
|
235
231
|
```
|
|
236
232
|
|
|
233
|
+
A `.clone()` whose only purpose is to satisfy the borrow checker behind `&mut self` is almost always one of these three.
|
|
234
|
+
|
|
235
|
+
### Reuse and Pre-Size
|
|
236
|
+
|
|
237
|
+
- Reserve from a known size: `Vec::with_capacity(n)` / `String::with_capacity(n)`, or `reserve(n)` before a batch `extend`.
|
|
238
|
+
- In a loop, hoist a scratch buffer and `clear()` it each iteration instead of allocating a fresh one; `clear` keeps the capacity. Drop or `shrink_to` it when one outlier would pin a huge allocation.
|
|
239
|
+
- Format into an existing buffer with `write!(buf, ...)` (`use std::fmt::Write as _`) instead of building intermediate `format!` strings, and pass `&str` literals where no formatting happens.
|
|
240
|
+
|
|
241
|
+
Apply these on paths a profile or benchmark shows to be hot; elsewhere the clearer allocation wins.
|
|
242
|
+
|
|
237
243
|
### Cow — Defer Allocation Until Mutation
|
|
238
244
|
|
|
239
245
|
```rust
|
|
@@ -264,15 +270,7 @@ Even in `std` code, the *mindset* applies: prefer `&[T]` over `Vec<T>` in functi
|
|
|
264
270
|
|
|
265
271
|
### Clippy Lints for Hidden Allocations
|
|
266
272
|
|
|
267
|
-
|
|
268
|
-
# Cargo.toml — catch accidental allocations
|
|
269
|
-
[lints.clippy]
|
|
270
|
-
# These warn on patterns that allocate when a borrow would suffice:
|
|
271
|
-
unnecessary_to_owned = "warn" # .to_string() / .to_vec() when borrow works
|
|
272
|
-
redundant_clone = "warn" # .clone() that's immediately consumed
|
|
273
|
-
large_stack_arrays = "warn" # accidental large stack usage
|
|
274
|
-
vec_init_then_push = "warn" # Vec::new() + push instead of vec![]
|
|
275
|
-
```
|
|
273
|
+
The groups in [cargo-strict.md](cargo-strict.md) already enable the allocation lints: `unnecessary_to_owned` and `vec_init_then_push` (in `all`, denied), `redundant_clone` (nursery), `large_stack_arrays` (pedantic). Do not re-list them in `[lints.clippy]`: a per-lint `"warn"` entry overrides the group and silently downgrades a deny.
|
|
276
274
|
|
|
277
275
|
---
|
|
278
276
|
|
|
@@ -408,22 +406,24 @@ Zig's `errdefer` runs cleanup only on error paths. Rust's `Drop` always runs, bu
|
|
|
408
406
|
### scopeguard — The errdefer Equivalent
|
|
409
407
|
|
|
410
408
|
```rust
|
|
411
|
-
use scopeguard::{
|
|
409
|
+
use scopeguard::{guard, ScopeGuard};
|
|
412
410
|
use std::fs;
|
|
413
411
|
|
|
414
412
|
fn create_and_process(path: &str) -> std::io::Result<()> {
|
|
415
413
|
let file = fs::File::create(path)?;
|
|
416
414
|
// If anything below fails, clean up the file.
|
|
417
415
|
// This is exactly Zig's errdefer.
|
|
418
|
-
let
|
|
419
|
-
let
|
|
416
|
+
let cleanup = guard((), |()| {
|
|
417
|
+
if let Err(error) = fs::remove_file(path) {
|
|
418
|
+
tracing::warn!(%error, path, "cleanup after failed write");
|
|
419
|
+
}
|
|
420
420
|
});
|
|
421
421
|
|
|
422
422
|
write_data(&file)?;
|
|
423
423
|
validate_data(path)?;
|
|
424
424
|
|
|
425
425
|
// Success: defuse the guard so it does NOT run cleanup.
|
|
426
|
-
|
|
426
|
+
ScopeGuard::into_inner(cleanup);
|
|
427
427
|
Ok(())
|
|
428
428
|
}
|
|
429
429
|
```
|
|
@@ -437,7 +437,7 @@ fn with_temp_dir() -> anyhow::Result<()> {
|
|
|
437
437
|
let dir = tempfile::tempdir()?;
|
|
438
438
|
defer! {
|
|
439
439
|
// Runs when scope exits, success or failure.
|
|
440
|
-
|
|
440
|
+
tracing::debug!(path = %dir.path().display(), "leaving temp dir scope");
|
|
441
441
|
// dir's Drop also cleans up, but this shows the pattern.
|
|
442
442
|
}
|
|
443
443
|
|
|
@@ -449,6 +449,7 @@ fn with_temp_dir() -> anyhow::Result<()> {
|
|
|
449
449
|
### Drop as RAII Cleanup
|
|
450
450
|
|
|
451
451
|
```rust
|
|
452
|
+
#[derive(Debug)]
|
|
452
453
|
struct TempFile { path: std::path::PathBuf }
|
|
453
454
|
|
|
454
455
|
impl TempFile {
|
|
@@ -461,7 +462,10 @@ impl TempFile {
|
|
|
461
462
|
|
|
462
463
|
impl Drop for TempFile {
|
|
463
464
|
fn drop(&mut self) {
|
|
464
|
-
|
|
465
|
+
// Drop cannot return an error; report it instead of discarding it.
|
|
466
|
+
if let Err(error) = std::fs::remove_file(&self.path) {
|
|
467
|
+
tracing::warn!(%error, path = %self.path.display(), "temp file not removed");
|
|
468
|
+
}
|
|
465
469
|
}
|
|
466
470
|
}
|
|
467
471
|
|
|
@@ -469,19 +473,25 @@ impl Drop for TempFile {
|
|
|
469
473
|
let tmp = TempFile::new("/tmp/scratch.dat")?;
|
|
470
474
|
```
|
|
471
475
|
|
|
476
|
+
### Drop Order Is Part of the Contract
|
|
477
|
+
|
|
478
|
+
Locals drop in reverse declaration order; struct fields drop in declaration order (first field first). When one resource must outlive another (a transaction before its connection, a worker handle before the channel it drains), encode it: declare the dependent field first, or call `drop(x)` at the exact point the dependency ends. Never rely on an order the code does not state.
|
|
479
|
+
|
|
472
480
|
### The errdefer Pattern — Defuse on Success
|
|
473
481
|
|
|
474
482
|
The key insight from Zig's `errdefer`: you want cleanup on error but NOT on success. In Rust:
|
|
475
483
|
|
|
476
484
|
```rust
|
|
477
|
-
use scopeguard::ScopeGuard;
|
|
485
|
+
use scopeguard::{guard, ScopeGuard};
|
|
478
486
|
|
|
479
487
|
fn deploy(artifact: &Path) -> Result<(), DeployError> {
|
|
480
488
|
let backup = backup_current()?;
|
|
481
489
|
|
|
482
490
|
// errdefer: restore backup if anything fails
|
|
483
|
-
let rollback = guard(backup
|
|
484
|
-
let
|
|
491
|
+
let rollback = guard(backup, |b| {
|
|
492
|
+
if let Err(error) = restore_from_backup(&b) {
|
|
493
|
+
tracing::error!(%error, "rollback failed; manual restore needed");
|
|
494
|
+
}
|
|
485
495
|
});
|
|
486
496
|
|
|
487
497
|
upload(artifact)?;
|
|
@@ -522,6 +532,6 @@ All achievable within Rust's single toolchain. You get Zig's explicitness **plus
|
|
|
522
532
|
- **Zero-alloc APIs** hurt readability when the function naturally produces owned data. Don't force `&mut [u8]` output buffers on a function that logically returns `String`.
|
|
523
533
|
- **`#[repr(packed)]`** only for wire formats and FFI. Never for regular domain types.
|
|
524
534
|
- **Scope guards** unnecessary when `Drop` on the value itself handles cleanup (e.g., `tempfile::NamedTempFile` already does this).
|
|
525
|
-
- **`const fn`** everything? No — only when
|
|
535
|
+
- **`const fn`** everything? No — only when a `const` context needs the function or it is trivially const-eligible. Don't contort logic just to be const.
|
|
526
536
|
|
|
527
537
|
The goal is **visible costs and explicit control**, not asceticism. Use `String` and `Vec` freely when they're the right tool. Reach for these patterns when allocation behavior matters for correctness or performance.
|
package/packages/shared-skills/skills/programming/references/rust-ub/miri-sanitizers-loom.md
CHANGED
|
@@ -66,7 +66,7 @@ cargo +nightly miri test
|
|
|
66
66
|
MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
**Protocol:** Run Stacked Borrows first. If it fails, fix it. Then run Tree Borrows
|
|
69
|
+
**Protocol:** Run Stacked Borrows first. If it fails, fix it. Then run Tree Borrows as a separate pass. The models are not nested: Stacked Borrows is stricter about most reborrow patterns, but Tree Borrows rejects some code Stacked Borrows accepts. Ship only when both are clean.
|
|
70
70
|
|
|
71
71
|
#### Strict Provenance
|
|
72
72
|
|
|
@@ -140,7 +140,7 @@ MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
|
|
|
140
140
|
- Passing a Rust `enum` to C without `#[repr(C)]` or `#[repr(i32)]`.
|
|
141
141
|
- Null pointer passed where C expects non-null (and Rust wraps it in `&T`).
|
|
142
142
|
- C code writing to Rust-owned memory through a pointer Rust considers immutable.
|
|
143
|
-
-
|
|
143
|
+
- Declaring a foreign function `safe fn` inside `unsafe extern` when it has caller preconditions (null, length, lifetime), so safe code can violate them.
|
|
144
144
|
- longjmp/setjmp across Rust frames (unwinding UB).
|
|
145
145
|
|
|
146
146
|
**Miri detection:** LIMITED — Miri cannot execute foreign code. It detects UB in the Rust-side handling of FFI return values.
|
|
@@ -236,12 +236,15 @@ MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
|
|
|
236
236
|
**Root cause:** A Rust panic unwinding through a frame that uses the C calling convention.
|
|
237
237
|
|
|
238
238
|
**Canonical triggers:**
|
|
239
|
-
- `panic!()` inside a `#[no_mangle] extern "C" fn` callback passed to C code.
|
|
239
|
+
- `panic!()` inside a `#[unsafe(no_mangle)] extern "C" fn` callback passed to C code.
|
|
240
240
|
- `unwrap()` inside FFI callbacks.
|
|
241
|
+
- Foreign code unwinding (C++ exceptions, `longjmp`) through a Rust frame declared `extern "C"`.
|
|
242
|
+
|
|
243
|
+
Since Rust 1.81 a Rust panic that reaches an `extern "C"` boundary aborts the process instead of unwinding into foreign frames: no longer UB, but still a crash.
|
|
241
244
|
|
|
242
245
|
**Miri detection:** PARTIAL — Miri does not model foreign unwinding, but it can detect the immediate UB if the panic reaches the FFI boundary.
|
|
243
246
|
|
|
244
|
-
**Fix pattern:** Use `std::panic::catch_unwind` at every FFI entry point.
|
|
247
|
+
**Fix pattern:** Use `std::panic::catch_unwind` at every FFI entry point and return an error code. Declare the ABI `extern "C-unwind"` (stable since 1.71) only when both sides are built to let unwinding cross.
|
|
245
248
|
|
|
246
249
|
---
|
|
247
250
|
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# No-excuse rule checker for Rust files.
|
|
3
|
-
#
|
|
4
|
-
# only rules that can be enforced via pure text matching live here.
|
|
3
|
+
# Only rules that can be enforced via pure text matching live here.
|
|
5
4
|
# Everything semantic is on clippy + miri + nextest.
|
|
5
|
+
#
|
|
6
|
+
# Rules:
|
|
7
|
+
# unwrap / expect outside tests, unless the previous line carries
|
|
8
|
+
# #[expect(clippy::unwrap_used|expect_used, reason = "...")]
|
|
9
|
+
# placeholder-macro todo!/unimplemented!/unreachable! in committed code
|
|
10
|
+
# box-dyn-error Box<dyn Error> in non-test code
|
|
11
|
+
# lib-panic panic!() in library code
|
|
12
|
+
# discarded-result `let _ = call(...)`, unless the previous line carries
|
|
13
|
+
# #[expect(clippy::let_underscore_must_use, reason = "...")]
|
|
14
|
+
# blocking-in-async std::thread::sleep / std::fs / reqwest::blocking /
|
|
15
|
+
# .blocking_*() inside an async fn or async block
|
|
16
|
+
# unsafe-no-safety unsafe { without // SAFETY: in the preceding 5 lines
|
|
17
|
+
# allow-attribute #[allow(...)] - silence lints with #[expect(lint, reason)]
|
|
18
|
+
# expect-without-reason #[expect(...)] with no `reason = "..."`
|
|
19
|
+
# narrowing-as-cast possible narrowing numeric `as` cast
|
|
6
20
|
|
|
7
21
|
set -euo pipefail
|
|
8
22
|
|
|
@@ -13,23 +27,22 @@ fi
|
|
|
13
27
|
|
|
14
28
|
violations=0
|
|
15
29
|
report() {
|
|
16
|
-
|
|
17
|
-
local line="$2"
|
|
18
|
-
local rule="$3"
|
|
19
|
-
local detail="$4"
|
|
20
|
-
echo "::error file=${file},line=${line}::[${rule}] ${detail}" >&2
|
|
30
|
+
echo "::error file=$1,line=$2::[$3] $4" >&2
|
|
21
31
|
violations=$((violations + 1))
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
is_test_path() {
|
|
25
|
-
|
|
26
|
-
case "$path" in
|
|
35
|
+
case "$1" in
|
|
27
36
|
*/tests/*|*/benches/*|*/examples/*|*/build.rs|*_test.rs|tests/*|benches/*|examples/*) return 0 ;;
|
|
28
37
|
esac
|
|
29
|
-
# In-file #[cfg(test)] modules are handled per-line below.
|
|
30
38
|
return 1
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
count_char() {
|
|
42
|
+
local only="${1//[^$2]/}"
|
|
43
|
+
echo "${#only}"
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
for file in "$@"; do
|
|
34
47
|
[ -f "$file" ] || continue
|
|
35
48
|
case "$file" in
|
|
@@ -37,108 +50,106 @@ for file in "$@"; do
|
|
|
37
50
|
*) continue ;;
|
|
38
51
|
esac
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
in_test_file=0
|
|
54
|
+
is_test_path "$file" && in_test_file=1
|
|
55
|
+
|
|
56
|
+
lines=()
|
|
57
|
+
while IFS= read -r raw || [ -n "$raw" ]; do lines+=("$raw"); done < "$file"
|
|
58
|
+
total=${#lines[@]}
|
|
59
|
+
|
|
60
|
+
# Brace-tracked regions: a region starts at its marker line and ends when the
|
|
61
|
+
# braces opened after the marker are closed again.
|
|
62
|
+
in_cfg_test=0; cfg_depth=0; cfg_opened=0
|
|
63
|
+
in_async=0; async_depth=0; async_opened=0
|
|
64
|
+
|
|
65
|
+
for ((i = 0; i < total; i++)); do
|
|
66
|
+
line_no=$((i + 1))
|
|
67
|
+
line="${lines[$i]}"
|
|
68
|
+
prev=""
|
|
69
|
+
[ "$i" -gt 0 ] && prev="${lines[$((i - 1))]}"
|
|
70
|
+
code_only="${line%%//*}"
|
|
71
|
+
opens=$(count_char "$code_only" "{")
|
|
72
|
+
closes=$(count_char "$code_only" "}")
|
|
73
|
+
|
|
61
74
|
if [[ "$line" =~ \#\[cfg\(test\)\] ]]; then
|
|
62
|
-
in_cfg_test=1
|
|
63
|
-
cfg_test_brace_depth=0
|
|
75
|
+
in_cfg_test=1; cfg_depth=0; cfg_opened=0
|
|
64
76
|
fi
|
|
65
77
|
if [ "$in_cfg_test" -eq 1 ]; then
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
cfg_depth=$((cfg_depth + opens - closes))
|
|
79
|
+
[ "$opens" -gt 0 ] && cfg_opened=1
|
|
80
|
+
if [ "$cfg_opened" -eq 1 ] && [ "$cfg_depth" -le 0 ]; then in_cfg_test=0; fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ "$in_async" -eq 0 ] && [[ "$code_only" =~ (^|[^[:alnum:]_])async([[:space:]]+(move[[:space:]]*)?\{|[[:space:]]+(unsafe[[:space:]]+)?fn[[:space:]]) ]]; then
|
|
84
|
+
in_async=1; async_depth=0; async_opened=0
|
|
85
|
+
fi
|
|
86
|
+
async_line=$in_async
|
|
87
|
+
if [ "$in_async" -eq 1 ]; then
|
|
88
|
+
async_depth=$((async_depth + opens - closes))
|
|
89
|
+
[ "$opens" -gt 0 ] && async_opened=1
|
|
90
|
+
if [ "$async_opened" -eq 1 ] && [ "$async_depth" -le 0 ]; then in_async=0; fi
|
|
72
91
|
fi
|
|
73
92
|
|
|
74
93
|
exempt=0
|
|
75
94
|
[ "$in_test_file" -eq 1 ] && exempt=1
|
|
76
95
|
[ "$in_cfg_test" -eq 1 ] && exempt=1
|
|
77
96
|
|
|
78
|
-
# Strip line comments before pattern checks - so doc comments and
|
|
79
|
-
# explanatory prose do not trip the regexes.
|
|
80
|
-
code_only="${line%%//*}"
|
|
81
|
-
|
|
82
97
|
if [ "$exempt" -eq 0 ]; then
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# Allow if previous line had // SAFE-UNWRAP: comment
|
|
86
|
-
prev_line=$(sed -n "$((line_no - 1))p" "$file" 2>/dev/null || true)
|
|
87
|
-
if [[ ! "$prev_line" =~ //[[:space:]]*SAFE-UNWRAP: ]]; then
|
|
88
|
-
report "$file" "$line_no" "unwrap" ".unwrap() outside tests - use ? / ok_or / pattern match or annotate previous line with // SAFE-UNWRAP: <reason>"
|
|
89
|
-
fi
|
|
98
|
+
if [[ "$code_only" =~ \.unwrap\(\) ]] && [[ ! "$prev" =~ \#\[expect\(clippy::unwrap_used,.*reason ]]; then
|
|
99
|
+
report "$file" "$line_no" "unwrap" ".unwrap() outside tests - use ? / ok_or / let-else, or for a proven invariant use .expect() behind #[expect(clippy::expect_used, reason = \"...\")]"
|
|
90
100
|
fi
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
prev_line=$(sed -n "$((line_no - 1))p" "$file" 2>/dev/null || true)
|
|
95
|
-
if [[ ! "$prev_line" =~ //[[:space:]]*SAFE-EXPECT: ]]; then
|
|
96
|
-
report "$file" "$line_no" "expect" ".expect() outside tests - use ? or annotate previous line with // SAFE-EXPECT: <reason>"
|
|
97
|
-
fi
|
|
102
|
+
if [[ "$code_only" =~ \.expect\( ]] && [[ ! "$prev" =~ \#\[expect\(clippy::expect_used,.*reason ]]; then
|
|
103
|
+
report "$file" "$line_no" "expect" ".expect() outside tests - use ?, or for a proven invariant annotate the previous line with #[expect(clippy::expect_used, reason = \"...\")]"
|
|
98
104
|
fi
|
|
99
105
|
|
|
100
|
-
# todo!() / unimplemented!() / unreachable!()
|
|
101
106
|
if [[ "$code_only" =~ (todo!|unimplemented!|unreachable!|unreachable_unchecked!) ]]; then
|
|
102
107
|
report "$file" "$line_no" "placeholder-macro" "todo!/unimplemented!/unreachable! in committed code"
|
|
103
108
|
fi
|
|
104
109
|
|
|
105
|
-
# Box<dyn Error
|
|
106
110
|
if [[ "$code_only" =~ Box\<dyn[[:space:]]+Error ]]; then
|
|
107
111
|
report "$file" "$line_no" "box-dyn-error" "Box<dyn Error> in non-test code - use anyhow::Error (apps) or thiserror enum (libs)"
|
|
108
112
|
fi
|
|
109
113
|
|
|
110
|
-
# panic!( in lib
|
|
111
114
|
if [[ "$file" == */src/lib.rs || "$file" == */src/*/mod.rs || ( "$file" == */src/*.rs && "$file" != */src/main.rs && "$file" != */src/bin/* ) ]]; then
|
|
112
115
|
if [[ "$code_only" =~ panic!\( ]]; then
|
|
113
116
|
report "$file" "$line_no" "lib-panic" "panic!() in library code - return Result"
|
|
114
117
|
fi
|
|
115
118
|
fi
|
|
119
|
+
|
|
120
|
+
if [[ "$code_only" =~ let[[:space:]]+_[[:space:]]*=[[:space:]]*[^\;]*\( ]] && [[ ! "$prev" =~ \#\[expect\(clippy::let_underscore_must_use,.*reason ]]; then
|
|
121
|
+
report "$file" "$line_no" "discarded-result" "let _ = call() drops its result - propagate or handle the error; log it on a best-effort path"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ "$async_line" -eq 1 ] && [[ "$code_only" =~ (thread::sleep\(|std::fs::|reqwest::blocking::|\.blocking_(recv|send|lock|read|write)\() ]]; then
|
|
125
|
+
report "$file" "$line_no" "blocking-in-async" "blocking call inside async code - use tokio::time::sleep / tokio::fs / the async client, or move it into spawn_blocking"
|
|
126
|
+
fi
|
|
116
127
|
fi
|
|
117
128
|
|
|
118
|
-
# unsafe { without preceding // SAFETY: in the last 5 lines (always enforced)
|
|
119
129
|
if [[ "$code_only" =~ unsafe[[:space:]]*\{ ]]; then
|
|
120
|
-
start=$((
|
|
121
|
-
window=$(
|
|
130
|
+
start=$((i > 5 ? i - 5 : 0))
|
|
131
|
+
window=$(printf '%s\n' "${lines[@]:$start:$((i - start + 1))}")
|
|
122
132
|
if [[ ! "$window" =~ //[[:space:]]*SAFETY: ]]; then
|
|
123
133
|
report "$file" "$line_no" "unsafe-no-safety-comment" "unsafe block without // SAFETY: comment in preceding 5 lines"
|
|
124
134
|
fi
|
|
125
135
|
fi
|
|
126
136
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
if [[ "$code_only" =~ \#!?\[allow\( ]]; then
|
|
138
|
+
report "$file" "$line_no" "allow-attribute" "#[allow(...)] - use #[expect(lint, reason = \"...\")] so the exception fails once the lint stops firing"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
if [[ "$code_only" =~ \#!?\[expect\( ]]; then
|
|
142
|
+
attr=$(printf '%s\n' "${lines[@]:$i:4}")
|
|
143
|
+
if [[ ! "$attr" =~ reason[[:space:]]*= ]]; then
|
|
144
|
+
report "$file" "$line_no" "expect-without-reason" "#[expect(...)] needs reason = \"...\" naming why the lint does not apply"
|
|
132
145
|
fi
|
|
133
146
|
fi
|
|
134
147
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
if [[ "$code_only" =~ as[[:space:]]+(u8|u16|u32|i8|i16|i32) ]] && \
|
|
138
|
-
[[ "$code_only" =~ (u16|u32|u64|u128|usize|i16|i32|i64|i128|isize)[[:space:]]+as[[:space:]]+(u8|u16|u32|i8|i16|i32) ]]; then
|
|
148
|
+
# Heuristic flag for human review; exact analysis belongs to clippy::cast_possible_truncation.
|
|
149
|
+
if [[ "$code_only" =~ (u16|u32|u64|u128|usize|i16|i32|i64|i128|isize)[[:space:]]+as[[:space:]]+(u8|u16|u32|i8|i16|i32)([^[:alnum:]_]|$) ]]; then
|
|
139
150
|
report "$file" "$line_no" "narrowing-as-cast" "possible narrowing 'as' cast - use TryFrom / try_into() for fallible conversion"
|
|
140
151
|
fi
|
|
141
|
-
done
|
|
152
|
+
done
|
|
142
153
|
done
|
|
143
154
|
|
|
144
155
|
if [ "$violations" -gt 0 ]; then
|
|
@@ -146,9 +157,9 @@ if [ "$violations" -gt 0 ]; then
|
|
|
146
157
|
echo "rust-programmer: ${violations} violation(s). Fix before declaring work done." >&2
|
|
147
158
|
echo "" >&2
|
|
148
159
|
echo "Then run the full toolchain gate:" >&2
|
|
149
|
-
echo " cargo
|
|
150
|
-
echo " cargo
|
|
151
|
-
echo " cargo nextest run --all-targets --all-features" >&2
|
|
160
|
+
echo " cargo fmt --all -- --check" >&2
|
|
161
|
+
echo " cargo clippy --all-targets --all-features -- -D warnings" >&2
|
|
162
|
+
echo " cargo nextest run --all-targets --all-features && cargo test --doc" >&2
|
|
152
163
|
echo " cargo +nightly miri nextest run --all-features # if unsafe touched" >&2
|
|
153
164
|
echo " cargo machete" >&2
|
|
154
165
|
echo " cargo deny check" >&2
|