diff --git a/Changelog.md b/Changelog.md index 70e948b88..cd38019be 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.8.10 (unreleased) Language Features: + * Inline Assembly: Support ``.address`` and ``.selector`` on external function pointers to access their address and function selector. Compiler Features: diff --git a/docs/assembly.rst b/docs/assembly.rst index 232e77777..c26a53e49 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -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 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 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 diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index ee0c7d726..6b58ab27c 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -219,13 +219,15 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier) { solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), ""); - static set suffixes{"slot", "offset", "length"}; + static set suffixes{"slot", "offset", "length", "address", "selector"}; string suffix; for (string const& s: suffixes) if (boost::algorithm::ends_with(_identifier.name.str(), "." + 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()); if (!suffix.empty()) { diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index eabf466a7..f11756b25 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -828,7 +828,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) if (!identifierInfo.suffix.empty()) { string const& suffix = identifierInfo.suffix; - solAssert((set{"offset", "slot", "length"}).count(suffix), ""); + solAssert((set{"offset", "slot", "length", "selector", "address"}).count(suffix), ""); if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage))) { if (suffix != "slot" && suffix != "offset") @@ -861,6 +861,19 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) return false; } } + else if (auto const* fpType = dynamic_cast(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 { m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type."); diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 9c08a374f..53931e426 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -211,7 +211,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation struct ExternalIdentifierInfo { 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; size_t valueSize = size_t(-1); }; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index fd79a6f62..326cca43e 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -31,6 +31,7 @@ #include #include +#include #include @@ -178,9 +179,12 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair(variable->type()); + functionType && functionType->kind() == FunctionType::Kind::External + ) + { + solAssert(suffix == "selector" || suffix == "address", ""); + solAssert(variable->type()->sizeOnStack() == 2, ""); + if (suffix == "selector") + stackDiff--; + } else solAssert(false, ""); } @@ -889,6 +899,16 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) solAssert(suffix.empty(), ""); } } + else if ( + auto const* functionType = dynamic_cast(variable->type()); + functionType && functionType->kind() == FunctionType::Kind::External + ) + { + solAssert(suffix == "selector" || suffix == "address", ""); + solAssert(variable->type()->sizeOnStack() == 2, ""); + if (suffix == "selector") + stackDiff--; + } else solAssert(suffix.empty(), ""); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 9298ac9d2..6d7ee86f5 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -188,6 +188,18 @@ private: solAssert(suffix == "offset" || suffix == "length", ""); value = IRVariable{*varDecl}.part(suffix).name(); } + else if ( + auto const* functionType = dynamic_cast(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 solAssert(false, ""); diff --git a/scripts/test_antlr_grammar.sh b/scripts/test_antlr_grammar.sh index 04c1b08ac..97aaa3b93 100755 --- a/scripts/test_antlr_grammar.sh +++ b/scripts/test_antlr_grammar.sh @@ -124,7 +124,9 @@ done < <( # Skipping license error, unrelated to the grammar grep -v -E 'license/license_double5.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=() diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol new file mode 100644 index 000000000..7c3ad6d50 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_assignment.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_assignment.sol new file mode 100644 index 000000000..7550e7785 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_assignment.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector.sol new file mode 100644 index 000000000..99ba2370f --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector_assignment.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector_assignment.sol new file mode 100644 index 000000000..3bec26382 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_selector_assignment.sol @@ -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 diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/external_function_pointer_offset.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/external_function_pointer_offset.sol new file mode 100644 index 000000000..f6abe2976 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/external_function_pointer_offset.sol @@ -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". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_address.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_address.sol new file mode 100644 index 000000000..c252657ca --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_address.sol @@ -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 (). diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_selector.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_selector.sol new file mode 100644 index 000000000..4913bf5d6 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/internal_function_pointer_selector.sol @@ -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 ().