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