{"record":{"id":"565ca8f79a09302f","repo":"gitbutlerapp/gitbutler","slug":"below-u16-max","errorCode":null,"errorMessage":"below u16::MAX","messagePattern":"below u16::MAX","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/but/src/id/id_usage.rs","lineNumber":75,"sourceCode":"\n        let mut result: usize = 0;\n\n        let index = Self::FIRST_CHARS.iter().position(|e| e == first_char)?;\n        result += index;\n\n        let index = Self::SUBSEQUENT_CHARS\n            .iter()\n            .position(|e| e == second_char)?;\n        result += index * 20;\n\n        if let Some(third_char) = third_char {\n            let index = Self::SUBSEQUENT_CHARS\n                .iter()\n                .position(|e| e == third_char)?;\n            result += (index + 1) * 20 * 36;\n        }\n\n        let result: u16 = result.try_into().expect(\"below u16::MAX\");\n        debug_assert!(\n            result < Self::LIMIT,\n            \"BUG: {result} is beyond limit of {}\",\n            Self::LIMIT\n        );\n        Some(Self(result))\n    }\n}\n\n/// A tracker of which [UintId]s have been used.\n#[derive(Clone, Default, Debug)]\npub(crate) struct IdUsage {\n    /// A [UintId] is used if it's in this set.\n    uint_ids_used: HashSet<UintId>,\n    /// A [UintId] is used if it's less than this number.\n    next_uint_id: UintId,\n}\n","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/id/id_usage.rs#L57-L93","documentation":"GitButler short change IDs (first char g-z, then 1-2 chars of 0-9a-z) are packed into a u16 by UintId::from_name. The arithmetic maxes out at 19 + 35*20 + 36*20*36 = 26,639 — far below u16::MAX — because every character is looked up in fixed tables (FIRST_CHARS/SUBSEQUENT_CHARS) before contributing. The usize→u16 TryInto expect documents that invariant; with the current tables it cannot fail, and there is a further debug_assert that result < LIMIT (26,640).","triggerScenarios":"Parsing any 2-3 character change ID (e.g. resolving `but <cmd> g5x` or a TUI selection) — the normal, non-panicking path. The expect itself only fires if the alphabet tables are enlarged so a packed value could exceed 65,535.","commonSituations":"None for users. Developers hit it when extending the ID alphabet or LENGTH_LIMIT in crates/but/src/id/id_usage.rs without re-checking the packing math.","solutions":["Keep FIRST_CHARS, SUBSEQUENT_CHARS, and LIMIT (20*36*37) in sync; if tables grow, widen the intermediate to u32 and re-derive LIMIT","Replace the expect with u16::try_from(result).ok()? — from_name already returns Option for invalid IDs, so overflow should be one more None case","Add a property test iterating all valid 2- and 3-char names asserting from_name returns Some and stays under LIMIT"],"exampleFix":"// before\nlet result: u16 = result.try_into().expect(\"below u16::MAX\");\n\n// after — invalid/too-large input is a None like any other parse failure\nlet result: u16 = result.try_into().ok()?;","handlingStrategy":"validation","validationCode":"// cheap shape check before handing a user-supplied short id to but\nfn looks_like_short_id(s: &str) -> bool {\n    let b = s.as_bytes();\n    (2..=3).contains(&b.len())\n        && b\"ghijklmnopqrstuvwxyz\".contains(&b[0])\n        && b[1..].iter().all(|c| b\"0123456789abcdefghijklmnopqrstuvwxyz\".contains(c))\n}","typeGuard":"fn is_valid_short_id(value: &str) -> bool {\n    let b = value.as_bytes();\n    matches!(b, [a, rest @ ..] if b\"ghijklmnopqrstuvwxyz\".contains(a)\n        && (rest.len() == 1 || rest.len() == 2)\n        && rest.iter().all(|c| b\"0123456789abcdefghijklmnopqrstuvwxyz\".contains(c)))\n}","tryCatchPattern":null,"preventionTips":["The overflow is unreachable with the shipped alphabets — only reachable if you edit FIRST_CHARS/SUBSEQUENT_CHARS/LIMIT; re-derive the max (20*36*37) when touching them","from_name returns None for any invalid character; rely on Option handling instead of expecting","Add a property test over all valid 2-3 char names asserting Some and result < LIMIT"],"tags":["rust","invariant","id-parsing","integer-conversion","unreachable"],"backgroundTag":"integer-conversion-overflow","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}