Unit
test/src/lib/parse/LibParsePragma.keyword.t.sol, three draws:
- line ~106:
charFromMask(seed, ~CMASK_WHITESPACE) in testPragmaKeywordNoWhitespace
- line ~124:
charFromMask(seed + 1, ~CMASK_INTERSTITIAL_HEAD) in testPragmaKeywordWhitespaceNoHex
- line ~156:
charFromMask(seed, ~CMASK_HEX) in testPragmaKeywordParseSubParserBasic
Violated property
LibConformString.charFromMask probes linearly from char 0 and only consults the seed after a miss (documented behavior as of rain.string PR #58). NUL is in none of WHITESPACE / INTERSTITIAL_HEAD / HEX, so every complement mask has bit 0 set and every one of these draws returns "\x00" for every seed. Across all fuzz runs the three tests exercise exactly one character each, while reading as if they cover the whole complement set.
testPragmaKeywordWhitespaceNoHex's cursor assertion is load-bearing on the bias: it holds because NUL is not a literal head. A uniform draw over ~CMASK_INTERSTITIAL_HEAD would hit 0-9/-/"/[ (~13/128 per draw), parsePragma's literal path would consume further, and assertEq(cursorAfter, cursor + 17) would fail — i.e. the test would find real behavior it currently never reaches.
Proposed fix
Derive the char from the seed across the whole target set — e.g. draw a byte from the seed and reroll while it misses the intended set (the conform-style loop), or use charFromMask(uint256(keccak256(abi.encode(seed))), mask) only for masks without bit 0 and an explicit rejection loop otherwise. Then make the WhitespaceNoHex expectation robust to non-NUL draws (restrict the set deliberately, or handle the literal-path outcome).
Triage note
charFromMask's bias is now documented upstream and rain.string deliberately kept it (behavior change would break exactly these tests). The gap is on this side: the tests claim fuzz coverage they don't have.
Found by the 2026-08-24 rain.string CMask consumer-oracle audit.
🤖 Generated with Claude Code
Unit
test/src/lib/parse/LibParsePragma.keyword.t.sol, three draws:charFromMask(seed, ~CMASK_WHITESPACE)intestPragmaKeywordNoWhitespacecharFromMask(seed + 1, ~CMASK_INTERSTITIAL_HEAD)intestPragmaKeywordWhitespaceNoHexcharFromMask(seed, ~CMASK_HEX)intestPragmaKeywordParseSubParserBasicViolated property
LibConformString.charFromMaskprobes linearly from char 0 and only consults the seed after a miss (documented behavior as of rain.string PR #58). NUL is in none of WHITESPACE / INTERSTITIAL_HEAD / HEX, so every complement mask has bit 0 set and every one of these draws returns"\x00"for every seed. Across all fuzz runs the three tests exercise exactly one character each, while reading as if they cover the whole complement set.testPragmaKeywordWhitespaceNoHex's cursor assertion is load-bearing on the bias: it holds because NUL is not a literal head. A uniform draw over~CMASK_INTERSTITIAL_HEADwould hit0-9/-/"/[(~13/128 per draw),parsePragma's literal path would consume further, andassertEq(cursorAfter, cursor + 17)would fail — i.e. the test would find real behavior it currently never reaches.Proposed fix
Derive the char from the seed across the whole target set — e.g. draw a byte from the seed and reroll while it misses the intended set (the conform-style loop), or use
charFromMask(uint256(keccak256(abi.encode(seed))), mask)only for masks without bit 0 and an explicit rejection loop otherwise. Then make the WhitespaceNoHex expectation robust to non-NUL draws (restrict the set deliberately, or handle the literal-path outcome).Triage note
charFromMask's bias is now documented upstream and rain.string deliberately kept it (behavior change would break exactly these tests). The gap is on this side: the tests claim fuzz coverage they don't have.
Found by the 2026-08-24 rain.string CMask consumer-oracle audit.
🤖 Generated with Claude Code