feat(lint): add empty-block detector#15540
Merged
Merged
Conversation
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.
c8c2ea6 to
3a73cbc
Compare
mablr
reviewed
Jul 2, 2026
mablr
reviewed
Jul 2, 2026
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.
stevencartavia
approved these changes
Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
empty-blockis one of the unchecked items of #14381: an empty body on a regular function is dead or unfinished code. This mirrors Aderyn'sempty-blockdetector (aderyn_core/src/detect/low/empty_block.rs), restricted to function bodies.Solution
New
Lowseverity 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:
receive/fallback(same as Aderyn): base calls, deployability, accepting ether or unknown calls;virtualfunctions: an empty body is the intentional default of an extension hook meant to be overridden (deviation from Aderyn, which has no such exemption);payablefunctions: 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.solhad 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.solcovers 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 emptyif).Running the detector over the repo's own
testdata/Solidity fixtures yields 16 findings, all empty test helpers in.t.solfiles (e.g.doNotRevert()incheats/ExpectRevert.t.sol), none in non-test sources.Part of #14381.