diff options
author | chriseth <chris@ethereum.org> | 2017-02-01 01:29:51 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-01 01:29:51 +0800 |
commit | 364da425d3116a4b85863df39a1864340861d71e (patch) | |
tree | 521a52c3f8182cd6a054f148bd649c8b6b0e2af9 /libsolidity | |
parent | 60cc1668517f56ce6ca8225555472e7a27eab8b0 (diff) | |
parent | 7b18c9df1dfa0076566bfa1e4a3bc5e5ba9c8594 (diff) | |
download | dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.gz dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.zst dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.zip |
Merge pull request #1622 from ethereum/develop
Solidity version 0.4.9
Diffstat (limited to 'libsolidity')
26 files changed, 1237 insertions, 629 deletions
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index f8c12c5b..b33c8568 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -42,20 +42,32 @@ Declaration const* DeclarationContainer::conflictingDeclaration( if (m_invisibleDeclarations.count(*_name)) declarations += m_invisibleDeclarations.at(*_name); - if (dynamic_cast<FunctionDefinition const*>(&_declaration)) + if ( + dynamic_cast<FunctionDefinition const*>(&_declaration) || + dynamic_cast<EventDefinition const*>(&_declaration) + ) { - // check that all other declarations with the same name are functions or a public state variable + // check that all other declarations with the same name are functions or a public state variable or events. + // And then check that the signatures are different. for (Declaration const* declaration: declarations) { - if (dynamic_cast<FunctionDefinition const*>(declaration)) - continue; if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration)) { if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic()) continue; return declaration; } - return declaration; + if ( + dynamic_cast<FunctionDefinition const*>(&_declaration) && + !dynamic_cast<FunctionDefinition const*>(declaration) + ) + return declaration; + if ( + dynamic_cast<EventDefinition const*>(&_declaration) && + !dynamic_cast<EventDefinition const*>(declaration) + ) + return declaration; + // Or, continue. } } else if (declarations.size() == 1 && declarations.front() == &_declaration) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 08323243..b0a82715 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -260,8 +260,8 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( for (auto it = _declarations.begin(); it != _declarations.end(); ++it) { solAssert(*it, ""); - // the declaration is functionDefinition or a VariableDeclaration while declarations > 1 - solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it), + // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 + solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<EventDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it), "Found overloading involving something not a function or a variable"); shared_ptr<FunctionType const> functionType { (*it)->functionType(false) }; diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 66bf1d0e..df579c3d 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -87,7 +87,6 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) { switch (_typeName.visibility()) { - case VariableDeclaration::Visibility::Default: case VariableDeclaration::Visibility::Internal: case VariableDeclaration::Visibility::External: break; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 67c8ac17..533c787b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -75,7 +75,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract) checkContractAbstractConstructors(_contract); FunctionDefinition const* function = _contract.constructor(); - if (function) { + if (function) + { + if (!function->isPublic()) + _contract.annotation().hasPublicConstructor = false; if (!function->returnParameters().empty()) typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); if (function->isDeclaredConst()) @@ -1280,6 +1283,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) fatalTypeError(_newExpression.location(), "Identifier is not a contract."); if (!contract->annotation().isFullyImplemented) typeError(_newExpression.location(), "Trying to create an instance of an abstract contract."); + if (!contract->annotation().hasPublicConstructor) + typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); solAssert(!!m_scope, ""); m_scope->annotation().contractDependencies.insert(contract); @@ -1560,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { + if (_literal.looksLikeAddress()) + { + if (_literal.passesAddressChecksum()) + { + _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address); + return; + } + else + warning(_literal.location(), "This looks like an address but has an invalid checksum."); + } _literal.annotation().type = Type::forLiteral(_literal); if (!_literal.annotation().type) fatalTypeError(_literal.location(), "Invalid literal value."); diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 6f7a64dc..616de54e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -20,8 +20,6 @@ * Solidity abstract syntax tree. */ -#include <algorithm> -#include <functional> #include <libsolidity/interface/Utils.h> #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ASTVisitor.h> @@ -30,11 +28,31 @@ #include <libdevcore/SHA3.h> +#include <boost/algorithm/string.hpp> + +#include <algorithm> +#include <functional> + using namespace std; using namespace dev; using namespace dev::solidity; +class IDDispenser +{ +public: + static size_t next() { return ++instance(); } + static void reset() { instance() = 0; } +private: + static size_t& instance() + { + static IDDispenser dispenser; + return dispenser.id; + } + size_t id = 0; +}; + ASTNode::ASTNode(SourceLocation const& _location): + m_id(IDDispenser::next()), m_location(_location) { } @@ -44,6 +62,11 @@ ASTNode::~ASTNode() delete m_annotation; } +void ASTNode::resetID() +{ + IDDispenser::reset(); +} + ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) @@ -189,7 +212,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat m_userDocumentation = _userDocumentation; } - vector<Declaration const*> const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) @@ -503,3 +525,19 @@ IdentifierAnnotation& Identifier::annotation() const m_annotation = new IdentifierAnnotation(); return static_cast<IdentifierAnnotation&>(*m_annotation); } + +bool Literal::looksLikeAddress() const +{ + if (subDenomination() != SubDenomination::None) + return false; + + string lit = value(); + return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; +} + +bool Literal::passesAddressChecksum() const +{ + string lit = value(); + solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix"); + return dev::passesAddressChecksum(lit, true); +} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 2d092408..743fdaa1 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -57,6 +57,11 @@ public: explicit ASTNode(SourceLocation const& _location); virtual ~ASTNode(); + /// @returns an identifier of this AST node that is unique for a single compilation run. + size_t id() const { return m_id; } + /// Resets the global ID counter. This invalidates all previous IDs. + static void resetID(); + virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; template <class T> @@ -94,6 +99,7 @@ public: ///@} protected: + size_t const m_id = 0; /// Annotation - is specialised in derived classes, is created upon request (because of polymorphism). mutable ASTAnnotation* m_annotation = nullptr; @@ -161,6 +167,7 @@ public: /// @returns the source name this declaration is present in. /// Can be combined with annotation().canonicalName to form a globally unique name. std::string sourceUnitName() const; + std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } virtual bool isLValue() const { return false; } virtual bool isPartOfExternalInterface() const { return false; } @@ -601,7 +608,7 @@ private: /** * Declaration of a variable. This can be used in various places, e.g. in function parameter - * lists, struct definitions and even function bodys. + * lists, struct definitions and even function bodies. */ class VariableDeclaration: public Declaration { @@ -862,7 +869,10 @@ public: std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); } std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); } - Declaration::Visibility visibility() const { return m_visibility; } + Declaration::Visibility visibility() const + { + return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility; + } bool isDeclaredConst() const { return m_isDeclaredConst; } bool isPayable() const { return m_isPayable; } @@ -1574,6 +1584,11 @@ public: SubDenomination subDenomination() const { return m_subDenomination; } + /// @returns true if this looks like a checksummed address. + bool looksLikeAddress() const; + /// @returns true if it passes the address checksum test. + bool passesAddressChecksum() const; + private: Token::Value m_token; ASTPointer<ASTString> m_value; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 9c4c3ae8..61e97a55 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -80,6 +80,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota { /// Whether all functions are implemented. bool isFullyImplemented = true; + /// Whether a public constructor (even the default one) is available. + bool hasPublicConstructor = true; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector<ContractDefinition const*> linearizedBaseContracts; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index abaad0fd..69c10c8d 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode( { Json::Value node; - node["id"] = reinterpret_cast<Json::UInt64>(&_node); + node["id"] = Json::UInt64(_node.id()); node["src"] = sourceLocationToString(_node.location()); node["name"] = _nodeName; if (_attributes.size() != 0) @@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) { Json::Value linearizedBaseContracts(Json::arrayValue); for (auto const& baseContract: _node.annotation().linearizedBaseContracts) - linearizedBaseContracts.append(reinterpret_cast<Json::UInt64>(baseContract)); + linearizedBaseContracts.append(Json::UInt64(baseContract->id())); addJsonNode(_node, "ContractDefinition", { make_pair("name", _node.name()), make_pair("isLibrary", _node.isLibrary()), diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 03ff8471..dbabc8db 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -21,15 +21,22 @@ */ #include <libsolidity/ast/Types.h> -#include <limits> -#include <boost/range/adaptor/reversed.hpp> -#include <boost/range/adaptor/sliced.hpp> + +#include <libsolidity/interface/Utils.h> +#include <libsolidity/ast/AST.h> + #include <libdevcore/CommonIO.h> #include <libdevcore/CommonData.h> #include <libdevcore/SHA3.h> #include <libdevcore/UTF8.h> -#include <libsolidity/interface/Utils.h> -#include <libsolidity/ast/AST.h> + +#include <boost/algorithm/string/join.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/range/adaptor/reversed.hpp> +#include <boost/range/adaptor/sliced.hpp> +#include <boost/range/adaptor/transformed.hpp> + +#include <limits> using namespace std; using namespace dev; @@ -117,6 +124,51 @@ u256 const& MemberList::storageSize() const return m_storageOffsets->storageSize(); } +/// Helper functions for type identifier +namespace +{ + +string parenthesizeIdentifier(string const& _internal) +{ + return "$_" + _internal + "_$"; +} + +template <class Range> +string identifierList(Range const&& _list) +{ + return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_")); +} + +string identifier(TypePointer const& _type) +{ + return _type ? _type->identifier() : ""; +} + +string identifierList(vector<TypePointer> const& _list) +{ + return identifierList(_list | boost::adaptors::transformed(identifier)); +} + +string identifierList(TypePointer const& _type) +{ + return parenthesizeIdentifier(identifier(_type)); +} + +string identifierList(TypePointer const& _type1, TypePointer const& _type2) +{ + TypePointers list; + list.push_back(_type1); + list.push_back(_type2); + return identifierList(list); +} + +string parenthesizeUserIdentifier(string const& _internal) +{ + return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$")); +} + +} + TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) { solAssert(Token::isElementaryTypeName(_type.token()), @@ -272,7 +324,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): solAssert( m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0, "Invalid bit number for integer type: " + dev::toString(_bits) - ); + ); +} + +string IntegerType::identifier() const +{ + if (isAddress()) + return "t_address"; + else + return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits()); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -345,6 +405,14 @@ string IntegerType::toString(bool) const return prefix + dev::toString(m_bits); } +u256 IntegerType::literalValue(Literal const* _literal) const +{ + solAssert(m_modifier == Modifier::Address, ""); + solAssert(_literal, ""); + solAssert(_literal->value().substr(0, 2) == "0x", ""); + return u256(_literal->value()); +} + TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { if ( @@ -412,7 +480,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint m_fractionalBits % 8 == 0, "Invalid bit number(s) for fixed type: " + dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits) - ); + ); +} + +string FixedPointType::identifier() const +{ + return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits()); } bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -770,6 +843,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ } } +string RationalNumberType::identifier() const +{ + return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str(); +} + bool RationalNumberType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -909,6 +987,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } +string StringLiteralType::identifier() const +{ + // Since we have to return a valid identifier and the string itself may contain + // anything, we hash it. + return "t_stringliteral_" + toHex(keccak256(m_value).asBytes()); +} + bool StringLiteralType::operator==(const Type& _other) const { if (_other.category() != category()) @@ -1002,6 +1087,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c return MemberList::MemberMap{MemberList::Member{"length", make_shared<IntegerType>(8)}}; } +string FixedBytesType::identifier() const +{ + return "t_bytes" + std::to_string(m_bytes); +} + bool FixedBytesType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1115,6 +1205,20 @@ string ReferenceType::stringForReferencePart() const return ""; } +string ReferenceType::identifierLocationSuffix() const +{ + string id; + if (location() == DataLocation::Storage) + id += "_storage"; + else if (location() == DataLocation::Memory) + id += "_memory"; + else + id += "_calldata"; + if (isPointer()) + id += "_ptr"; + return id; +} + bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { if (_convertTo.category() != category()) @@ -1170,6 +1274,27 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const return true; } +string ArrayType::identifier() const +{ + string id; + if (isString()) + id = "t_string"; + else if (isByteArray()) + id = "t_bytes"; + else + { + id = "t_array"; + id += identifierList(baseType()); + if (isDynamicallySized()) + id += "dyn"; + else + id += length().str(); + } + id += identifierLocationSuffix(); + + return id; +} + bool ArrayType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1184,7 +1309,7 @@ bool ArrayType::operator==(Type const& _other) const return false; if (*other.baseType() != *baseType()) return false; - return isDynamicallySized() || length() == other.length(); + return isDynamicallySized() || length() == other.length(); } unsigned ArrayType::calldataEncodedSize(bool _padded) const @@ -1356,6 +1481,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) return copy; } +string ContractType::identifier() const +{ + return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id()); +} + bool ContractType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1465,6 +1595,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const return this->m_struct == convertTo.m_struct; } +string StructType::identifier() const +{ + return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix(); +} + bool StructType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1605,6 +1740,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); } +string EnumType::identifier() const +{ + return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id()); +} + bool EnumType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -1686,6 +1826,11 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const return false; } +string TupleType::identifier() const +{ + return "t_tuple" + identifierList(components()); +} + bool TupleType::operator==(Type const& _other) const { if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) @@ -1934,6 +2079,53 @@ TypePointers FunctionType::parameterTypes() const return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend()); } +string FunctionType::identifier() const +{ + string id = "t_function_"; + switch (location()) + { + case Location::Internal: id += "internal"; break; + case Location::External: id += "external"; break; + case Location::CallCode: id += "callcode"; break; + case Location::DelegateCall: id += "delegatecall"; break; + case Location::Bare: id += "bare"; break; + case Location::BareCallCode: id += "barecallcode"; break; + case Location::BareDelegateCall: id += "baredelegatecall"; break; + case Location::Creation: id += "creation"; break; + case Location::Send: id += "send"; break; + case Location::SHA3: id += "sha3"; break; + case Location::Selfdestruct: id += "selfdestruct"; break; + case Location::ECRecover: id += "ecrecover"; break; + case Location::SHA256: id += "sha256"; break; + case Location::RIPEMD160: id += "ripemd160"; break; + case Location::Log0: id += "log0"; break; + case Location::Log1: id += "log1"; break; + case Location::Log2: id += "log2"; break; + case Location::Log3: id += "log3"; break; + case Location::Log4: id += "log4"; break; + case Location::Event: id += "event"; break; + case Location::SetGas: id += "setgas"; break; + case Location::SetValue: id += "setvalue"; break; + case Location::BlockHash: id += "blockhash"; break; + case Location::AddMod: id += "addmod"; break; + case Location::MulMod: id += "mulmod"; break; + case Location::ArrayPush: id += "arraypush"; break; + case Location::ByteArrayPush: id += "bytearraypush"; break; + case Location::ObjectCreation: id += "objectcreation"; break; + default: solAssert(false, "Unknown function location."); break; + } + if (isConstant()) + id += "_constant"; + id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); + if (m_gasSet) + id += "gas"; + if (m_valueSet) + id += "value"; + if (bound()) + id += "bound_to" + identifierList(selfType()); + return id; +} + bool FunctionType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2320,25 +2512,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) ); } -vector<string> const FunctionType::parameterTypeNames(bool _addDataLocation) const -{ - vector<string> names; - for (TypePointer const& t: parameterTypes()) - names.push_back(t->canonicalName(_addDataLocation)); - - return names; -} - -vector<string> const FunctionType::returnParameterTypeNames(bool _addDataLocation) const -{ - vector<string> names; - for (TypePointer const& t: m_returnParameterTypes) - names.push_back(t->canonicalName(_addDataLocation)); - - return names; -} - -TypePointer FunctionType::selfType() const +TypePointer const& FunctionType::selfType() const { solAssert(bound(), "Function is not bound."); solAssert(m_parameterTypes.size() > 0, "Function has no self type."); @@ -2354,6 +2528,11 @@ ASTPointer<ASTString> FunctionType::documentation() const return ASTPointer<ASTString>(); } +string MappingType::identifier() const +{ + return "t_mapping" + identifierList(m_keyType, m_valueType); +} + bool MappingType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2372,6 +2551,11 @@ string MappingType::canonicalName(bool) const return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")"; } +string TypeType::identifier() const +{ + return "t_type" + identifierList(actualType()); +} + bool TypeType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2456,6 +2640,11 @@ u256 ModifierType::storageSize() const << errinfo_comment("Storage size of non-storable type type requested.")); } +string ModifierType::identifier() const +{ + return "t_modifier" + identifierList(m_parameterTypes); +} + bool ModifierType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2480,6 +2669,11 @@ string ModifierType::toString(bool _short) const return name + ")"; } +string ModuleType::identifier() const +{ + return "t_module_" + std::to_string(m_sourceUnit.id()); +} + bool ModuleType::operator==(Type const& _other) const { if (_other.category() != category()) @@ -2501,6 +2695,22 @@ string ModuleType::toString(bool) const return string("module \"") + m_sourceUnit.annotation().path + string("\""); } +string MagicType::identifier() const +{ + switch (m_kind) + { + case Kind::Block: + return "t_magic_block"; + case Kind::Message: + return "t_magic_message"; + case Kind::Transaction: + return "t_magic_transaction"; + default: + solAssert(false, "Unknown kind of magic"); + } + return ""; +} + bool MagicType::operator==(Type const& _other) const { if (_other.category() != category()) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 26e2b8f2..a5147f17 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -22,18 +22,21 @@ #pragma once -#include <memory> -#include <string> -#include <map> -#include <boost/noncopyable.hpp> -#include <boost/rational.hpp> -#include <libdevcore/Common.h> -#include <libdevcore/CommonIO.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/parsing/Token.h> + +#include <libdevcore/Common.h> +#include <libdevcore/CommonIO.h> #include <libdevcore/UndefMacros.h> +#include <boost/noncopyable.hpp> +#include <boost/rational.hpp> + +#include <memory> +#include <string> +#include <map> + namespace dev { namespace solidity @@ -155,6 +158,13 @@ public: static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); virtual Category category() const = 0; + /// @returns a valid solidity identifier such that two types should compare equal if and + /// only if they have the same identifier. + /// The identifier should start with "t_". + /// More complex identifier strings use "parentheses", where $_ is interpreted as as + /// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that + /// appears as part of a user-supplied identifier is escaped as _$$$_. + virtual std::string identifier() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const { @@ -288,6 +298,7 @@ public: explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned); + virtual std::string identifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -303,6 +314,8 @@ public: virtual std::string toString(bool _short) const override; + virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } @@ -329,6 +342,7 @@ public: explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned); + virtual std::string identifier() const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -378,6 +392,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -416,6 +431,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -449,6 +465,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; @@ -476,6 +493,7 @@ class BoolType: public Type public: BoolType() {} virtual Category category() const override { return Category::Bool; } + virtual std::string identifier() const override { return "t_bool"; } virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; @@ -533,6 +551,8 @@ protected: TypePointer copyForLocationIfReference(TypePointer const& _type) const; /// @returns a human-readable description of the reference part of the type. std::string stringForReferencePart() const; + /// @returns the suffix computed from the reference part to be used by identifier(); + std::string identifierLocationSuffix() const; DataLocation m_location = DataLocation::Storage; bool m_isPointer = true; @@ -573,6 +593,7 @@ public: virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(const Type& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } @@ -622,6 +643,7 @@ public: /// Contracts can be converted to themselves and to integers. virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded ) const override { @@ -677,6 +699,7 @@ public: explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): ReferenceType(_location), m_struct(_struct) {} virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; u256 memorySize() const; @@ -720,6 +743,7 @@ public: virtual Category category() const override { return Category::Enum; } explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual unsigned calldataEncodedSize(bool _padded) const override { @@ -760,6 +784,7 @@ 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 std::string identifier() 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; @@ -890,13 +915,12 @@ public: TypePointers parameterTypes() const; std::vector<std::string> parameterNames() const; - std::vector<std::string> const parameterTypeNames(bool _addDataLocation) const; TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; } - std::vector<std::string> const returnParameterTypeNames(bool _addDataLocation) const; /// @returns the "self" parameter type for a bound function - TypePointer selfType() const; + TypePointer const& selfType() const; + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; @@ -995,6 +1019,7 @@ public: MappingType(TypePointer const& _keyType, TypePointer const& _valueType): m_keyType(_keyType), m_valueType(_valueType) {} + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; @@ -1029,6 +1054,7 @@ public: TypePointer const& actualType() const { return m_actualType; } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual u256 storageSize() const override; @@ -1056,6 +1082,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override { return 0; } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; @@ -1080,6 +1107,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -1109,6 +1137,7 @@ public: return TypePointer(); } + virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -1132,6 +1161,7 @@ class InaccessibleDynamicType: public Type public: virtual Category category() const override { return Category::InaccessibleDynamic; } + virtual std::string identifier() const override { return "t_inaccessible"; } virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 352c7177..bdd29abd 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - IntegerType uint256(256); - Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType()); - Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType()); + TypePointer uint256 = make_shared<IntegerType>(256); + TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); + TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); // TODO unroll loop for small sizes @@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } // stack: target_ref source_ref source_length - m_context << Instruction::DUP3; - // stack: target_ref source_ref source_length target_ref - retrieveLength(_targetType); - // stack: target_ref source_ref source_length target_ref target_length - if (_targetType.isDynamicallySized()) - // store new target length - if (!_targetType.isByteArray()) - // Otherwise, length will be stored below. - m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; - if (sourceBaseType->category() == Type::Category::Mapping) - { - solAssert(targetBaseType->category() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == DataLocation::Storage, ""); - // nothing to copy - m_context - << Instruction::POP << Instruction::POP - << Instruction::POP << Instruction::POP; - return; - } - // stack: target_ref source_ref source_length target_ref target_length - // compute hashes (data positions) - m_context << Instruction::SWAP1; - if (_targetType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref source_ref source_length target_length target_data_pos - m_context << Instruction::SWAP1; - convertLengthToSize(_targetType); - m_context << Instruction::DUP2 << Instruction::ADD; - // stack: target_ref source_ref source_length target_data_pos target_data_end - m_context << Instruction::SWAP3; - // stack: target_ref target_data_end source_length target_data_pos source_ref - - eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); - - // special case for short byte arrays: Store them together with their length. - if (_targetType.isByteArray()) - { - // stack: target_ref target_data_end source_length target_data_pos source_ref - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem longByteArray = m_context.appendConditionalJump(); - // store the short byte array - solAssert(_sourceType.isByteArray(), ""); - if (_sourceType.location() == DataLocation::Storage) - { - // just copy the slot, it contains length and data - m_context << Instruction::DUP1 << Instruction::SLOAD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } - else + TypePointer targetType = _targetType.shared_from_this(); + TypePointer sourceType = _sourceType.shared_from_this(); + m_context.callLowLevelFunction( + "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), + 3, + 1, + [=](CompilerContext& _context) { - m_context << Instruction::DUP1; - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - // stack: target_ref target_data_end source_length target_data_pos source_ref value - // clear the lower-order byte - which will hold the length - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // fetch the length and shift it left by one - m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; - // combine value and length and store them - m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; - } - // end of special case, jump right into cleaning target data area - m_context.appendJumpTo(copyLoopEndWithoutByteOffset); - m_context << longByteArray; - // Store length (2*length+1) - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - m_context << Instruction::DUP6 << Instruction::SSTORE; - } + ArrayUtils utils(_context); + ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType); + ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType); + // stack: target_ref source_ref source_length + _context << Instruction::DUP3; + // stack: target_ref source_ref source_length target_ref + utils.retrieveLength(_targetType); + // stack: target_ref source_ref source_length target_ref target_length + if (_targetType.isDynamicallySized()) + // store new target length + if (!_targetType.isByteArray()) + // Otherwise, length will be stored below. + _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; + if (sourceBaseType->category() == Type::Category::Mapping) + { + solAssert(targetBaseType->category() == Type::Category::Mapping, ""); + solAssert(_sourceType.location() == DataLocation::Storage, ""); + // nothing to copy + _context + << Instruction::POP << Instruction::POP + << Instruction::POP << Instruction::POP; + return; + } + // stack: target_ref source_ref source_length target_ref target_length + // compute hashes (data positions) + _context << Instruction::SWAP1; + if (_targetType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref source_ref source_length target_length target_data_pos + _context << Instruction::SWAP1; + utils.convertLengthToSize(_targetType); + _context << Instruction::DUP2 << Instruction::ADD; + // stack: target_ref source_ref source_length target_data_pos target_data_end + _context << Instruction::SWAP3; + // stack: target_ref target_data_end source_length target_data_pos source_ref + + eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); + + // special case for short byte arrays: Store them together with their length. + if (_targetType.isByteArray()) + { + // stack: target_ref target_data_end source_length target_data_pos source_ref + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem longByteArray = _context.appendConditionalJump(); + // store the short byte array + solAssert(_sourceType.isByteArray(), ""); + if (_sourceType.location() == DataLocation::Storage) + { + // just copy the slot, it contains length and data + _context << Instruction::DUP1 << Instruction::SLOAD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } + else + { + _context << Instruction::DUP1; + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + // stack: target_ref target_data_end source_length target_data_pos source_ref value + // clear the lower-order byte - which will hold the length + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // fetch the length and shift it left by one + _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; + // combine value and length and store them + _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; + } + // end of special case, jump right into cleaning target data area + _context.appendJumpTo(copyLoopEndWithoutByteOffset); + _context << longByteArray; + // Store length (2*length+1) + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + _context << Instruction::DUP6 << Instruction::SSTORE; + } - // skip copying if source length is zero - m_context << Instruction::DUP3 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); - - if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - // stack: target_ref target_data_end source_length target_data_pos source_data_pos - m_context << Instruction::SWAP2; - convertLengthToSize(_sourceType); - m_context << Instruction::DUP3 << Instruction::ADD; - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end - if (haveByteOffsetTarget) - m_context << u256(0); - if (haveByteOffsetSource) - m_context << u256(0); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - eth::AssemblyItem copyLoopStart = m_context.newTag(); - m_context << copyLoopStart; - // check for loop condition - m_context - << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump(); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // copy - if (sourceBaseType->category() == Type::Category::Array) - { - solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); - auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); - m_context << Instruction::DUP3; - if (sourceBaseArrayType.location() == DataLocation::Memory) - m_context << Instruction::MLOAD; - m_context << Instruction::DUP3; - copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); - m_context << Instruction::POP; - } - else if (directCopy) - { - solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); - m_context - << Instruction::DUP3 << Instruction::SLOAD - << Instruction::DUP3 << Instruction::SSTORE; - } - else - { - // Note that we have to copy each element on its own in case conversion is involved. - // We might copy too much if there is padding at the last element, but this way end - // checking is easier. - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - m_context << dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == DataLocation::Storage) - { + // skip copying if source length is zero + _context << Instruction::DUP3 << Instruction::ISZERO; + _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); + + if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) + CompilerUtils(_context).computeHashStatic(); + // stack: target_ref target_data_end source_length target_data_pos source_data_pos + _context << Instruction::SWAP2; + utils.convertLengthToSize(_sourceType); + _context << Instruction::DUP3 << Instruction::ADD; + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + _context << u256(0); if (haveByteOffsetSource) - m_context << Instruction::DUP2; + _context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + eth::AssemblyItem copyLoopStart = _context.newTag(); + _context << copyLoopStart; + // check for loop condition + _context + << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // copy + if (sourceBaseType->category() == Type::Category::Array) + { + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); + auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); + _context << Instruction::DUP3; + if (sourceBaseArrayType.location() == DataLocation::Memory) + _context << Instruction::MLOAD; + _context << Instruction::DUP3; + utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); + _context << Instruction::POP; + } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + _context + << Instruction::DUP3 << Instruction::SLOAD + << Instruction::DUP3 << Instruction::SSTORE; + } + else + { + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + _context << dupInstruction(3 + byteOffsetSize); + if (_sourceType.location() == DataLocation::Storage) + { + if (haveByteOffsetSource) + _context << Instruction::DUP2; + else + _context << u256(0); + StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } + else if (sourceBaseType->isValueType()) + CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); + else + solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... + solAssert( + 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, + "Stack too deep, try removing local variables." + ); + // fetch target storage reference + _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); + if (haveByteOffsetTarget) + _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); + else + _context << u256(0); + StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); + } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + // increment source + if (haveByteOffsetSource) + utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); else - m_context << u256(0); - StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + { + _context << swapInstruction(2 + byteOffsetSize); + if (sourceIsStorage) + _context << sourceBaseType->storageSize(); + else if (_sourceType.location() == DataLocation::Memory) + _context << sourceBaseType->memoryHeadSize(); + else + _context << sourceBaseType->calldataEncodedSize(true); + _context + << Instruction::ADD + << swapInstruction(2 + byteOffsetSize); + } + // increment target + if (haveByteOffsetTarget) + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + _context + << swapInstruction(1 + byteOffsetSize) + << targetBaseType->storageSize() + << Instruction::ADD + << swapInstruction(1 + byteOffsetSize); + _context.appendJumpTo(copyLoopStart); + _context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); + _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); + StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); + utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); + _context.appendJumpTo(copyLoopEnd); + + _context << copyCleanupLoopEnd; + _context << Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + _context << Instruction::POP; + _context << copyLoopEndWithoutByteOffset; + + // zero-out leftovers in target + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end + _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + utils.clearStorageLoop(targetBaseType); + _context << Instruction::POP; } - else if (sourceBaseType->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - else - solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... - solAssert( - 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, - "Stack too deep, try removing local variables." - ); - // fetch target storage reference - m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); - if (haveByteOffsetTarget) - m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); - else - m_context << u256(0); - StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - } - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // increment source - if (haveByteOffsetSource) - incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); - else - { - m_context << swapInstruction(2 + byteOffsetSize); - if (sourceIsStorage) - m_context << sourceBaseType->storageSize(); - else if (_sourceType.location() == DataLocation::Memory) - m_context << sourceBaseType->memoryHeadSize(); - else - m_context << sourceBaseType->calldataEncodedSize(true); - m_context - << Instruction::ADD - << swapInstruction(2 + byteOffsetSize); - } - // increment target - if (haveByteOffsetTarget) - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - else - m_context - << swapInstruction(1 + byteOffsetSize) - << targetBaseType->storageSize() - << Instruction::ADD - << swapInstruction(1 + byteOffsetSize); - m_context.appendJumpTo(copyLoopStart); - m_context << copyLoopEnd; - if (haveByteOffsetTarget) - { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); - m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); - incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - m_context.appendJumpTo(copyLoopEnd); - - m_context << copyCleanupLoopEnd; - m_context << Instruction::POP; // might pop the source, but then target is popped next - } - if (haveByteOffsetSource) - m_context << Instruction::POP; - m_context << copyLoopEndWithoutByteOffset; - - // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end - m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - clearStorageLoop(*targetBaseType); - m_context << Instruction::POP; + ); } void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const @@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord } } -void ArrayUtils::clearArray(ArrayType const& _type) const +void ArrayUtils::clearArray(ArrayType const& _typeIn) const { - unsigned stackHeightStart = m_context.stackHeight(); - solAssert(_type.location() == DataLocation::Storage, ""); - if (_type.baseType()->storageBytes() < 32) - { - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); - } - if (_type.baseType()->isValueType()) - solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); - - m_context << Instruction::POP; // remove byte offset - if (_type.isDynamicallySized()) - clearDynamicArray(_type); - else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) - m_context << Instruction::POP; - else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) - { - // unroll loop for small arrays @todo choose a good value - // Note that we loop over storage slots here, not elements. - for (unsigned i = 1; i < _type.storageSize(); ++i) - m_context - << u256(0) << Instruction::DUP2 << Instruction::SSTORE - << u256(1) << Instruction::ADD; - m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; - } - else if (!_type.baseType()->isValueType() && _type.length() <= 4) - { - // unroll loop for small arrays @todo choose a good value - solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); - for (unsigned i = 1; i < _type.length(); ++i) + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$clearArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) { - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false); - m_context - << Instruction::POP - << u256(_type.baseType()->storageSize()) << Instruction::ADD; + ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); + unsigned stackHeightStart = _context.stackHeight(); + solAssert(_type.location() == DataLocation::Storage, ""); + if (_type.baseType()->storageBytes() < 32) + { + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); + } + if (_type.baseType()->isValueType()) + solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); + + _context << Instruction::POP; // remove byte offset + if (_type.isDynamicallySized()) + ArrayUtils(_context).clearDynamicArray(_type); + else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) + _context << Instruction::POP; + else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) + { + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.storageSize(); ++i) + _context + << u256(0) << Instruction::DUP2 << Instruction::SSTORE + << u256(1) << Instruction::ADD; + _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; + } + else if (!_type.baseType()->isValueType() && _type.length() <= 4) + { + // unroll loop for small arrays @todo choose a good value + solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); + for (unsigned i = 1; i < _type.length(); ++i) + { + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); + _context + << Instruction::POP + << u256(_type.baseType()->storageSize()) << Instruction::ADD; + } + _context << u256(0); + StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + else + { + _context << Instruction::DUP1 << _type.length(); + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::ADD << Instruction::SWAP1; + if (_type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + else + ArrayUtils(_context).clearStorageLoop(_type.baseType()); + _context << Instruction::POP; + } + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); } - m_context << u256(0); - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); - } - else - { - m_context << Instruction::DUP1 << _type.length(); - convertLengthToSize(_type); - m_context << Instruction::ADD << Instruction::SWAP1; - if (_type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); - m_context << Instruction::POP; - } - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + ); } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const @@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const << Instruction::SWAP1; // stack: data_pos_end data_pos if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); + clearStorageLoop(make_shared<IntegerType>(256)); else - clearStorageLoop(*_type.baseType()); + clearStorageLoop(_type.baseType()); // cleanup m_context << endTag; m_context << Instruction::POP; } -void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const +void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const { - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - unsigned stackHeightStart = m_context.stackHeight(); - eth::AssemblyItem resizeEnd = m_context.newTag(); - - // stack: ref new_length - // fetch old length - retrieveLength(_type, 1); - // stack: ref new_length old_length - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2"); - - // Special case for short byte arrays, they are stored together with their length - if (_type.isByteArray()) - { - eth::AssemblyItem regularPath = m_context.newTag(); - // We start by a large case-distinction about the old and new length of the byte array. - - m_context << Instruction::DUP3 << Instruction::SLOAD; - // stack: ref new_length current_length ref_value - - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::DUP2 << u256(31) << Instruction::LT; - eth::AssemblyItem currentIsLong = m_context.appendConditionalJump(); - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - eth::AssemblyItem newIsLong = m_context.appendConditionalJump(); - - // Here: short -> short - - // Compute 1 << (256 - 8 * new_size) - eth::AssemblyItem shortToShort = m_context.newTag(); - m_context << shortToShort; - m_context << Instruction::DUP3 << u256(8) << Instruction::MUL; - m_context << u256(0x100) << Instruction::SUB; - m_context << u256(2) << Instruction::EXP; - // Divide and multiply by that value, clearing bits. - m_context << Instruction::DUP1 << Instruction::SWAP2; - m_context << Instruction::DIV << Instruction::MUL; - // Insert 2*length. - m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - m_context << Instruction::OR; - // Store. - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); - - m_context.adjustStackOffset(1); // we have to do that because of the jumps - // Here: short -> long - - m_context << newIsLong; - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - // Zero out lower-order byte. - m_context << u256(0xff) << Instruction::NOT << Instruction::AND; - // Store at data location. - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::SSTORE; - // stack: ref new_length current_length - // Store new length: Compule 2*length + 1 and store it. - m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; - m_context << u256(1) << Instruction::ADD; - // stack: ref new_length current_length 2*new_length+1 - m_context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - m_context.appendJumpTo(resizeEnd); - - m_context.adjustStackOffset(1); // we have to do that because of the jumps - - m_context << currentIsLong; - m_context << Instruction::DUP3 << u256(31) << Instruction::LT; - m_context.appendConditionalJumpTo(regularPath); - - // Here: long -> short - // Read the first word of the data and store it on the stack. Clear the data location and - // then jump to the short -> short case. - - // stack: ref new_length current_length ref_value - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context << Instruction::POP << Instruction::DUP3; - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location - m_context << Instruction::DUP3; - convertLengthToSize(_type); - m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location_end data_location - clearStorageLoop(IntegerType(256)); - m_context << Instruction::POP; - // stack: ref new_length current_length first_word - solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - m_context.appendJumpTo(shortToShort); - - m_context << regularPath; - // stack: ref new_length current_length ref_value - m_context << Instruction::POP; - } + TypePointer type = _typeIn.shared_from_this(); + m_context.callLowLevelFunction( + "$resizeDynamicArray_" + _typeIn.identifier(), + 2, + 0, + [type](CompilerContext& _context) + { + ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + + unsigned stackHeightStart = _context.stackHeight(); + eth::AssemblyItem resizeEnd = _context.newTag(); + + // stack: ref new_length + // fetch old length + ArrayUtils(_context).retrieveLength(_type, 1); + // stack: ref new_length old_length + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); + + // Special case for short byte arrays, they are stored together with their length + if (_type.isByteArray()) + { + eth::AssemblyItem regularPath = _context.newTag(); + // We start by a large case-distinction about the old and new length of the byte array. + + _context << Instruction::DUP3 << Instruction::SLOAD; + // stack: ref new_length current_length ref_value + + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::DUP2 << u256(31) << Instruction::LT; + eth::AssemblyItem currentIsLong = _context.appendConditionalJump(); + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + eth::AssemblyItem newIsLong = _context.appendConditionalJump(); + + // Here: short -> short + + // Compute 1 << (256 - 8 * new_size) + eth::AssemblyItem shortToShort = _context.newTag(); + _context << shortToShort; + _context << Instruction::DUP3 << u256(8) << Instruction::MUL; + _context << u256(0x100) << Instruction::SUB; + _context << u256(2) << Instruction::EXP; + // Divide and multiply by that value, clearing bits. + _context << Instruction::DUP1 << Instruction::SWAP2; + _context << Instruction::DIV << Instruction::MUL; + // Insert 2*length. + _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; + _context << Instruction::OR; + // Store. + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); + + _context.adjustStackOffset(1); // we have to do that because of the jumps + // Here: short -> long + + _context << newIsLong; + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + // Zero out lower-order byte. + _context << u256(0xff) << Instruction::NOT << Instruction::AND; + // Store at data location. + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::SSTORE; + // stack: ref new_length current_length + // Store new length: Compule 2*length + 1 and store it. + _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; + _context << u256(1) << Instruction::ADD; + // stack: ref new_length current_length 2*new_length+1 + _context << Instruction::DUP4 << Instruction::SSTORE; + solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); + _context.appendJumpTo(resizeEnd); + + _context.adjustStackOffset(1); // we have to do that because of the jumps + + _context << currentIsLong; + _context << Instruction::DUP3 << u256(31) << Instruction::LT; + _context.appendConditionalJumpTo(regularPath); + + // Here: long -> short + // Read the first word of the data and store it on the stack. Clear the data location and + // then jump to the short -> short case. + + // stack: ref new_length current_length ref_value + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context << Instruction::POP << Instruction::DUP3; + CompilerUtils(_context).computeHashStatic(); + _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location + _context << Instruction::DUP3; + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; + // stack: ref new_length current_length first_word data_location_end data_location + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + _context << Instruction::POP; + // stack: ref new_length current_length first_word + solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); + _context.appendJumpTo(shortToShort); + + _context << regularPath; + // stack: ref new_length current_length ref_value + _context << Instruction::POP; + } - // Change of length for a regular array (i.e. length at location, data at sha3(location)). - // stack: ref new_length old_length - // store new length - m_context << Instruction::DUP2; - if (_type.isByteArray()) - // For a "long" byte array, store length as 2*length+1 - m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - m_context<< Instruction::DUP4 << Instruction::SSTORE; - // skip if size is not reduced - m_context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::ISZERO << Instruction::GT; - m_context.appendConditionalJumpTo(resizeEnd); - - // size reduced, clear the end of the array - // stack: ref new_length old_length - convertLengthToSize(_type); - m_context << Instruction::DUP2; - convertLengthToSize(_type); - // stack: ref new_length old_size new_size - // compute data positions - m_context << Instruction::DUP4; - CompilerUtils(m_context).computeHashStatic(); - // stack: ref new_length old_size new_size data_pos - m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; - // stack: ref new_length data_pos new_size delete_end - m_context << Instruction::SWAP2 << Instruction::ADD; - // stack: ref new_length delete_end delete_start - if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(IntegerType(256)); - else - clearStorageLoop(*_type.baseType()); + // Change of length for a regular array (i.e. length at location, data at sha3(location)). + // stack: ref new_length old_length + // store new length + _context << Instruction::DUP2; + if (_type.isByteArray()) + // For a "long" byte array, store length as 2*length+1 + _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; + _context<< Instruction::DUP4 << Instruction::SSTORE; + // skip if size is not reduced + _context << Instruction::DUP2 << Instruction::DUP2 + << Instruction::ISZERO << Instruction::GT; + _context.appendConditionalJumpTo(resizeEnd); + + // size reduced, clear the end of the array + // stack: ref new_length old_length + ArrayUtils(_context).convertLengthToSize(_type); + _context << Instruction::DUP2; + ArrayUtils(_context).convertLengthToSize(_type); + // stack: ref new_length old_size new_size + // compute data positions + _context << Instruction::DUP4; + CompilerUtils(_context).computeHashStatic(); + // stack: ref new_length old_size new_size data_pos + _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; + // stack: ref new_length data_pos new_size delete_end + _context << Instruction::SWAP2 << Instruction::ADD; + // stack: ref new_length delete_end delete_start + if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) + ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256)); + else + ArrayUtils(_context).clearStorageLoop(_type.baseType()); - m_context << resizeEnd; - // cleanup - m_context << Instruction::POP << Instruction::POP << Instruction::POP; - solAssert(m_context.stackHeight() == stackHeightStart - 2, ""); + _context << resizeEnd; + // cleanup + _context << Instruction::POP << Instruction::POP << Instruction::POP; + solAssert(_context.stackHeight() == stackHeightStart - 2, ""); + } + ); } -void ArrayUtils::clearStorageLoop(Type const& _type) const +void ArrayUtils::clearStorageLoop(TypePointer const& _type) const { - unsigned stackHeightStart = m_context.stackHeight(); - if (_type.category() == Type::Category::Mapping) - { - m_context << Instruction::POP; - return; - } - // stack: end_pos pos - - // jump to and return from the loop to allow for duplicate code removal - eth::AssemblyItem returnTag = m_context.pushNewTag(); - m_context << Instruction::SWAP2 << Instruction::SWAP1; - - // stack: <return tag> end_pos pos - eth::AssemblyItem loopStart = m_context.appendJumpToNew(); - m_context << loopStart; - // check for loop condition - m_context << Instruction::DUP1 << Instruction::DUP3 - << Instruction::GT << Instruction::ISZERO; - eth::AssemblyItem zeroLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(zeroLoopEnd); - // delete - m_context << u256(0); - StorageItem(m_context, _type).setToZero(SourceLocation(), false); - m_context << Instruction::POP; - // increment - m_context << _type.storageSize() << Instruction::ADD; - m_context.appendJumpTo(loopStart); - // cleanup - m_context << zeroLoopEnd; - m_context << Instruction::POP << Instruction::SWAP1; - // "return" - m_context << Instruction::JUMP; - - m_context << returnTag; - solAssert(m_context.stackHeight() == stackHeightStart - 1, ""); + m_context.callLowLevelFunction( + "$clearStorageLoop_" + _type->identifier(), + 2, + 1, + [_type](CompilerContext& _context) + { + unsigned stackHeightStart = _context.stackHeight(); + if (_type->category() == Type::Category::Mapping) + { + _context << Instruction::POP; + return; + } + // stack: end_pos pos + + // jump to and return from the loop to allow for duplicate code removal + eth::AssemblyItem returnTag = _context.pushNewTag(); + _context << Instruction::SWAP2 << Instruction::SWAP1; + + // stack: <return tag> end_pos pos + eth::AssemblyItem loopStart = _context.appendJumpToNew(); + _context << loopStart; + // check for loop condition + _context << Instruction::DUP1 << Instruction::DUP3 + << Instruction::GT << Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = _context.newTag(); + _context.appendConditionalJumpTo(zeroLoopEnd); + // delete + _context << u256(0); + StorageItem(_context, *_type).setToZero(SourceLocation(), false); + _context << Instruction::POP; + // increment + _context << _type->storageSize() << Instruction::ADD; + _context.appendJumpTo(loopStart); + // cleanup + _context << zeroLoopEnd; + _context << Instruction::POP << Instruction::SWAP1; + // "return" + _context << Instruction::JUMP; + + _context << returnTag; + solAssert(_context.stackHeight() == stackHeightStart - 1, ""); + } + ); } void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const @@ -859,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c // check out-of-bounds access m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; // out-of-bounds access throws exception - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) // remove length if present diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index d0ba2892..806fbea7 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -22,6 +22,8 @@ #pragma once +#include <memory> + namespace dev { namespace solidity @@ -30,6 +32,7 @@ namespace solidity class CompilerContext; class Type; class ArrayType; +using TypePointer = std::shared_ptr<Type const>; /** * Class that provides code generation for handling arrays. @@ -67,7 +70,7 @@ public: /// 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; + void clearStorageLoop(TypePointer const& _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index c14ab845..a8316109 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -21,15 +21,18 @@ */ #include <libsolidity/codegen/CompilerContext.h> -#include <utility> -#include <numeric> -#include <boost/algorithm/string/replace.hpp> +#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/Version.h> #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmStack.h> +#include <boost/algorithm/string/replace.hpp> + +#include <utility> +#include <numeric> + using namespace std; namespace dev @@ -57,6 +60,62 @@ void CompilerContext::startFunction(Declaration const& _function) *this << functionEntryLabel(_function); } +void CompilerContext::callLowLevelFunction( + string const& _name, + unsigned _inArgs, + unsigned _outArgs, + function<void(CompilerContext&)> const& _generator +) +{ + eth::AssemblyItem retTag = pushNewTag(); + CompilerUtils(*this).moveIntoStack(_inArgs); + + *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator); + + appendJump(eth::AssemblyItem::JumpType::IntoFunction); + adjustStackOffset(int(_outArgs) - 1 - _inArgs); + *this << retTag.tag(); +} + +eth::AssemblyItem CompilerContext::lowLevelFunctionTag( + string const& _name, + unsigned _inArgs, + unsigned _outArgs, + function<void(CompilerContext&)> const& _generator +) +{ + auto it = m_lowLevelFunctions.find(_name); + if (it == m_lowLevelFunctions.end()) + { + eth::AssemblyItem tag = newTag().pushTag(); + m_lowLevelFunctions.insert(make_pair(_name, tag)); + m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator)); + return tag; + } + else + return it->second; +} + +void CompilerContext::appendMissingLowLevelFunctions() +{ + while (!m_lowLevelFunctionGenerationQueue.empty()) + { + string name; + unsigned inArgs; + unsigned outArgs; + function<void(CompilerContext&)> generator; + tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front(); + m_lowLevelFunctionGenerationQueue.pop(); + + setStackOffset(inArgs + 1); + *this << m_lowLevelFunctions.at(name).tag(); + generator(*this); + CompilerUtils(*this).moveToStackTop(outArgs); + appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + "."); + } +} + void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { @@ -167,6 +226,20 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy return *this << item; } +CompilerContext& CompilerContext::appendInvalid() +{ + return *this << Instruction::INVALID; +} + +CompilerContext& CompilerContext::appendConditionalInvalid() +{ + *this << Instruction::ISZERO; + eth::AssemblyItem afterTag = appendConditionalJump(); + *this << Instruction::INVALID; + *this << afterTag; + return *this; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack<ASTNode const*> newStack; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 80671528..c37142c9 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,17 +22,21 @@ #pragma once -#include <ostream> -#include <stack> -#include <queue> -#include <utility> -#include <libevmasm/Instruction.h> -#include <libevmasm/Assembly.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> + +#include <libevmasm/Instruction.h> +#include <libevmasm/Assembly.h> + #include <libdevcore/Common.h> +#include <ostream> +#include <stack> +#include <queue> +#include <utility> +#include <functional> + namespace dev { namespace solidity { @@ -90,6 +94,29 @@ public: /// as "having code". void startFunction(Declaration const& _function); + /// Appends a call to the named low-level function and inserts the generator into the + /// list of low-level-functions to be generated, unless it already exists. + /// Note that the generator should not assume that objects are still alive when it is called, + /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). + void callLowLevelFunction( + std::string const& _name, + unsigned _inArgs, + unsigned _outArgs, + std::function<void(CompilerContext&)> const& _generator + ); + /// Returns the tag of the named low-level function and inserts the generator into the + /// list of low-level-functions to be generated, unless it already exists. + /// Note that the generator should not assume that objects are still alive when it is called, + /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). + eth::AssemblyItem lowLevelFunctionTag( + std::string const& _name, + unsigned _inArgs, + unsigned _outArgs, + std::function<void(CompilerContext&)> const& _generator + ); + /// Generates the code for missing low-level functions, i.e. calls the generators passed above. + void appendMissingLowLevelFunctions(); + ModifierDefinition const& functionModifier(std::string const& _name) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; @@ -110,6 +137,10 @@ public: eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); + /// Appends an INVALID instruction + CompilerContext& appendInvalid(); + /// Appends a conditional INVALID instruction + CompilerContext& appendConditionalInvalid(); /// Returns an "ErrorTag" eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag @@ -248,6 +279,10 @@ private: CompilerContext *m_runtimeContext; /// The index of the runtime subroutine. size_t m_runtimeSub = -1; + /// An index of low-level function labels by name. + std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions; + /// The queue of low-level functions to generate. + std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7d382aba..477f021a 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -468,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } break; @@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } else if (targetTypeCategory == Type::Category::FixedPoint) @@ -807,7 +807,9 @@ void CompilerUtils::pushZeroValue(Type const& _type) { if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.errorTag(); + m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { + _context.appendInvalid(); + }); return; } } @@ -820,37 +822,46 @@ void CompilerUtils::pushZeroValue(Type const& _type) } solAssert(referenceType->location() == DataLocation::Memory, ""); - m_context << u256(max(32u, _type.calldataEncodedSize())); - allocateMemory(); - m_context << Instruction::DUP1; + TypePointer type = _type.shared_from_this(); + m_context.callLowLevelFunction( + "$pushZeroValue_" + referenceType->identifier(), + 0, + 1, + [type](CompilerContext& _context) { + CompilerUtils utils(_context); + _context << u256(max(32u, type->calldataEncodedSize())); + utils.allocateMemory(); + _context << Instruction::DUP1; - if (auto structType = dynamic_cast<StructType const*>(&_type)) - for (auto const& member: structType->members(nullptr)) - { - pushZeroValue(*member.type); - storeInMemoryDynamic(*member.type); - } - else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) - { - if (arrayType->isDynamicallySized()) - { - // zero length - m_context << u256(0); - storeInMemoryDynamic(IntegerType(256)); - } - else if (arrayType->length() > 0) - { - m_context << arrayType->length() << Instruction::SWAP1; - // stack: items_to_do memory_pos - zeroInitialiseMemoryArray(*arrayType); - // stack: updated_memory_pos - } - } - else - solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); + if (auto structType = dynamic_cast<StructType const*>(type.get())) + for (auto const& member: structType->members(nullptr)) + { + utils.pushZeroValue(*member.type); + utils.storeInMemoryDynamic(*member.type); + } + else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) + { + if (arrayType->isDynamicallySized()) + { + // zero length + _context << u256(0); + utils.storeInMemoryDynamic(IntegerType(256)); + } + else if (arrayType->length() > 0) + { + _context << arrayType->length() << Instruction::SWAP1; + // stack: items_to_do memory_pos + utils.zeroInitialiseMemoryArray(*arrayType); + // stack: updated_memory_pos + } + } + else + solAssert(false, "Requested initialisation for unknown type: " + type->toString()); - // remove the updated memory pointer - m_context << Instruction::POP; + // remove the updated memory pointer + _context << Instruction::POP; + } + ); } void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a0f196bc..4d33927d 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -106,7 +106,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -271,7 +271,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); } else - m_context.appendJumpTo(m_context.errorTag()); + m_context.appendInvalid(); for (auto const& it: interfaceFunctions) { @@ -486,7 +486,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) stackLayout.push_back(i); stackLayout += vector<int>(c_localVariablesSize, -1); - solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables."); + if (stackLayout.size() > 17) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_function.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); while (stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) { @@ -551,6 +556,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) @@ -575,7 +581,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) { solAssert(contract->isLibrary(), ""); - _assembly.appendLibraryAddress(contract->name()); + _assembly.appendLibraryAddress(contract->fullyQualifiedName()); } else solAssert(false, "Invalid declaration type."); @@ -591,6 +597,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) if (stackDiff > 16 || stackDiff < 1) BOOST_THROW_EXCEPTION( CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); for (unsigned i = 0; i < size; ++i) { @@ -820,6 +827,7 @@ void ContractCompiler::appendMissingFunctions() function->accept(*this); solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); } + m_context.appendMissingLowLevelFunctions(); } void ContractCompiler::appendModifierOrFunctionCode() @@ -910,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() a << Instruction::DELEGATECALL; //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; - a.appendJumpI(a.errorTag()); + a << Instruction::ISZERO; + eth::AssemblyItem afterTag = a.appendJumpI().tag(); + a << Instruction::INVALID << afterTag; //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; return make_shared<eth::Assembly>(a); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 3922da88..b66a3e12 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -250,7 +250,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) } if (lvalueSize > 0) { - solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables."); + if (itemSize + lvalueSize > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_assignment.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); // value [lvalue_ref] updated_value for (unsigned i = 0; i < itemSize; ++i) m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP; @@ -551,20 +556,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arg->accept(*this); argumentTypes.push_back(arg->annotation().type); } - ContractDefinition const& contract = - dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); - // copy the contract's code into memory - eth::Assembly const& assembly = m_context.compiledContract(contract); - utils().fetchFreeMemoryPointer(); - // TODO we create a copy here, which is actually what we want. - // This should be revisited at the point where we fix - // https://github.com/ethereum/solidity/issues/1092 - // pushes size - auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly)); - m_context << Instruction::DUP1 << subroutine; - m_context << Instruction::DUP4 << Instruction::CODECOPY; - - m_context << Instruction::ADD; + ContractDefinition const* contract = + &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); + m_context.callLowLevelFunction( + "$copyContractCreationCodeToMemory_" + contract->type()->identifier(), + 0, + 1, + [contract](CompilerContext& _context) + { + // copy the contract's code into memory + eth::Assembly const& assembly = _context.compiledContract(*contract); + CompilerUtils(_context).fetchFreeMemoryPointer(); + // pushes size + auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly)); + _context << Instruction::DUP1 << subroutine; + _context << Instruction::DUP4 << Instruction::CODECOPY; + _context << Instruction::ADD; + } + ); utils().encodeToMemory(argumentTypes, function.parameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment @@ -576,7 +585,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::CREATE; // Check if zero (out of stack or not enough balance). m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -892,7 +901,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(funType->location() == FunctionType::Location::DelegateCall, ""); auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope()); solAssert(contract && contract->isLibrary(), ""); - m_context.appendLibraryAddress(contract->name()); + m_context.appendLibraryAddress(contract->fullyQualifiedName()); m_context << funType->externalIdentifier(); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2); } @@ -1225,7 +1234,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context << u256(fixedBytesType.numBytes()); m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; // out-of-bounds access throws exception - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); m_context << Instruction::BYTE; m_context << (u256(1) << (256 - 8)) << Instruction::MUL; @@ -1270,7 +1279,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) { if (contract->isLibrary()) - m_context.appendLibraryAddress(contract->name()); + m_context.appendLibraryAddress(contract->fullyQualifiedName()); } else if (dynamic_cast<EventDefinition const*>(declaration)) { @@ -1299,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { case Type::Category::RationalNumber: case Type::Category::Bool: + case Type::Category::Integer: m_context << type->literalValue(&_literal); break; case Type::Category::StringLiteral: @@ -1406,7 +1416,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty { // Test for division by zero m_context << Instruction::DUP2 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); if (_operator == Token::Div) m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); @@ -1467,7 +1477,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co if (c_amountSigned) { m_context << u256(0) << Instruction::DUP3 << Instruction::SLT; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } switch (_operator) @@ -1653,7 +1663,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); existenceChecked = true; } @@ -1689,7 +1699,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { //Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + m_context.appendConditionalInvalid(); } utils().popStackSlots(remainsSize); diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index ef3da255..fcc92dbb 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -71,6 +71,8 @@ assembly::Statement Parser::parseStatement() expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = m_scanner->currentLiteral(); + if (instructions().count(assignment.variableName.name)) + fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); return assignment; @@ -101,6 +103,8 @@ assembly::Statement Parser::parseStatement() { // functional assignment FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location); + if (instructions().count(identifier.name)) + fatalParserError("Cannot use instruction names for identifier names."); m_scanner->next(); funAss.variableName = identifier; funAss.value.reset(new Statement(parseExpression())); @@ -130,7 +134,7 @@ assembly::Statement Parser::parseExpression() return operation; } -assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +std::map<string, dev::solidity::Instruction> const& Parser::instructions() { // Allowed instructions, lowercase names. static map<string, dev::solidity::Instruction> s_instructions; @@ -151,7 +155,11 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) // add alias for selfdestruct s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE; } + return s_instructions; +} +assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) +{ Statement ret; switch (m_scanner->currentToken()) { @@ -170,9 +178,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = m_scanner->currentLiteral(); // first search the set of instructions. - if (s_instructions.count(literal)) + if (instructions().count(literal)) { - dev::solidity::Instruction const& instr = s_instructions[literal]; + dev::solidity::Instruction const& instr = instructions().at(literal); if (_onlySinglePusher) { InstructionInfo info = dev::solidity::instructionInfo(instr); @@ -207,6 +215,8 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() VariableDeclaration varDecl = createWithLocation<VariableDeclaration>(); expectToken(Token::Let); varDecl.name = m_scanner->currentLiteral(); + if (instructions().count(varDecl.name)) + fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); expectToken(Token::Colon); expectToken(Token::Assign); diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 8b56ab90..643548dd 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -64,6 +64,7 @@ protected: Statement parseStatement(); /// Parses a functional expression that has to push exactly one stack element Statement parseExpression(); + std::map<std::string, dev::solidity::Instruction> const& instructions(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index a31df584..3335c40e 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -21,6 +21,7 @@ * Full-stack compiler that converts a source code string to bytecode. */ + #include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/Version.h> @@ -111,6 +112,7 @@ bool CompilerStack::parse() { //reset m_errors.clear(); + ASTNode::resetID(); m_parseSuccessful = false; if (SemVerVersion{string(VersionString)}.isPrerelease()) @@ -180,11 +182,15 @@ bool CompilerStack::parse() if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false; if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false; if (!resolver.resolveNamesAndTypes(*contract)) return false; - m_contracts[contract->name()].contract = contract; - } - if (!checkLibraryNameClashes()) - noErrors = false; + // Note that we now reference contracts by their fully qualified names, and + // thus contracts can only conflict if declared in the same source file. This + // already causes a double-declaration error elsewhere, so we do not report + // an error here and instead silently drop any additional contracts we find. + + if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) + m_contracts[contract->fullyQualifiedName()].contract = contract; + } for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) @@ -201,7 +207,13 @@ bool CompilerStack::parse() else noErrors = false; - m_contracts[contract->name()].contract = contract; + // Note that we now reference contracts by their fully qualified names, and + // thus contracts can only conflict if declared in the same source file. This + // already causes a double-declaration error elsewhere, so we do not report + // an error here and instead silently drop any additional contracts we find. + + if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) + m_contracts[contract->fullyQualifiedName()].contract = contract; } if (noErrors) @@ -315,6 +327,27 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c return c.runtimeSourceMapping.get(); } +std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const +{ + // Look up the contract (by its fully-qualified name) + Contract const& matchContract = m_contracts.at(_contractName); + // Check to see if it could collide on name + for (auto const& contract: m_contracts) + { + if (contract.second.contract->name() == matchContract.contract->name() && + contract.second.contract != matchContract.contract) + { + // If it does, then return its fully-qualified name, made fs-friendly + std::string friendlyName = boost::algorithm::replace_all_copy(_contractName, "/", "_"); + boost::algorithm::replace_all(friendlyName, ":", "_"); + boost::algorithm::replace_all(friendlyName, ".", "_"); + return friendlyName; + } + } + // If no collision, return the contract's name + return matchContract.contract->name(); +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { return contract(_contractName).object; @@ -569,37 +602,6 @@ void CompilerStack::resolveImports() swap(m_sourceOrder, sourceOrder); } -bool CompilerStack::checkLibraryNameClashes() -{ - bool clashFound = false; - map<string, SourceLocation> libraries; - for (Source const* source: m_sourceOrder) - for (ASTPointer<ASTNode> const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) - if (contract->isLibrary()) - { - if (libraries.count(contract->name())) - { - auto err = make_shared<Error>(Error::Type::DeclarationError); - *err << - errinfo_sourceLocation(contract->location()) << - errinfo_comment( - "Library \"" + contract->name() + "\" declared twice " - "(will create ambiguities during linking)." - ) << - errinfo_secondarySourceLocation(SecondarySourceLocation().append( - "The other declaration is here:", libraries[contract->name()] - )); - - m_errors.push_back(err); - clashFound = true; - } - else - libraries[contract->name()] = contract->location(); - } - return !clashFound; -} - string CompilerStack::absolutePath(string const& _path, string const& _reference) const { using path = boost::filesystem::path; @@ -622,13 +624,17 @@ void CompilerStack::compileContract( map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts ) { - if (_compiledContracts.count(&_contract) || !_contract.annotation().isFullyImplemented) + if ( + _compiledContracts.count(&_contract) || + !_contract.annotation().isFullyImplemented || + !_contract.annotation().hasPublicConstructor + ) return; for (auto const* dependency: _contract.annotation().contractDependencies) compileContract(*dependency, _compiledContracts); shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns); - Contract& compiledContract = m_contracts.at(_contract.name()); + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); string onChainMetadata = createOnChainMetadata(compiledContract); bytes cborEncodedMetadata = // CBOR-encoding of {"bzzr0": dev::swarmHash(onChainMetadata)} @@ -674,10 +680,27 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa for (auto const& it: m_sources) for (ASTPointer<ASTNode> const& node: it.second.ast->nodes()) if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) - contractName = contract->name(); + contractName = contract->fullyQualifiedName(); auto it = m_contracts.find(contractName); - if (it == m_contracts.end()) + // To provide a measure of backward-compatibility, if a contract is not located by its + // fully-qualified name, a lookup will be attempted purely on the contract's name to see + // if anything will satisfy. + if (it == m_contracts.end() && contractName.find(":") == string::npos) + { + for (auto const& contractEntry: m_contracts) + { + stringstream ss; + ss.str(contractEntry.first); + // All entries are <source>:<contract> + string source; + string foundName; + getline(ss, source, ':'); + getline(ss, foundName, ':'); + if (foundName == contractName) return contractEntry.second; + } + // If we get here, both lookup methods failed. BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); + } return it->second; } @@ -695,7 +718,7 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const Json::Value meta; meta["version"] = 1; meta["language"] = "Solidity"; - meta["compiler"]["version"] = VersionString; + meta["compiler"]["version"] = VersionStringStrict; meta["sources"] = Json::objectValue; for (auto const& s: m_sources) @@ -703,10 +726,15 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const solAssert(s.second.scanner, "Scanner not available"); meta["sources"][s.first]["keccak256"] = "0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes()); - meta["sources"][s.first]["urls"] = Json::arrayValue; - meta["sources"][s.first]["urls"].append( - "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) - ); + if (m_metadataLiteralSources) + meta["sources"][s.first]["content"] = s.second.scanner->source(); + else + { + meta["sources"][s.first]["urls"] = Json::arrayValue; + meta["sources"][s.first]["urls"].append( + "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) + ); + } } meta["settings"]["optimizer"]["enabled"] = m_optimize; meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index d49a8df1..9ee70215 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -148,6 +148,10 @@ public: /// @returns the string that provides a mapping between runtime bytecode and sourcecode. /// if the contract does not (yet) have bytecode. std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; + + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use + std::string const filesystemFriendlyName(std::string const& _contractName) const; + /// @returns hash of the runtime bytecode for the contract, i.e. the code that is /// returned by the constructor or the zero-h256 if the contract still needs to be linked or /// does not have runtime code. @@ -173,6 +177,7 @@ public: /// Can be one of 4 types defined at @c DocumentationType Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const; std::string const& onChainMetadata(std::string const& _contractName) const; + void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; @@ -230,9 +235,6 @@ private: StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path); std::string applyRemapping(std::string const& _path, std::string const& _context); void resolveImports(); - /// Checks whether there are libraries with the same name, reports that as an error and - /// @returns false in this case. - bool checkLibraryNameClashes(); /// @returns the absolute path corresponding to @a _path relative to @a _reference. std::string absolutePath(std::string const& _path, std::string const& _reference) const; /// Helper function to return path converted strings. @@ -273,6 +275,7 @@ private: std::map<std::string const, Contract> m_contracts; std::string m_formalTranslation; ErrorList m_errors; + bool m_metadataLiteralSources = false; }; } diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 9944bb22..6c1bb0c4 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -30,20 +30,6 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe { Json::Value abi(Json::arrayValue); - auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes) - { - Json::Value params(Json::arrayValue); - solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); - for (unsigned i = 0; i < _paramNames.size(); ++i) - { - Json::Value param; - param["name"] = _paramNames[i]; - param["type"] = _paramTypes[i]; - params.append(param); - } - return params; - }; - for (auto it: _contractDef.interfaceFunctions()) { auto externalFunctionType = it.second->interfaceFunctionType(); @@ -52,13 +38,15 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe method["name"] = it.second->declaration().name(); method["constant"] = it.second->isConstant(); method["payable"] = it.second->isPayable(); - method["inputs"] = populateParameters( + method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), - externalFunctionType->parameterTypeNames(_contractDef.isLibrary()) + externalFunctionType->parameterTypes(), + _contractDef.isLibrary() ); - method["outputs"] = populateParameters( + method["outputs"] = formatTypeList( externalFunctionType->returnParameterNames(), - externalFunctionType->returnParameterTypeNames(_contractDef.isLibrary()) + externalFunctionType->returnParameterTypes(), + _contractDef.isLibrary() ); abi.append(method); } @@ -69,9 +57,10 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["payable"] = externalFunction->isPayable(); - method["inputs"] = populateParameters( + method["inputs"] = formatTypeList( externalFunction->parameterNames(), - externalFunction->parameterTypeNames(_contractDef.isLibrary()) + externalFunction->parameterTypes(), + _contractDef.isLibrary() ); abi.append(method); } @@ -179,6 +168,25 @@ Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contra return doc; } +Json::Value InterfaceHandler::formatTypeList( + vector<string> const& _names, + vector<TypePointer> const& _types, + bool _forLibrary +) +{ + Json::Value params(Json::arrayValue); + solAssert(_names.size() == _types.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _names.size(); ++i) + { + solAssert(_types[i], ""); + Json::Value param; + param["name"] = _names[i]; + param["type"] = _types[i]->canonicalName(_forLibrary); + params.append(param); + } + return params; +} + string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name) { string value; diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h index b7e1bb00..56927d44 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/InterfaceHandler.h @@ -37,6 +37,8 @@ namespace solidity // Forward declarations class ContractDefinition; +class Type; +using TypePointer = std::shared_ptr<Type const>; struct DocTag; enum class DocumentationType: uint8_t; @@ -84,6 +86,14 @@ public: static Json::Value devDocumentation(ContractDefinition const& _contractDef); private: + /// @returns a json value suitable for a list of types in function input or output + /// parameters or other places. If @a _forLibrary is true, complex types are referenced + /// by name, otherwise they are anonymously expanded. + static Json::Value formatTypeList( + std::vector<std::string> const& _names, + std::vector<TypePointer> const& _types, + bool _forLibrary + ); /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name); }; diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index ff66f039..0d23f9c3 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -38,6 +38,10 @@ string const dev::solidity::VersionString = (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); +string const dev::solidity::VersionStringStrict = + string(dev::solidity::VersionNumber) + + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + + (string(SOL_VERSION_COMMIT).empty() ? "" : "+" + string(SOL_VERSION_COMMIT)); bytes dev::solidity::binaryVersion() { diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h index 5b07b3f4..24c3555d 100644 --- a/libsolidity/interface/Version.h +++ b/libsolidity/interface/Version.h @@ -32,6 +32,7 @@ namespace solidity extern char const* VersionNumber; extern std::string const VersionString; +extern std::string const VersionStringStrict; /// @returns a binary form of the version string, where A.B.C-HASH is encoded such that /// the first byte is zero, the following three bytes encode A B and C (interpreted as decimals) diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index bbee35f5..8e912126 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -1,14 +1,19 @@ #include <libsolidity/parsing/DocStringParser.h> -#include <boost/range/irange.hpp> #include <libsolidity/interface/Utils.h> +#include <boost/range/irange.hpp> +#include <boost/range/algorithm.hpp> + using namespace std; using namespace dev; using namespace dev::solidity; -static inline string::const_iterator skipLineOrEOS( +namespace +{ + +string::const_iterator skipLineOrEOS( string::const_iterator _nlPos, string::const_iterator _end ) @@ -16,14 +21,34 @@ static inline string::const_iterator skipLineOrEOS( return (_nlPos == _end) ? _end : ++_nlPos; } -static inline string::const_iterator firstSpaceOrNl( +string::const_iterator firstSpaceOrTab( string::const_iterator _pos, string::const_iterator _end ) { - auto spacePos = find(_pos, _end, ' '); - auto nlPos = find(_pos, _end, '\n'); - return (spacePos < nlPos) ? spacePos : nlPos; + return boost::range::find_first_of(make_pair(_pos, _end), " \t"); +} + +string::const_iterator firstWhitespaceOrNewline( + string::const_iterator _pos, + string::const_iterator _end +) +{ + return boost::range::find_first_of(make_pair(_pos, _end), " \t\n"); +} + + +string::const_iterator skipWhitespace( + string::const_iterator _pos, + string::const_iterator _end +) +{ + auto currPos = _pos; + while (currPos != _end && (*currPos == ' ' || *currPos == '\t')) + currPos += 1; + return currPos; +} + } bool DocStringParser::parse(string const& _docString, ErrorList& _errors) @@ -43,7 +68,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors) if (tagPos != end && tagPos < nlPos) { // we found a tag - auto tagNameEndPos = firstSpaceOrNl(tagPos, end); + auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end); if (tagNameEndPos == end) { appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); @@ -75,27 +100,40 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo { solAssert(!!m_lastTag, ""); auto nlPos = find(_pos, _end, '\n'); - if (_appending && _pos < _end && *_pos != ' ') + if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t') m_lastTag->content += " "; + else if (!_appending) + _pos = skipWhitespace(_pos, _end); copy(_pos, nlPos, back_inserter(m_lastTag->content)); return skipLineOrEOS(nlPos, _end); } DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) { - // find param name - auto currPos = find(_pos, _end, ' '); - if (currPos == _end) + // find param name start + auto nameStartPos = skipWhitespace(_pos, _end); + if (nameStartPos == _end) { - appendError("End of param name not found" + string(_pos, _end)); + appendError("No param name given"); return _end; } + auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); + if (nameEndPos == _end) + { + appendError("End of param name not found: " + string(nameStartPos, _end)); + return _end; + } + auto paramName = string(nameStartPos, nameEndPos); - auto paramName = string(_pos, currPos); + auto descStartPos = skipWhitespace(nameEndPos, _end); + if (descStartPos == _end) + { + appendError("No description given for param " + paramName); + return _end; + } - currPos += 1; - auto nlPos = find(currPos, _end, '\n'); - auto paramDesc = string(currPos, nlPos); + auto nlPos = find(descStartPos, _end, '\n'); + auto paramDesc = string(descStartPos, nlPos); newTag("param"); m_lastTag->paramName = paramName; m_lastTag->content = paramDesc; |