aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity/codegen
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity/codegen')
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp61
-rw-r--r--libsolidity/codegen/ABIFunctions.h4
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp96
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/Compiler.cpp13
-rw-r--r--libsolidity/codegen/Compiler.h6
-rw-r--r--libsolidity/codegen/CompilerContext.cpp27
-rw-r--r--libsolidity/codegen/CompilerContext.h6
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp153
-rw-r--r--libsolidity/codegen/CompilerUtils.h12
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp198
-rw-r--r--libsolidity/codegen/ContractCompiler.h32
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp364
13 files changed, 581 insertions, 396 deletions
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index 3e3aa0ae..6c27533c 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -17,7 +17,7 @@
/**
* @author Christian <chris@ethereum.org>
* @date 2017
- * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ * Routines that generate Yul code related to ABI encoding, decoding and type conversions.
*/
#include <libsolidity/codegen/ABIFunctions.h>
@@ -197,6 +197,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
templ("functionName", functionName);
switch (_type.category())
{
+ case Type::Category::Address:
+ templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)");
+ break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
@@ -228,7 +231,8 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
if (type.numBytes() == 32)
templ("body", "cleaned := value");
else if (type.numBytes() == 0)
- templ("body", "cleaned := 0");
+ // This is disallowed in the type system.
+ solAssert(false, "");
else
{
size_t numBits = type.numBytes() * 8;
@@ -238,8 +242,14 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
break;
}
case Type::Category::Contract:
- templ("body", "cleaned := " + cleanupFunction(IntegerType(160, IntegerType::Modifier::Address)) + "(value)");
+ {
+ AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
+ StateMutability::Payable :
+ StateMutability::NonPayable
+ );
+ templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
break;
+ }
case Type::Category::Enum:
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
@@ -283,6 +293,12 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
auto fromCategory = _from.category();
switch (fromCategory)
{
+ case Type::Category::Address:
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(IntegerType(160), _to))
+ .render();
+ break;
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Contract:
@@ -313,16 +329,19 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
.render();
}
else if (toCategory == Type::Category::FixedPoint)
- {
solUnimplemented("Not yet implemented - FixedPointType.");
- }
+ else if (toCategory == Type::Category::Address)
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(_from, IntegerType(160)))
+ .render();
else
{
solAssert(
toCategory == Type::Category::Integer ||
toCategory == Type::Category::Contract,
"");
- IntegerType const addressType(160, IntegerType::Modifier::Address);
+ IntegerType const addressType(160);
IntegerType const& to =
toCategory == Type::Category::Integer ?
dynamic_cast<IntegerType const&>(_to) :
@@ -374,6 +393,11 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
("shift", shiftRightFunction(256 - from.numBytes() * 8))
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
.render();
+ else if (toCategory == Type::Category::Address)
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(_from, IntegerType(160)))
+ .render();
else
{
// clear for conversion to longer bytes
@@ -409,7 +433,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
solAssert(false, "");
}
- solAssert(!body.empty(), "");
+ solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
templ("body", body);
return templ.render();
});
@@ -471,13 +495,8 @@ string ABIFunctions::abiEncodingFunction(
bool _fromStack
)
{
- solUnimplementedAssert(
- _to.mobileType() &&
- _to.mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + _to.toString() + "\" not yet implemented."
- );
- TypePointer toInterface = _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer toInterface = _to.fullEncodingType(_encodeAsLibraryTypes, true, false);
+ solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented.");
Type const& to = *toInterface;
if (_from.category() == Type::Category::StringLiteral)
@@ -886,13 +905,8 @@ string ABIFunctions::abiEncodingFunctionStruct(
solAssert(member.type, "");
if (!member.type->canLiveOutsideStorage())
continue;
- solUnimplementedAssert(
- member.type->mobileType() &&
- member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + member.type->toString() + "\" not yet implemented."
- );
- auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer memberTypeTo = member.type->fullEncodingType(_encodeAsLibraryTypes, true, false);
+ solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented.");
auto memberTypeFrom = _from.memberType(member.name);
solAssert(memberTypeFrom, "");
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
@@ -989,7 +1003,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
)");
templ("functionName", functionName);
- // TODO this can make use of CODECOPY for large strings once we have that in JULIA
+ // TODO this can make use of CODECOPY for large strings once we have that in Yul
size_t words = (value.size() + 31) / 32;
templ("overallSize", to_string(32 + words * 32));
templ("length", to_string(value.size()));
@@ -1188,7 +1202,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
if (!_type.isDynamicallySized())
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
- solAssert(!_type.baseType()->isDynamicallyEncoded(), "");
+ if (_type.baseType()->isDynamicallyEncoded())
+ solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity.");
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
string functionName =
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
index db4d40f5..3caaa1d9 100644
--- a/libsolidity/codegen/ABIFunctions.h
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -17,7 +17,7 @@
/**
* @author Christian <chris@ethereum.org>
* @date 2017
- * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ * Routines that generate Yul code related to ABI encoding, decoding and type conversions.
*/
#pragma once
@@ -203,7 +203,7 @@ private:
std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required
/// to store an array in memory given its length (internally encoded, not ABI encoded).
- /// The function reverts for too large lengthes.
+ /// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 0fe66d2d..2b77db8f 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -303,12 +303,17 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
m_context << _sourceType.length();
if (baseSize > 1)
m_context << u256(baseSize) << Instruction::MUL;
- // stack: target source_offset source_len
- m_context << Instruction::DUP1 << Instruction::DUP3 << Instruction::DUP5;
- // stack: target source_offset source_len source_len source_offset target
- m_context << Instruction::CALLDATACOPY;
- m_context << Instruction::DUP3 << Instruction::ADD;
- m_context << Instruction::SWAP2 << Instruction::POP << Instruction::POP;
+
+ string routine = "calldatacopy(target, source, len)\n";
+ if (_padToWordBoundaries)
+ routine += R"(
+ // Set padding suffix to zero
+ mstore(add(target, len), 0)
+ len := and(add(len, 0x1f), not(0x1f))
+ )";
+ routine += "target := add(target, len)\n";
+ m_context.appendInlineAssembly("{" + routine + "}", {"target", "source", "len"});
+ m_context << Instruction::POP << Instruction::POP;
}
else if (_sourceType.location() == DataLocation::Memory)
{
@@ -823,6 +828,85 @@ void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const
})", {"ref"});
}
+void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
+{
+ solAssert(_type.location() == DataLocation::Storage, "");
+ solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+
+ if (_type.isByteArray())
+ {
+ m_context.appendInlineAssembly(R"({
+ let slot_value := sload(ref)
+ switch and(slot_value, 1)
+ case 0 {
+ // short byte array
+ let length := and(div(slot_value, 2), 0x1f)
+ if iszero(length) { invalid() }
+
+ // Zero-out the suffix including the least significant byte.
+ let mask := sub(exp(0x100, sub(33, length)), 1)
+ length := sub(length, 1)
+ slot_value := or(and(not(mask), slot_value), mul(length, 2))
+ sstore(ref, slot_value)
+ }
+ case 1 {
+ // long byte array
+ mstore(0, ref)
+ let length := div(slot_value, 2)
+ let slot := keccak256(0, 0x20)
+ switch length
+ case 32
+ {
+ let data := sload(slot)
+ sstore(slot, 0)
+ data := and(data, not(0xff))
+ sstore(ref, or(data, 62))
+ }
+ default
+ {
+ let offset_inside_slot := and(sub(length, 1), 0x1f)
+ slot := add(slot, div(sub(length, 1), 32))
+ let data := sload(slot)
+
+ // Zero-out the suffix of the byte array by masking it.
+ // ((1<<(8 * (32 - offset))) - 1)
+ let mask := sub(exp(0x100, sub(32, offset_inside_slot)), 1)
+ data := and(not(mask), data)
+ sstore(slot, data)
+
+ // Reduce the length by 1
+ slot_value := sub(slot_value, 2)
+ sstore(ref, slot_value)
+ }
+ }
+ })", {"ref"});
+ m_context << Instruction::POP;
+ }
+ else
+ {
+ // stack: ArrayReference
+ retrieveLength(_type);
+ // stack: ArrayReference oldLength
+ m_context << Instruction::DUP1;
+ // stack: ArrayReference oldLength oldLength
+ m_context << Instruction::ISZERO;
+ m_context.appendConditionalInvalid();
+
+ // Stack: ArrayReference oldLength
+ m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB;
+ // Stack ArrayReference newLength
+ m_context << Instruction::DUP2 << Instruction::DUP2;
+ // Stack ArrayReference newLength ArrayReference newLength;
+ accessIndex(_type, false);
+ // Stack: ArrayReference newLength storage_slot byte_offset
+ StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ // Stack: ArrayReference newLength
+ m_context << Instruction::SWAP1 << Instruction::SSTORE;
+ }
+}
+
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
m_context.callLowLevelFunction(
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index 99786397..daf50bf5 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -73,6 +73,11 @@ public:
/// Stack pre: reference (excludes byte offset)
/// Stack post: new_length
void incrementDynamicArraySize(ArrayType const& _type) const;
+ /// Decrements the size of a dynamic array by one if length is nonzero. Causes an invalid instruction otherwise.
+ /// Clears the removed data element. In case of a byte array, this might move the data.
+ /// Stack pre: reference
+ /// Stack post:
+ void popStorageArrayElement(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp
index d3afada5..55f1d252 100644
--- a/libsolidity/codegen/Compiler.cpp
+++ b/libsolidity/codegen/Compiler.cpp
@@ -46,19 +46,6 @@ void Compiler::compileContract(
m_context.optimise(m_optimize, m_optimizeRuns);
}
-void Compiler::compileClone(
- ContractDefinition const& _contract,
- map<ContractDefinition const*, eth::Assembly const*> const& _contracts
-)
-{
- solAssert(!_contract.isLibrary(), "");
- ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
- ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize);
- m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts);
-
- m_context.optimise(m_optimize, m_optimizeRuns);
-}
-
eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const
{
return m_runtimeContext.functionEntryLabelIfExists(_function);
diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h
index f6865d75..4028ae63 100644
--- a/libsolidity/codegen/Compiler.h
+++ b/libsolidity/codegen/Compiler.h
@@ -50,12 +50,6 @@ public:
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts,
bytes const& _metadata
);
- /// Compiles a contract that uses DELEGATECALL to call into a pre-deployed version of the given
- /// contract at runtime, but contains the full creation-time code.
- void compileClone(
- ContractDefinition const& _contract,
- std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
- );
/// @returns Entire assembly.
eth::Assembly const& assembly() const { return m_context.assembly(); }
/// @returns The entire assembled object (with constructor).
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index a35eea73..71b615b8 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -127,10 +127,14 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, "");
+ unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack();
+ // Variables should not have stack size other than [1, 2],
+ // but that might change when new types are introduced.
+ solAssert(sizeOnStack == 1 || sizeOnStack == 2, "");
m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent);
}
-void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
+void CompilerContext::removeVariable(Declaration const& _declaration)
{
solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), "");
m_localVariables[&_declaration].pop_back();
@@ -138,6 +142,25 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
m_localVariables.erase(&_declaration);
}
+void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight)
+{
+ vector<Declaration const*> toRemove;
+ for (auto _var: m_localVariables)
+ {
+ solAssert(!_var.second.empty(), "");
+ solAssert(_var.second.back() <= stackHeight(), "");
+ if (_var.second.back() >= _stackHeight)
+ toRemove.push_back(_var.first);
+ }
+ for (auto _var: toRemove)
+ removeVariable(*_var);
+}
+
+unsigned CompilerContext::numberOfLocalVariables() const
+{
+ return m_localVariables.size();
+}
+
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const
{
auto ret = m_compiledContracts.find(&_contract);
@@ -388,7 +411,7 @@ FunctionDefinition const& CompilerContext::resolveVirtualFunction(
if (
function->name() == name &&
!function->isConstructor() &&
- FunctionType(*function).hasEqualArgumentTypes(functionType)
+ FunctionType(*function).hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 5776b5d1..3f357821 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -71,7 +71,11 @@ public:
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
- void removeVariable(VariableDeclaration const& _declaration);
+ void removeVariable(Declaration const& _declaration);
+ /// Removes all local variables currently allocated above _stackHeight.
+ void removeVariablesAboveStackHeight(unsigned _stackHeight);
+ /// Returns the number of currently allocated local variables.
+ unsigned numberOfLocalVariables() const;
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index d9f17263..e6ad6d9c 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -181,12 +181,12 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
}
}
-void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory, bool _revertOnOutOfBounds)
+void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory)
{
/// Stack: <source_offset> <length>
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
{
- // Use the new JULIA-based decoding function
+ // Use the new Yul-based decoding function
auto stackHeightBefore = m_context.stackHeight();
abiDecodeV2(_typeParameters, _fromMemory);
solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, "");
@@ -194,14 +194,10 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
}
//@todo this does not yet support nested dynamic arrays
-
- if (_revertOnOutOfBounds)
- {
- size_t encodedSize = 0;
- for (auto const& t: _typeParameters)
- encodedSize += t->decodingType()->calldataEncodedSize(true);
- m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
- }
+ size_t encodedSize = 0;
+ for (auto const& t: _typeParameters)
+ encodedSize += t->decodingType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
@@ -231,26 +227,21 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
{
// compute data pointer
m_context << Instruction::DUP1 << Instruction::MLOAD;
- if (_revertOnOutOfBounds)
- {
- // Check that the data pointer is valid and that length times
- // item size is still inside the range.
- Whiskers templ(R"({
- if gt(ptr, 0x100000000) { revert(0, 0) }
- ptr := add(ptr, base_offset)
- let array_data_start := add(ptr, 0x20)
- if gt(array_data_start, input_end) { revert(0, 0) }
- let array_length := mload(ptr)
- if or(
- gt(array_length, 0x100000000),
- gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
- ) { revert(0, 0) }
- })");
- templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
- m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
- }
- else
- m_context << Instruction::DUP3 << Instruction::ADD;
+ // Check that the data pointer is valid and that length times
+ // item size is still inside the range.
+ Whiskers templ(R"({
+ if gt(ptr, 0x100000000) { revert(0, 0) }
+ ptr := add(ptr, base_offset)
+ let array_data_start := add(ptr, 0x20)
+ if gt(array_data_start, input_end) { revert(0, 0) }
+ let array_length := mload(ptr)
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
+ ) { revert(0, 0) }
+ })");
+ templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
+ m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k)
moveIntoStack(3);
m_context << u256(0x20) << Instruction::ADD;
@@ -273,30 +264,25 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
m_context << Instruction::SWAP1;
// stack: input_end base_offset next_pointer data_offset
- if (_revertOnOutOfBounds)
- m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
+ m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
m_context << Instruction::DUP3 << Instruction::ADD;
// stack: input_end base_offset next_pointer array_head_ptr
- if (_revertOnOutOfBounds)
- m_context.appendInlineAssembly(
- "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
- {"input_end", "base_offset", "next_ptr", "array_head_ptr"}
- );
+ m_context.appendInlineAssembly(
+ "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
+ {"input_end", "base_offset", "next_ptr", "array_head_ptr"}
+ );
// retrieve length
loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
// stack: input_end base_offset next_pointer array_length data_pointer
m_context << Instruction::SWAP2;
// stack: input_end base_offset data_pointer array_length next_pointer
- if (_revertOnOutOfBounds)
- {
- unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
- m_context.appendInlineAssembly(R"({
- if or(
- gt(array_length, 0x100000000),
- gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
- ) { revert(0, 0) }
- })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
- }
+ unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly(R"({
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
+ ) { revert(0, 0) }
+ })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
}
else
{
@@ -347,28 +333,21 @@ void CompilerUtils::encodeToMemory(
)
{
// stack: <v1> <v2> ... <vn> <mem>
+ bool const encoderV2 = m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
solAssert(targetTypes.size() == _givenTypes.size(), "");
for (TypePointer& t: targetTypes)
{
- solUnimplementedAssert(
- t->mobileType() &&
- t->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + t->toString() + "\" not yet implemented."
- );
- t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries);
+ solUnimplementedAssert(tEncoding, "Encoding type \"" + t->toString() + "\" not yet implemented.");
+ t = std::move(tEncoding);
}
if (_givenTypes.empty())
return;
- else if (
- _padToWordBoundaries &&
- !_copyDynamicDataInPlace &&
- m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
- )
+ else if (_padToWordBoundaries && !_copyDynamicDataInPlace && encoderV2)
{
- // Use the new JULIA-based encoding function
+ // Use the new Yul-based encoding function
auto stackHeightBefore = m_context.stackHeight();
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes);
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
@@ -377,8 +356,8 @@ void CompilerUtils::encodeToMemory(
// Stack during operation:
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
- // The values dyn_head_i are added during the first loop and they point to the head part
- // of the ith dynamic parameter, which is filled once the dynamic parts are processed.
+ // The values dyn_head_n are added during the first loop and they point to the head part
+ // of the nth dynamic parameter, which is filled once the dynamic parts are processed.
// store memory start pointer
m_context << Instruction::DUP1;
@@ -524,7 +503,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
codecopy(memptr, codesize(), size)
memptr := add(memptr, size)
})");
- templ("element_size", to_string(_type.baseType()->memoryHeadSize()));
+ templ("element_size", to_string(_type.isByteArray() ? 1 : _type.baseType()->memoryHeadSize()));
m_context.appendInlineAssembly(templ.render(), {"length", "memptr"});
}
else
@@ -678,6 +657,11 @@ void CompilerUtils::convertType(
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
+ else if (targetTypeCategory == Type::Category::Address)
+ {
+ solAssert(typeOnStack.numBytes() * 8 == 160, "");
+ rightShiftNumberOnStack(256 - 160);
+ }
else
{
// clear for conversion to longer bytes
@@ -711,23 +695,33 @@ void CompilerUtils::convertType(
break;
case Type::Category::FixedPoint:
solUnimplemented("Not yet implemented - FixedPointType.");
+ case Type::Category::Address:
case Type::Category::Integer:
case Type::Category::Contract:
case Type::Category::RationalNumber:
if (targetTypeCategory == Type::Category::FixedBytes)
{
- solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber,
- "Invalid conversion to FixedBytesType requested.");
+ solAssert(
+ stackTypeCategory == Type::Category::Address ||
+ stackTypeCategory == Type::Category::Integer ||
+ stackTypeCategory == Type::Category::RationalNumber,
+ "Invalid conversion to FixedBytesType requested."
+ );
// conversion from bytes to string. no need to clean the high bit
// only to shift left because of opposite alignment
FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
+ {
if (targetBytesType.numBytes() * 8 > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
+ }
+ else if (stackTypeCategory == Type::Category::Address)
+ solAssert(targetBytesType.numBytes() * 8 == 160, "");
leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8);
}
else if (targetTypeCategory == Type::Category::Enum)
{
+ solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested.");
solAssert(_typeOnStack.mobileType(), "");
// just clean
convertType(_typeOnStack, *_typeOnStack.mobileType(), true);
@@ -754,8 +748,8 @@ void CompilerUtils::convertType(
}
else
{
- solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
- IntegerType addressType(160, IntegerType::Modifier::Address);
+ solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, "");
+ IntegerType addressType(160);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_targetType) : addressType;
if (stackTypeCategory == Type::Category::RationalNumber)
@@ -968,20 +962,12 @@ void CompilerUtils::convertType(
{
TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack);
TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType);
- // fillRight: remove excess values at right side, !fillRight: remove eccess values at left side
- bool fillRight = !targetTuple.components().empty() && (
- !targetTuple.components().back() ||
- targetTuple.components().front()
- );
+ solAssert(targetTuple.components().size() == sourceTuple.components().size(), "");
unsigned depth = sourceTuple.sizeOnStack();
for (size_t i = 0; i < sourceTuple.components().size(); ++i)
{
TypePointer sourceType = sourceTuple.components()[i];
- TypePointer targetType;
- if (fillRight && i < targetTuple.components().size())
- targetType = targetTuple.components()[i];
- else if (!fillRight && targetTuple.components().size() + i >= sourceTuple.components().size())
- targetType = targetTuple.components()[targetTuple.components().size() - (sourceTuple.components().size() - i)];
+ TypePointer targetType = targetTuple.components()[i];
if (!sourceType)
{
solAssert(!targetType, "");
@@ -1025,10 +1011,8 @@ void CompilerUtils::convertType(
m_context << Instruction::ISZERO << Instruction::ISZERO;
break;
default:
- if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Integer)
+ if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address)
{
- IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType);
- solAssert(targetType.isAddress(), "Function type can only be converted to address.");
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
@@ -1184,6 +1168,15 @@ void CompilerUtils::popStackSlots(size_t _amount)
m_context << Instruction::POP;
}
+void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo)
+{
+ solAssert(m_context.stackHeight() >= _toHeight, "");
+ unsigned amount = m_context.stackHeight() - _toHeight;
+ popStackSlots(amount);
+ m_context.appendJumpTo(_jumpTo);
+ m_context.adjustStackOffset(amount);
+}
+
unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes)
{
unsigned size = 0;
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index 8e3a8a5d..ad3d7327 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -97,12 +97,12 @@ public:
/// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data.
- /// Calls revert if @a _revertOnOutOfBounds is true and the supplied size is shorter
- /// than the static data requirements or if dynamic data pointers reach outside of the
- /// area. Also has a hard cap of 0x100000000 for any given length/offset field.
+ /// Calls revert if the supplied size is shorter than the static data requirements
+ /// or if dynamic data pointers reach outside of the area.
+ /// Also has a hard cap of 0x100000000 for any given length/offset field.
/// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen>
- void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false, bool _revertOnOutOfBounds = false);
+ void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false);
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
@@ -241,6 +241,10 @@ public:
void popStackElement(Type const& _type);
/// Removes element from the top of the stack _amount times.
void popStackSlots(size_t _amount);
+ /// Pops slots from the stack such that its height is _toHeight.
+ /// Adds jump to _jumpTo.
+ /// Readjusts the stack offset to the original value.
+ void popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo);
template <class T>
static unsigned sizeOnStack(std::vector<T> const& _variables);
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 0889ac7c..e26bc13a 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -50,7 +50,7 @@ class StackHeightChecker
public:
explicit StackHeightChecker(CompilerContext const& _context):
m_context(_context), stackHeight(m_context.stackHeight()) {}
- void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); }
+ void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)); }
private:
CompilerContext const& m_context;
unsigned stackHeight;
@@ -71,7 +71,11 @@ void ContractCompiler::compileContract(
appendDelegatecallCheck();
initializeContext(_contract, _contracts);
+ // This generates the dispatch function for externally visible functions
+ // and adds the function to the compilation queue. Additionally internal functions,
+ // which are referenced directly or indirectly will be added.
appendFunctionSelector(_contract);
+ // This processes the above populated queue until it is empty.
appendMissingFunctions();
}
@@ -90,27 +94,6 @@ size_t ContractCompiler::compileConstructor(
}
}
-size_t ContractCompiler::compileClone(
- ContractDefinition const& _contract,
- map<ContractDefinition const*, eth::Assembly const*> const& _contracts
-)
-{
- initializeContext(_contract, _contracts);
-
- appendInitAndConstructorCode(_contract);
-
- //@todo determine largest return size of all runtime functions
- auto runtimeSub = m_context.addSubroutine(cloneRuntime());
-
- // stack contains sub size
- m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY;
- m_context << u256(0) << Instruction::RETURN;
-
- appendMissingFunctions();
-
- return size_t(runtimeSub.data());
-}
-
void ContractCompiler::initializeContext(
ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts
@@ -427,7 +410,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context.startFunction(_function);
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
- // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
+ // reserve additional slots: [retarg0] ... [retargm]
unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters());
if (!_function.isConstructor())
@@ -441,8 +424,6 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- appendStackVariableInitialisation(*localVariable);
if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
@@ -451,12 +432,11 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
solAssert(m_returnTags.empty(), "");
m_breakTags.clear();
m_continueTags.clear();
- m_stackCleanupForReturn = 0;
m_currentFunction = &_function;
m_modifierDepth = -1;
+ m_scopeStackHeight.clear();
appendModifierOrFunctionCode();
-
solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
@@ -467,14 +447,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters());
unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters());
- unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables());
vector<int> stackLayout;
stackLayout.push_back(c_returnValuesSize); // target of return address
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
for (unsigned i = 0; i < c_returnValuesSize; ++i)
stackLayout.push_back(i);
- stackLayout += vector<int>(c_localVariablesSize, -1);
if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION(
@@ -493,18 +471,23 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
- //@todo assert that everything is in place now
+ for (int i = 0; i < int(stackLayout.size()); ++i)
+ if (stackLayout[i] != i)
+ solAssert(false, "Invalid stack layout on cleanup.");
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters())
m_context.removeVariable(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize);
/// The constructor and the fallback function doesn't to jump out.
- if (!_function.isConstructor() && !_function.isFallback())
- m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ if (!_function.isConstructor())
+ {
+ solAssert(m_context.numberOfLocalVariables() == 0, "");
+ if (!_function.isFallback())
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ }
+
return false;
}
@@ -666,32 +649,36 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
{
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _whileStatement);
+
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
- m_continueTags.push_back(loopStart);
- m_breakTags.push_back(loopEnd);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
m_context << loopStart;
- // While loops have the condition prepended
- if (!_whileStatement.isDoWhile())
+ if (_whileStatement.isDoWhile())
{
- compileExpression(_whileStatement.condition());
- m_context << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(loopEnd);
- }
+ eth::AssemblyItem condition = m_context.newTag();
+ m_continueTags.push_back({condition, m_context.stackHeight()});
- _whileStatement.body().accept(*this);
+ _whileStatement.body().accept(*this);
- // Do-while loops have the condition appended
- if (_whileStatement.isDoWhile())
+ m_context << condition;
+ compileExpression(_whileStatement.condition());
+ m_context << Instruction::ISZERO << Instruction::ISZERO;
+ m_context.appendConditionalJumpTo(loopStart);
+ }
+ else
{
+ m_continueTags.push_back({loopStart, m_context.stackHeight()});
compileExpression(_whileStatement.condition());
m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd);
- }
- m_context.appendJumpTo(loopStart);
+ _whileStatement.body().accept(*this);
+
+ m_context.appendJumpTo(loopStart);
+ }
m_context << loopEnd;
m_continueTags.pop_back();
@@ -708,12 +695,14 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
eth::AssemblyItem loopNext = m_context.newTag();
- m_continueTags.push_back(loopNext);
- m_breakTags.push_back(loopEnd);
+
+ storeStackHeight(&_forStatement);
if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
+ m_continueTags.push_back({loopNext, m_context.stackHeight()});
m_context << loopStart;
// if there is no terminating condition in for, default is to always be true
@@ -733,11 +722,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
_forStatement.loopExpression()->accept(*this);
m_context.appendJumpTo(loopStart);
+
m_context << loopEnd;
m_continueTags.pop_back();
m_breakTags.pop_back();
+ // For the case where no break/return is executed:
+ // loop initialization variables have to be freed
+ popScopedVariables(&_forStatement);
+
checker.check();
return false;
}
@@ -745,16 +739,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
bool ContractCompiler::visit(Continue const& _continueStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
- if (!m_continueTags.empty())
- m_context.appendJumpTo(m_continueTags.back());
+ solAssert(!m_continueTags.empty(), "");
+ CompilerUtils(m_context).popAndJump(m_continueTags.back().second, m_continueTags.back().first);
return false;
}
bool ContractCompiler::visit(Break const& _breakStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
- if (!m_breakTags.empty())
- m_context.appendJumpTo(m_breakTags.back());
+ solAssert(!m_breakTags.empty(), "");
+ CompilerUtils(m_context).popAndJump(m_breakTags.back().second, m_breakTags.back().first);
return false;
}
@@ -780,18 +774,14 @@ bool ContractCompiler::visit(Return const& _return)
for (auto const& retVariable: boost::adaptors::reverse(returnParameters))
CompilerUtils(m_context).moveToStackVariable(*retVariable);
}
- for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
- m_context << Instruction::POP;
- m_context.appendJumpTo(m_returnTags.back());
- m_context.adjustStackOffset(m_stackCleanupForReturn);
+
+ CompilerUtils(m_context).popAndJump(m_returnTags.back().second, m_returnTags.back().first);
return false;
}
-bool ContractCompiler::visit(Throw const& _throw)
+bool ContractCompiler::visit(Throw const&)
{
- CompilerContext::LocationSetter locationSetter(m_context, _throw);
- // Do not send back an error detail.
- m_context.appendRevert();
+ solAssert(false, "Throw statement is disallowed.");
return false;
}
@@ -806,8 +796,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit)
bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
- StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
+
+ // Local variable slots are reserved when their declaration is visited,
+ // and freed in the end of their scope.
+ for (auto _decl: _variableDeclarationStatement.declarations())
+ if (_decl)
+ appendStackVariableInitialisation(*_decl);
+
+ StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue())
{
CompilerUtils utils(m_context);
@@ -817,20 +814,19 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
valueTypes = tupleType->components();
else
valueTypes = TypePointers{expression->annotation().type};
- auto const& assignments = _variableDeclarationStatement.annotation().assignments;
- solAssert(assignments.size() == valueTypes.size(), "");
- for (size_t i = 0; i < assignments.size(); ++i)
+ auto const& declarations = _variableDeclarationStatement.declarations();
+ solAssert(declarations.size() == valueTypes.size(), "");
+ for (size_t i = 0; i < declarations.size(); ++i)
{
- size_t j = assignments.size() - i - 1;
+ size_t j = declarations.size() - i - 1;
solAssert(!!valueTypes[j], "");
- VariableDeclaration const* varDecl = assignments[j];
- if (!varDecl)
- utils.popStackElement(*valueTypes[j]);
- else
+ if (VariableDeclaration const* varDecl = declarations[j].get())
{
utils.convertType(*valueTypes[j], *varDecl->annotation().type);
utils.moveToStackVariable(*varDecl);
}
+ else
+ utils.popStackElement(*valueTypes[j]);
}
}
checker.check();
@@ -857,6 +853,18 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
return true;
}
+bool ContractCompiler::visit(Block const& _block)
+{
+ storeStackHeight(&_block);
+ return true;
+}
+
+void ContractCompiler::endVisit(Block const& _block)
+{
+ // Frees local variables declared in the scope of this block.
+ popScopedVariables(&_block);
+}
+
void ContractCompiler::appendMissingFunctions()
{
while (Declaration const* function = m_context.nextFunctionToCompile())
@@ -912,27 +920,19 @@ void ContractCompiler::appendModifierOrFunctionCode()
modifier.parameters()[i]->annotation().type
);
}
- for (VariableDeclaration const* localVariable: modifier.localVariables())
- {
- addedVariables.push_back(localVariable);
- appendStackVariableInitialisation(*localVariable);
- }
- stackSurplus =
- CompilerUtils::sizeOnStack(modifier.parameters()) +
- CompilerUtils::sizeOnStack(modifier.localVariables());
+ stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters());
codeBlock = &modifier.body();
}
}
if (codeBlock)
{
- m_returnTags.push_back(m_context.newTag());
-
+ m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()});
codeBlock->accept(*this);
solAssert(!m_returnTags.empty(), "");
- m_context << m_returnTags.back();
+ m_context << m_returnTags.back().first;
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
@@ -957,25 +957,19 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);
}
-eth::AssemblyPointer ContractCompiler::cloneRuntime() const
-{
- eth::Assembly a;
- a << Instruction::CALLDATASIZE;
- a << u256(0) << Instruction::DUP1 << Instruction::CALLDATACOPY;
- //@todo adjust for larger return values, make this dynamic.
- a << u256(0x20) << u256(0) << Instruction::CALLDATASIZE;
- a << u256(0);
- // this is the address which has to be substituted by the linker.
- //@todo implement as special "marker" AssemblyItem.
- a << u256("0xcafecafecafecafecafecafecafecafecafecafe");
- a << u256(eth::GasCosts::callGas(m_context.evmVersion()) + 10) << Instruction::GAS << Instruction::SUB;
- a << Instruction::DELEGATECALL;
- //Propagate error condition (if DELEGATECALL pushes 0 on stack).
- a << Instruction::ISZERO;
- a << Instruction::ISZERO;
- eth::AssemblyItem afterTag = a.appendJumpI().tag();
- a << Instruction::INVALID << afterTag;
- //@todo adjust for larger return values, make this dynamic.
- a << u256(0x20) << u256(0) << Instruction::RETURN;
- return make_shared<eth::Assembly>(a);
+void ContractCompiler::popScopedVariables(ASTNode const* _node)
+{
+ unsigned blockHeight = m_scopeStackHeight.at(m_modifierDepth).at(_node);
+ m_context.removeVariablesAboveStackHeight(blockHeight);
+ solAssert(m_context.stackHeight() >= blockHeight, "");
+ unsigned stackDiff = m_context.stackHeight() - blockHeight;
+ CompilerUtils(m_context).popStackSlots(stackDiff);
+ m_scopeStackHeight[m_modifierDepth].erase(_node);
+ if (m_scopeStackHeight[m_modifierDepth].size() == 0)
+ m_scopeStackHeight.erase(m_modifierDepth);
+}
+
+void ContractCompiler::storeStackHeight(ASTNode const* _node)
+{
+ m_scopeStackHeight[m_modifierDepth][_node] = m_context.stackHeight();
}
diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h
index 02a3452f..5fa650b1 100644
--- a/libsolidity/codegen/ContractCompiler.h
+++ b/libsolidity/codegen/ContractCompiler.h
@@ -56,13 +56,6 @@ public:
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
);
- /// Compiles a contract that uses DELEGATECALL to call into a pre-deployed version of the given
- /// contract at runtime, but contains the full creation-time code.
- /// @returns the identifier of the runtime sub-assembly.
- size_t compileClone(
- ContractDefinition const& _contract,
- std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
- );
private:
/// Registers the non-function objects inside the contract with the context and stores the basic
@@ -109,6 +102,8 @@ private:
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
virtual bool visit(ExpressionStatement const& _expressionStatement) override;
virtual bool visit(PlaceholderStatement const&) override;
+ virtual bool visit(Block const& _block) override;
+ virtual void endVisit(Block const& _block) override;
/// Repeatedly visits all function which are referenced but which are not compiled yet.
void appendMissingFunctions();
@@ -120,22 +115,31 @@ private:
void appendStackVariableInitialisation(VariableDeclaration const& _variable);
void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer());
- /// @returns the runtime assembly for clone contracts.
- eth::AssemblyPointer cloneRuntime() const;
+ /// Frees the variables of a certain scope (to be used when leaving).
+ void popScopedVariables(ASTNode const* _node);
+
+ /// Sets the stack height for the visited loop.
+ void storeStackHeight(ASTNode const* _node);
bool const m_optimise;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;
- std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
- std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
- /// Tag to jump to for a "return" statement, needs to be stacked because of modifiers.
- std::vector<eth::AssemblyItem> m_returnTags;
+ /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_breakTags;
+ /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_continueTags;
+ /// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables.
+ /// Needs to be stacked because of modifiers.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_returnTags;
unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction = nullptr;
- unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
+
// arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
+
+ /// Stores the variables that were declared inside a specific scope, for each modifier depth.
+ std::map<unsigned, std::map<ASTNode const*, unsigned>> m_scopeStackHeight;
};
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index f38c1e67..bd863e05 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -281,19 +281,19 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
if (_tuple.isInlineArray())
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
-
+
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
m_context << max(u256(32u), arrayType.memorySize());
utils().allocateMemory();
m_context << Instruction::DUP1;
-
+
for (auto const& component: _tuple.components())
{
component->accept(*this);
utils().convertType(*component->annotation().type, *arrayType.baseType(), true);
- utils().storeInMemoryDynamic(*arrayType.baseType(), true);
+ utils().storeInMemoryDynamic(*arrayType.baseType(), true);
}
-
+
m_context << Instruction::POP;
}
else
@@ -349,6 +349,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved.");
+ solUnimplementedAssert(
+ _unaryOperation.annotation().type->category() != Type::Category::FixedPoint,
+ "Not yet implemented - FixedPointType."
+ );
m_currentLValue->retrieveValue(_unaryOperation.location());
if (!_unaryOperation.isPrefixOperation())
{
@@ -562,11 +566,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break;
}
case FunctionType::Kind::External:
- case FunctionType::Kind::CallCode:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
break;
@@ -697,18 +701,26 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.appendRevert();
break;
}
- case FunctionType::Kind::SHA3:
+ case FunctionType::Kind::KECCAK256:
{
- TypePointers argumentTypes;
- for (auto const& arg: arguments)
+ solAssert(arguments.size() == 1, "");
+ solAssert(!function.padArguments(), "");
+ TypePointer const& argType = arguments.front()->annotation().type;
+ solAssert(argType, "");
+ arguments.front()->accept(*this);
+ // Optimization: If type is bytes or string, then do not encode,
+ // but directly compute keccak256 on memory.
+ if (*argType == ArrayType(DataLocation::Memory) || *argType == ArrayType(DataLocation::Memory, true))
{
- arg->accept(*this);
- argumentTypes.push_back(arg->annotation().type);
+ ArrayUtils(m_context).retrieveLength(ArrayType(DataLocation::Memory));
+ m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD;
+ }
+ else
+ {
+ utils().fetchFreeMemoryPointer();
+ utils().packedEncode({argType}, TypePointers());
+ utils().toSizeAfterFreeMemoryPointer();
}
- utils().fetchFreeMemoryPointer();
- solAssert(!function.padArguments(), "");
- utils().packedEncode(argumentTypes, TypePointers());
- utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::KECCAK256;
break;
}
@@ -866,6 +878,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
break;
}
+ case FunctionType::Kind::ArrayPop:
+ {
+ _functionCall.expression().accept(*this);
+ solAssert(function.parameterTypes().empty(), "");
+
+ ArrayType const& arrayType = dynamic_cast<ArrayType const&>(
+ *dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression().annotation().type
+ );
+ solAssert(arrayType.dataStoredIn(DataLocation::Storage), "");
+
+ ArrayUtils(m_context).popStorageArrayElement(arrayType);
+ break;
+ }
case FunctionType::Kind::ObjectCreation:
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
@@ -1045,6 +1070,31 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// stack now: <memory pointer>
break;
}
+ case FunctionType::Kind::ABIDecode:
+ {
+ arguments.front()->accept(*this);
+ TypePointer firstArgType = arguments.front()->annotation().type;
+ TypePointers targetTypes;
+ if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type.get()))
+ targetTypes = targetTupleType->components();
+ else
+ targetTypes = TypePointers{_functionCall.annotation().type};
+ if (
+ *firstArgType == ArrayType(DataLocation::CallData) ||
+ *firstArgType == ArrayType(DataLocation::CallData, true)
+ )
+ utils().abiDecode(targetTypes, false);
+ else
+ {
+ utils().convertType(*firstArgType, ArrayType(DataLocation::Memory));
+ m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
+ m_context << Instruction::SWAP1 << Instruction::MLOAD;
+ // stack now: <mem_pos> <length>
+
+ utils().abiDecode(targetTypes, true);
+ }
+ break;
+ }
case FunctionType::Kind::GasLeft:
m_context << Instruction::GAS;
break;
@@ -1118,18 +1168,18 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "event not found");
// no-op, because the parent node will do the job
break;
+ case FunctionType::Kind::DelegateCall:
+ _memberAccess.expression().accept(*this);
+ m_context << funType->externalIdentifier();
+ break;
case FunctionType::Kind::External:
case FunctionType::Kind::Creation:
- case FunctionType::Kind::DelegateCall:
- case FunctionType::Kind::CallCode:
case FunctionType::Kind::Send:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Transfer:
- _memberAccess.expression().accept(*this);
- m_context << funType->externalIdentifier();
- break;
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
@@ -1189,63 +1239,66 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
switch (_memberAccess.expression().annotation().type->category())
{
case Type::Category::Contract:
- case Type::Category::Integer:
{
- bool alsoSearchInteger = false;
- if (_memberAccess.expression().annotation().type->category() == Type::Category::Contract)
+ ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
+ if (type.isSuper())
{
- ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
- if (type.isSuper())
- {
- solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
- utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
- dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
- type.contractDefinition()
- ));
- }
+ solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
+ utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
+ dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
+ type.contractDefinition()
+ ));
+ }
+ // ordinary contract type
+ else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
+ {
+ u256 identifier;
+ if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ identifier = FunctionType(*variable).externalIdentifier();
+ else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
+ identifier = FunctionType(*function).externalIdentifier();
else
- {
- // ordinary contract type
- if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
- {
- u256 identifier;
- if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
- identifier = FunctionType(*variable).externalIdentifier();
- else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
- identifier = FunctionType(*function).externalIdentifier();
- else
- solAssert(false, "Contract member is neither variable nor function.");
- utils().convertType(type, IntegerType(160, IntegerType::Modifier::Address), true);
- m_context << identifier;
- }
- else
- // not found in contract, search in members inherited from address
- alsoSearchInteger = true;
- }
+ solAssert(false, "Contract member is neither variable nor function.");
+ utils().convertType(type, AddressType(type.isPayable() ? StateMutability::Payable : StateMutability::NonPayable), true);
+ m_context << identifier;
}
else
- alsoSearchInteger = true;
-
- if (alsoSearchInteger)
+ solAssert(false, "Invalid member access in contract");
+ break;
+ }
+ case Type::Category::Integer:
+ {
+ solAssert(false, "Invalid member access to integer");
+ break;
+ }
+ case Type::Category::Address:
+ {
+ if (member == "balance")
{
- if (member == "balance")
- {
- utils().convertType(
- *_memberAccess.expression().annotation().type,
- IntegerType(160, IntegerType::Modifier::Address),
- true
- );
- m_context << Instruction::BALANCE;
- }
- else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member))
- utils().convertType(
- *_memberAccess.expression().annotation().type,
- IntegerType(160, IntegerType::Modifier::Address),
- true
- );
- else
- solAssert(false, "Invalid member access to integer");
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::NonPayable),
+ true
+ );
+ m_context << Instruction::BALANCE;
}
+ else if ((set<string>{"send", "transfer"}).count(member))
+ {
+ solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, "");
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::Payable),
+ true
+ );
+ }
+ else if ((set<string>{"call", "callcode", "delegatecall", "staticcall"}).count(member))
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::NonPayable),
+ true
+ );
+ else
+ solAssert(false, "Invalid member access to address");
break;
}
case Type::Category::Function:
@@ -1345,11 +1398,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
break;
}
}
- else if (member == "push")
+ else if (member == "push" || member == "pop")
{
solAssert(
- type.isDynamicallySized() && type.location() == DataLocation::Storage,
- "Tried to use .push() on a non-dynamically sized array"
+ type.isDynamicallySized() &&
+ type.location() == DataLocation::Storage &&
+ type.category() == Type::Category::Array,
+ "Tried to use ." + member + "() on a non-dynamically sized array"
);
}
else
@@ -1532,12 +1587,12 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
CompilerContext::LocationSetter locationSetter(m_context, _literal);
TypePointer type = _literal.annotation().type;
-
+
switch (type->category())
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
- case Type::Category::Integer:
+ case Type::Category::Address:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:
@@ -1624,12 +1679,12 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
{
- IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
- bool const c_isSigned = type.isSigned();
-
if (_type.category() == Type::Category::FixedPoint)
solUnimplemented("Not yet implemented - FixedPointType.");
+ IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
+ bool const c_isSigned = type.isSigned();
+
switch (_operator)
{
case Token::Add:
@@ -1722,11 +1777,36 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
m_context << u256(2) << Instruction::EXP << Instruction::MUL;
break;
case Token::SAR:
- // NOTE: SAR rounds differently than SDIV
- if (m_context.evmVersion().hasBitwiseShifting() && !c_valueSigned)
- m_context << Instruction::SHR;
+ if (m_context.evmVersion().hasBitwiseShifting())
+ m_context << (c_valueSigned ? Instruction::SAR : Instruction::SHR);
else
- m_context << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
+ {
+ if (c_valueSigned)
+ // In the following assembly snippet, xor_mask will be zero, if value_to_shift is positive.
+ // Therefore xor'ing with xor_mask is the identity and the computation reduces to
+ // div(value_to_shift, exp(2, shift_amount)), which is correct, since for positive values
+ // arithmetic right shift is dividing by a power of two (which, as a bitwise operation, results
+ // in discarding bits on the right and filling with zeros from the left).
+ // For negative values arithmetic right shift, viewed as a bitwise operation, discards bits to the
+ // right and fills in ones from the left. This is achieved as follows:
+ // If value_to_shift is negative, then xor_mask will have all bits set, so xor'ing with xor_mask
+ // will flip all bits. First all bits in value_to_shift are flipped. As for the positive case,
+ // dividing by a power of two using integer arithmetic results in discarding bits to the right
+ // and filling with zeros from the left. Flipping all bits in the result again, turns all zeros
+ // on the left to ones and restores the non-discarded, shifted bits to their original value (they
+ // have now been flipped twice). In summary we now have discarded bits to the right and filled with
+ // ones from the left, i.e. we have performed an arithmetic right shift.
+ m_context.appendInlineAssembly(R"({
+ let xor_mask := sub(0, slt(value_to_shift, 0))
+ value_to_shift := xor(div(xor(value_to_shift, xor_mask), exp(2, shift_amount)), xor_mask)
+ })", {"value_to_shift", "shift_amount"});
+ else
+ m_context.appendInlineAssembly(R"({
+ value_to_shift := div(value_to_shift, exp(2, shift_amount))
+ })", {"value_to_shift", "shift_amount"});
+ m_context << Instruction::POP;
+
+ }
break;
case Token::SHR:
default:
@@ -1763,71 +1843,46 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
auto funKind = _functionType.kind();
- bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall;
- bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
+
+ solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
+
+ bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
+ bool isCallCode = funKind == FunctionType::Kind::BareCallCode;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
- bool useStaticCall =
- _functionType.stateMutability() <= StateMutability::View &&
- m_context.experimentalFeatureActive(ExperimentalFeature::V050) &&
- m_context.evmVersion().hasStaticCall();
+ bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
- TypePointers returnTypes;
- if (returnSuccessCondition)
- retSize = 0; // return value actually is success condition
- else if (haveReturndatacopy)
- returnTypes = _functionType.returnParameterTypes();
- else
- returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
-
bool dynamicReturnSize = false;
- for (auto const& retType: returnTypes)
- if (retType->isDynamicallyEncoded())
- {
- solAssert(haveReturndatacopy, "");
- dynamicReturnSize = true;
- retSize = 0;
- break;
- }
+ TypePointers returnTypes;
+ if (!returnSuccessConditionAndReturndata)
+ {
+ if (haveReturndatacopy)
+ returnTypes = _functionType.returnParameterTypes();
else
- retSize += retType->calldataEncodedSize();
+ returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
+
+ for (auto const& retType: returnTypes)
+ if (retType->isDynamicallyEncoded())
+ {
+ solAssert(haveReturndatacopy, "");
+ dynamicReturnSize = true;
+ retSize = 0;
+ break;
+ }
+ else
+ retSize += retType->calldataEncodedSize();
+ }
// Evaluate arguments.
TypePointers argumentTypes;
TypePointers parameterTypes = _functionType.parameterTypes();
- bool manualFunctionId = false;
- if (
- (funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) &&
- !_arguments.empty()
- )
- {
- solAssert(_arguments.front()->annotation().type->mobileType(), "");
- manualFunctionId =
- _arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) ==
- CompilerUtils::dataStartOffset;
- }
- if (manualFunctionId)
- {
- // If we have a Bare* and the first type has exactly 4 bytes, use it as
- // function identifier.
- _arguments.front()->accept(*this);
- utils().convertType(
- *_arguments.front()->annotation().type,
- IntegerType(8 * CompilerUtils::dataStartOffset),
- true
- );
- for (unsigned i = 0; i < gasValueSize; ++i)
- m_context << swapInstruction(gasValueSize - i);
- gasStackPos++;
- valueStackPos++;
- }
if (_functionType.bound())
{
argumentTypes.push_back(_functionType.selfType());
parameterTypes.insert(parameterTypes.begin(), _functionType.selfType());
}
- for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i)
+ for (size_t i = 0; i < _arguments.size(); ++i)
{
_arguments[i]->accept(*this);
argumentTypes.push_back(_arguments[i]->annotation().type);
@@ -1856,26 +1911,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
m_context << u256(0);
utils().fetchFreeMemoryPointer();
- // This touches too much, but that way we save some rounding arithmetics
+ // This touches too much, but that way we save some rounding arithmetic
m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE;
}
}
// Copy function identifier to memory.
utils().fetchFreeMemoryPointer();
- if (!_functionType.isBareCall() || manualFunctionId)
+ if (!_functionType.isBareCall())
{
m_context << dupInstruction(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes));
utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false);
}
- // If the function takes arbitrary parameters, copy dynamic length data in place.
+
+ // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
// Move arguments to memory, will not update the free memory pointer (but will update the memory
// pointer on the stack).
utils().encodeToMemory(
argumentTypes,
parameterTypes,
_functionType.padArguments(),
- _functionType.takesArbitraryParameters(),
+ _functionType.takesArbitraryParameters() || _functionType.isBareCall(),
isCallCode || isDelegateCall
);
@@ -1920,7 +1976,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool existenceChecked = false;
// Check the the target contract exists (has code) for non-low-level calls.
- if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
+ if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
@@ -1956,11 +2012,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
unsigned remainsSize =
2 + // contract address, input_memory_end
- _functionType.valueSet() +
- _functionType.gasSet() +
- (!_functionType.isBareCall() || manualFunctionId);
+ (_functionType.valueSet() ? 1 : 0) +
+ (_functionType.gasSet() ? 1 : 0) +
+ (!_functionType.isBareCall() ? 1 : 0);
- if (returnSuccessCondition)
+ if (returnSuccessConditionAndReturndata)
m_context << swapInstruction(remainsSize);
else
{
@@ -1971,9 +2027,31 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().popStackSlots(remainsSize);
- if (returnSuccessCondition)
+ if (returnSuccessConditionAndReturndata)
{
- // already there
+ // success condition is already there
+ // The return parameter types can be empty, when this function is used as
+ // an internal helper function e.g. for ``send`` and ``transfer``. In that
+ // case we're only interested in the success condition, not the return data.
+ if (!_functionType.returnParameterTypes().empty())
+ {
+ if (haveReturndatacopy)
+ {
+ m_context << Instruction::RETURNDATASIZE;
+ m_context.appendInlineAssembly(R"({
+ switch v case 0 {
+ v := 0x60
+ } default {
+ v := mload(0x40)
+ mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
+ mstore(v, returndatasize())
+ returndatacopy(add(v, 0x20), 0, returndatasize())
+ }
+ })", {"v"});
+ }
+ else
+ utils().pushZeroPointer();
+ }
}
else if (funKind == FunctionType::Kind::RIPEMD160)
{
@@ -2025,7 +2103,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
mstore(0x40, newMem)
})", {"start", "size"});
- utils().abiDecode(returnTypes, true, true);
+ utils().abiDecode(returnTypes, true);
}
}