audit(rust-consts): catch literal-vs-const drift + cleanup BOOK_LEVELS=10
Audit script (audit-rust-consts.sh) scans Rust src/examples for numeric
literals mirroring structural kernel-side consts (N_ACTIONS, Q_N_ATOMS,
HIDDEN_DIM, MAX_UNITS, BOOK_LEVELS). Closes the layer-3 gap noted in
feedback_use_consts_not_literals_for_structural_dims:
Layer 1: kernel `#define` allowlist → audit-isv
Layer 2: Rust `pub const` canonical → exists (e.g. N_ACTIONS in rl/common.rs)
Layer 3: Rust literals mirroring (2) → audit-rust-consts (this commit)
Honors `// audit-ignore: <SYMBOL>` per-line markers and skips `[u8; N]`
byte-buffer patterns (high false-positive class — almost always I/O
scratch, not structural dims).
Cleanup driven by first run (19 real flags, no grandfathering):
* New canonical: `BOOK_LEVELS` in `ml-alpha/src/cfc/snap_features.rs`
(10 book levels = same place as `Mbp10RawInput` struct)
* `ml-backtesting/src/lob/mod.rs`: redefine as `pub use` re-export from
ml-alpha (single source of truth; ml-backtesting depends on ml-alpha
via `Mbp10RawInput` already)
* 19 sites switched literal `10` → `BOOK_LEVELS`:
- snap_features.rs:44-47 (struct fields)
- data/loader.rs:872-876, 960 (Mbp10Snapshot → Mbp10RawInput convert)
- data/aggregation.rs:161 (level-wise aggregation loop)
- trainer/perception.rs:2750-2756, 6272-6278, 6686-6690, 7247-7253
(snapshot → batch staging loops)
- tests/lob_sim_fuzz.rs:21, lob_sim_integrated_fuzz.rs:22 (duplicate
const → use ml_backtesting::lob::BOOK_LEVELS)
* 5 sites marked `// audit-ignore: BOOK_LEVELS — <reason>`:
- harness.rs:572,574,594 (conviction-bucket histograms, 10 ≠ depth)
- multi_horizon_labels.rs:489,557,564 (10-element test price vecs)
Re-run after fixes: 0 suspect literals flagged. PASS.
This commit is contained in:
@@ -20,6 +20,17 @@ const KERNEL_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/snap_featu
|
||||
pub const ES_TICK_SIZE: f32 = 0.25;
|
||||
pub const FEATURE_DIM: usize = 40;
|
||||
|
||||
/// Number of book-side levels in one MBP-10 snapshot (bid + ask).
|
||||
/// Canonical structural dim — every Rust array sized for book depth
|
||||
/// (`[f32; BOOK_LEVELS]`) and every loop iterating over book depth
|
||||
/// (`0..BOOK_LEVELS`) MUST reference this constant, not the literal `10`.
|
||||
/// Per feedback_use_consts_not_literals_for_structural_dims.
|
||||
///
|
||||
/// Re-exported by `ml_backtesting::lob::BOOK_LEVELS` for downstream use
|
||||
/// in the LOB simulator (ml-backtesting depends on ml-alpha already via
|
||||
/// `Mbp10RawInput` import).
|
||||
pub const BOOK_LEVELS: usize = 10;
|
||||
|
||||
/// Number of regime-context features pre-computed by the loader and
|
||||
/// piped into snap_features slots `out[20..20+REGIME_DIM]`. These give
|
||||
/// the model microstructure-burst (~2.6ms) and 1-2-second-clustering
|
||||
@@ -41,10 +52,10 @@ pub const REGIME_DIM: usize = 6;
|
||||
/// converts predecoded sidecar rows to this shape.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Mbp10RawInput {
|
||||
pub bid_px: [f32; 10],
|
||||
pub bid_sz: [f32; 10],
|
||||
pub ask_px: [f32; 10],
|
||||
pub ask_sz: [f32; 10],
|
||||
pub bid_px: [f32; BOOK_LEVELS],
|
||||
pub bid_sz: [f32; BOOK_LEVELS],
|
||||
pub ask_px: [f32; BOOK_LEVELS],
|
||||
pub ask_sz: [f32; BOOK_LEVELS],
|
||||
pub prev_mid: f32,
|
||||
pub trade_signed_vol: f32,
|
||||
pub trade_count: u32,
|
||||
|
||||
@@ -133,7 +133,7 @@ impl std::fmt::Display for MultiResolutionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::cfc::snap_features::Mbp10RawInput;
|
||||
use crate::cfc::snap_features::{Mbp10RawInput, BOOK_LEVELS};
|
||||
|
||||
/// Aggregate a slice of consecutive `Mbp10RawInput`s into one pseudo-snapshot.
|
||||
/// Field semantics:
|
||||
@@ -158,7 +158,7 @@ pub fn aggregate_window<T: AsRef<Mbp10RawInput>>(window: &[T]) -> Mbp10RawInput
|
||||
|
||||
let mut out = Mbp10RawInput::default();
|
||||
|
||||
for lvl in 0..10 {
|
||||
for lvl in 0..BOOK_LEVELS {
|
||||
let mut sum_bid_px = 0.0_f32;
|
||||
let mut sum_bid_sz = 0.0_f32;
|
||||
let mut sum_ask_px = 0.0_f32;
|
||||
|
||||
@@ -24,7 +24,7 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::cfc::snap_features::{Mbp10RawInput, ES_TICK_SIZE, REGIME_DIM};
|
||||
use crate::cfc::snap_features::{Mbp10RawInput, BOOK_LEVELS, ES_TICK_SIZE, REGIME_DIM};
|
||||
use crate::heads::N_HORIZONS;
|
||||
use crate::multi_horizon_labels::{generate_labels, generate_outcome_labels_ab};
|
||||
|
||||
@@ -869,11 +869,11 @@ fn convert(
|
||||
prev: &Mbp10Snapshot,
|
||||
regime: [f32; REGIME_DIM],
|
||||
) -> Mbp10RawInput {
|
||||
let mut bid_px = [0.0f32; 10];
|
||||
let mut bid_sz = [0.0f32; 10];
|
||||
let mut ask_px = [0.0f32; 10];
|
||||
let mut ask_sz = [0.0f32; 10];
|
||||
for (i, lvl) in cur.levels.iter().take(10).enumerate() {
|
||||
let mut bid_px = [0.0f32; BOOK_LEVELS];
|
||||
let mut bid_sz = [0.0f32; BOOK_LEVELS];
|
||||
let mut ask_px = [0.0f32; BOOK_LEVELS];
|
||||
let mut ask_sz = [0.0f32; BOOK_LEVELS];
|
||||
for (i, lvl) in cur.levels.iter().take(BOOK_LEVELS).enumerate() {
|
||||
bid_px[i] = BidAskPair::price_to_f64(lvl.bid_px) as f32;
|
||||
bid_sz[i] = lvl.bid_sz as f32;
|
||||
ask_px[i] = BidAskPair::price_to_f64(lvl.ask_px) as f32;
|
||||
@@ -957,7 +957,7 @@ mod trade_flow_tests {
|
||||
use super::*;
|
||||
|
||||
fn snap_with_l1(ts: u64, bid_px: f64, bid_sz: u32, ask_px: f64, ask_sz: u32, trade_count: u32) -> Mbp10Snapshot {
|
||||
let mut levels = vec![BidAskPair::empty(); 10];
|
||||
let mut levels = vec![BidAskPair::empty(); BOOK_LEVELS];
|
||||
levels[0] = BidAskPair {
|
||||
bid_px: BidAskPair::price_from_f64(bid_px),
|
||||
bid_sz,
|
||||
|
||||
@@ -486,7 +486,7 @@ mod tests {
|
||||
// pnl_long = +1.0 > 0 → y_prof_long = 1 at every valid t (9 of them).
|
||||
// pnl_short = -1.0 ≤ 0 → y_prof_short = 0 at every valid t.
|
||||
// pos_fraction[long, h=0] should be 1.0; [short, h=0] should be 0.0.
|
||||
let prices: Vec<f32> = (0..10).map(|i| 100.0 + i as f32).collect();
|
||||
let prices: Vec<f32> = (0..10).map(|i| 100.0 + i as f32).collect(); // audit-ignore: BOOK_LEVELS — 10-step test price ramp, unrelated to book depth
|
||||
let labels = generate_outcome_labels_ab(&prices, &[1; N_HORIZONS], 0.0).unwrap();
|
||||
// Manual count.
|
||||
let mut pos_l = 0_usize;
|
||||
@@ -554,14 +554,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn ab_labels_reject_zero_horizon() {
|
||||
let prices = vec![100.0_f32; 10];
|
||||
let prices = vec![100.0_f32; 10]; // audit-ignore: BOOK_LEVELS — 10-element test price vec, unrelated to book depth
|
||||
let err = generate_outcome_labels_ab(&prices, &[0; N_HORIZONS], 0.5);
|
||||
assert!(err.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ab_labels_reject_negative_cost() {
|
||||
let prices = vec![100.0_f32; 10];
|
||||
let prices = vec![100.0_f32; 10]; // audit-ignore: BOOK_LEVELS — 10-element test price vec, unrelated to book depth
|
||||
let err = generate_outcome_labels_ab(&prices, &[3; N_HORIZONS], -0.1);
|
||||
assert!(err.is_err());
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ use crate::aux_heads::{
|
||||
AuxMaskedHuberLoss, DEFAULT_HUBER_DELTA, N_AUX_HORIZONS,
|
||||
};
|
||||
use crate::cfc::aux_trunk::AUX_HIDDEN;
|
||||
use crate::cfc::snap_features::{Mbp10RawInput, ES_TICK_SIZE, FEATURE_DIM, REGIME_DIM};
|
||||
use crate::cfc::snap_features::{Mbp10RawInput, BOOK_LEVELS, ES_TICK_SIZE, FEATURE_DIM, REGIME_DIM};
|
||||
use crate::cfc::AuxTrunk;
|
||||
use crate::cfc::AuxTrunkConfig;
|
||||
use crate::heads::{HEAD_MID_DIM, HIDDEN_DIM, N_HORIZONS};
|
||||
@@ -2747,13 +2747,13 @@ impl PerceptionTrainer {
|
||||
for (b_idx, snapshots) in snapshots_batch.iter().enumerate() {
|
||||
for (k, snap) in snapshots.iter().enumerate() {
|
||||
let n = b_idx * k_seq + k;
|
||||
let base10 = n * 10;
|
||||
let base_book = n * BOOK_LEVELS;
|
||||
let base6 = n * REGIME_DIM;
|
||||
for i in 0..10 {
|
||||
bid_px_h[base10 + i] = snap.bid_px[i];
|
||||
bid_sz_h[base10 + i] = snap.bid_sz[i];
|
||||
ask_px_h[base10 + i] = snap.ask_px[i];
|
||||
ask_sz_h[base10 + i] = snap.ask_sz[i];
|
||||
for i in 0..BOOK_LEVELS {
|
||||
bid_px_h[base_book + i] = snap.bid_px[i];
|
||||
bid_sz_h[base_book + i] = snap.bid_sz[i];
|
||||
ask_px_h[base_book + i] = snap.ask_px[i];
|
||||
ask_sz_h[base_book + i] = snap.ask_sz[i];
|
||||
}
|
||||
for i in 0..REGIME_DIM {
|
||||
regime_h[base6 + i] = snap.regime[i];
|
||||
@@ -6269,13 +6269,13 @@ impl PerceptionTrainer {
|
||||
for k in 0..k_seq {
|
||||
let n = b_idx * k_seq + k;
|
||||
let snap = &snapshots[n];
|
||||
let base10 = n * 10;
|
||||
let base_book = n * BOOK_LEVELS;
|
||||
let base6 = n * REGIME_DIM;
|
||||
for i in 0..10 {
|
||||
bid_px_h[base10 + i] = snap.bid_px[i];
|
||||
bid_sz_h[base10 + i] = snap.bid_sz[i];
|
||||
ask_px_h[base10 + i] = snap.ask_px[i];
|
||||
ask_sz_h[base10 + i] = snap.ask_sz[i];
|
||||
for i in 0..BOOK_LEVELS {
|
||||
bid_px_h[base_book + i] = snap.bid_px[i];
|
||||
bid_sz_h[base_book + i] = snap.bid_sz[i];
|
||||
ask_px_h[base_book + i] = snap.ask_px[i];
|
||||
ask_sz_h[base_book + i] = snap.ask_sz[i];
|
||||
}
|
||||
for i in 0..REGIME_DIM {
|
||||
regime_h[base6 + i] = snap.regime[i];
|
||||
@@ -6683,7 +6683,7 @@ impl PerceptionTrainer {
|
||||
let ask_px_h = self.stg_step_ask_px.host_slice_mut();
|
||||
let ask_sz_h = self.stg_step_ask_sz.host_slice_mut();
|
||||
let regime_h = self.stg_step_regime.host_slice_mut();
|
||||
for i in 0..10 {
|
||||
for i in 0..BOOK_LEVELS {
|
||||
bid_px_h[i] = snapshot.bid_px[i];
|
||||
bid_sz_h[i] = snapshot.bid_sz[i];
|
||||
ask_px_h[i] = snapshot.ask_px[i];
|
||||
@@ -7244,13 +7244,13 @@ impl PerceptionTrainer {
|
||||
for (b_idx, snapshots) in snapshots_batch.iter().enumerate() {
|
||||
for (k, snap) in snapshots.iter().enumerate() {
|
||||
let n = b_idx * k_seq + k;
|
||||
let base10 = n * 10;
|
||||
let base_book = n * BOOK_LEVELS;
|
||||
let base6 = n * REGIME_DIM;
|
||||
for i in 0..10 {
|
||||
bid_px_h[base10 + i] = snap.bid_px[i];
|
||||
bid_sz_h[base10 + i] = snap.bid_sz[i];
|
||||
ask_px_h[base10 + i] = snap.ask_px[i];
|
||||
ask_sz_h[base10 + i] = snap.ask_sz[i];
|
||||
for i in 0..BOOK_LEVELS {
|
||||
bid_px_h[base_book + i] = snap.bid_px[i];
|
||||
bid_sz_h[base_book + i] = snap.bid_sz[i];
|
||||
ask_px_h[base_book + i] = snap.ask_px[i];
|
||||
ask_sz_h[base_book + i] = snap.ask_sz[i];
|
||||
}
|
||||
for i in 0..REGIME_DIM {
|
||||
regime_h[base6 + i] = snap.regime[i];
|
||||
|
||||
@@ -567,11 +567,11 @@ impl BacktestHarness {
|
||||
);
|
||||
}
|
||||
|
||||
// Group B — smoothed-conviction histogram.
|
||||
// Group B — smoothed-conviction histogram (10 buckets at 0.1 intervals).
|
||||
let mut conv_str = String::new();
|
||||
for bk in 0..10 {
|
||||
for bk in 0..10 { // audit-ignore: BOOK_LEVELS — conviction-bucket count, not book depth
|
||||
let count: u64 = (0..n_b)
|
||||
.map(|b| diag.conv_hist[b * 10 + bk] as u64)
|
||||
.map(|b| diag.conv_hist[b * 10 + bk] as u64) // audit-ignore: BOOK_LEVELS
|
||||
.sum();
|
||||
let lo = bk as f32 / 10.0;
|
||||
let hi = (bk + 1) as f32 / 10.0;
|
||||
@@ -590,8 +590,8 @@ impl BacktestHarness {
|
||||
}
|
||||
eprintln!("crt_diag hold_time_hist:{}", hold_str);
|
||||
|
||||
// Group D — outcome by entry-conviction.
|
||||
for bk in 0..10 {
|
||||
// Group D — outcome by entry-conviction (10 buckets).
|
||||
for bk in 0..10 { // audit-ignore: BOOK_LEVELS — conviction-bucket count, not book depth
|
||||
let n_total: u64 = (0..n_b)
|
||||
.map(|b| diag.outcome_n[b * 10 + bk] as u64)
|
||||
.sum();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! Host-side mirrors of cuda/lob_state.cuh layouts.
|
||||
//! See spec §2 (CUDA data layout) and §5b (state ownership).
|
||||
|
||||
pub const BOOK_LEVELS: usize = 10;
|
||||
/// Re-export of `ml_alpha::cfc::snap_features::BOOK_LEVELS` — single source
|
||||
/// of truth. Definition lives in ml-alpha because ml-backtesting depends
|
||||
/// on ml-alpha (`Mbp10RawInput` import in `harness.rs`).
|
||||
pub const BOOK_LEVELS: usize = ml_alpha::cfc::snap_features::BOOK_LEVELS;
|
||||
/// Re-export of `ml_alpha::heads::N_HORIZONS` — single source of truth.
|
||||
pub const N_HORIZONS: usize = ml_alpha::heads::N_HORIZONS;
|
||||
pub const MAX_LIMITS: usize = 32;
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
//! See docs/superpowers/specs/2026-05-18-real-lob-integration-design.md §8 Ring 2.
|
||||
|
||||
use anyhow::Result;
|
||||
use ml_backtesting::lob::BOOK_LEVELS;
|
||||
use ml_backtesting::sim::LobSimCuda;
|
||||
use ml_core::device::MlDevice;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
const BOOK_LEVELS: usize = 10;
|
||||
const TICK: f32 = 0.25;
|
||||
|
||||
fn make_initial_book() -> ([f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS]) {
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
//! resting-order pressure + trade-flow simulation.
|
||||
|
||||
use anyhow::Result;
|
||||
use ml_backtesting::lob::BOOK_LEVELS;
|
||||
use ml_backtesting::policy::{IsvKellyStateHost, N_HORIZONS};
|
||||
use ml_backtesting::sim::LobSimCuda;
|
||||
use ml_core::device::MlDevice;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
const BOOK_LEVELS: usize = 10;
|
||||
const TICK: f32 = 0.25;
|
||||
|
||||
fn make_initial_book() -> ([f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS]) {
|
||||
|
||||
15
scripts/audit-manifest/rust-consts.txt
Normal file
15
scripts/audit-manifest/rust-consts.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# Rust-side structural constants that MUST be referenced (not duplicated
|
||||
# as literals) per feedback_use_consts_not_literals_for_structural_dims.
|
||||
#
|
||||
# Format: `SYMBOL=VALUE`
|
||||
# The audit greps `crates/*/src/` and `crates/*/examples/` for the VALUE
|
||||
# used in array-sizing or range contexts, and flags occurrences in files
|
||||
# that do NOT also reference SYMBOL by name (suggesting literal drift).
|
||||
#
|
||||
# Append here whenever a kernel-side `#define` is mirrored by a Rust
|
||||
# `pub const` — the audit then catches all future drift.
|
||||
N_ACTIONS=11
|
||||
Q_N_ATOMS=21
|
||||
HIDDEN_DIM=128
|
||||
MAX_UNITS=4
|
||||
BOOK_LEVELS=10
|
||||
129
scripts/audit-rust-consts.sh
Executable file
129
scripts/audit-rust-consts.sh
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bash
|
||||
# audit-rust-consts.sh — catch Rust literals that mirror kernel-side
|
||||
# structural dimensions but should reference the const, per
|
||||
# `feedback_use_consts_not_literals_for_structural_dims`.
|
||||
#
|
||||
# Reads scripts/audit-manifest/rust-consts.txt which maps SYMBOL=VALUE
|
||||
# pairs. For each pair, greps `crates/*/src/` and `crates/*/examples/`
|
||||
# for the VALUE appearing in suspect contexts:
|
||||
# * `[T; <value>]` array sizing
|
||||
# * `[<expr>; <value>]` array literal
|
||||
# * `0..<value>` and `0..=<value>` range bounds
|
||||
# * `< <value>` and `<= <value>` comparison bounds
|
||||
# * `== <value>` equality on usize-likely lhs
|
||||
#
|
||||
# Flags occurrences in files that do NOT also reference the SYMBOL
|
||||
# by name — suggests the literal could/should be the const reference.
|
||||
#
|
||||
# Heuristic: false positives possible (e.g., `[u8; 4]` for 4-byte
|
||||
# arrays unrelated to MAX_UNITS). Manifest can be tuned per-project;
|
||||
# the audit's job is to RAISE flags for human review, not auto-fix.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — zero suspect literals found
|
||||
# 1 — one or more suspect literals found
|
||||
# 2 — usage / setup error
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
ROOT=$(git rev-parse --show-toplevel)
|
||||
MANIFEST="$ROOT/scripts/audit-manifest/rust-consts.txt"
|
||||
SCAN_DIRS=(
|
||||
"$ROOT/crates/ml-alpha/src"
|
||||
"$ROOT/crates/ml-alpha/examples"
|
||||
"$ROOT/crates/ml-backtesting/src"
|
||||
)
|
||||
|
||||
if [[ ! -f "$MANIFEST" ]]; then
|
||||
echo "ERROR: manifest not found: $MANIFEST" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mapfile -t entries < <(grep -vE '^\s*(#|$)' "$MANIFEST")
|
||||
|
||||
if [[ ${#entries[@]} -eq 0 ]]; then
|
||||
echo "audit-rust-consts: manifest empty — nothing to audit. PASS."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
violations=0
|
||||
flagged=0
|
||||
|
||||
echo "audit-rust-consts: scanning ${#entries[@]} const(s)..."
|
||||
|
||||
for entry in "${entries[@]}"; do
|
||||
symbol="${entry%%=*}"
|
||||
value="${entry##*=}"
|
||||
if [[ -z "$symbol" || -z "$value" || "$symbol" == "$value" ]]; then
|
||||
echo "ERROR: malformed manifest entry: '$entry' (expected SYMBOL=VALUE)" >&2
|
||||
exit 2
|
||||
fi
|
||||
# Patterns that suggest mirroring the const value:
|
||||
# [T; <value>] array sizing
|
||||
# 0..<value> exclusive range
|
||||
# 0..=<value> inclusive range
|
||||
# < <value> upper bound
|
||||
# <= <value> upper bound inclusive
|
||||
# == <value> equality vs usize-likely
|
||||
#
|
||||
# Combined into one extended regex. Word boundary `\b` on the value
|
||||
# to avoid `11` matching inside `1100`. The patterns INCLUDE typical
|
||||
# spacing variants.
|
||||
pat="(\\[[^]]+;[[:space:]]*${value}[[:space:]]*\\]|0\\.\\.=?${value}\\b|<[[:space:]]*${value}\\b|<=[[:space:]]*${value}\\b|==[[:space:]]*${value}\\b)"
|
||||
|
||||
for dir in "${SCAN_DIRS[@]}"; do
|
||||
[[ ! -d "$dir" ]] && continue
|
||||
# Find .rs files containing the suspect pattern.
|
||||
while IFS= read -r match_file; do
|
||||
[[ -z "$match_file" ]] && continue
|
||||
# If file already references the symbol by name, no flag.
|
||||
if grep -qE "\\b${symbol}\\b" "$match_file"; then
|
||||
continue
|
||||
fi
|
||||
# Otherwise flag with line excerpts.
|
||||
while IFS=: read -r lineno text; do
|
||||
# Strip leading whitespace from the excerpt for compact output.
|
||||
excerpt="$(echo "$text" | sed -E 's/^[[:space:]]+//;s/[[:space:]]+$//')"
|
||||
# Skip obvious false positives in this file context that aren't
|
||||
# caught by the file-level filter: `// ... 11 ...` in pure
|
||||
# comments that happen to match. Cheap check: skip lines that
|
||||
# are entirely comment.
|
||||
if [[ "$excerpt" =~ ^// ]]; then
|
||||
continue
|
||||
fi
|
||||
# Explicit per-line ignore marker. Format:
|
||||
# `<code> // audit-ignore: <SYMBOL>[ — <reason>]`
|
||||
# Use sparingly — prefer fixing the literal. Reason is required
|
||||
# so future readers know WHY the literal stays.
|
||||
if echo "$excerpt" | grep -qE "//[[:space:]]*audit-ignore:[[:space:]]*${symbol}\\b"; then
|
||||
continue
|
||||
fi
|
||||
# Skip byte-buffer patterns `[<num>u8; <value>]` / `[u8; <value>]`
|
||||
# — these are almost always I/O scratch buffers (e.g., u32 decode),
|
||||
# not structural-dim arrays. False-positive class is too noisy to
|
||||
# require per-site ignore markers.
|
||||
if [[ "$excerpt" =~ \[(0?u8|u8)\;[[:space:]]*${value}[[:space:]]*\] ]]; then
|
||||
continue
|
||||
fi
|
||||
echo " FLAG ${match_file}:${lineno} ($symbol=$value) — $excerpt"
|
||||
flagged=$((flagged + 1))
|
||||
done < <(grep -nE "$pat" "$match_file" 2>/dev/null || true)
|
||||
done < <(grep -rlE "$pat" "$dir" --include='*.rs' 2>/dev/null || true)
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
echo "audit-rust-consts: $flagged suspect literal(s) flagged"
|
||||
|
||||
if [[ $flagged -gt 0 ]]; then
|
||||
echo
|
||||
echo "Per feedback_use_consts_not_literals_for_structural_dims:"
|
||||
echo " if these literals mirror a structural const, replace with the const reference."
|
||||
echo " if they're unrelated (false positive), confirm by inspection."
|
||||
echo " False positives can be excluded by ensuring the file imports + uses"
|
||||
echo " the const by name elsewhere (audit then skips that file)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "audit-rust-consts: PASS"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user