diff --git a/Changelog.md b/Changelog.md index 8e991f703..9b4826386 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: * Build system: Update the soljson.js build to emscripten 2.0.12 and boost 1.75.0. + * Code Generator: Reduce the cost of ``
.code.length`` by using ``extcodesize`` directly. * Command Line Interface: Allow "=" as separator between library name and address in ``--libraries`` commandline option. * Command Line Interface: New option ``--model-checker-targets`` allows specifying which targets should be checked. The valid options are ``all``, ``constantCondition``, ``underflow``, ``overflow``, ``divByZero``, ``balance``, ``assert``, ``popEmptyArray``, where the default is ``all``. Multiple targets can be chosen at the same time, separated by a comma without spaces: ``underflow,overflow,assert``. * Command Line Interface: Only accept the library address that is prefixed with "0x" in ``--libraries`` commandline option. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2abf03453..4a3b7c7b4 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1473,6 +1473,29 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) return false; } + // Another special case for `address.code.length`, which should simply call extcodesize + if ( + auto innerExpression = dynamic_cast(&_memberAccess.expression()); + member == "length" && + innerExpression && + innerExpression->memberName() == "code" && + innerExpression->expression().annotation().type->category() == Type::Category::Address + ) + { + solAssert(innerExpression->annotation().type->category() == Type::Category::Array, ""); + + innerExpression->expression().accept(*this); + + utils().convertType( + *innerExpression->expression().annotation().type, + *TypeProvider::address(), + true + ); + m_context << Instruction::EXTCODESIZE; + + return false; + } + _memberAccess.expression().accept(*this); switch (_memberAccess.expression().annotation().type->category()) { diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 392c47ca7..e8fc1f55d 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1554,6 +1554,28 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options) } } +bool IRGeneratorForStatements::visit(MemberAccess const& _memberAccess) +{ + // A shortcut for
.code.length. We skip visiting
.code and directly visit + //
. The actual code is generated in endVisit. + if ( + auto innerExpression = dynamic_cast(&_memberAccess.expression()); + _memberAccess.memberName() == "length" && + innerExpression && + innerExpression->memberName() == "code" && + innerExpression->expression().annotation().type->category() == Type::Category::Address + ) + { + solAssert(innerExpression->annotation().type->category() == Type::Category::Array, ""); + // Skip visiting
.code + innerExpression->expression().accept(*this); + + return false; + } + + return true; +} + void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { setLocation(_memberAccess); @@ -1846,13 +1868,26 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) case Type::Category::Array: { auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); - if (member == "length") - define(_memberAccess) << - m_utils.arrayLengthFunction(type) << - "(" << - IRVariable(_memberAccess.expression()).commaSeparatedList() << - ")\n"; + { + // shortcut for
.code.length + if ( + auto innerExpression = dynamic_cast(&_memberAccess.expression()); + innerExpression && + innerExpression->memberName() == "code" && + innerExpression->expression().annotation().type->category() == Type::Category::Address + ) + define(_memberAccess) << + "extcodesize(" << + expressionAsType(innerExpression->expression(), *TypeProvider::address()) << + ")\n"; + else + define(_memberAccess) << + m_utils.arrayLengthFunction(type) << + "(" << + IRVariable(_memberAccess.expression()).commaSeparatedList() << + ")\n"; + } else if (member == "pop" || member == "push") { solAssert(type.location() == DataLocation::Storage, ""); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index e03215966..b978e632d 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -87,6 +87,7 @@ public: bool visit(BinaryOperation const& _binOp) override; void endVisit(FunctionCall const& _funCall) override; void endVisit(FunctionCallOptions const& _funCallOptions) override; + bool visit(MemberAccess const& _memberAccess) override; void endVisit(MemberAccess const& _memberAccess) override; bool visit(InlineAssembly const& _inlineAsm) override; void endVisit(IndexAccess const& _indexAccess) override; diff --git a/test/libsolidity/semanticTests/various/code_length.sol b/test/libsolidity/semanticTests/various/code_length.sol new file mode 100644 index 000000000..a807933a6 --- /dev/null +++ b/test/libsolidity/semanticTests/various/code_length.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0 +contract C { + uint len1; + uint len2; + constructor() { + uint mem_ptr_before; + uint mem_ptr_after; + + assembly { + mem_ptr_before := mload(64) + } + + len1 = address(0).code.length; + + assembly { + mem_ptr_after := mload(64) + } + + // To check that no memory was allocated and written. + assert(mem_ptr_before == mem_ptr_after); + + len2 = address(this).code.length; + + // To check that no memory was allocated and written. + assembly { + mem_ptr_after := mload(64) + } + + assert(mem_ptr_before == mem_ptr_after); + + } + + function f() public view returns (bool r1, bool r2) { + uint mem_ptr_before; + uint mem_ptr_after; + + assembly { + mem_ptr_before := mload(64) + } + + r1 = address(this).code.length > 50; + + assembly { + mem_ptr_after := mload(64) + } + + // To check that no memory was allocated and written. + assert(mem_ptr_before == mem_ptr_after); + + address a = address(0); + r2 = a.code.length == 0; + + // To check that no memory was allocated and written. + assembly { + mem_ptr_after := mload(64) + } + + } +} +// ==== +// compileViaYul: also +// ---- +// constructor() +// f(): true, true -> true, true diff --git a/test/libsolidity/semanticTests/various/code_length_contract_member.sol b/test/libsolidity/semanticTests/various/code_length_contract_member.sol new file mode 100644 index 000000000..a6184a318 --- /dev/null +++ b/test/libsolidity/semanticTests/various/code_length_contract_member.sol @@ -0,0 +1,17 @@ +// Test to see if type.code.length does extcodesize(type) only when type is an address. +struct S { + bytes32 code; + bytes32 another; +} + +contract C { + S s; + + function f() public returns (uint, uint, bool) { + return (s.code.length, s.another.length, address(this).code.length > 50); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 0x20, 0x20, true