Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/lib/parse/LibParseMeta.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}
}
Expand Down
4 changes: 4 additions & 0 deletions test/src/lib/bytecode/LibBytecode.sourceRelativeOffset.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
16 changes: 16 additions & 0 deletions test/src/lib/codegen/LibGenParseMeta.buildMeta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
48 changes: 48 additions & 0 deletions test/src/lib/parse/LibParseMeta.lookupWord.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -185,27 +213,41 @@ 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);
}

/// 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);
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
Loading