diff --git a/AST.cpp b/AST.cpp index c37e8c374..aba355768 100644 --- a/AST.cpp +++ b/AST.cpp @@ -209,6 +209,33 @@ vector, FunctionTypePointer>> const& ContractDefinition::getIn return *m_interfaceFunctionList; } +vector const& ContractDefinition::getInheritableMembers() const +{ + if (!m_inheritableMembers) + { + set memberSeen; + m_inheritableMembers.reset(new vector()); + auto addInheritableMember = [&](Declaration const* _decl) + { + if (memberSeen.count(_decl->getName()) == 0 && _decl->isVisibleInDerivedContracts()) + { + memberSeen.insert(_decl->getName()); + m_inheritableMembers->push_back(_decl); + } + }; + + for (ASTPointer const& f: getDefinedFunctions()) + addInheritableMember(f.get()); + + for (ASTPointer const& v: getStateVariables()) + addInheritableMember(v.get()); + + for (ASTPointer const& s: getDefinedStructs()) + addInheritableMember(s.get()); + } + return *m_inheritableMembers; +} + TypePointer EnumValue::getType(ContractDefinition const*) const { EnumDefinition const* parentDef = dynamic_cast(getScope()); @@ -281,7 +308,9 @@ void FunctionDefinition::checkTypeRequirements() if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); for (ASTPointer const& modifier: m_functionModifiers) - modifier->checkTypeRequirements(); + modifier->checkTypeRequirements(isConstructor() ? + dynamic_cast(*getScope()).getBaseContracts() : + vector>()); m_body->checkTypeRequirements(); } @@ -324,19 +353,34 @@ void ModifierDefinition::checkTypeRequirements() m_body->checkTypeRequirements(); } -void ModifierInvocation::checkTypeRequirements() +void ModifierInvocation::checkTypeRequirements(vector> const& _bases) { m_modifierName->checkTypeRequirements(); for (ASTPointer const& argument: m_arguments) argument->checkTypeRequirements(); - ModifierDefinition const* modifier = dynamic_cast(m_modifierName->getReferencedDeclaration()); - solAssert(modifier, "Function modifier not found."); - vector> const& parameters = modifier->getParameters(); - if (parameters.size() != m_arguments.size()) + auto declaration = m_modifierName->getReferencedDeclaration(); + vector> emptyParameterList; + vector> const* parameters = nullptr; + if (auto modifier = dynamic_cast(declaration)) + parameters = &modifier->getParameters(); + else + // check parameters for Base constructors + for (auto const& base: _bases) + if (declaration == base->getName()->getReferencedDeclaration()) + { + if (auto referencedConstructor = dynamic_cast(*declaration).getConstructor()) + parameters = &referencedConstructor->getParameters(); + else + parameters = &emptyParameterList; + break; + } + if (!parameters) + BOOST_THROW_EXCEPTION(createTypeError("Referenced declaration is neither modifier nor base class.")); + if (parameters->size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for modifier invocation.")); for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*(*parameters)[i]->getType())) BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in modifier invocation.")); } diff --git a/AST.h b/AST.h index dea0fba63..c91c433ed 100644 --- a/AST.h +++ b/AST.h @@ -144,7 +144,7 @@ public: Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; } bool isPublic() const { return getVisibility() >= Visibility::Public; } bool isVisibleInContract() const { return getVisibility() != Visibility::External; } - bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Internal; } + virtual bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Internal; } /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. @@ -247,6 +247,9 @@ public: /// as intended for use by the ABI. std::map, FunctionTypePointer> getInterfaceFunctions() const; + /// @returns a list of the inheritable members of this contract + std::vector const& getInheritableMembers() const; + /// List of all (direct and indirect) base contracts in order from derived to base, including /// the contract itself. Available after name resolution std::vector const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } @@ -273,6 +276,7 @@ private: std::vector m_linearizedBaseContracts; mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr>> m_interfaceEvents; + mutable std::unique_ptr> m_inheritableMembers; }; class InheritanceSpecifier: public ASTNode @@ -405,6 +409,11 @@ public: ASTPointer const& getReturnParameterList() const { return m_returnParameters; } Block const& getBody() const { return *m_body; } + virtual bool isVisibleInDerivedContracts() const override + { + return !isConstructor() && !getName().empty() && isVisibleInContract() && + getVisibility() >= Visibility::Internal; + } virtual TypePointer getType(ContractDefinition const*) const override; /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. @@ -501,7 +510,7 @@ private: }; /** - * Invocation/usage of a modifier in a function header. + * Invocation/usage of a modifier in a function header or a base constructor call. */ class ModifierInvocation: public ASTNode { @@ -516,7 +525,8 @@ public: ASTPointer const& getName() const { return m_modifierName; } std::vector> const& getArguments() const { return m_arguments; } - void checkTypeRequirements(); + /// @param _bases is the list of base contracts for base constructor calls. For modifiers an empty vector should be passed. + void checkTypeRequirements(std::vector> const& _bases); private: ASTPointer m_modifierName; diff --git a/ArrayUtils.cpp b/ArrayUtils.cpp new file mode 100644 index 000000000..43cc6fd49 --- /dev/null +++ b/ArrayUtils.cpp @@ -0,0 +1,304 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Code generation utils that handle arrays. + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace solidity; + +void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const +{ + // stack layout: [source_ref] target_ref (top) + // need to leave target_ref on the stack at the end + solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); + + IntegerType uint256(256); + Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); + Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); + + switch (_sourceType.getLocation()) + { + case ArrayType::Location::CallData: + { + solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); + solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); + // This also assumes that after "length" we only have zeros, i.e. it cannot be used to + // slice a byte array from calldata. + + // stack: source_offset source_len target_ref + // fetch old length and convert to words + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + convertLengthToSize(_targetType); + // stack here: source_offset source_len target_ref target_length_words + // actual array data is stored at SHA3(storage_offset) + m_context << eth::Instruction::DUP2; + CompilerUtils(m_context).computeHashStatic(); + // compute target_data_end + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack here: source_offset source_len target_ref target_data_end target_data_ref + // store length (in bytes) + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 + << eth::Instruction::SSTORE; + // jump to end if length is zero + m_context << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // store start offset + m_context << eth::Instruction::DUP5; + // stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart + // copy from calldata and store + << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE + // increment target_data_ref by 1 + << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD + // increment calldata_offset by 32 + << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD + // check for loop condition + << eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT; + m_context.appendConditionalJumpTo(copyLoopStart); + m_context << eth::Instruction::POP; + m_context << copyLoopEnd; + + // now clear leftover bytes of the old value + // stack now: source_offset source_len target_ref target_data_end target_data_ref + clearStorageLoop(IntegerType(256)); + // stack now: source_offset source_len target_ref target_data_end + + m_context << eth::Instruction::POP << eth::Instruction::SWAP2 + << eth::Instruction::POP << eth::Instruction::POP; + break; + } + case ArrayType::Location::Storage: + { + // this copies source to target and also clears target if it was larger + + solAssert(sourceBaseType->getStorageSize() == targetBaseType->getStorageSize(), + "Copying with different storage sizes not yet implemented."); + // stack: source_ref target_ref + // store target_ref + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; + // stack: target_ref source_ref target_ref + // fetch lengthes + retrieveLength(_targetType); + m_context << eth::Instruction::SWAP2; + // stack: target_ref target_len target_ref source_ref + retrieveLength(_sourceType); + // stack: target_ref target_len target_ref source_ref source_len + if (_targetType.isDynamicallySized()) + // store new target length + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // compute hashes (data positions) + m_context << eth::Instruction::SWAP2; + if (_targetType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SWAP1; + if (_sourceType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref target_len source_len target_data_pos source_data_pos + m_context << eth::Instruction::DUP4; + convertLengthToSize(_sourceType); + m_context << eth::Instruction::DUP4; + convertLengthToSize(_sourceType); + // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size + // @todo we might be able to go without a third counter + m_context << u256(0); + // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // copy + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD; + StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + m_context << eth::dupInstruction(5 + sourceBaseType->getSizeOnStack()) + << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << eth::Instruction::ADD; + StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); + // increment + m_context << targetBaseType->getStorageSize() << eth::Instruction::ADD; + m_context.appendJumpTo(copyLoopStart); + m_context << copyLoopEnd; + + // zero-out leftovers in target + // stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter + // add counter to target_data_pos + m_context << eth::Instruction::DUP5 << eth::Instruction::ADD + << eth::Instruction::SWAP5 << eth::Instruction::POP; + // stack: target_ref target_len target_data_pos_updated target_data_pos source_data_pos target_size source_size + // add size to target_data_pos to get target_data_end + m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SWAP4 + << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + clearStorageLoop(*targetBaseType); + m_context << eth::Instruction::POP; + break; + } + default: + solAssert(false, "Given byte array location not implemented."); + } +} + +void ArrayUtils::clearArray(ArrayType const& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + if (_type.isDynamicallySized()) + clearDynamicArray(_type); + else if (_type.getLength() == 0) + m_context << eth::Instruction::POP; + else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value + { + for (unsigned i = 1; i < _type.getLength(); ++i) + { + StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); + m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; + } + StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); + } + else + { + m_context + << eth::Instruction::DUP1 << u256(_type.getLength()) + << u256(_type.getBaseType()->getStorageSize()) + << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; + clearStorageLoop(*_type.getBaseType()); + m_context << eth::Instruction::POP; + } +} + +void ArrayUtils::clearDynamicArray(ArrayType const& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + + // fetch length + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // set length to zero + m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + // stack: ref old_length + convertLengthToSize(_type); + // compute data positions + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: len data_pos (len is in slots for byte array and in items for other arrays) + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack: data_pos_end data_pos + if (_type.isByteArray()) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); + // cleanup + m_context << eth::Instruction::POP; +} + +void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const +{ + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + + eth::AssemblyItem resizeEnd = m_context.newTag(); + + // stack: ref new_length + // fetch old length + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: ref new_length old_length + // store new length + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // skip if size is not reduced + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2 + << eth::Instruction::ISZERO << eth::Instruction::GT; + m_context.appendConditionalJumpTo(resizeEnd); + + // size reduced, clear the end of the array + // stack: ref new_length old_length + convertLengthToSize(_type); + m_context << eth::Instruction::DUP2; + convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + m_context << eth::Instruction::DUP4; + CompilerUtils(m_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + m_context << eth::Instruction::SWAP2 << eth::Instruction::DUP3 << eth::Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray()) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); + + m_context << resizeEnd; + // cleanup + m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; +} + +void ArrayUtils::clearStorageLoop(Type const& _type) const +{ + // stack: end_pos pos + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(zeroLoopEnd); + // delete + StorageItem(m_context, _type).setToZero(SourceLocation(), false); + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(loopStart); + // cleanup + m_context << zeroLoopEnd; + m_context << eth::Instruction::POP; +} + +void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType) const +{ + if (_arrayType.isByteArray()) + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + else if (_arrayType.getBaseType()->getStorageSize() > 1) + m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; +} + +void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const +{ + if (_arrayType.isDynamicallySized()) + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + else + m_context << _arrayType.getLength(); +} + diff --git a/ArrayUtils.h b/ArrayUtils.h new file mode 100644 index 000000000..73e88340e --- /dev/null +++ b/ArrayUtils.h @@ -0,0 +1,78 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Code generation utils that handle arrays. + */ + +#pragma once + +namespace dev +{ +namespace solidity +{ + +class CompilerContext; +class Type; +class ArrayType; + +/** + * Class that provides code generation for handling arrays. + */ +class ArrayUtils +{ +public: + ArrayUtils(CompilerContext& _context): m_context(_context) {} + + /// Copies an array to an array in storage. The arrays can be of different types only if + /// their storage representation is the same. + /// Stack pre: [source_reference] target_reference + /// Stack post: target_reference + void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; + /// Clears the given dynamic or static array. + /// Stack pre: reference + /// Stack post: + void clearArray(ArrayType const& _type) const; + /// Clears the length and data elements of the array referenced on the stack. + /// Stack pre: reference + /// Stack post: + void clearDynamicArray(ArrayType const& _type) const; + /// Changes the size of a dynamic array and clears the tail if it is shortened. + /// Stack pre: reference new_length + /// Stack post: + void resizeDynamicArray(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 + void clearStorageLoop(Type const& _type) const; + /// Converts length to size (multiplies by size on stack), rounds up for byte arrays. + /// Stack pre: length + /// Stack post: size + void convertLengthToSize(ArrayType const& _arrayType) const; + /// Retrieves the length (number of elements) of the array ref on the stack. This also + /// works for statically-sized arrays. + /// Stack pre: reference + /// Stack post: reference length + void retrieveLength(ArrayType const& _arrayType) const; + +private: + CompilerContext& m_context; +}; + +} +} diff --git a/Compiler.cpp b/Compiler.cpp index 2f75d2ea4..acc30cf35 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -58,7 +58,10 @@ void Compiler::compileContract(ContractDefinition const& _contract, while (!functions.empty()) { for (Declaration const* function: functions) + { + m_context.setStackOffset(0); function->accept(*this); + } functions = m_context.getFunctionsWithoutCode(); } @@ -79,37 +82,38 @@ void Compiler::initializeContext(ContractDefinition const& _contract, void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) { - // arguments for base constructors, filled in derived-to-base order - map> const*> baseArguments; - // Determine the arguments that are used for the base constructors. std::vector const& bases = _contract.getLinearizedBaseContracts(); for (ContractDefinition const* contract: bases) + { + if (FunctionDefinition const* constructor = contract->getConstructor()) + for (auto const& modifier: constructor->getModifiers()) + { + auto baseContract = dynamic_cast( + modifier->getName()->getReferencedDeclaration()); + if (baseContract) + if (m_baseArguments.count(baseContract->getConstructor()) == 0) + m_baseArguments[baseContract->getConstructor()] = &modifier->getArguments(); + } + for (ASTPointer const& base: contract->getBaseContracts()) { ContractDefinition const* baseContract = dynamic_cast( base->getName()->getReferencedDeclaration()); solAssert(baseContract, ""); - if (baseArguments.count(baseContract) == 0) - baseArguments[baseContract] = &base->getArguments(); - } - // Call constructors in base-to-derived order. - // The Constructor for the most derived contract is called later. - for (unsigned i = 1; i < bases.size(); i++) - { - ContractDefinition const* base = bases[bases.size() - i]; - solAssert(base, ""); - initializeStateVariables(*base); - FunctionDefinition const* baseConstructor = base->getConstructor(); - if (!baseConstructor) - continue; - solAssert(baseArguments[base], ""); - appendBaseConstructorCall(*baseConstructor, *baseArguments[base]); + if (m_baseArguments.count(baseContract->getConstructor()) == 0) + m_baseArguments[baseContract->getConstructor()] = &base->getArguments(); + } } - initializeStateVariables(_contract); - if (_contract.getConstructor()) - appendConstructorCall(*_contract.getConstructor()); + // Initialization of state variables in base-to-derived order. + for (ContractDefinition const* contract: boost::adaptors::reverse(bases)) + initializeStateVariables(*contract); + + if (FunctionDefinition const* constructor = _contract.getConstructor()) + appendConstructor(*constructor); + else if (auto c = m_context.getNextConstructor(_contract)) + appendBaseConstructor(*c); eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly()); // stack contains sub size @@ -126,22 +130,23 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp } } -void Compiler::appendBaseConstructorCall(FunctionDefinition const& _constructor, - vector> const& _arguments) +void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, &_constructor); FunctionType constructorType(_constructor); - eth::AssemblyItem returnLabel = m_context.pushNewTag(); - for (unsigned i = 0; i < _arguments.size(); ++i) - compileExpression(*_arguments[i], constructorType.getParameterTypes()[i]); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); - m_context << returnLabel; + if (!constructorType.getParameterTypes().empty()) + { + std::vector> const* arguments = m_baseArguments[&_constructor]; + solAssert(arguments, ""); + for (unsigned i = 0; i < arguments->size(); ++i) + compileExpression(*(arguments->at(i)), constructorType.getParameterTypes()[i]); + } + _constructor.accept(*this); } -void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) +void Compiler::appendConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, &_constructor); - eth::AssemblyItem returnTag = m_context.pushNewTag(); // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program unsigned argumentSize = 0; for (ASTPointer const& var: _constructor.getParameters()) @@ -155,8 +160,7 @@ void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) m_context << eth::Instruction::CODECOPY; appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true); } - m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); - m_context << returnTag; + _constructor.accept(*this); } void Compiler::appendFunctionSelector(ContractDefinition const& _contract) @@ -201,42 +205,49 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool { // We do not check the calldata size, everything is zero-padded. unsigned offset(CompilerUtils::dataStartOffset); - bool const c_padToWords = true; - unsigned dynamicParameterCount = 0; + bigint parameterHeadEnd = offset; for (TypePointer const& type: _typeParameters) - if (type->isDynamicallySized()) - dynamicParameterCount++; - offset += dynamicParameterCount * 32; - unsigned currentDynamicParameter = 0; + parameterHeadEnd += type->isDynamicallySized() ? 32 : + CompilerUtils::getPaddedSize(type->getCalldataEncodedSize()); + solAssert(parameterHeadEnd <= numeric_limits::max(), "Arguments too large."); + + unsigned stackHeightOfPreviousDynamicArgument = 0; + ArrayType const* previousDynamicType = nullptr; for (TypePointer const& type: _typeParameters) - if (type->isDynamicallySized()) + { + switch (type->getCategory()) { - // value on stack: [calldata_offset] (only if we are already in dynamic mode) - if (currentDynamicParameter == 0) - // switch from static to dynamic + case Type::Category::Array: + if (type->isDynamicallySized()) + { + // put on stack: data_offset length + unsigned newStackHeight = m_context.getStackHeight(); + if (previousDynamicType) + { + // Retrieve data start offset by adding length to start offset of previous dynamic type + unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; + m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); + ArrayUtils(m_context).convertLengthToSize(*previousDynamicType); + m_context << u256(32) << eth::Instruction::MUL << eth::Instruction::ADD; + } + else + m_context << u256(parameterHeadEnd); + stackHeightOfPreviousDynamicArgument = newStackHeight; + previousDynamicType = &dynamic_cast(*type); + offset += CompilerUtils(m_context).loadFromMemory(offset, IntegerType(256), !_fromMemory); + } + else + { m_context << u256(offset); - // retrieve length - CompilerUtils(m_context).loadFromMemory( - CompilerUtils::dataStartOffset + currentDynamicParameter * 32, - IntegerType(256), !_fromMemory, c_padToWords); - // stack: offset length - // add 32-byte padding to copy of length - m_context << u256(32) << eth::Instruction::DUP1 << u256(31) - << eth::Instruction::DUP4 << eth::Instruction::ADD - << eth::Instruction::DIV << eth::Instruction::MUL; - // stack: offset length padded_length - m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; - currentDynamicParameter++; - // stack: offset length next_calldata_offset + offset += CompilerUtils::getPaddedSize(type->getCalldataEncodedSize()); + } + break; + default: + solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); + offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, true); } - else if (currentDynamicParameter == 0) - // we can still use static load - offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, c_padToWords); - else - CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, c_padToWords); - if (dynamicParameterCount > 0) - m_context << eth::Instruction::POP; + } } void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) @@ -296,6 +307,29 @@ bool Compiler::visit(FunctionDefinition const& _function) // although note that this reduces the size of the visible stack m_context.startFunction(_function); + + // stack upon entry: [return address] [arg0] [arg1] ... [argn] + // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + + unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); + if (!_function.isConstructor()) + // adding 1 for return address. + m_context.adjustStackOffset(parametersSize + 1); + for (ASTPointer const& variable: _function.getParameters()) + { + m_context.addVariable(*variable, parametersSize); + parametersSize -= variable->getType()->getSizeOnStack(); + } + + for (ASTPointer const& variable: _function.getReturnParameters()) + m_context.addAndInitializeVariable(*variable); + for (VariableDeclaration const* localVariable: _function.getLocalVariables()) + m_context.addAndInitializeVariable(*localVariable); + + if (_function.isConstructor()) + if (auto c = m_context.getNextConstructor(dynamic_cast(*_function.getScope()))) + appendBaseConstructor(*c); + m_returnTag = m_context.newTag(); m_breakTags.clear(); m_continueTags.clear(); @@ -303,21 +337,6 @@ bool Compiler::visit(FunctionDefinition const& _function) m_currentFunction = &_function; m_modifierDepth = 0; - // stack upon entry: [return address] [arg0] [arg1] ... [argn] - // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] - - unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); - m_context.adjustStackOffset(parametersSize); - for (ASTPointer const& variable: _function.getParameters()) - { - m_context.addVariable(*variable, parametersSize); - parametersSize -= variable->getType()->getSizeOnStack(); - } - for (ASTPointer const& variable: _function.getReturnParameters()) - m_context.addAndInitializeVariable(*variable); - for (VariableDeclaration const* localVariable: _function.getLocalVariables()) - m_context.addAndInitializeVariable(*localVariable); - appendModifierOrFunctionCode(); m_context << m_returnTag; @@ -352,8 +371,14 @@ bool Compiler::visit(FunctionDefinition const& _function) } //@todo assert that everything is in place now - m_context << eth::Instruction::JUMP; + for (ASTPointer const& variable: _function.getParameters() + _function.getReturnParameters()) + m_context.removeVariable(*variable); + for (VariableDeclaration const* localVariable: _function.getLocalVariables()) + m_context.removeVariable(*localVariable); + m_context.adjustStackOffset(-c_returnValuesSize); + if (!_function.isConstructor()) + m_context << eth::Instruction::JUMP; return false; } @@ -515,6 +540,16 @@ void Compiler::appendModifierOrFunctionCode() else { ASTPointer const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; + + // constructor call should be excluded + if (dynamic_cast(modifierInvocation->getName()->getReferencedDeclaration())) + { + ++m_modifierDepth; + appendModifierOrFunctionCode(); + --m_modifierDepth; + return; + } + ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); CompilerContext::LocationSetter locationSetter(m_context, &modifier); solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); diff --git a/Compiler.h b/Compiler.h index 3ad2d8c61..2804e8eca 100644 --- a/Compiler.h +++ b/Compiler.h @@ -55,9 +55,8 @@ private: /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); - void appendBaseConstructorCall(FunctionDefinition const& _constructor, - std::vector> const& _arguments); - void appendConstructorCall(FunctionDefinition const& _constructor); + void appendBaseConstructor(FunctionDefinition const& _constructor); + void appendConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. /// From memory if @a _fromMemory is true, otherwise from call data. @@ -94,6 +93,8 @@ private: unsigned m_modifierDepth = 0; FunctionDefinition const* m_currentFunction; unsigned m_stackCleanupForReturn; ///< 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> const*> m_baseArguments; }; } diff --git a/CompilerContext.cpp b/CompilerContext.cpp index 18be337fa..61c25052c 100644 --- a/CompilerContext.cpp +++ b/CompilerContext.cpp @@ -51,8 +51,6 @@ void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) void CompilerContext::startFunction(Declaration const& _function) { m_functionsWithCode.insert(&_function); - m_localVariables.clear(); - m_asm.setDeposit(0); *this << getFunctionEntryLabel(_function); } @@ -63,6 +61,12 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; } +void CompilerContext::removeVariable(VariableDeclaration const& _declaration) +{ + solAssert(m_localVariables.count(&_declaration), ""); + m_localVariables.erase(&_declaration); +} + void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) { LocationSetter locationSetter(*this, &_declaration); @@ -110,11 +114,8 @@ eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefiniti eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base) { - // search for first contract after _base - solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_base); - solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); - for (++it; it != m_inheritanceHierarchy.end(); ++it) + auto it = getSuperContract(_base); + for (; it != m_inheritanceHierarchy.end(); ++it) for (ASTPointer const& function: (*it)->getDefinedFunctions()) if (!function->isConstructor() && function->getName() == _name) return getFunctionEntryLabel(*function); @@ -122,6 +123,16 @@ eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _nam return m_asm.newTag(); // not reached } +FunctionDefinition const* CompilerContext::getNextConstructor(ContractDefinition const& _contract) const +{ + vector::const_iterator it = getSuperContract(_contract); + for (; it != m_inheritanceHierarchy.end(); ++it) + if ((*it)->getConstructor()) + return (*it)->getConstructor(); + + return nullptr; +} + set CompilerContext::getFunctionsWithoutCode() { set functions; @@ -201,5 +212,13 @@ CompilerContext& CompilerContext::operator<<(bytes const& _data) return *this; } +vector::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract); + solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); + return ++it; +} + } } diff --git a/CompilerContext.h b/CompilerContext.h index 94d6443e9..301ef1468 100644 --- a/CompilerContext.h +++ b/CompilerContext.h @@ -43,11 +43,13 @@ public: void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); + void removeVariable(VariableDeclaration const& _declaration); void addAndInitializeVariable(VariableDeclaration const& _declaration); void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } bytes const& getCompiledContract(ContractDefinition const& _contract) const; + void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } unsigned getStackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } @@ -62,6 +64,8 @@ public: /// @returns the entry label of function with the given name from the most derived class just /// above _base in the current inheritance hierarchy. eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base); + FunctionDefinition const* getNextConstructor(ContractDefinition const& _contract) const; + /// @returns the set of functions for which we still need to generate code std::set getFunctionsWithoutCode(); /// Resets function specific members, inserts the function entry label and marks the function @@ -126,9 +130,11 @@ public: LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } }; - eth::Assembly m_asm; -private: +private: + std::vector::const_iterator getSuperContract(const ContractDefinition &_contract) const; + + eth::Assembly m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. diff --git a/CompilerStack.cpp b/CompilerStack.cpp index 69dbced00..10504a245 100644 --- a/CompilerStack.cpp +++ b/CompilerStack.cpp @@ -155,6 +155,16 @@ bytes const& CompilerStack::compile(string const& _sourceCode, bool _optimize) return getBytecode(); } +eth::AssemblyItems const& CompilerStack::getAssemblyItems(string const& _contractName) const +{ + return getContract(_contractName).compiler->getAssemblyItems(); +} + +eth::AssemblyItems const& CompilerStack::getRuntimeAssemblyItems(string const& _contractName) const +{ + return getContract(_contractName).compiler->getRuntimeAssemblyItems(); +} + bytes const& CompilerStack::getBytecode(string const& _contractName) const { return getContract(_contractName).bytecode; diff --git a/CompilerStack.h b/CompilerStack.h index 812f41863..2f34ab722 100644 --- a/CompilerStack.h +++ b/CompilerStack.h @@ -26,12 +26,22 @@ #include #include #include +#include #include #include #include -namespace dev { -namespace solidity { +namespace dev +{ + +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector; +} + +namespace solidity +{ // forward declarations class Scanner; @@ -85,6 +95,10 @@ public: bytes const& getBytecode(std::string const& _contractName = "") const; /// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor. bytes const& getRuntimeBytecode(std::string const& _contractName = "") const; + /// @returns normal contract assembly items + eth::AssemblyItems const& getAssemblyItems(std::string const& _contractName = "") const; + /// @returns runtime contract assembly items + eth::AssemblyItems const& getRuntimeAssemblyItems(std::string const& _contractName = "") const; /// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor. dev::h256 getContractCodeHash(std::string const& _contractName = "") const; diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index c7ce94456..826651e61 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -164,134 +164,6 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari m_context << u256(length) << u256(0) << eth::Instruction::SHA3; } -void CompilerUtils::copyByteArrayToStorage( - ArrayType const& _targetType, ArrayType const& _sourceType) const -{ - // stack layout: [source_ref] target_ref (top) - // need to leave target_ref on the stack at the end - solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); - solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); - solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); - - switch (_sourceType.getLocation()) - { - case ArrayType::Location::CallData: - { - // This also assumes that after "length" we only have zeros, i.e. it cannot be used to - // slice a byte array from calldata. - - // stack: source_offset source_len target_ref - // fetch old length and convert to words - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - m_context << u256(31) << eth::Instruction::ADD - << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - // stack here: source_offset source_len target_ref target_length_words - // actual array data is stored at SHA3(storage_offset) - m_context << eth::Instruction::DUP2; - CompilerUtils(m_context).computeHashStatic(); - // compute target_data_end - m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD - << eth::Instruction::SWAP1; - // stack here: source_offset source_len target_ref target_data_end target_data_ref - // store length (in bytes) - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 - << eth::Instruction::SSTORE; - // jump to end if length is zero - m_context << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // store start offset - m_context << eth::Instruction::DUP5; - // stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart - // copy from calldata and store - << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD - << eth::Instruction::DUP3 << eth::Instruction::SSTORE - // increment target_data_ref by 1 - << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD - // increment calldata_offset by 32 - << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD - // check for loop condition - << eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT; - m_context.appendConditionalJumpTo(copyLoopStart); - m_context << eth::Instruction::POP; - m_context << copyLoopEnd; - - // now clear leftover bytes of the old value - // stack now: source_offset source_len target_ref target_data_end target_data_ref - clearStorageLoop(); - // stack now: source_offset source_len target_ref target_data_end - - m_context << eth::Instruction::POP << eth::Instruction::SWAP2 - << eth::Instruction::POP << eth::Instruction::POP; - break; - } - case ArrayType::Location::Storage: - { - // this copies source to target and also clears target if it was larger - - // stack: source_ref target_ref - // store target_ref - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - // fetch lengthes - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2 - << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - // stack: target_ref target_len_bytes target_ref source_ref source_len_bytes - // store new target length - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; - // compute hashes (data positions) - m_context << eth::Instruction::SWAP2; - CompilerUtils(m_context).computeHashStatic(); - m_context << eth::Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos - // convert lengthes from bytes to storage slots - m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV - << eth::Instruction::SWAP2 - << eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV; - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len - // @todo we might be able to go without a third counter - m_context << u256(0); - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::GT << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); - // copy - m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD - << eth::Instruction::SLOAD - << eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SSTORE; - // increment - m_context << u256(1) << eth::Instruction::ADD; - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - - // zero-out leftovers in target - // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter - // add counter to target_data_pos - m_context << eth::Instruction::DUP5 << eth::Instruction::ADD - << eth::Instruction::SWAP5 << eth::Instruction::POP; - // stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len - // add length to target_data_pos to get target_data_end - m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SWAP4 - << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(); - m_context << eth::Instruction::POP; - break; - } - default: - solAssert(false, "Given byte array location not implemented."); - } -} - unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned _encodedSize = _type.getCalldataEncodedSize(); @@ -316,28 +188,6 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } -void CompilerUtils::clearByteArray(ArrayType const& _type) const -{ - solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); - solAssert(_type.isByteArray(), "Non byte arrays not yet implemented here."); - - // fetch length - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; - // set length to zero - m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; - // convert length from bytes to storage slots - m_context << u256(31) << eth::Instruction::ADD - << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - // compute data positions - m_context << eth::Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD - << eth::Instruction::SWAP1; - clearStorageLoop(); - // cleanup - m_context << eth::Instruction::POP; -} unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { @@ -356,25 +206,5 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBou return numBytes; } -void CompilerUtils::clearStorageLoop() const -{ - // stack: end_pos pos - eth::AssemblyItem loopStart = m_context.newTag(); - m_context << loopStart; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 - << eth::Instruction::GT << eth::Instruction::ISZERO; - eth::AssemblyItem zeroLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(zeroLoopEnd); - // zero out - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE; - // increment - m_context << u256(1) << eth::Instruction::ADD; - m_context.appendJumpTo(loopStart); - // cleanup - m_context << zeroLoopEnd; - m_context << eth::Instruction::POP; -} - } } diff --git a/CompilerUtils.h b/CompilerUtils.h index 2fb97d808..2df85f11c 100644 --- a/CompilerUtils.h +++ b/CompilerUtils.h @@ -79,15 +79,6 @@ public: /// @note Only works for types of fixed size. void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false); - /// Copies a byte array to a byte array in storage. - /// Stack pre: [source_reference] target_reference - /// Stack post: target_reference - void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; - /// Clears the length and data elements of the byte array referenced on the stack. - /// Stack pre: reference - /// Stack post: - void clearByteArray(ArrayType const& _type) const; - /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. static const unsigned int dataStartOffset; @@ -97,10 +88,6 @@ private: unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; /// Loads type from memory assuming memory offset is on stack top. unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); - /// Appends a loop that clears a sequence of storage slots (excluding end). - /// Stack pre: end_ref start_ref - /// Stack post: end_ref - void clearStorageLoop() const; CompilerContext& m_context; }; diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index 430e46b06..619b06738 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -93,7 +93,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << eth::Instruction::DUP1 << structType->getStorageOffsetOfMember(names[i]) << eth::Instruction::ADD; - StorageItem(m_context, types[i]).retrieveValue(SourceLocation(), true); + StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); m_context << eth::Instruction::SWAP1; retSizeOnStack += types[i]->getSizeOnStack(); @@ -104,7 +104,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); - StorageItem(m_context, returnType).retrieveValue(SourceLocation(), true); + StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } solAssert(retSizeOnStack <= 15, "Stack too deep."); @@ -680,7 +680,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; case ArrayType::Location::Storage: - setLValueToStorageItem(_memberAccess); + setLValue(_memberAccess, type); break; default: solAssert(false, "Unsupported array location."); @@ -984,9 +984,10 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); else // send all gas except for the 21 needed to execute "SUB" and "CALL" - m_context << u256(21) << eth::Instruction::GAS << eth::Instruction::SUB; - m_context << eth::Instruction::CALL - << eth::Instruction::POP; // @todo do not ignore failure indicator + m_context << u256(_functionType.valueSet() ? 6741 : 41) << eth::Instruction::GAS << eth::Instruction::SUB; + m_context << eth::Instruction::CALL; + auto tag = m_context.appendConditionalJump(); + m_context << eth::Instruction::STOP << tag; // STOP if CALL leaves 0. if (_functionType.valueSet()) m_context << eth::Instruction::POP; if (_functionType.gasSet()) @@ -999,10 +1000,12 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true); } -void ExpressionCompiler::appendArgumentsCopyToMemory(vector> const& _arguments, - TypePointers const& _types, - bool _padToWordBoundaries, - bool _padExceptionIfFourBytes) +void ExpressionCompiler::appendArgumentsCopyToMemory( + vector> const& _arguments, + TypePointers const& _types, + bool _padToWordBoundaries, + bool _padExceptionIfFourBytes +) { solAssert(_types.empty() || _types.size() == _arguments.size(), ""); for (size_t i = 0; i < _arguments.size(); ++i) @@ -1044,7 +1047,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) { - setLValue(_expression, _expression.getType()); + setLValue(_expression, *_expression.getType()); } } diff --git a/LValue.cpp b/LValue.cpp index db59c41af..452ca1c73 100644 --- a/LValue.cpp +++ b/LValue.cpp @@ -32,15 +32,14 @@ using namespace solidity; StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration): - LValue(_compilerContext, _declaration.getType()), + LValue(_compilerContext, *_declaration.getType()), m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)), - m_size(m_dataType->getSizeOnStack()) + m_size(m_dataType.getSizeOnStack()) { } -void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) const +void StackVariable::retrieveValue(SourceLocation const& _location, bool) const { - (void)_remove; unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory BOOST_THROW_EXCEPTION(CompilerError() @@ -49,9 +48,8 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) m_context << eth::dupInstruction(stackPos + 1); } -void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const { - (void)_sourceType; unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; if (stackDiff > 16) BOOST_THROW_EXCEPTION(CompilerError() @@ -63,7 +61,7 @@ void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _l retrieveValue(_location); } -void StackVariable::setToZero(SourceLocation const& _location) const +void StackVariable::setToZero(SourceLocation const& _location, bool) const { unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset); if (stackDiff > 16) @@ -77,20 +75,20 @@ void StackVariable::setToZero(SourceLocation const& _location) const StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): - StorageItem(_compilerContext, _declaration.getType()) + StorageItem(_compilerContext, *_declaration.getType()) { m_context << m_context.getStorageLocationOfVariable(_declaration); } -StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _type): +StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): LValue(_compilerContext, _type) { - if (m_dataType->isValueType()) + if (m_dataType.isValueType()) { - solAssert(m_dataType->getStorageSize() == m_dataType->getSizeOnStack(), ""); - solAssert(m_dataType->getStorageSize() <= numeric_limits::max(), - "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType->getStorageSize()); + solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); + solAssert(m_dataType.getStorageSize() <= numeric_limits::max(), + "The storage size of " + m_dataType.toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType.getStorageSize()); } else m_size = 0; // unused @@ -98,7 +96,7 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { - if (!m_dataType->isValueType()) + if (!m_dataType.isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) m_context << eth::Instruction::DUP1; @@ -118,7 +116,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const { // stack layout: value value ... value target_ref - if (m_dataType->isValueType()) + if (m_dataType.isValueType()) { if (!_move) // copy values { @@ -143,20 +141,20 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc } else { - solAssert(_sourceType.getCategory() == m_dataType->getCategory(), + solAssert(_sourceType.getCategory() == m_dataType.getCategory(), "Wrong type conversation for assignment."); - if (m_dataType->getCategory() == Type::Category::Array) + if (m_dataType.getCategory() == Type::Category::Array) { - CompilerUtils(m_context).copyByteArrayToStorage( - dynamic_cast(*m_dataType), + ArrayUtils(m_context).copyArrayToStorage( + dynamic_cast(m_dataType), dynamic_cast(_sourceType)); if (_move) m_context << eth::Instruction::POP; } - else if (m_dataType->getCategory() == Type::Category::Struct) + else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: source_ref target_ref - auto const& structType = dynamic_cast(*m_dataType); + auto const& structType = dynamic_cast(m_dataType); solAssert(structType == _sourceType, "Struct assignment with conversion."); for (auto const& member: structType.getMembers()) { @@ -167,12 +165,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << structType.getStorageOffsetOfMember(member.first) << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; // stack: source_ref target_ref member_offset source_member_ref - StorageItem(m_context, memberType).retrieveValue(_location, true); + StorageItem(m_context, *memberType).retrieveValue(_location, true); // stack: source_ref target_ref member_offset source_value... m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; // stack: source_ref target_ref member_offset source_value... target_member_ref - StorageItem(m_context, memberType).storeValue(*memberType, _location, true); + StorageItem(m_context, *memberType).storeValue(*memberType, _location, true); m_context << eth::Instruction::POP; } if (_move) @@ -187,16 +185,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc } } - -void StorageItem::setToZero(SourceLocation const& _location) const +void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const { - (void)_location; - if (m_dataType->getCategory() == Type::Category::Array) - CompilerUtils(m_context).clearByteArray(dynamic_cast(*m_dataType)); - else if (m_dataType->getCategory() == Type::Category::Struct) + if (m_dataType.getCategory() == Type::Category::Array) + { + if (!_removeReference) + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).clearArray(dynamic_cast(m_dataType)); + } + else if (m_dataType.getCategory() == Type::Category::Struct) { // stack layout: ref - auto const& structType = dynamic_cast(*m_dataType); + auto const& structType = dynamic_cast(m_dataType); for (auto const& member: structType.getMembers()) { // zero each member that is not a mapping @@ -205,19 +205,61 @@ void StorageItem::setToZero(SourceLocation const& _location) const continue; m_context << structType.getStorageOffsetOfMember(member.first) << eth::Instruction::DUP2 << eth::Instruction::ADD; - StorageItem(m_context, memberType).setToZero(); + StorageItem(m_context, *memberType).setToZero(); } - m_context << eth::Instruction::POP; + if (_removeReference) + m_context << eth::Instruction::POP; } else { - if (m_size == 0) + if (m_size == 0 && _removeReference) m_context << eth::Instruction::POP; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; + else if (m_size == 1) + m_context + << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) + << eth::Instruction::SSTORE; + else + { + if (!_removeReference) + m_context << eth::Instruction::DUP1; + for (unsigned i = 0; i < m_size; ++i) + if (i + 1 >= m_size) + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + else + m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + } } } + + +StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): + LValue(_compilerContext, *_arrayType.getMemberType("length")), + m_arrayType(_arrayType) +{ + solAssert(m_arrayType.isDynamicallySized(), ""); +} + +void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const +{ + if (!_remove) + m_context << eth::Instruction::DUP1; + m_context << eth::Instruction::SLOAD; +} + +void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const +{ + if (_move) + m_context << eth::Instruction::SWAP1; + else + m_context << eth::Instruction::DUP2; + ArrayUtils(m_context).resizeDynamicArray(m_arrayType); +} + +void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const +{ + if (!_removeReference) + m_context << eth::Instruction::DUP1; + ArrayUtils(m_context).clearDynamicArray(m_arrayType); +} + diff --git a/LValue.h b/LValue.h index bfa681a48..1ed0ae011 100644 --- a/LValue.h +++ b/LValue.h @@ -24,6 +24,7 @@ #include #include +#include namespace dev { @@ -32,6 +33,7 @@ namespace solidity class Declaration; class Type; +class ArrayType; class CompilerContext; /** @@ -40,7 +42,7 @@ class CompilerContext; class LValue { protected: - LValue(CompilerContext& _compilerContext, std::shared_ptr const& _dataType): + LValue(CompilerContext& _compilerContext, Type const& _dataType): m_context(_compilerContext), m_dataType(_dataType) {} public: @@ -56,13 +58,14 @@ public: /// Stack post: if !_move: value_of(lvalue_ref) virtual void storeValue(Type const& _sourceType, SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; - /// Stores zero in the lvalue. + /// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true. /// @a _location is the source location of the requested operation - virtual void setToZero(SourceLocation const& _location = SourceLocation()) const = 0; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const = 0; protected: CompilerContext& m_context; - std::shared_ptr m_dataType; + Type const& m_dataType; }; /** @@ -71,13 +74,14 @@ protected: class StackVariable: public LValue { public: - explicit StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); + StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); virtual bool storesReferenceOnStack() const { return false; } 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()) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override; private: /// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable. @@ -93,14 +97,15 @@ class StorageItem: public LValue { public: /// Constructs the LValue and pushes the location of @a _declaration onto the stack. - explicit StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); + StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. - explicit StorageItem(CompilerContext& _compilerContext, std::shared_ptr const& _type); + StorageItem(CompilerContext& _compilerContext, Type const& _type); virtual bool storesReferenceOnStack() const { return true; } 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()) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override; private: /// Number of stack elements occupied by the value (not the reference). @@ -108,5 +113,26 @@ private: unsigned m_size; }; +/** + * Reference to the "length" member of a dynamically-sized array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus arrays members have to be + * deleted. + */ +class StorageArrayLength: public LValue +{ +public: + /// Constructs the LValue, assumes that the reference to the array head is already on the stack. + StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); + virtual bool storesReferenceOnStack() const { return true; } + 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: + ArrayType const& m_arrayType; +}; + } } diff --git a/SourceReferenceFormatter.cpp b/SourceReferenceFormatter.cpp index 489a676ed..b5e83b8c9 100644 --- a/SourceReferenceFormatter.cpp +++ b/SourceReferenceFormatter.cpp @@ -44,8 +44,14 @@ void SourceReferenceFormatter::printSourceLocation(ostream& _stream, tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end); if (startLine == endLine) { - _stream << _scanner.getLineAtPosition(_location.start) << endl - << string(startColumn, ' ') << "^"; + string line = _scanner.getLineAtPosition(_location.start); + _stream << line << endl; + std::for_each(line.cbegin(), line.cbegin() + startColumn, + [&_stream](char const& ch) + { + _stream << (ch == '\t' ? '\t' : ' '); + }); + _stream << "^"; if (endColumn > startColumn + 2) _stream << string(endColumn - startColumn - 2, '-'); if (endColumn > startColumn + 1) diff --git a/Types.cpp b/Types.cpp index adcd2e542..96feefff5 100644 --- a/Types.cpp +++ b/Types.cpp @@ -537,7 +537,19 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { - return _convertTo.getCategory() == getCategory(); + if (_convertTo.getCategory() != getCategory()) + return false; + auto& convertTo = dynamic_cast(_convertTo); + // let us not allow assignment to memory arrays for now + if (convertTo.getLocation() != Location::Storage) + return false; + if (convertTo.isByteArray() != isByteArray()) + return false; + if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType())) + return false; + if (convertTo.isDynamicallySized()) + return true; + return !isDynamicallySized() && convertTo.getLength() >= getLength(); } TypePointer ArrayType::unaryOperatorResult(Token::Value _operator) const @@ -552,7 +564,19 @@ bool ArrayType::operator==(Type const& _other) const if (_other.getCategory() != getCategory()) return false; ArrayType const& other = dynamic_cast(_other); - return other.m_location == m_location; + if (other.m_location != m_location || other.isByteArray() != isByteArray() || + other.isDynamicallySized() != isDynamicallySized()) + return false; + return isDynamicallySized() || getLength() == other.getLength(); +} + +unsigned ArrayType::getCalldataEncodedSize() const +{ + if (isDynamicallySized()) + return 0; + bigint size = bigint(getLength()) * (isByteArray() ? 1 : getBaseType()->getCalldataEncodedSize()); + solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); + return unsigned(size); } u256 ArrayType::getStorageSize() const @@ -571,8 +595,8 @@ u256 ArrayType::getStorageSize() const unsigned ArrayType::getSizeOnStack() const { if (m_location == Location::CallData) - // offset, length (stack top) - return 2; + // offset [length] (stack top) + return 1 + (isDynamicallySized() ? 1 : 0); else // offset return 1; @@ -628,8 +652,7 @@ MemberList const& ContractType::getMembers() const { for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) for (ASTPointer const& function: base->getDefinedFunctions()) - if (!function->isConstructor() && !function->getName().empty()&& - function->isVisibleInDerivedContracts()) + if (function->isVisibleInDerivedContracts()) members.push_back(make_pair(function->getName(), make_shared(*function, true))); } else @@ -1024,10 +1047,9 @@ MemberList const& TypeType::getMembers() const vector currentBases = m_currentContract->getLinearizedBaseContracts(); if (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()) // We are accessing the type of a base contract, so add all public and protected - // functions. Note that this does not add inherited functions on purpose. - for (ASTPointer const& f: contract.getDefinedFunctions()) - if (!f->isConstructor() && !f->getName().empty() && f->isVisibleInDerivedContracts()) - members.push_back(make_pair(f->getName(), make_shared(*f))); + // members. Note that this does not add inherited functions on purpose. + for (Declaration const* decl: contract.getInheritableMembers()) + members.push_back(make_pair(decl->getName(), decl->getType())); } else if (m_actualType->getCategory() == Category::Enum) { diff --git a/Types.h b/Types.h index 9961f03a3..8474c6c03 100644 --- a/Types.h +++ b/Types.h @@ -302,6 +302,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(const Type& _other) const override; + virtual unsigned getCalldataEncodedSize() const override; virtual bool isDynamicallySized() const { return m_hasDynamicLength; } virtual u256 getStorageSize() const override; virtual unsigned getSizeOnStack() const override;