Skip to main content
Sigvex

Dropped Side Effects on .selector Access

Flags contracts compiled with Solidity 0.6.2 through 0.8.20 on the legacy pipeline, where side effects of an expression before .selector could be silently discarded.

Dropped Side Effects on .selector Access

Overview

On the legacy (non-IR) code generation pipeline, Solidity discarded the evaluation of an expression whose only use was a .selector access. In h().f.selector, the call to h() was never emitted — along with any state writes, new calls, external calls, or events it would have performed. The function selector is a compile-time constant, so the compiler folded it in during code generation and then dropped the now-dead receiver expression together with its side effects. The bug affects 0.6.2 through 0.8.20 when the legacy pipeline is in use and was fixed in 0.8.21.

This miscompilation is intrinsically hard to detect from bytecode, because its effect is the absence of opcodes that should have been emitted rather than the presence of a recognizable pattern. There is no positive signal to observe. Sigvex therefore reports it primarily from recovered compiler metadata, requiring that the version sit in the affected range and that code generation used the legacy pipeline. As a weak corroboration it also requires at least two recovered functions, a minimum precondition for the interprocedural .selector access that the bug needs.

Confidence is held below the level used for detectors that observe a concrete pattern. When metadata confirms the version is affected and the legacy pipeline was used, the finding is reported at moderate confidence; when the code generation path is unknown, it is reported lower.

Why This Is an Issue

If the dropped expression performed meaningful work — recording state, deploying a contract, transferring value, or emitting an event — that work simply never happened, while the surrounding code proceeded as though it had. The contract’s behaviour diverges from its source in a way that is invisible to source review, since the source plainly shows the call. The impact depends entirely on what the discarded expression did, but any contract that took a .selector from a call with side effects on an affected build is suspect.

How to Resolve

Recompile with a fixed compiler, or rebuild the same source through the IR pipeline.

// Affected: legacy-pipeline build on 0.6.2 - 0.8.20
pragma solidity 0.8.19;

contract Registry {
    uint256 public count;

    function register() public returns (bytes4) {
        count += 1;          // side effect
        return this.register.selector;
    }

    function use() external returns (bytes4) {
        // On a legacy build, register()'s state write may be dropped here.
        return register().selector;
    }
}

// Fixed: compile with 0.8.21 or later, or enable the IR pipeline (viaIR: true)
pragma solidity 0.8.21;

For a manual audit, search the source for foo().bar.selector or any <expr>.<fn>.selector where <expr> has side effects — state writes, new, external calls, or emits — because those side effects were silently dropped under legacy code generation.

Detection Methodology

The detector relies on recovered compiler metadata. It confirms the version falls inside the affected range and that the legacy code generation path was used, suppressing the finding when the IR pipeline is known to have been active. Because the bug emits fewer opcodes rather than a distinctive sequence, there is no positive bytecode pattern to match; the only structural requirement is that the recovered program contain at least two functions, which is the minimum surface for the interprocedural selector access the bug depends on. Confidence is reduced when the code generation path cannot be recovered, and further reduced when metadata is unreliable.

Sample Sigvex Output

{
  "detector_id": "solc-2023-07-selector-side-effects",
  "severity": "medium",
  "confidence": 0.7,
  "description": "solc-2023-07-selector-side-effects: Contract was compiled with Solidity 0.8.19, which is in the affected range [0.6.2, 0.8.21). Version is in the affected range and viaIR is confirmed false (legacy codegen).",
  "location": {
    "function": "use",
    "offset": 144
  }
}

Limitations

This detector is metadata-dominated, so its evidence is necessarily weak. It cannot confirm that any .selector access with side effects actually exists in the contract — only that the build is capable of the bug — which makes false positives likely on affected builds that never used the pattern. False negatives occur when compiler metadata or the code generation path cannot be recovered, or when the recovered program has fewer than two functions.

References