Skip to content

feat(lint): add empty-block detector#15540

Merged
mablr merged 4 commits into
foundry-rs:masterfrom
0xMars42:feat/lint-empty-block
Jul 4, 2026
Merged

feat(lint): add empty-block detector#15540
mablr merged 4 commits into
foundry-rs:masterfrom
0xMars42:feat/lint-empty-block

Conversation

@0xMars42

@0xMars42 0xMars42 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Motivation

empty-block is one of the unchecked items of #14381: an empty body on a regular function is dead or unfinished code. This mirrors Aderyn's empty-block detector (aderyn_core/src/detect/low/empty_block.rs), restricted to function bodies.

Solution

New Low severity early pass: fires on a regular function whose body is {} (a comment does not make a body non-empty).

Exempt, because the empty body is the behavior:

  • constructors and receive/fallback (same as Aderyn): base calls, deployability, accepting ether or unknown calls;
  • virtual functions: an empty body is the intentional default of an extension hook meant to be overridden (deviation from Aderyn, which has no such exemption);
  • payable functions: an intentional ether sink, e.g. function deposit() external payable {} (deviation from Aderyn).

Functions without a body (interfaces, abstract declarations) never fire, and an empty modifier body is a solc compile error (2883), so it never reaches the linter. Empty blocks nested inside a non-empty body (if (x) {}) are out of scope: the checklist item is "Empty function body", while Aderyn also flags any nested empty block.

BooleanCst.sol had an intentionally empty test helper (takesBool) that the new lint flags; it now has a trivial body so the fixture stays single-lint.

Test

testdata/EmptyBlock.sol covers the firing forms (free function, library function, each visibility and mutability, comment-only body, empty non-virtual override, empty body behind a modifier) and the exempt forms (constructor with a parameter, constructor with a base call, receive/fallback, virtual hook, virtual override middle hook, payable sink, bodiless declaration, nested empty if).

Running the detector over the repo's own testdata/ Solidity fixtures yields 16 findings, all empty test helpers in .t.sol files (e.g. doNotRevert() in cheats/ExpectRevert.t.sol), none in non-test sources.

Part of #14381.

Flags a regular function whose body is empty, which is dead or unfinished
code. Constructors, receive/fallback, virtual functions (extension hooks)
and payable functions (ether sinks) are exempt: their empty body is the
behavior. Functions without a body never fire, an empty modifier body is a
solc compile error and never reaches the linter, and nested empty blocks
are out of scope.

BooleanCst.sol had an intentionally empty test helper the new lint flags;
give it a trivial body to keep that fixture single-lint.

Part of foundry-rs#14381.
The empty-block lint runs during forge build and added a warning to the
can_fail_compile_with_warnings snapshots. Emit an event so the fixture
stays lint-silent; the test is about compiler warnings (SPDX), not the
function body.
@0xMars42 0xMars42 force-pushed the feat/lint-empty-block branch from c8c2ea6 to 3a73cbc Compare July 2, 2026 15:45
Comment thread crates/forge/tests/cli/cmd.rs
Comment thread crates/lint/src/sol/low/empty_block.rs
Comment thread crates/lint/src/sol/low/empty_block.rs Outdated
The empty-block lint fires on the empty mixed-case fixture functions and
broke the exact stderr snapshots of the lint config tests. Give each one
a statement that neither solc nor any lint flags: a state write in
ContractWithLints (compiled by the build tests, so the body must not add
solc warnings) and a local declaration in the lint-only fixtures.
…odies

Two review findings:

- functions with modifiers commonly wrap an intentionally empty body
  (initialize() external initializer {}, _authorizeUpgrade(address)
  internal override onlyOwner {}): the modifier carries the behavior,
  so they are exempt now;
- the blanket payable exemption also suppressed empty payable functions
  with return values, which silently return the default and read as an
  unfinished stub: payable is exempt only without return values.

@mablr mablr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@mablr mablr merged commit d9d47aa into foundry-rs:master Jul 4, 2026
19 checks passed
@github-project-automation github-project-automation Bot moved this to Done in Foundry Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants