Skip to content

solc: std::bad_cast in code generation for require(false, (CustomError(1))) #16683

Description

@msooseth

A small program that calls require with a custom-error expression wrapped in
parentheses as the second argument is accepted by the type checker, but
crashes both code-generation pipelines (legacy and via-IR) with an uncaught
std::bad_cast originating from a dynamic_cast inside the FunctionCall
visitor.

solc itself catches the exception at its top-level handler and exits with
status 2, so the bug surfaces as Uncaught exception: std::bad_cast on the
command line. In any embedding of solc that does not catch std::exception
for example our AFL differential harness — the same input terminates the
process via std::terminate / SIGABRT.

Environment

component revision
solidity-fuzzing repo c07ea6b6afbceecdf4b76cee8f974d8b2754ca27
solidity submodule b83005c900d356e82f4d2da52be1601e1d8b3539
solidity version string 0.8.35-develop.2026.5.6+commit.b83005c9.mod.Linux.g++ (v0.8.35-pre.1-97-gb83005c90)
host compiler g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
OS Linux 6.8.0-52-generic, x86_64

Reproducing input (inline)

File bad_cast.sol — 265 bytes, sha256 e087829af451064baed6b7995555cac59c2061a01a68b7bd2b2ac54900de96ae:

error CustomError(uint256);

contract C
{
    function f() public pure returns (uint256)
    {
        require(false, (CustomError(1)));
        return 2;
    }
}

// ----
// TypeError 9322: (118-125): No matching declaration found after argument-dependent lookup.

How to reproduce

The bug shows up the moment any code-generation phase is requested. The
type-checker alone (solc bad_cast.sol with no codegen flag) accepts the
program and only emits unrelated SPDX/pragma warnings.

Crashes both pipelines:

$ ./build/solidity/solc/solc --bin bad_cast.sol
Warning: This is a pre-release compiler version, please do not use it in production.

Warning: SPDX license identifier not provided in source file. ...
Warning: Source file does not specify required compiler version!

Uncaught exception:
Dynamic exception type: std::bad_cast
std::exception::what: std::bad_cast
$ echo $?
2

$ ./build/solidity/solc/solc --bin --via-ir bad_cast.sol
... (same warnings) ...
Uncaught exception:
Dynamic exception type: std::bad_cast
std::exception::what: std::bad_cast

$ ./build/solidity/solc/solc --ir bad_cast.sol
... (same warnings) ...
Uncaught exception:
Dynamic exception type: std::bad_cast
std::exception::what: std::bad_cast

Crash signature

Uncaught exception:
Dynamic exception type: std::bad_cast
std::exception::what: std::bad_cast

The exception originates from __cxa_bad_cast — i.e. a dynamic_cast to a
reference type returned nullptr/bad_cast because the runtime type does not
match what code generation expected for the AST node it was visiting.

Backtraces

Captured under gdb with catch throw std::bad_cast (process is
./build/solidity/solc/solc).

Legacy codegen — --bin

#0  __cxa_throw                              (libstdc++.so.6)
#1  __cxa_bad_cast                           (libstdc++.so.6)
#2  solidity::frontend::ExpressionCompiler::visit(solidity::frontend::FunctionCall const&) [clone .cold]
#3  solidity::frontend::FunctionCall::accept(solidity::frontend::ASTConstVisitor&) const
#4  solidity::frontend::ContractCompiler::compileExpression(...) [clone .constprop.0]
#5  solidity::frontend::ContractCompiler::visit(solidity::frontend::ExpressionStatement const&)
#6  solidity::frontend::ExpressionStatement::accept(solidity::frontend::ASTConstVisitor&) const
#7  solidity::frontend::Block::accept(solidity::frontend::ASTConstVisitor&) const
#8  solidity::frontend::ContractCompiler::appendModifierOrFunctionCode()
#9  solidity::frontend::ContractCompiler::visit(solidity::frontend::FunctionDefinition const&)
#10 solidity::frontend::FunctionDefinition::accept(solidity::frontend::ASTConstVisitor&) const
#11 solidity::frontend::ContractCompiler::appendMissingFunctions()
#12 solidity::frontend::ContractCompiler::packIntoContractCreator(solidity::frontend::ContractDefinition const&)
#13 solidity::frontend::ContractCompiler::compileConstructor(...)
#14 solidity::frontend::Compiler::compileContract(...)
#15 solidity::frontend::CompilerStack::compileContract(...)
#16 solidity::frontend::CompilerStack::compile(solidity::frontend::CompilerStack::State)
#17 solidity::frontend::CommandLineInterface::compile()
#18 solidity::frontend::CommandLineInterface::processInput()
#19 solidity::frontend::CommandLineInterface::run(int, char const* const*)
#20 main

The throwing frame is
solidity::frontend::ExpressionCompiler::visit(FunctionCall const&).

Via-IR codegen — --bin --via-ir (and --ir)

#0  __cxa_throw                              (libstdc++.so.6)
#1  __cxa_bad_cast                           (libstdc++.so.6)
#2  solidity::frontend::IRGeneratorForStatements::endVisit(solidity::frontend::FunctionCall const&) [clone .cold]
#3  solidity::frontend::ExpressionStatement::accept(solidity::frontend::ASTConstVisitor&) const
#4  solidity::frontend::Block::accept(solidity::frontend::ASTConstVisitor&) const
#5  solidity::frontend::IRGeneratorForStatements::generate(solidity::frontend::Block const&)
#6  solidity::frontend::IRGenerator::generate[abi:cxx11](solidity::frontend::Block const&)
#7  solidity::frontend::IRGenerator::generateFunction[abi:cxx11](solidity::frontend::FunctionDefinition const&)::{lambda()#1}::operator()() const
#8  std::_Function_handler<...>::_M_invoke(std::_Any_data const&)
#9  solidity::frontend::MultiUseYulFunctionCollector::createFunction(...)
#10 solidity::frontend::IRGenerator::generateFunction[abi:cxx11](...)
#11 solidity::frontend::IRGenerator::generateQueuedFunctions()
#12 solidity::frontend::IRGenerator::generate[abi:cxx11](...)
#13 solidity::frontend::IRGenerator::run[abi:cxx11](...)
#14 solidity::frontend::CompilerStack::generateIR(...)
#15 solidity::frontend::CompilerStack::compile(solidity::frontend::CompilerStack::State)
#16 solidity::frontend::CommandLineInterface::compile()
#17 solidity::frontend::CommandLineInterface::processInput()
#18 solidity::frontend::CommandLineInterface::run(int, char const* const*)
#19 main

The throwing frame is
solidity::frontend::IRGeneratorForStatements::endVisit(FunctionCall const&).

The two pipelines have entirely separate codegen visitors but both crash on the
same input — pointing to a shared upstream invariant that the analysis stage
should have enforced (or that both visitors should handle robustly) for the
require(bool, parenthesised-error-expression) shape.

Notes

  • The unwrapped form require(false, CustomError(1)) (no surrounding parens)
    is intentionally supported since 0.8.27 and compiles cleanly.
  • The exact mutation that triggers the crash is the parenthesisation around
    the error-call argument — (CustomError(1)) — which apparently changes the
    expression's resolved type just enough for the codegen dynamic_cast to
    fail, while leaving the type-checker satisfied.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions