Skip to content

Fix crash when declaring function selector of parent class as public constant #16053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2910,9 +2910,11 @@ void IRGeneratorForStatements::assignInternalFunctionIDIfNotCalledDirectly(
if (_expression.annotation().calledDirectly)
return;

define(IRVariable(_expression).part("functionIdentifier")) <<
std::to_string(m_context.mostDerivedContract().annotation().internalFunctionIDs.at(&_referencedFunction)) <<
"\n";
auto const it = m_context.mostDerivedContract().annotation().internalFunctionIDs.find(&_referencedFunction);
if (it == m_context.mostDerivedContract().annotation().internalFunctionIDs.end())
return;
Copy link
Member

@cameel cameel May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not declaring the variable is not necessarily the right solution in a general case. It helps in the repro because we're only accessing the selector, so the variable would go unused, but if we were instead trying to get a pointer to the function, we'd end up trying to access a variable that does not exist.

Theoretically, you should be able to reproduce that by making a constant of an internal function type:

function() constant G = B.g;

But we don't allow this:

Error: Initial value for constant variable has to be compile-time constant.

It actually looks to me like an unrelated bug - I can't see a good reason not to treat references to functions as compile-time constants. It's not like functions can change at runtime.

The weird thing is that the crash happens only when you try to access the selector inside a constant initializer, and not e.g. in the body of a function. You should check why. I'd expect assignInternalFunctionIDIfNotCalledDirectly() to be called in this case too so apparently it does find the ID then. I suspect that the actual cause might be in how (or when) internalFunctionIDs is populated.


define(IRVariable(_expression).part("functionIdentifier")) << std::to_string(it->second) << '\n';
m_context.addToInternalDispatch(_referencedFunction);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contract B {
function g() public {}
}

contract C is B {
bytes4 public constant s2 = B.g.selector;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is enough as a repro for the issue, but for completeness you should do something with the constant, because we only run the ConstantEvaluator at that point.

E.g. use negation on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test also does not really check if the value of the constant is correct (I'd have to calculate it and compare to be sure). Would be better to have the test verify it:

assert(s2 == B.g.selector);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bytes4 public constant s2 = B.g.selector;
bytes4 public constant SELECTOR = B.g.selector;

Copy link
Member

@cameel cameel May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some coverage for related cases:

  • Using selector in other places outside of functions. E.g.:
    • constructor args when called as a modifier
    • constructor args when called after is
    • immutable initialization
    • state variable initialization
    • file-level constant
    • constant in an unrelated contract or in a library
  • Using the reference to the function itself (but still without calling it).

}


// ----
// s2() -> 0xe2179b8e00000000000000000000000000000000000000000000000000000000