diff options
-rw-r--r-- | libsolidity/Compiler.cpp | 15 | ||||
-rw-r--r-- | libsolidity/CompilerUtils.cpp | 63 | ||||
-rw-r--r-- | libsolidity/CompilerUtils.h | 9 | ||||
-rw-r--r-- | libsolidity/ExpressionCompiler.cpp | 41 | ||||
-rw-r--r-- | libsolidity/ExpressionCompiler.h | 1 | ||||
-rw-r--r-- | libsolidity/LValue.cpp | 195 | ||||
-rw-r--r-- | libsolidity/LValue.h | 31 | ||||
-rw-r--r-- | libsolidity/TypeChecker.cpp | 8 | ||||
-rw-r--r-- | libsolidity/Types.cpp | 72 | ||||
-rw-r--r-- | libsolidity/Types.h | 12 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 47 |
11 files changed, 389 insertions, 105 deletions
diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index cc1228a1..679704ba 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -597,13 +597,20 @@ bool Compiler::visit(Break const& _breakStatement) bool Compiler::visit(Return const& _return) { CompilerContext::LocationSetter locationSetter(m_context, _return); - //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.expression()) { solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); - VariableDeclaration const& firstVariable = *_return.annotation().functionReturnParameters->parameters().front(); - compileExpression(*expression, firstVariable.annotation().type); - CompilerUtils(m_context).moveToStackVariable(firstVariable); + vector<ASTPointer<VariableDeclaration>> const& returnParameters = + _return.annotation().functionReturnParameters->parameters(); + TypePointers types; + for (auto const& retVariable: returnParameters) + types.push_back(retVariable->annotation().type); + + TypePointer expectedType = types.size() == 1 ? types.front() : make_shared<TupleType>(types); + compileExpression(*expression, expectedType); + + for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) + CompilerUtils(m_context).moveToStackVariable(*retVariable); } for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) m_context << eth::Instruction::POP; diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index e1152202..34bb08ba 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -550,6 +550,55 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } break; } + case Type::Category::Tuple: + { + //@TODO wildcards + TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack); + TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType); + solAssert(sourceTuple.components().size() == targetTuple.components().size(), ""); + unsigned depth = sourceTuple.sizeOnStack(); + for (size_t i = 0; i < sourceTuple.components().size(); ++i) + { + TypePointer const& sourceType = sourceTuple.components()[i]; + TypePointer const& targetType = targetTuple.components()[i]; + if (!sourceType) + { + solAssert(!targetType, ""); + continue; + } + unsigned sourceSize = sourceType->sizeOnStack(); + unsigned targetSize = targetType->sizeOnStack(); + if (*sourceType != *targetType || _cleanupNeeded) + { + if (sourceSize > 0) + copyToStackTop(depth, sourceSize); + + convertType(*sourceType, *targetType, _cleanupNeeded); + + if (sourceSize > 0 || targetSize > 0) + { + // Move it back into its place. + for (unsigned j = 0; j < min(sourceSize, targetSize); ++j) + m_context << + eth::swapInstruction(depth + targetSize - sourceSize) << + eth::Instruction::POP; + if (targetSize < sourceSize) + moveToStackTop(sourceSize - targetSize, depth ); + // Value shrank + for (unsigned j = targetSize; j < sourceSize; ++j) + { + moveToStackTop(depth - 1, 1); + m_context << eth::Instruction::POP; + } + // Value grew + if (targetSize > sourceSize) + moveIntoStack(depth + targetSize - sourceSize, targetSize - sourceSize); + } + } + depth -= sourceSize; + } + break; + } default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); @@ -631,18 +680,20 @@ void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) m_context << eth::dupInstruction(_stackDepth); } -void CompilerUtils::moveToStackTop(unsigned _stackDepth) +void CompilerUtils::moveToStackTop(unsigned _stackDepth, unsigned _itemSize) { solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); - for (unsigned i = 0; i < _stackDepth; ++i) - m_context << eth::swapInstruction(1 + i); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = 0; i < _stackDepth + _itemSize - 1; ++i) + m_context << eth::swapInstruction(1 + i); } -void CompilerUtils::moveIntoStack(unsigned _stackDepth) +void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) { solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); - for (unsigned i = _stackDepth; i > 0; --i) - m_context << eth::swapInstruction(i); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = _stackDepth; i > 0; --i) + m_context << eth::swapInstruction(i + _itemSize - 1); } void CompilerUtils::popStackElement(Type const& _type) diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index f335eed5..01b9f422 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -124,10 +124,11 @@ public: /// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth /// to the top of the stack. void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); - /// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack. - void moveToStackTop(unsigned _stackDepth); - /// Moves a single stack element past @a _stackDepth other stack elements - void moveIntoStack(unsigned _stackDepth); + /// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth + /// slots above it to the top of the stack. + void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1); + /// Moves @a _itemSize elements past @a _stackDepth other stack elements + void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1); /// Removes the current value from the top of the stack. void popStackElement(Type const& _type); /// Removes element from the top of the stack _amount times. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 85302afc..8109c03b 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -177,20 +177,18 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& bool ExpressionCompiler::visit(Assignment const& _assignment) { +// cout << "-----Assignment" << endl; CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.rightHandSide().accept(*this); - TypePointer type = _assignment.rightHandSide().annotation().type; - if (!_assignment.annotation().type->dataStoredIn(DataLocation::Storage)) - { - utils().convertType(*type, *_assignment.annotation().type); - type = _assignment.annotation().type; - } - else - { - utils().convertType(*type, *type->mobileType()); - type = type->mobileType(); - } + // Perform some conversion already. This will convert storage types to memory and literals + // to their actual type, but will not convert e.g. memory to storage. + TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( + _assignment.leftHandSide().annotation().type + ); +// cout << "-----Type conversion" << endl; + utils().convertType(*_assignment.rightHandSide().annotation().type, *type); +// cout << "-----LHS" << endl; _assignment.leftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -216,11 +214,32 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; } } +// cout << "-----Store" << endl; m_currentLValue->storeValue(*type, _assignment.location()); m_currentLValue.reset(); return false; } +bool ExpressionCompiler::visit(TupleExpression const& _tuple) +{ + vector<unique_ptr<LValue>> lvalues; + for (auto const& component: _tuple.components()) + if (component) + { + component->accept(*this); + if (_tuple.annotation().lValueRequested) + { + solAssert(!!m_currentLValue, ""); + lvalues.push_back(move(m_currentLValue)); + } + } + else if (_tuple.annotation().lValueRequested) + lvalues.push_back(unique_ptr<LValue>()); + if (_tuple.annotation().lValueRequested) + m_currentLValue.reset(new TupleObject(m_context, move(lvalues))); + return false; +} + bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index d8fef5af..44d27ea2 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -72,6 +72,7 @@ public: private: virtual bool visit(Assignment const& _assignment) override; + virtual bool visit(TupleExpression const& _tuple) override; virtual bool visit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; virtual bool visit(FunctionCall const& _functionCall) override; diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 9f33e846..20aa59e1 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -32,9 +32,9 @@ using namespace solidity; StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): - LValue(_compilerContext, *_declaration.annotation().type), + LValue(_compilerContext, _declaration.annotation().type.get()), m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), - m_size(m_dataType.sizeOnStack()) + m_size(m_dataType->sizeOnStack()) { } @@ -70,23 +70,23 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo void StackVariable::setToZero(SourceLocation const& _location, bool) const { - CompilerUtils(m_context).pushZeroValue(m_dataType); - storeValue(m_dataType, _location, true); + CompilerUtils(m_context).pushZeroValue(*m_dataType); + storeValue(*m_dataType, _location, true); } MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): - LValue(_compilerContext, _type), + LValue(_compilerContext, &_type), m_padded(_padded) { } void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const { - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { if (!_remove) m_context << eth::Instruction::DUP1; - CompilerUtils(m_context).loadFromMemoryDynamic(m_dataType, false, m_padded, false); + CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false); } else m_context << eth::Instruction::MLOAD; @@ -95,24 +95,24 @@ void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const { CompilerUtils utils(m_context); - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { solAssert(_sourceType.isValueType(), ""); utils.moveIntoStack(_sourceType.sizeOnStack()); - utils.convertType(_sourceType, m_dataType, true); + utils.convertType(_sourceType, *m_dataType, true); if (!_move) { - utils.moveToStackTop(m_dataType.sizeOnStack()); - utils.copyToStackTop(2, m_dataType.sizeOnStack()); + utils.moveToStackTop(m_dataType->sizeOnStack()); + utils.copyToStackTop(2, m_dataType->sizeOnStack()); } - utils.storeInMemoryDynamic(m_dataType, m_padded); + utils.storeInMemoryDynamic(*m_dataType, m_padded); m_context << eth::Instruction::POP; } else { - solAssert(_sourceType == m_dataType, "Conversion not implemented for assignment to memory."); + solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); - solAssert(m_dataType.sizeOnStack() == 1, ""); + solAssert(m_dataType->sizeOnStack() == 1, ""); if (!_move) m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; // stack: [value] value lvalue @@ -126,8 +126,8 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const CompilerUtils utils(m_context); if (!_removeReference) m_context << eth::Instruction::DUP1; - utils.pushZeroValue(m_dataType); - utils.storeInMemoryDynamic(m_dataType, m_padded); + utils.pushZeroValue(*m_dataType); + utils.storeInMemoryDynamic(*m_dataType, m_padded); m_context << eth::Instruction::POP; } @@ -139,21 +139,21 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration } StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): - LValue(_compilerContext, _type) + LValue(_compilerContext, &_type) { - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { - solAssert(m_dataType.storageSize() == m_dataType.sizeOnStack(), ""); - solAssert(m_dataType.storageSize() == 1, "Invalid storage size."); + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { // stack: storage_key storage_offset - if (!m_dataType.isValueType()) + if (!m_dataType->isValueType()) { - solAssert(m_dataType.sizeOnStack() == 1, "Invalid storage ref size."); + solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size."); if (_remove) m_context << eth::Instruction::POP; // remove byte offset else @@ -162,22 +162,22 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const } if (!_remove) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - if (m_dataType.storageBytes() == 32) + if (m_dataType->storageBytes() == 32) m_context << eth::Instruction::POP << eth::Instruction::SLOAD; else { m_context << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; - if (m_dataType.category() == Type::Category::FixedBytes) - m_context << (u256(0x1) << (256 - 8 * m_dataType.storageBytes())) << eth::Instruction::MUL; + if (m_dataType->category() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL; else if ( - m_dataType.category() == Type::Category::Integer && - dynamic_cast<IntegerType const&>(m_dataType).isSigned() + m_dataType->category() == Type::Category::Integer && + dynamic_cast<IntegerType const&>(*m_dataType).isSigned() ) - m_context << u256(m_dataType.storageBytes() - 1) << eth::Instruction::SIGNEXTEND; + m_context << u256(m_dataType->storageBytes() - 1) << eth::Instruction::SIGNEXTEND; else - m_context << ((u256(0x1) << (8 * m_dataType.storageBytes())) - 1) << eth::Instruction::AND; + m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::AND; } } @@ -185,11 +185,11 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc { CompilerUtils utils(m_context); // stack: value storage_key storage_offset - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { - solAssert(m_dataType.storageBytes() <= 32, "Invalid storage bytes size."); - solAssert(m_dataType.storageBytes() > 0, "Invalid storage bytes size."); - if (m_dataType.storageBytes() == 32) + solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType->storageBytes() == 32) { // offset should be zero m_context << eth::Instruction::POP; @@ -207,24 +207,24 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storege_ref multiplier old_full_value // clear bytes in old value m_context - << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) + << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::MUL; m_context << eth::Instruction::NOT << eth::Instruction::AND; // stack: value storage_ref multiplier cleared_value m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4; // stack: value storage_ref cleared_value multiplier value - if (m_dataType.category() == Type::Category::FixedBytes) + if (m_dataType->category() == Type::Category::FixedBytes) m_context - << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).numBytes())) + << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) << eth::Instruction::SWAP1 << eth::Instruction::DIV; else if ( - m_dataType.category() == Type::Category::Integer && - dynamic_cast<IntegerType const&>(m_dataType).isSigned() + m_dataType->category() == Type::Category::Integer && + dynamic_cast<IntegerType const&>(*m_dataType).isSigned() ) // remove the higher order bits m_context - << (u256(1) << (8 * (32 - m_dataType.storageBytes()))) + << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::MUL @@ -239,23 +239,23 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc else { solAssert( - _sourceType.category() == m_dataType.category(), + _sourceType.category() == m_dataType->category(), "Wrong type conversation for assignment."); - if (m_dataType.category() == Type::Category::Array) + if (m_dataType->category() == Type::Category::Array) { m_context << eth::Instruction::POP; // remove byte offset ArrayUtils(m_context).copyArrayToStorage( - dynamic_cast<ArrayType const&>(m_dataType), + dynamic_cast<ArrayType const&>(*m_dataType), dynamic_cast<ArrayType const&>(_sourceType)); if (_move) m_context << eth::Instruction::POP; } - else if (m_dataType.category() == Type::Category::Struct) + else if (m_dataType->category() == Type::Category::Struct) { // stack layout: source_ref target_ref target_offset // note that we have structs, so offset should be zero and are ignored m_context << eth::Instruction::POP; - auto const& structType = dynamic_cast<StructType const&>(m_dataType); + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); auto const& sourceType = dynamic_cast<StructType const&>(_sourceType); solAssert( structType.structDefinition() == sourceType.structDefinition(), @@ -313,18 +313,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const { - if (m_dataType.category() == Type::Category::Array) + if (m_dataType->category() == Type::Category::Array) { if (!_removeReference) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); + ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType)); } - else if (m_dataType.category() == Type::Category::Struct) + else if (m_dataType->category() == Type::Category::Struct) { // stack layout: storage_key storage_offset // @todo this can be improved: use StorageItem for non-value types, and just store 0 in // all slots that contain value types later. - auto const& structType = dynamic_cast<StructType const&>(m_dataType); + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); for (auto const& member: structType.members()) { // zero each member that is not a mapping @@ -342,10 +342,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const } else { - solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); + solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString()); if (!_removeReference) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - if (m_dataType.storageBytes() == 32) + if (m_dataType->storageBytes() == 32) { // offset should be zero m_context @@ -361,7 +361,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const // stack: storege_ref multiplier old_full_value // clear bytes in old value m_context - << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) + << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::MUL; m_context << eth::Instruction::NOT << eth::Instruction::AND; // stack: storage_ref cleared_value @@ -374,7 +374,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const static FixedBytesType byteType(1); StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): - LValue(_compilerContext, byteType) + LValue(_compilerContext, &byteType) { } @@ -427,7 +427,7 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer } StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): - LValue(_compilerContext, *_arrayType.memberType("length")), + LValue(_compilerContext, _arrayType.memberType("length").get()), m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); @@ -455,3 +455,92 @@ void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) m_context << eth::Instruction::DUP1; ArrayUtils(m_context).clearDynamicArray(m_arrayType); } + + +TupleObject::TupleObject( + CompilerContext& _compilerContext, + std::vector<std::unique_ptr<LValue>>&& _lvalues +): + LValue(_compilerContext), m_lvalues(move(_lvalues)) +{ +} + +unsigned TupleObject::sizeOnStack() const +{ + unsigned size = 0; + for (auto const& lv: m_lvalues) + if (lv) + size += lv->sizeOnStack(); + return size; +} + +void TupleObject::retrieveValue(SourceLocation const& _location, bool _remove) const +{ + unsigned initialDepth = sizeOnStack(); + unsigned initialStack = m_context.stackHeight(); + for (auto const& lv: m_lvalues) + if (lv) + { + solAssert(initialDepth + m_context.stackHeight() >= initialStack, ""); + unsigned depth = initialDepth + m_context.stackHeight() - initialStack; + if (lv->sizeOnStack() > 0) + if (_remove && depth > lv->sizeOnStack()) + CompilerUtils(m_context).moveToStackTop(depth, depth - lv->sizeOnStack()); + else if (!_remove && depth > 0) + CompilerUtils(m_context).copyToStackTop(depth, lv->sizeOnStack()); + lv->retrieveValue(_location, true); + } +} + +void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + // values are below the lvalue references + unsigned valuePos = sizeOnStack(); + + //@TODO wildcards + + TypePointers const& valueTypes = dynamic_cast<TupleType const&>(_sourceType).components(); + // valuePos .... refPos ... + // We will assign from right to left to optimize stack layout. + for (size_t i = 0; i < m_lvalues.size(); ++i) + { + unique_ptr<LValue> const& lvalue = m_lvalues[m_lvalues.size() - i - 1]; + TypePointer const& valType = valueTypes[valueTypes.size() - i - 1]; + unsigned stackHeight = m_context.stackHeight(); + solAssert(!!valType, ""); + valuePos += valType->sizeOnStack(); + if (lvalue) + { + // copy value to top + CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack()); + // move lvalue ref above value + CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack()); + lvalue->storeValue(*valType, _location, true); + } + valuePos += m_context.stackHeight() - stackHeight; + } + // As the type of an assignment to a tuple type is the empty tuple, we always move. + CompilerUtils(m_context).popStackElement(_sourceType); +} + +void TupleObject::setToZero(SourceLocation const& _location, bool _removeReference) const +{ + if (_removeReference) + { + for (size_t i = 0; i < m_lvalues.size(); ++i) + if (m_lvalues[m_lvalues.size() - i]) + m_lvalues[m_lvalues.size() - i]->setToZero(_location, true); + } + else + { + unsigned depth = sizeOnStack(); + for (auto const& val: m_lvalues) + if (val) + { + if (val->sizeOnStack() > 0) + CompilerUtils(m_context).copyToStackTop(depth, val->sizeOnStack()); + val->setToZero(_location, false); + depth -= val->sizeOnStack(); + } + } +} diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h index cbbfb102..94c8d3b8 100644 --- a/libsolidity/LValue.h +++ b/libsolidity/LValue.h @@ -23,6 +23,7 @@ #pragma once #include <memory> +#include <vector> #include <libevmasm/SourceLocation.h> #include <libsolidity/ArrayUtils.h> @@ -33,6 +34,7 @@ namespace solidity class Declaration; class Type; +class TupleType; class ArrayType; class CompilerContext; class VariableDeclaration; @@ -43,7 +45,7 @@ class VariableDeclaration; class LValue { protected: - LValue(CompilerContext& _compilerContext, Type const& _dataType): + explicit LValue(CompilerContext& _compilerContext, Type const* _dataType = nullptr): m_context(_compilerContext), m_dataType(_dataType) {} public: @@ -68,7 +70,7 @@ public: protected: CompilerContext& m_context; - Type const& m_dataType; + Type const* m_dataType; }; /** @@ -193,5 +195,30 @@ private: ArrayType const& m_arrayType; }; +/** + * Tuple object that can itself hold several LValues. + */ +class TupleObject: public LValue +{ +public: + /// Constructs the LValue assuming that the other LValues are present on the stack. + /// Empty unique_ptrs are possible if e.g. some values should be ignored during assignment. + TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues); + virtual unsigned sizeOnStack() const; + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; + +private: + std::vector<std::unique_ptr<LValue>> m_lvalues; +}; + } } diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index c7b693bb..e9d01a84 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -730,7 +730,13 @@ bool TypeChecker::visit(Assignment const& _assignment) requireLValue(_assignment.leftHandSide()); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; - if (t->category() == Type::Category::Mapping) + if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get())) + { + // Sequenced assignments of tuples is not valid. + _assignment.annotation().type = make_shared<TupleType const>(); + expectType(_assignment.rightHandSide(), *tupleType); + } + else if (t->category() == Type::Category::Mapping) { typeError(_assignment, "Mappings cannot be assigned to."); _assignment.rightHandSide().accept(*this); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index d7beab26..aca959b3 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1242,6 +1242,38 @@ unsigned int EnumType::memberValue(ASTString const& _member) const BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member)); } +bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +{ + if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) + { + TypePointers const& targets = tupleType->components(); + if (targets.empty()) + return components().empty(); + if (components().size() != targets.size() && !targets.front() && !targets.back()) + return false; // (,a,) = (1,2,3,4) - unable to position `a` in the tuple. + size_t minNumValues = targets.size(); + if (!targets.back() || !targets.front()) + --minNumValues; // wildcards can also match 0 components + if (components().size() < minNumValues) + return false; + if (components().size() > targets.size() && targets.front() && targets.back()) + return false; // larger source and no wildcard + bool fillRight = !targets.back() || targets.front(); + for (size_t i = 0; i < min(targets.size(), components().size()); ++i) + { + auto const& s = components()[fillRight ? i : components().size() - i - 1]; + auto const& t = targets[fillRight ? i : targets.size() - i - 1]; + if (!s && t) + return false; + else if (s && t && !s->isImplicitlyConvertibleTo(*t)) + return false; + } + return true; + } + else + return false; +} + bool TupleType::operator==(Type const& _other) const { if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) @@ -1252,10 +1284,10 @@ bool TupleType::operator==(Type const& _other) const string TupleType::toString(bool _short) const { - if (m_components.empty()) + if (components().empty()) return "tuple()"; string str = "tuple("; - for (auto const& t: m_components) + for (auto const& t: components()) str += (t ? t->toString(_short) : "") + ","; str.pop_back(); return str + ")"; @@ -1272,29 +1304,33 @@ u256 TupleType::storageSize() const unsigned TupleType::sizeOnStack() const { unsigned size = 0; - for (auto const& t: m_components) + for (auto const& t: components()) size += t ? t->sizeOnStack() : 0; return size; } -bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +TypePointer TupleType::mobileType() const { - if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) + TypePointers mobiles; + for (auto const& c: components()) + mobiles.push_back(c ? c->mobileType() : TypePointer()); + return make_shared<TupleType>(mobiles); +} + +TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) const +{ + solAssert(!!_targetType, ""); + TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components(); + bool fillRight = !targetComponents.empty() && (!targetComponents.back() || targetComponents.front()); + TypePointers tempComponents(targetComponents.size()); + for (size_t i = 0; i < min(targetComponents.size(), components().size()); ++i) { - if (components().size() != tupleType->components().size()) - return false; - for (size_t i = 0; i < components().size(); ++i) - if ((!components()[i]) != (!tupleType->components()[i])) - return false; - else if ( - components()[i] && - !components()[i]->isImplicitlyConvertibleTo(*tupleType->components()[i]) - ) - return false; - return true; + size_t si = fillRight ? i : components().size() - i - 1; + size_t ti = fillRight ? i : targetComponents.size() - i - 1; + if (components()[si] && targetComponents[ti]) + tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[si]); } - else - return false; + return make_shared<TupleType>(tempComponents); } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e8d65c41..626ebbe4 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -210,6 +210,13 @@ public: /// @returns true if this is a non-value type and the data of this type is stored at the /// given location. virtual bool dataStoredIn(DataLocation) const { return false; } + /// @returns the type of a temporary during assignment to a variable of the given type. + /// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type) + /// and the mobile type otherwise. + virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const + { + return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; + } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& members() const { return EmptyMemberList; } @@ -689,6 +696,7 @@ class TupleType: public Type public: virtual Category category() const override { return Category::Tuple; } explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {} + virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString(bool) const override; @@ -696,7 +704,9 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; - virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; + virtual TypePointer mobileType() const override; + /// Converts components to their temporary types and performs some wildcard matching. + virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; std::vector<TypePointer> const& components() const { return m_components; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 1d9a403a..36114689 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -5693,14 +5693,14 @@ BOOST_AUTO_TEST_CASE(tuples) data[0] = 3; uint a; uint b; (a, b) = this.h(); - if (a != 1 || b != 2) return 1; + if (a != 5 || b != 6) return 1; uint[] storage c; (a, b, c) = g(); - if (a != 5 || b != 6 || c[0] != 3) return 2; + if (a != 1 || b != 2 || c[0] != 3) return 2; (a, b) = (b, a); - if (a != 6 || b != 5) return 3; - (a, , b, ) = (8, 9, 10, 11, 12); - if (a != 8 || b != 10) return 3; + if (a != 2 || b != 1) return 3; +// (a, , b, ) = (8, 9, 10, 11, 12); +// if (a != 8 || b != 10) return 3; } } )"; @@ -5708,6 +5708,42 @@ BOOST_AUTO_TEST_CASE(tuples) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); } +BOOST_AUTO_TEST_CASE(destructuring_assignment) +{ + char const* sourceCode = R"( + contract C { + uint x = 7; + bytes data; + uint[] y; + uint[] arrayData; + function returnsArray() returns (uint[]) { + arrayData.length = 9; + arrayData[2] = 5; + arrayData[7] = 4; + return arrayData; + } + function f(bytes s) returns (uint) { + uint loc; + uint[] memArray; + (loc, x, y, data, arrayData[3]) = (8, 4, returnsArray(), s, 2); + if (loc != 8) return 1; + if (x != 4) return 2; + if (y.length != 9) return 3; + if (y[2] != 5) return 4; + if (y[7] != 4) return 5; + if (data.length != s.length) return 6; + if (data[3] != s[3]) return 7; + if (arrayData[3] != 2) return 8; + (memArray, loc) = (arrayData, 3); + if (loc != 3) return 9; + if (memArray.length != arrayData.length) return 10; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(bytes)", u256(0x20), u256(5), string("abcde")) == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard) { char const* sourceCode = R"( @@ -5732,6 +5768,7 @@ BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard) compileAndRun(sourceCode); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); } + BOOST_AUTO_TEST_SUITE_END() } |