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.
A small program that calls
requirewith a custom-error expression wrapped inparentheses 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_castoriginating from adynamic_castinside theFunctionCallvisitor.
solcitself catches the exception at its top-level handler and exits withstatus 2, so the bug surfaces as
Uncaught exception: std::bad_caston thecommand 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
solidity-fuzzingrepoc07ea6b6afbceecdf4b76cee8f974d8b2754ca27soliditysubmoduleb83005c900d356e82f4d2da52be1601e1d8b3539solidityversion string0.8.35-develop.2026.5.6+commit.b83005c9.mod.Linux.g++(v0.8.35-pre.1-97-gb83005c90)Reproducing input (inline)
File
bad_cast.sol— 265 bytes,sha256 e087829af451064baed6b7995555cac59c2061a01a68b7bd2b2ac54900de96ae:How to reproduce
The bug shows up the moment any code-generation phase is requested. The
type-checker alone (
solc bad_cast.solwith no codegen flag) accepts theprogram and only emits unrelated SPDX/pragma warnings.
Crashes both pipelines:
Crash signature
The exception originates from
__cxa_bad_cast— i.e. adynamic_castto areference type returned
nullptr/bad_castbecause the runtime type does notmatch what code generation expected for the AST node it was visiting.
Backtraces
Captured under
gdbwithcatch throw std::bad_cast(process is./build/solidity/solc/solc).Legacy codegen —
--binThe throwing frame is
solidity::frontend::ExpressionCompiler::visit(FunctionCall const&).Via-IR codegen —
--bin --via-ir(and--ir)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
require(false, CustomError(1))(no surrounding parens)is intentionally supported since 0.8.27 and compiles cleanly.
the error-call argument —
(CustomError(1))— which apparently changes theexpression's resolved type just enough for the codegen
dynamic_casttofail, while leaving the type-checker satisfied.