Skip to content

Commit

Permalink
Add .address and .selector in inside assembly for external fu…
Browse files Browse the repository at this point in the history
…nction pointers
  • Loading branch information
Marenz committed Oct 4, 2021
1 parent 529087b commit a8d0312
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 4 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
21 changes: 21 additions & 0 deletions docs/assembly.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/analysis/ReferencesResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");

static set<string> suffixes{"slot", "offset", "length"};
static set<string> suffixes{"slot", "offset", "length", "address", "selector"};
string suffix;
for (string const& s: suffixes)
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
Expand Down
15 changes: 14 additions & 1 deletion libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (!identifierInfo.suffix.empty())
{
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 (suffix != "slot" && suffix != "offset")
Expand Down Expand Up @@ -861,6 +861,19 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
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
{
m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type.");
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/ASTAnnotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>

#include <boost/algorithm/string/join.hpp>

Expand Down Expand Up @@ -178,9 +179,12 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
tuple["declaration"] = idOrNull(_info.second.declaration);
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");

if (!_info.second.suffix.empty())
tuple["suffix"] = Json::Value(_info.second.suffix);

tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));

return tuple;
}

Expand Down
20 changes: 20 additions & 0 deletions libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,16 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (suffix == "length")
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
solAssert(false, "");
}
Expand Down Expand Up @@ -889,6 +899,16 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
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
solAssert(suffix.empty(), "");

Expand Down
12 changes: 12 additions & 0 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ struct CopyTranslate: public yul::ASTCopier
solAssert(suffix == "offset" || suffix == "length", "");
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
solAssert(false, "");

Expand Down
4 changes: 3 additions & 1 deletion scripts/test_antlr_grammar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=()
Expand Down
2 changes: 2 additions & 0 deletions test/libsolidity/ASTJSON/assembly/slot_offset.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"declaration": 6,
"isOffset": true,
"isSlot": false,
"specialAccess": true,
"src": "106:8:1",
"suffix": "offset",
"valueSize": 1
Expand All @@ -199,6 +200,7 @@
"declaration": 6,
"isOffset": false,
"isSlot": true,
"specialAccess": true,
"src": "128:6:1",
"suffix": "slot",
"valueSize": 1
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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".
Original file line number Diff line number Diff line change
@@ -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 ().
Original file line number Diff line number Diff line change
@@ -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 ().

0 comments on commit a8d0312

Please sign in to comment.