Merge pull request #12016 from ethereum/external-fp-10358

Add ``.address`` and ``.selector`` in inside assembly for external function pointers
This commit is contained in:
chriseth 2021-10-05 14:48:58 +02:00 committed by GitHub
commit ecfcca1a27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 211 additions and 5 deletions

View File

@ -1,6 +1,7 @@
### 0.8.10 (unreleased) ### 0.8.10 (unreleased)
Language Features: Language Features:
* Inline Assembly: Support ``.address`` and ``.selector`` on external function pointers to access their address and function selector.
Compiler Features: Compiler Features:

View File

@ -136,6 +136,27 @@ evaluate to the address of the variable in calldata, not the value itself.
The variable can also be assigned a new offset, but note that no validation to ensure that The variable can also be assigned a new offset, but note that no validation to ensure that
the variable will not point beyond ``calldatasize()`` is performed. the variable will not point beyond ``calldatasize()`` is performed.
For external function pointers the address and the function selector can be
accessed using ``x.address`` and ``x.selector``.
The selector consists of four right-aligned bytes.
Both values are can be assigned to. For example:
.. code-block:: solidity
:force:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.10 <0.9.0;
contract C {
// Assigns a new selector and address to the return variable @fun
function combineToFunctionPointer(address newAddress, uint newSelector) public pure returns (function() external fun) {
assembly {
fun.selector := newSelector
fun.address := newAddress
}
}
}
For dynamic calldata arrays, you can access For dynamic calldata arrays, you can access
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``. their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
Both expressions can also be assigned to, but as for the static case, no validation will be performed Both expressions can also be assigned to, but as for the static case, no validation will be performed

View File

@ -219,13 +219,15 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{ {
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), ""); solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
static set<string> suffixes{"slot", "offset", "length"}; static set<string> suffixes{"slot", "offset", "length", "address", "selector"};
string suffix; string suffix;
for (string const& s: suffixes) for (string const& s: suffixes)
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s)) if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
suffix = s; suffix = s;
// Could also use `pathFromCurrentScope`, split by '.' // Could also use `pathFromCurrentScope`, split by '.'.
// If we do that, suffix should only be set for when it has a special
// meaning, not for normal identifierPaths.
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (!suffix.empty()) if (!suffix.empty())
{ {

View File

@ -828,7 +828,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (!identifierInfo.suffix.empty()) if (!identifierInfo.suffix.empty())
{ {
string const& suffix = identifierInfo.suffix; string const& suffix = identifierInfo.suffix;
solAssert((set<string>{"offset", "slot", "length"}).count(suffix), ""); solAssert((set<string>{"offset", "slot", "length", "selector", "address"}).count(suffix), "");
if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage))) if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage)))
{ {
if (suffix != "slot" && suffix != "offset") if (suffix != "slot" && suffix != "offset")
@ -861,6 +861,19 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
return false; return false;
} }
} }
else if (auto const* fpType = dynamic_cast<FunctionTypePointer>(var->type()))
{
if (suffix != "selector" && suffix != "address")
{
m_errorReporter.typeError(9272_error, nativeLocationOf(_identifier), "Variables of type function pointer only support \".selector\" and \".address\".");
return false;
}
if (fpType->kind() != FunctionType::Kind::External)
{
m_errorReporter.typeError(8533_error, nativeLocationOf(_identifier), "Only Variables of type external function pointer support \".selector\" and \".address\".");
return false;
}
}
else else
{ {
m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type."); m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type.");

View File

@ -211,7 +211,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation
struct ExternalIdentifierInfo struct ExternalIdentifierInfo
{ {
Declaration const* declaration = nullptr; Declaration const* declaration = nullptr;
/// Suffix used, one of "slot", "offset", "length" or empty. /// Suffix used, one of "slot", "offset", "length", "address", "selector" or empty.
std::string suffix; std::string suffix;
size_t valueSize = size_t(-1); size_t valueSize = size_t(-1);
}; };

View File

@ -31,6 +31,7 @@
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h> #include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
@ -178,9 +179,12 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
tuple["declaration"] = idOrNull(_info.second.declaration); tuple["declaration"] = idOrNull(_info.second.declaration);
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot"); tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset"); tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");
if (!_info.second.suffix.empty()) if (!_info.second.suffix.empty())
tuple["suffix"] = Json::Value(_info.second.suffix); tuple["suffix"] = Json::Value(_info.second.suffix);
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize)); tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
return tuple; return tuple;
} }

View File

@ -823,6 +823,16 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (suffix == "length") if (suffix == "length")
stackDiff--; stackDiff--;
} }
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(variable->type());
functionType && functionType->kind() == FunctionType::Kind::External
)
{
solAssert(suffix == "selector" || suffix == "address", "");
solAssert(variable->type()->sizeOnStack() == 2, "");
if (suffix == "selector")
stackDiff--;
}
else else
solAssert(false, ""); solAssert(false, "");
} }
@ -889,6 +899,16 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(suffix.empty(), ""); solAssert(suffix.empty(), "");
} }
} }
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(variable->type());
functionType && functionType->kind() == FunctionType::Kind::External
)
{
solAssert(suffix == "selector" || suffix == "address", "");
solAssert(variable->type()->sizeOnStack() == 2, "");
if (suffix == "selector")
stackDiff--;
}
else else
solAssert(suffix.empty(), ""); solAssert(suffix.empty(), "");

View File

@ -188,6 +188,18 @@ private:
solAssert(suffix == "offset" || suffix == "length", ""); solAssert(suffix == "offset" || suffix == "length", "");
value = IRVariable{*varDecl}.part(suffix).name(); value = IRVariable{*varDecl}.part(suffix).name();
} }
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(varDecl->type());
functionType && functionType->kind() == FunctionType::Kind::External
)
{
solAssert(suffix == "selector" || suffix == "address", "");
solAssert(varDecl->type()->sizeOnStack() == 2, "");
if (suffix == "selector")
value = IRVariable{*varDecl}.part("functionSelector").name();
else
value = IRVariable{*varDecl}.part("address").name();
}
else else
solAssert(false, ""); solAssert(false, "");

View File

@ -124,7 +124,9 @@ done < <(
# Skipping license error, unrelated to the grammar # Skipping license error, unrelated to the grammar
grep -v -E 'license/license_double5.sol' | grep -v -E 'license/license_double5.sol' |
grep -v -E 'license/license_hidden_unicode.sol' | grep -v -E 'license/license_hidden_unicode.sol' |
grep -v -E 'license/license_unicode.sol' grep -v -E 'license/license_unicode.sol' |
# Skipping tests with 'something.address' as 'address' as the grammar fails on those
grep -v -E 'inlineAssembly/external_function_pointer_address.*.sol'
) )
YUL_FILES=() YUL_FILES=()

View File

@ -0,0 +1,19 @@
contract C {
function testFunction() external {}
function testYul() public returns (address adr) {
function() external fp = this.testFunction;
assembly {
adr := fp.address
}
}
function testSol() public returns (address) {
return this.testFunction.address;
}
}
// ====
// compileViaYul: also
// ----
// testYul() -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b
// testSol() -> 0x0fdd67305928fcac8d213d1e47bfa6165cd0b87b

View File

@ -0,0 +1,18 @@
contract C {
function testFunction() external {}
function testYul(address newAddress) view public returns (address adr) {
function() external fp = this.testFunction;
assembly {
fp.address := newAddress
}
return fp.address;
}
}
// ====
// compileViaYul: also
// ----
// testYul(address): 0x1234567890 -> 0x1234567890
// testYul(address): 0xC0FFEE3EA7 -> 0xC0FFEE3EA7

View File

@ -0,0 +1,23 @@
contract C {
function testFunction() external {}
function testYul() public returns (uint32) {
function() external fp = this.testFunction;
uint selectorValue = 0;
assembly {
selectorValue := fp.selector
}
// Value is right-aligned, we shift it so it can be compared
return uint32(bytes4(bytes32(selectorValue << (256 - 32))));
}
function testSol() public returns (uint32) {
return uint32(this.testFunction.selector);
}
}
// ====
// compileViaYul: also
// ----
// testYul() -> 0xe16b4a9b
// testSol() -> 0xe16b4a9b

View File

@ -0,0 +1,18 @@
contract C {
function testFunction() external {}
function testYul(uint32 newSelector) view public returns (uint32) {
function() external fp = this.testFunction;
assembly {
fp.selector := newSelector
}
return uint32(fp.selector);
}
}
// ====
// compileViaYul: also
// ----
// testYul(uint32): 0x12345678 -> 0x12345678
// testYul(uint32): 0xABCDEF00 -> 0xABCDEF00

View File

@ -0,0 +1,15 @@
contract C {
function testFunction() external {}
function testYul() public {
function() external fp = this.testFunction;
uint myOffset;
assembly {
myOffset := fp.offset
}
}
}
// ----
// TypeError 9272: (173-182): Variables of type function pointer only support ".selector" and ".address".

View File

@ -0,0 +1,18 @@
contract C {
function testFunction() internal {}
function testYul() public returns (address adr) {
function() internal fp = testFunction;
uint selectorValue = 0;
assembly {
adr := fp.address
}
}
function testSol() public returns (address) {
return testFunction.address;
}
}
// ----
// TypeError 8533: (193-203): Only Variables of type external function pointer support ".selector" and ".address".
// TypeError 9582: (267-287): Member "address" not found or not visible after argument-dependent lookup in function ().

View File

@ -0,0 +1,20 @@
contract C {
function testFunction() internal {}
function testYul() public returns (uint32) {
function() internal fp = testFunction;
uint selectorValue = 0;
assembly {
selectorValue := fp.selector
}
return uint32(bytes4(bytes32(selectorValue)));
}
function testSol() public returns (uint32) {
return uint32(testFunction.selector);
}
}
// ----
// TypeError 8533: (198-209): Only Variables of type external function pointer support ".selector" and ".address".
// TypeError 9582: (329-350): Member "selector" not found or not visible after argument-dependent lookup in function ().