{"record":{"id":"fc3f44ce407cf4f2","repo":"nautechsystems/nautilus_trader","slug":"year-arithmetic-underflow","errorCode":null,"errorMessage":"year arithmetic underflow","messagePattern":"year arithmetic underflow","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bar.rs","lineNumber":306,"sourceCode":"\n            // Reconstruct from Jan 1 + origin each time to avoid leap-day drift\n            let year_start = |year: i32| {\n                let year = i16::try_from(year).expect(\"year exceeds Jiff supported range\");\n                Offset::UTC\n                    .to_timestamp(\n                        Date::new(year, 1, 1)\n                            .expect(\"valid year start date\")\n                            .at(0, 0, 0, 0),\n                    )\n                    .expect(\"valid UTC year start\")\n                    + origin_offset\n            };\n\n            let mut year = i32::from(Offset::UTC.to_datetime(now).year());\n            if year_start(year) > now {\n                year = year\n                    .checked_sub(step_i32)\n                    .expect(\"year arithmetic underflow\");\n            }\n\n            loop {\n                let next_year = year\n                    .checked_add(step_i32)\n                    .expect(\"year arithmetic overflow\");\n\n                if year_start(next_year) > now {\n                    break;\n                }\n                year = next_year;\n            }\n\n            year_start(year)\n        }\n        _ => panic!(\n            \"Aggregation type {} not supported for time bars\",\n            spec.aggregation","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bar.rs#L288-L324","documentation":"When the origin-adjusted Jan-1 anchor is after `now`, the current year is decremented by step_i32 using checked_sub; if that would underflow i32 (only possible with a negative or huge step combined with boundary years), the expect panics. It guards the backward year walk.","triggerScenarios":"get_time_bar_start with a Year bar where year.checked_sub(step_i32) underflows i32 — requires a near-i32::MIN result, i.e. an extreme step_i32 and an extreme input year, or a negative step that passed earlier checks.","commonSituations":"Crafted/adversarial bar specs with near-i32::MAX magnitude steps; timestamps near the calendar era boundaries in synthetic data.","solutions":["Use a sane positive Year step; validate step bounds before constructing the BarType.","Ensure `now` yields a normal UTC year (e.g. within 1900-2200).","Reject bar specs whose step magnitude makes year ± step exceed i32 at config validation.","File an issue if reproducible with realistic inputs."],"exampleFix":"// before\nBarType::from_spec(2_147_483_647, BarAggregation::Year) // step near i32::MAX\n// after\nassert!(step > 0 && step <= 9_000);\nBarType::from_spec(step, BarAggregation::Year)","handlingStrategy":"validation","validationCode":"let step = step_to_i64(spec.step);\nif step <= 0 || step > 9_000 {\n    return Err(format!(\"year step {step} out of safe range\"));\n}","typeGuard":"fn safe_year_step(step: i64) -> bool { step > 0 && step <= 9_000 }","tryCatchPattern":"let start = std::panic::catch_unwind(|| get_time_bar_start(now, &bar_type, origin))\n    .map_err(|_| anyhow::anyhow!(\"year arithmetic underflow\"))?;","preventionTips":["Bound Year step magnitudes at configuration validation.","Keep input timestamps within ordinary years so the backward walk stays safe.","Add fuzz tests for Year aggregation with extreme steps."],"tags":["rust","panic","integer-overflow","bar-aggregation"],"backgroundTag":"value-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}