#16957·solidity

Warning 6417 (`type(C).runtimeCode` may differ from deployed bytecode) is not emitted when the constructor's inline assembly sits in a modifier

Author: Lokkw0510Created Aug 31, 2026Updated Sep 9, 2026
Labelsbug :bug:low effortlow impact

Description

Warning 6417 tells the user that type(C).runtimeCode might not match what is actually deployed, because C's constructor uses inline assembly. It is not emitted when that inline assembly is written in a modifier applied to the constructor, even though a constructor's modifiers always execute as part of construction.

libsolidity/analysis/StaticAnalyzer.cpp:56-62:

cpp
class Checker: public ASTConstVisitor
{
public:
    Checker(FunctionDefinition const& _f) { _f.accept(*this); }
    bool visit(InlineAssembly const&) override { assemblySeen = true; return false; }
    bool assemblySeen = false;
};

_f.accept(*this) walks the constructor's own FunctionDefinition. That subtree contains the ModifierInvocation nodes (name and arguments) but not the ModifierDefinition bodies, which hang off the contract elsewhere. So assemblySeen stays false and the check at :233 reports no assembly.

Inheritance is handled — checkInternal iterates linearizedBaseContracts, and assembly in a base constructor does warn — so this is one missing edge rather than a check that never traverses.

Expected: warning 6417, as for the same assembly written directly in the constructor body.

Actual: no diagnostic at all.

A sibling analysis in the same directory does look inside constructor modifiers

ViewPureChecker treats a modifier's body as part of the function that applies it, and has a dedicated message for the constructor case (ViewPureChecker.cpp:274-283, error 4006):

solidity
contract A {
    modifier needsValue() { require(msg.value >= 1); _; }
    constructor() needsValue() {}
}
Error: This modifier uses "msg.value" or "callvalue()" and thus the constructor has to be payable.

So the compiler already propagates a constructor modifier's contents to the constructor for one analysis, and does not for this one. That asymmetry is the reason I think this is an oversight rather than a deliberate scope limit.

There is also precedent for treating this exact shape as a bug: #3770"Modifier that uses msg.value should generate a warning if used in non-payable function" — was labelled bug :bug: and fixed by #4590. Same situation: a diagnostic that did not look into the body of a modifier applied to a function.

Environment

  • Compiler version: 0.8.36+commit.8a079791 (the cited lines are byte-identical on current develop; the code dates to 4669b06ab, 2019-01-15, which introduced the warning)
  • Compilation pipeline (legacy, IR, SSA CFG): all — this is in the analysis phase, before codegen
  • Target EVM version (as per compiler settings): default; not EVM-version dependent
  • Framework/IDE (e.g. Foundry, Hardhat, Remix): solc command line directly
  • EVM execution environment / backend / blockchain client: n/a — compile-time only
  • Operating system: macOS 26.5.2

Steps to Reproduce

1. The control — assembly directly in the constructor (warning is emitted)

solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract A { constructor() { assembly { let z := 1 } } }
contract B { function f() external pure returns (bytes memory) { return type(A).runtimeCode; } }
$ solc --bin control.sol
Warning: The constructor of the contract (or its base) uses inline assembly. Because of that,
it might be that the deployed bytecode is different from type(...).runtimeCode.

2. The same assembly moved into a constructor modifier (no warning)

solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract A {
    modifier m() { assembly { let z := 1 } _; }
    constructor() m() {}
}
contract B { function f() external pure returns (bytes memory) { return type(A).runtimeCode; } }
$ solc --bin modifier.sol
(no diagnostics)

3. The case the warning exists for: the deployed bytecode really does differ

solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract A {
    modifier replaceRuntime() { assembly { mstore(0, 0x42) return(0, 32) } _; }
    constructor() replaceRuntime() {}
    function real() external pure returns (uint) { return 1; }
}
contract B { function f() external pure returns (bytes memory) { return type(A).runtimeCode; } }

A's creation code is

6080604052348015600e575f5ffd5b50 6042 5f 52 6020 5f f3 fe
                                 mstore(0, 0x42)  return(0, 32)

so the deployed bytecode is 32 bytes, while type(A).runtimeCode is the normal runtime code containing real(). That is exactly the divergence 6417 describes, and the only diagnostic emitted is an unrelated Warning: Unreachable code. for the now-dead _;.

Notes

I searched the issue tracker before opening this (6417, ConstructorUsesAssembly, runtimeCode with assembly / modifier / "deployed bytecode differs", StaticAnalyzer + modifier, "assembly in modifier", and the PR that introduced the warning, #5775, whose discussion never mentions modifiers) and found nothing covering this. I may still have missed prior art — please close as a duplicate if so.