Skip to content

Fix 16-bit pointer truncation in sub-parser linked list#477

Merged
thedavidmeister merged 4 commits intomainfrom
rename-prelude-command
Apr 12, 2026
Merged

Fix 16-bit pointer truncation in sub-parser linked list#477
thedavidmeister merged 4 commits intomainfrom
rename-prelude-command

Conversation

@thedavidmeister
Copy link
Copy Markdown
Contributor

@thedavidmeister thedavidmeister commented Apr 10, 2026

Summary

  • Fix 16-bit pointer truncation in sub-parser linked list traversal that caused incorrect pointer resolution for large memory offsets
  • Update Cargo.lock for crate renames

Test plan

  • Added LibParseState.pushSubParser.largeMemory.t.sol test reproducing the truncation bug
  • Existing sub-parser tests continue to pass
  • CI green on prior push

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Replaced hardcoded pointer bit-shifts with a shared named constant and updated parsing logic to use it.
  • Tests

    • Added a regression test for sub-parser behavior with large memory offsets.
    • Updated existing tests to reference the new constant for consistent pointer handling.
  • Chores

    • Adjusted CI workflow environment handling for artifact deployment.

thedavidmeister and others added 2 commits April 9, 2026 12:29
The sub-parser linked list packed a memory pointer into the top 16 bits
of a bytes32 (shift 0xF0 = 240). When the free memory pointer exceeded
0xFFFF the pointer was silently truncated, corrupting the list.

Widen to 96 bits (shift 0xA0 = 160) and extract the shift into a named
constant SUB_PARSER_POINTER_SHIFT to prevent future inconsistencies.

Closes #473
Closes #474

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a823ad9c-47f2-42e0-ba7f-6951955cd823

📥 Commits

Reviewing files that changed from the base of the PR and between bde3041 and 09d6b8e.

📒 Files selected for processing (3)
  • src/lib/parse/LibParseState.sol
  • test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol
  • test/src/lib/parse/LibParseState.pushSubParser.t.sol
✅ Files skipped from review due to trivial changes (1)
  • test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/src/lib/parse/LibParseState.pushSubParser.t.sol
  • src/lib/parse/LibParseState.sol

📝 Walkthrough

Walkthrough

The pull request replaces hardcoded sub-parser pointer shifts with a named constant SUB_PARSER_POINTER_SHIFT = 0xA0 across the parsing library and tests, and adds a regression test validating sub-parser behavior when memory offsets are large.

Changes

Cohort / File(s) Summary
Core parse state & sub-parse logic
src/lib/parse/LibParseState.sol, src/lib/parse/LibSubParse.sol
Added uint256 constant SUB_PARSER_POINTER_SHIFT = 0xA0; replaced hardcoded 0xF0 shift uses with SUB_PARSER_POINTER_SHIFT in pushSubParser, exportSubParsers, and sub-parser traversal.
Tests — updated shifts
test/src/lib/parse/LibParsePragma.keyword.t.sol, test/src/lib/parse/LibParseState.pushSubParser.t.sol
Imported SUB_PARSER_POINTER_SHIFT and replaced inline-assembly right-shift literals (0xF0) with the constant in all pointer-dereferencing test logic.
Tests — new large-memory regression
test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol
Added LibParseStatePushSubParserLargeMemoryTest::testPushSubParserLargeMemoryOffset() to verify pushing/exporting sub-parsers works when the free memory pointer is moved beyond 0xFFFF.
CI workflow change
.github/workflows/manual-sol-artifacts.yaml
Set DEPLOYMENT_KEY unconditionally from secrets.PRIVATE_KEY, removing prior branch-based conditional selection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble constants in the night,
Naming shifts to make things right.
Pointers hop and find their bed,
Three addresses, neatly spread.
A rabbit cheers — the tests all fled! 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: changing a 16-bit pointer shift constant to a larger value (0xA0) to correctly handle large memory offsets in sub-parser linked list traversal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rename-prelude-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol (1)

20-46: Solid regression test for the pointer truncation bug.

The test correctly:

  1. Allocates 0x20000 bytes to push the free memory pointer past 0xFFFF
  2. Creates a minimal ParseState with required fields
  3. Pushes multiple sub-parsers to exercise the linked list
  4. Verifies round-trip correctness via exportSubParsers

Consider adding an assertion to verify the free memory pointer actually exceeded 0xFFFF, making the test more self-documenting and robust against future allocation changes.

Optional: Add assertion for memory pointer
         // Allocate enough memory to push the free memory pointer past 0xFFFF.
         bytes memory padding = new bytes(0x20000);
         // Suppress unused variable warning.
         assembly ("memory-safe") {
             pop(padding)
         }
+        
+        // Verify the free memory pointer is past 0xFFFF (the bug trigger condition).
+        uint256 freeMemPtr;
+        assembly ("memory-safe") {
+            freeMemPtr := mload(0x40)
+        }
+        assertGt(freeMemPtr, 0xFFFF, "free memory pointer should exceed 0xFFFF");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol` around
lines 20 - 46, Add an assertion in testPushSubParserLargeMemoryOffset to
explicitly verify the free memory pointer exceeded 0xFFFF after allocating
padding: read mload(0x40) (the free memory pointer) via inline assembly after
creating padding and assert it is > 0xFFFF; this should reference the existing
padding allocation and test function name (testPushSubParserLargeMemoryOffset)
so the purpose is clear and robust against future allocation changes.
src/lib/parse/LibSubParse.sol (1)

3-3: Minor: Solidity version pragma should be exact =0.8.25.

The guidelines specify exactly 0.8.25, but this file uses ^0.8.25.

Proposed fix
-pragma solidity ^0.8.25;
+pragma solidity =0.8.25;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/parse/LibSubParse.sol` at line 3, Update the Solidity pragma in this
file from a caret range to an exact version; specifically replace the `pragma
solidity ^0.8.25;` declaration with the strict `pragma solidity =0.8.25;` so the
compiler version is pinned exactly (locate the pragma at the top of
LibSubParse.sol and update it accordingly).
src/lib/parse/LibParseState.sol (1)

314-320: Outdated comment: pointer is now 96 bits, not 16 bits.

The comment still states "the pointer for the linked list is ORed in the 16 high bits" but after this fix, the pointer occupies the high 96 bits.

Proposed fix
     /// `@notice` Pushes a `uint256` representation of a sub parser onto the linked list of
-    /// sub parsers in memory. The sub parser is expected to be an `address` so
-    /// the pointer for the linked list is ORed in the 16 high bits of the
-    /// `uint256`. Only 16 bits are available for the linked-list pointer, so
-    /// this function relies on `checkParseMemoryOverflow` keeping the free
-    /// memory pointer below `0x10000`. If that invariant is violated, the
-    /// tail pointer will be silently truncated and the linked list corrupted.
+    /// sub parsers in memory. The sub parser is expected to be an `address` so
+    /// the pointer for the linked list is ORed in the high 96 bits of the
+    /// `uint256` (above the 160-bit address). This provides ample space for
+    /// any practical memory pointer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/parse/LibParseState.sol` around lines 314 - 320, Update the outdated
comment in LibParseState.sol that describes how sub-parser pointers are encoded:
change the text to state that the linked-list pointer is ORed into the high 96
bits (not 16 bits) of the uint256 and update the invariant description to
reference that checkParseMemoryOverflow must keep the free memory pointer below
2**96 (0x1_0000000000000000000000000) so the high-96-bit pointer isn't
truncated; ensure the comment mentions the functions/guards involved (the push
routine in this file and checkParseMemoryOverflow) so readers know where the
invariant is enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol`:
- Line 3: Update the Solidity version pragma at the top of the file by replacing
the loose pragma "pragma solidity ^0.8.18;" with the exact required version
specifier "pragma solidity =0.8.25;" so the contract/compiler uses the mandated
0.8.25 compiler; locate the existing pragma line (the "pragma solidity"
declaration) and change its version operator and number accordingly.

In `@test/src/lib/parse/LibParseState.pushSubParser.t.sol`:
- Line 3: The pragma in this test file currently uses a caret range ("pragma
solidity ^0.8.18;") which violates the guideline requiring an exact Solidity
version; update the Solidity version pragma to "pragma solidity =0.8.25;" in
this file (look for the top-level "pragma solidity" directive) and ensure any
nearby test helpers or imports in the same test suite are compatible with
0.8.25.

---

Nitpick comments:
In `@src/lib/parse/LibParseState.sol`:
- Around line 314-320: Update the outdated comment in LibParseState.sol that
describes how sub-parser pointers are encoded: change the text to state that the
linked-list pointer is ORed into the high 96 bits (not 16 bits) of the uint256
and update the invariant description to reference that checkParseMemoryOverflow
must keep the free memory pointer below 2**96 (0x1_0000000000000000000000000) so
the high-96-bit pointer isn't truncated; ensure the comment mentions the
functions/guards involved (the push routine in this file and
checkParseMemoryOverflow) so readers know where the invariant is enforced.

In `@src/lib/parse/LibSubParse.sol`:
- Line 3: Update the Solidity pragma in this file from a caret range to an exact
version; specifically replace the `pragma solidity ^0.8.25;` declaration with
the strict `pragma solidity =0.8.25;` so the compiler version is pinned exactly
(locate the pragma at the top of LibSubParse.sol and update it accordingly).

In `@test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol`:
- Around line 20-46: Add an assertion in testPushSubParserLargeMemoryOffset to
explicitly verify the free memory pointer exceeded 0xFFFF after allocating
padding: read mload(0x40) (the free memory pointer) via inline assembly after
creating padding and assert it is > 0xFFFF; this should reference the existing
padding allocation and test function name (testPushSubParserLargeMemoryOffset)
so the purpose is clear and robust against future allocation changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2cb8ba7d-ddfd-44c8-be6f-8343c2ef91e3

📥 Commits

Reviewing files that changed from the base of the PR and between 22d387c and b50d3a0.

⛔ Files ignored due to path filters (4)
  • Cargo.lock is excluded by !**/*.lock
  • src/generated/Rainlang.pointers.sol is excluded by !**/generated/**
  • src/generated/RainlangExpressionDeployer.pointers.sol is excluded by !**/generated/**
  • src/generated/RainlangParser.pointers.sol is excluded by !**/generated/**
📒 Files selected for processing (6)
  • .gas-snapshot
  • src/lib/parse/LibParseState.sol
  • src/lib/parse/LibSubParse.sol
  • test/src/lib/parse/LibParsePragma.keyword.t.sol
  • test/src/lib/parse/LibParseState.pushSubParser.largeMemory.t.sol
  • test/src/lib/parse/LibParseState.pushSubParser.t.sol

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/manual-sol-artifacts.yaml:
- Line 45: The workflow currently sets DEPLOYMENT_KEY to secrets.PRIVATE_KEY
unconditionally, allowing non-main manual runs to use the production signer;
change the DEPLOYMENT_KEY environment assignment so it selects a branch-scoped
secret or fails for non-main refs (e.g., set DEPLOYMENT_KEY to the production
secret only when github.ref or github.ref_name equals main, otherwise pick a
branch-specific secret or throw), ensuring script/Deploy.sol's
deployAndBroadcast path only sees the prod key for main runs and cannot
broadcast from other refs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62d391ef-340a-4c62-8f30-e4da605ec124

📥 Commits

Reviewing files that changed from the base of the PR and between b50d3a0 and bde3041.

📒 Files selected for processing (1)
  • .github/workflows/manual-sol-artifacts.yaml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister merged commit 15f0cbd into main Apr 12, 2026
10 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

Tip

For best results, initiate chat on the files or code changes.

SIZE=M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant