Skip to content

Commit 56f16a4

Browse files
committed
fix function selector as public constant in inheritance
Function selectors have a result type of bytes4, not the usual FunctionType. They were omitted during FunctionCallGraph construction, which triggered the ICE.
1 parent 459ad24 commit 56f16a4

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

libsolidity/analysis/FunctionCallGraph.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
178178
m_graph.bytecodeDependency.emplace(&accessedContractType.contractDefinition(), &_memberAccess);
179179
}
180180

181+
// Handle .selector property access (not function calls named "selector") - the result type is bytes4, not FunctionType
182+
if (memberName == "selector" && !dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
183+
// Check if we're accessing .selector on a function type
184+
if (auto const* exprFunctionType = dynamic_cast<FunctionType const*>(exprType))
185+
// Only track internal functions (they need internal function IDs)
186+
if (exprFunctionType->kind() == FunctionType::Kind::Internal)
187+
{
188+
// Ensure we have a concrete function definition
189+
if (auto const* functionDef = dynamic_cast<FunctionDefinition const*>(&exprFunctionType->declaration()))
190+
{
191+
// Add function to call graph so it gets assigned an internal function ID
192+
functionReferenced(*functionDef, false);
193+
return true;
194+
}
195+
}
196+
197+
181198
auto functionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
182199
auto functionDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
183200
if (!functionType || !functionDef || functionType->kind() != FunctionType::Kind::Internal)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// File-level constant
2+
bytes4 constant FILE_LEVEL_SELECTOR = Base.testFunc.selector;
3+
4+
contract Base {
5+
function testFunc() public {}
6+
}
7+
8+
library TestLib {
9+
function libFunc() public {}
10+
bytes4 public constant LIB_SELECTOR = TestLib.libFunc.selector;
11+
}
12+
13+
contract B {
14+
function g() public {}
15+
}
16+
17+
contract ModifierTest {
18+
function modFunc() public {}
19+
20+
modifier testModifier(bytes4 selector) {
21+
require(selector == this.modFunc.selector);
22+
_;
23+
}
24+
25+
constructor() testModifier(this.modFunc.selector) {
26+
}
27+
}
28+
29+
// Testing constructor args when called after 'is'
30+
contract InheritanceTest is B {
31+
bytes4 public constant INHERIT_SELECTOR = B.g.selector;
32+
33+
constructor(bytes4 expectedSelector) {
34+
assert(INHERIT_SELECTOR == expectedSelector);
35+
}
36+
}
37+
38+
// Unrelated contract with constants
39+
contract UnrelatedContract {
40+
bytes4 public constant UNRELATED_SELECTOR = B.g.selector;
41+
}
42+
43+
// Main test contract - should be last
44+
contract C is B {
45+
bytes4 public constant SELECTOR = B.g.selector;
46+
47+
// State variable initialization
48+
bytes4 public stateSelector = B.g.selector;
49+
50+
// Immutable initialization
51+
bytes4 public immutable IMMUTABLE_SELECTOR = B.g.selector;
52+
53+
constructor() {
54+
assert(SELECTOR == B.g.selector);
55+
assert(stateSelector == B.g.selector);
56+
assert(IMMUTABLE_SELECTOR == B.g.selector);
57+
}
58+
59+
function testFunctionReference() public view returns (bytes4) {
60+
// Using function reference without calling it - just accessing selector
61+
return B.g.selector;
62+
}
63+
64+
function testFileLevelConstant() public pure returns (bytes4) {
65+
return FILE_LEVEL_SELECTOR;
66+
}
67+
68+
function testLibraryConstant() public pure returns (bytes4) {
69+
return TestLib.LIB_SELECTOR;
70+
}
71+
72+
function testUnrelatedConstant() public pure returns (bytes4) {
73+
// Test that we can access selector from unrelated contract's function
74+
return B.g.selector;
75+
}
76+
77+
function testConstantNegation() public pure returns (bytes4) {
78+
// Force ConstantEvaluator to run by using negation on the constant
79+
return ~SELECTOR;
80+
}
81+
82+
function testConstantArithmetic() public pure returns (bytes4) {
83+
// Force ConstantEvaluator to run by using arithmetic on the constant
84+
return bytes4(uint32(SELECTOR) + 1);
85+
}
86+
}
87+
88+
89+
// ----
90+
// SELECTOR() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000
91+
// stateSelector() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000
92+
// IMMUTABLE_SELECTOR() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000
93+
// testFunctionReference() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000
94+
// testFileLevelConstant() -> 0x037a417c00000000000000000000000000000000000000000000000000000000
95+
// testLibraryConstant() -> 0xb1678ce400000000000000000000000000000000000000000000000000000000
96+
// testUnrelatedConstant() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000
97+
// testConstantNegation() -> 0x1de8647100000000000000000000000000000000000000000000000000000000
98+
// testConstantArithmetic() -> 0xe2179b8f00000000000000000000000000000000000000000000000000000000

0 commit comments

Comments
 (0)