From bf53ef34347e5acbc47f08495a30409ce04d3317 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Wed, 16 Sep 2026 13:30:05 +0000 Subject: [PATCH] chore: scope forge-lint disables for the 37 findings on main `forge lint -D warnings` reports 37 findings on unmodified `main`. Every one is a false positive or a deliberate construct the test asserts, so each gets a scoped `//forge-lint: disable-next-line()` and a reason. No code changes. - boolean-cst x3, `src/lib/parse/LibParseMeta.sol`: `lookupWord` returns `(bool, uint256)` and the org bans named returns, so the found flag can only be a literal in the return tuple. The rule is about a boolean constant used as a condition operand; a return value is not one. - incorrect-shift x3: the rule documents itself as a Yul `shl`/`shr` argument order check. All three sites are Solidity `<<`, whose operand order is fixed by the language, in the canonical `1 << n` single-bit idiom. - unsafe-typecast x27: `bytes32("")`. solc rejects a literal wider than 32 bytes at compile time, so there is no runtime value to truncate. - unsafe-typecast x2: `uint8(x & 0xFF)`, masked to 8 bits in the same expression. - unsafe-typecast x1: `bytes1(uint8(offset >> 8))` where `offset` is uint16. - unsafe-typecast x1: `bytes1(uint8(offset))` where `offset` is uint16. This one does truncate, and that is the point: it writes the low byte of a 16 bit big-endian offset, which the surrounding test asserts is read back whole. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01V8ViHcKLVk2YoS2joH4HdN --- src/lib/parse/LibParseMeta.sol | 6 +++ .../LibBytecode.sourceRelativeOffset.t.sol | 4 ++ .../codegen/LibGenParseMeta.buildMeta.t.sol | 16 +++++++ .../lib/parse/LibParseMeta.lookupWord.t.sol | 48 +++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/src/lib/parse/LibParseMeta.sol b/src/lib/parse/LibParseMeta.sol index 9afee2c..8b4cc96 100644 --- a/src/lib/parse/LibParseMeta.sol +++ b/src/lib/parse/LibParseMeta.sol @@ -159,6 +159,8 @@ library LibParseMeta { // is not in the set. No word was mapped to this bit, so // there is nothing to collide with at any depth. if (expansion & shifted == 0) { + // The literal is this function's found/not-found return value, not a condition operand. + //forge-lint: disable-next-line(boolean-cst) return (false, 0); } @@ -176,11 +178,15 @@ library LibParseMeta { assembly ("memory-safe") { index := byte(28, posData) } + // The literal is this function's found/not-found return value, not a condition operand. + //forge-lint: disable-next-line(boolean-cst) return (true, index); } else { cumulativeCt += LibCtPop.ctpop(expansion); } } + // The literal is this function's found/not-found return value, not a condition operand. + //forge-lint: disable-next-line(boolean-cst) return (false, 0); } } diff --git a/test/src/lib/bytecode/LibBytecode.sourceRelativeOffset.t.sol b/test/src/lib/bytecode/LibBytecode.sourceRelativeOffset.t.sol index 63b4e97..f3060b7 100644 --- a/test/src/lib/bytecode/LibBytecode.sourceRelativeOffset.t.sol +++ b/test/src/lib/bytecode/LibBytecode.sourceRelativeOffset.t.sol @@ -101,7 +101,11 @@ contract LibBytecodeSourceRelativeOffsetTest is BytecodeTest { // count = 1, single offset taken from the fuzzed high value. bytes memory bytecode = new bytes(3); bytecode[0] = bytes1(uint8(1)); + // offset is uint16 so its high byte always fits in uint8. + //forge-lint: disable-next-line(unsafe-typecast) bytecode[1] = bytes1(uint8(offset >> 8)); + // Writing the low byte of a 16 bit offset, so truncating is the intent. + //forge-lint: disable-next-line(unsafe-typecast) bytecode[2] = bytes1(uint8(offset)); assertEq(LibBytecode.sourceRelativeOffset(bytecode, 0), offset); assertEq(LibBytecode.sourceRelativeOffset(bytecode, 0), LibBytecodeSlow.sourceRelativeOffsetSlow(bytecode, 0)); diff --git a/test/src/lib/codegen/LibGenParseMeta.buildMeta.t.sol b/test/src/lib/codegen/LibGenParseMeta.buildMeta.t.sol index 9d96ae4..708fe12 100644 --- a/test/src/lib/codegen/LibGenParseMeta.buildMeta.t.sol +++ b/test/src/lib/codegen/LibGenParseMeta.buildMeta.t.sol @@ -21,6 +21,8 @@ contract LibGenParseMetaBuildMetaTest is Test { /// 4-byte items). Previously the constant was (1 << 4) - 1 = 0xF which /// is only 4 bits. function testMetaItemMask() external pure { + // Solidity << is value then shift amount, so 1 << n is the correct order. + //forge-lint: disable-next-line(incorrect-shift) assertEq(META_ITEM_MASK, (1 << (META_ITEM_SIZE * 8)) - 1); assertEq(META_ITEM_MASK, type(uint32).max); } @@ -175,8 +177,14 @@ contract LibGenParseMetaBuildMetaTest is Test { /// correct indices. function testParseMetaConstantStringRoundtrip() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](3); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: "Add two numbers"}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[1] = AuthoringMetaV2({word: bytes32("sub"), description: "Subtract"}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[2] = AuthoringMetaV2({word: bytes32("mul"), description: "Multiply"}); bytes memory encoded = abi.encode(metas); @@ -208,6 +216,8 @@ contract LibGenParseMetaBuildMetaTest is Test { /// built internally should correctly look up that word. function testParseMetaConstantStringSingleWord() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("only"), description: "The only word"}); bytes memory encoded = abi.encode(metas); @@ -216,6 +226,8 @@ contract LibGenParseMetaBuildMetaTest is Test { assertTrue(bytes(result).length > 0); bytes memory parseMeta = LibGenParseMeta.buildParseMetaV2(metas, 1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) (bool exists, uint256 index) = LibParseMeta.lookupWord(parseMeta, bytes32("only")); assertTrue(exists); assertEq(index, 0); @@ -225,7 +237,11 @@ contract LibGenParseMetaBuildMetaTest is Test { /// input, and the underlying parse meta should remain functional. function testParseMetaConstantStringBuildDepths() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](2); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("foo"), description: ""}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[1] = AuthoringMetaV2({word: bytes32("bar"), description: ""}); bytes memory encoded = abi.encode(metas); diff --git a/test/src/lib/parse/LibParseMeta.lookupWord.t.sol b/test/src/lib/parse/LibParseMeta.lookupWord.t.sol index acb7381..0fb1982 100644 --- a/test/src/lib/parse/LibParseMeta.lookupWord.t.sol +++ b/test/src/lib/parse/LibParseMeta.lookupWord.t.sol @@ -23,8 +23,14 @@ contract LibParseMetaLookupWordTest is Test { /// buildParseMetaV2 output must always pass structural validation. function testCheckParseMetaStructureBuildOutput() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](3); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: ""}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[1] = AuthoringMetaV2({word: bytes32("sub"), description: ""}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[2] = AuthoringMetaV2({word: bytes32("mul"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 8); LibParseMeta.checkParseMetaStructure(meta); @@ -43,6 +49,8 @@ contract LibParseMetaLookupWordTest is Test { /// Truncated meta should fail validation. function testCheckParseMetaStructureTruncated() external { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 8); @@ -58,6 +66,8 @@ contract LibParseMetaLookupWordTest is Test { /// Extra trailing bytes should fail validation. function testCheckParseMetaStructureExtraBytes() external { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 8); @@ -86,6 +96,8 @@ contract LibParseMetaLookupWordTest is Test { /// This should return (false, 0) but currently returns (true, fakeIndex) /// because the bit-set check is missing. function testLookupWordMissingBitCheck() external pure { + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) bytes32 word = bytes32("notinmeta"); uint8 seed = 0; @@ -120,6 +132,8 @@ contract LibParseMetaLookupWordTest is Test { // Find the word's bit position. uint256 bitPos; for (uint256 i = 0; i < 256; i++) { + // Solidity << is value then shift amount, so 1 << n is the correct order. + //forge-lint: disable-next-line(incorrect-shift) if (shifted == (1 << i)) { bitPos = i; break; @@ -129,6 +143,8 @@ contract LibParseMetaLookupWordTest is Test { // Pick a different bit ABOVE the word's bit so ctpop gives pos = 0. // Wrap around if needed — the key requirement is the bit differs. uint256 fakeBitPos = (bitPos + 128) % 256; + // Solidity << is value then shift amount, so 1 << n is the correct order. + //forge-lint: disable-next-line(incorrect-shift) uint256 fakeExpansion = 1 << fakeBitPos; // Determine what pos lookupWord will compute: @@ -151,14 +167,22 @@ contract LibParseMetaLookupWordTest is Test { meta[itemOffset] = bytes1(uint8(42)); meta[itemOffset + 1] = bytes1(uint8((wordFingerprint >> 16) & 0xFF)); meta[itemOffset + 2] = bytes1(uint8((wordFingerprint >> 8) & 0xFF)); + // Masked to 8 bits before the cast, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) meta[itemOffset + 3] = bytes1(uint8(wordFingerprint & 0xFF)); } /// Build meta from known words, look them all up, verify indices. function testLookupWordKnown() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](3); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: ""}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[1] = AuthoringMetaV2({word: bytes32("sub"), description: ""}); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[2] = AuthoringMetaV2({word: bytes32("mul"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 8); @@ -173,10 +197,14 @@ contract LibParseMetaLookupWordTest is Test { /// Looking up a word not in meta should return false with index 0. function testLookupWordNotFound() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("add"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 8); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) (bool exists, uint256 index) = LibParseMeta.lookupWord(meta, bytes32("notaword")); assertFalse(exists); assertEq(index, 0); @@ -185,15 +213,21 @@ contract LibParseMetaLookupWordTest is Test { /// Single-depth meta with a single word. function testLookupWordSingleDepth() external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("only"), description: ""}); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) (bool exists, uint256 index) = LibParseMeta.lookupWord(meta, bytes32("only")); assertTrue(exists); assertEq(index, 0); // Not-found on single-depth meta. + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) (bool notExists,) = LibParseMeta.lookupWord(meta, bytes32("other")); assertFalse(notExists); } @@ -201,11 +235,19 @@ contract LibParseMetaLookupWordTest is Test { /// Multiple not-found lookups should all return false. function testLookupWordMultipleNotFound(bytes32 a, bytes32 b, bytes32 c) external pure { AuthoringMetaV2[] memory metas = new AuthoringMetaV2[](1); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) metas[0] = AuthoringMetaV2({word: bytes32("known"), description: ""}); // Ensure fuzzed words differ from the known word. + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) vm.assume(a != bytes32("known")); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) vm.assume(b != bytes32("known")); + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) vm.assume(c != bytes32("known")); bytes memory meta = LibGenParseMeta.buildParseMetaV2(metas, 3); @@ -254,6 +296,8 @@ contract LibParseMetaLookupWordTest is Test { meta[itemOffset] = bytes1(opcodeIndex); meta[itemOffset + 1] = bytes1(uint8((fingerprint >> 16) & 0xFF)); meta[itemOffset + 2] = bytes1(uint8((fingerprint >> 8) & 0xFF)); + // Masked to 8 bits before the cast, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) meta[itemOffset + 3] = bytes1(uint8(fingerprint & 0xFF)); } @@ -283,6 +327,8 @@ contract LibParseMetaLookupWordTest is Test { /// layer and adds cumulativeCt 1 (one bit set at depth 0), so it reads /// item slot 1 and returns (true, 7). function testLookupWordCollisionDescent() external pure { + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) bytes32 word = bytes32("descend"); bytes memory meta = new bytes(META_PREFIX_SIZE + 2 * META_EXPANSION_SIZE + 2 * META_ITEM_SIZE); @@ -326,6 +372,8 @@ contract LibParseMetaLookupWordTest is Test { /// matches must miss once the loop runs past the last layer. The descent /// visits both layers and the post-loop return yields (false, 0). function testLookupWordDescendThenMiss() external pure { + // Casting a string literal that fits in 32 bytes, so it cannot truncate. + //forge-lint: disable-next-line(unsafe-typecast) bytes32 word = bytes32("nomatch"); bytes memory meta = new bytes(META_PREFIX_SIZE + 2 * META_EXPANSION_SIZE + 2 * META_ITEM_SIZE);