Skip to content

Commit ecd8b97

Browse files
authored
Merge pull request #12470 from nishant-sachdeva/equality_operator_for_external_functions
Equality operator allowed for external function types
2 parents a07b3ec + a0d6c11 commit ecd8b97

11 files changed

+244
-12
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Language Features:
44
* General: Support ``ContractName.functionName`` for ``abi.encodeCall``, in addition to external function pointers.
5+
* General: Add equality-comparison operators for external function types.
56

67

78
Compiler Features:

libsolidity/ast/Types.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3018,8 +3018,18 @@ TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _othe
30183018
if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual))
30193019
return nullptr;
30203020
FunctionType const& other = dynamic_cast<FunctionType const&>(*_other);
3021-
if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1)
3021+
if (kind() == Kind::Internal && sizeOnStack() == 1 && other.kind() == Kind::Internal && other.sizeOnStack() == 1)
30223022
return commonType(this, _other);
3023+
else if (
3024+
kind() == Kind::External &&
3025+
sizeOnStack() == 2 &&
3026+
!bound() &&
3027+
other.kind() == Kind::External &&
3028+
other.sizeOnStack() == 2 &&
3029+
!other.bound()
3030+
)
3031+
return commonType(this, _other);
3032+
30233033
return nullptr;
30243034
}
30253035

libsolidity/codegen/ExpressionCompiler.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,26 +2278,44 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO
22782278

22792279
void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& _type)
22802280
{
2281-
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
22822281
if (_operator == Token::Equal || _operator == Token::NotEqual)
22832282
{
2284-
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
2283+
FunctionType const* functionType = dynamic_cast<decltype(functionType)>(&_type);
2284+
if (functionType && functionType->kind() == FunctionType::Kind::External)
22852285
{
2286-
if (funType->kind() == FunctionType::Kind::Internal)
2286+
solUnimplementedAssert(functionType->sizeOnStack() == 2, "");
2287+
m_context << Instruction::SWAP3;
2288+
2289+
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
2290+
m_context << Instruction::SWAP1;
2291+
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
2292+
m_context << Instruction::EQ;
2293+
m_context << Instruction::SWAP2;
2294+
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
2295+
m_context << Instruction::SWAP1;
2296+
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
2297+
m_context << Instruction::EQ;
2298+
m_context << Instruction::AND;
2299+
}
2300+
else
2301+
{
2302+
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
2303+
if (functionType && functionType->kind() == FunctionType::Kind::Internal)
22872304
{
22882305
// We have to remove the upper bits (construction time value) because they might
22892306
// be "unknown" in one of the operands and not in the other.
22902307
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
22912308
m_context << Instruction::SWAP1;
22922309
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
22932310
}
2311+
m_context << Instruction::EQ;
22942312
}
2295-
m_context << Instruction::EQ;
22962313
if (_operator == Token::NotEqual)
22972314
m_context << Instruction::ISZERO;
22982315
}
22992316
else
23002317
{
2318+
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
23012319
bool isSigned = false;
23022320
if (auto type = dynamic_cast<IntegerType const*>(&_type))
23032321
isSigned = type->isSigned();

libsolidity/codegen/YulUtilFunctions.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <libsolutil/FunctionSelector.h>
3030
#include <libsolutil/Whiskers.h>
3131
#include <libsolutil/StringUtils.h>
32+
#include <libsolidity/ast/TypeProvider.h>
3233

3334
using namespace std;
3435
using namespace solidity;
@@ -4548,3 +4549,31 @@ string YulUtilFunctions::externalCodeFunction()
45484549
.render();
45494550
});
45504551
}
4552+
4553+
std::string YulUtilFunctions::externalFunctionPointersEqualFunction()
4554+
{
4555+
std::string const functionName = "externalFunctionPointersEqualFunction";
4556+
return m_functionCollector.createFunction(functionName, [&]() {
4557+
return util::Whiskers(R"(
4558+
function <functionName>(
4559+
leftAddress,
4560+
leftSelector,
4561+
rightAddress,
4562+
rightSelector
4563+
) -> result {
4564+
result := and(
4565+
eq(
4566+
<addressCleanUpFunction>(leftAddress), <addressCleanUpFunction>(rightAddress)
4567+
),
4568+
eq(
4569+
<selectorCleanUpFunction>(leftSelector), <selectorCleanUpFunction>(rightSelector)
4570+
)
4571+
)
4572+
}
4573+
)")
4574+
("functionName", functionName)
4575+
("addressCleanUpFunction", cleanupFunction(*TypeProvider::address()))
4576+
("selectorCleanUpFunction", cleanupFunction(*TypeProvider::uint(32)))
4577+
.render();
4578+
});
4579+
}

libsolidity/codegen/YulUtilFunctions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,9 @@ class YulUtilFunctions
522522
/// Signature: (address) -> mpos
523523
std::string externalCodeFunction();
524524

525+
/// @return the name of a function that that checks if two external functions pointers are equal or not
526+
std::string externalFunctionPointersEqualFunction();
527+
525528
private:
526529
/// @returns the name of a function that copies a struct from calldata or memory to storage
527530
/// signature: (slot, value) ->

libsolidity/codegen/ir/IRGeneratorForStatements.cpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -799,22 +799,34 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
799799

800800
if (TokenTraits::isCompareOp(op))
801801
{
802-
if (auto type = dynamic_cast<FunctionType const*>(commonType))
803-
{
804-
solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!");
805-
solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!");
806-
}
807-
808802
solAssert(commonType->isValueType(), "");
803+
809804
bool isSigned = false;
810805
if (auto type = dynamic_cast<IntegerType const*>(commonType))
811806
isSigned = type->isSigned();
812807

813808
string args = expressionAsType(_binOp.leftExpression(), *commonType, true);
814809
args += ", " + expressionAsType(_binOp.rightExpression(), *commonType, true);
815810

811+
auto functionType = dynamic_cast<FunctionType const*>(commonType);
812+
solAssert(functionType ? (op == Token::Equal || op == Token::NotEqual) : true, "Invalid function pointer comparison!");
813+
816814
string expr;
817-
if (op == Token::Equal)
815+
816+
if (functionType && functionType->kind() == FunctionType::Kind::External)
817+
{
818+
solUnimplementedAssert(functionType->sizeOnStack() == 2, "");
819+
expr = m_utils.externalFunctionPointersEqualFunction() +
820+
"(" +
821+
IRVariable{_binOp.leftExpression()}.part("address").name() + "," +
822+
IRVariable{_binOp.leftExpression()}.part("functionSelector").name() + "," +
823+
IRVariable{_binOp.rightExpression()}.part("address").name() + "," +
824+
IRVariable{_binOp.rightExpression()}.part("functionSelector").name() +
825+
")";
826+
if (op == Token::NotEqual)
827+
expr = "iszero(" + expr + ")";
828+
}
829+
else if (op == Token::Equal)
818830
expr = "eq(" + move(args) + ")";
819831
else if (op == Token::NotEqual)
820832
expr = "iszero(eq(" + move(args) + "))";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
contract C {
2+
function g() external {}
3+
function comparison_operators_for_external_function_pointers_with_dirty_bits() external returns (bool) {
4+
function() external g_ptr_dirty = this.g;
5+
assembly {
6+
g_ptr_dirty.address := or(g_ptr_dirty.address, shl(160, sub(0,1)))
7+
g_ptr_dirty.selector := or(g_ptr_dirty.selector, shl(32, sub(0,1)))
8+
}
9+
function() external g_ptr = this.g;
10+
return g_ptr == g_ptr_dirty;
11+
}
12+
}
13+
// ====
14+
// compileViaYul: also
15+
// EVMVersion: >=constantinople
16+
// ----
17+
// comparison_operators_for_external_function_pointers_with_dirty_bits() -> true
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
contract C {
2+
function f() external {}
3+
function g() external {}
4+
function h() pure external {}
5+
function i() view external {}
6+
7+
function comparison_operators_for_external_functions() public returns (bool) {
8+
assert(
9+
this.f != this.g &&
10+
this.f != this.h &&
11+
this.f != this.i &&
12+
13+
this.g != this.h &&
14+
this.g != this.i &&
15+
16+
this.h != this.i &&
17+
18+
this.f == this.f &&
19+
this.g == this.g &&
20+
this.h == this.h &&
21+
this.i == this.i
22+
);
23+
return true;
24+
}
25+
26+
function comparison_operators_for_local_external_function_pointers() public returns (bool) {
27+
function () external f_local = this.f;
28+
function () external g_local = this.g;
29+
function () external pure h_local = this.h;
30+
function () external view i_local = this.i;
31+
32+
assert(
33+
f_local == this.f &&
34+
g_local == this.g &&
35+
h_local == this.h &&
36+
i_local == this.i &&
37+
38+
f_local != this.g &&
39+
f_local != this.h &&
40+
f_local != this.i &&
41+
42+
g_local != this.f &&
43+
g_local != this.h &&
44+
g_local != this.i &&
45+
46+
h_local != this.f &&
47+
h_local != this.g &&
48+
h_local != this.i &&
49+
50+
i_local != this.f &&
51+
i_local != this.g &&
52+
i_local != this.h
53+
);
54+
55+
assert(
56+
f_local == f_local &&
57+
f_local != g_local &&
58+
f_local != h_local &&
59+
f_local != i_local
60+
);
61+
62+
assert(
63+
g_local == g_local &&
64+
g_local != h_local &&
65+
g_local != i_local
66+
);
67+
68+
assert(
69+
h_local == h_local &&
70+
i_local == i_local &&
71+
h_local != i_local
72+
);
73+
74+
return true;
75+
}
76+
}
77+
// ====
78+
// compileViaYul: also
79+
// ----
80+
// comparison_operators_for_external_functions() -> true
81+
// comparison_operators_for_local_external_function_pointers() -> true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
contract C {
2+
function external_test_function() external {}
3+
function comparison_operator_for_external_function_with_extra_slots() external returns (bool) {
4+
return (
5+
(this.external_test_function{gas: 4} == this.external_test_function) &&
6+
(this.external_test_function{gas: 4} == this.external_test_function{gas: 4})
7+
);
8+
}
9+
}
10+
// ----
11+
// TypeError 2271: (193-259): Operator == not compatible with types function () external and function () external
12+
// TypeError 2271: (277-351): Operator == not compatible with types function () external and function () external
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
contract C {
2+
function external_test_function() external {}
3+
function internal_test_function() internal {}
4+
5+
function comparison_operator_between_internal_and_external_function_pointers() external returns (bool) {
6+
function () external external_function_pointer_local = this.external_test_function;
7+
function () internal internal_function_pointer_local = internal_test_function;
8+
9+
assert(
10+
this.external_test_function == external_function_pointer_local &&
11+
internal_function_pointer_local == internal_test_function
12+
);
13+
assert(
14+
internal_function_pointer_local != external_function_pointer_local &&
15+
internal_test_function != this.external_test_function
16+
);
17+
18+
return true;
19+
}
20+
}
21+
// ----
22+
// TypeError 2271: (606-672): Operator != not compatible with types function () and function () external
23+
// TypeError 2271: (688-741): Operator != not compatible with types function () and function () external

0 commit comments

Comments
 (0)