diff --git a/eth/main.cpp b/eth/main.cpp index 32862edf1..540cb8c4c 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -102,6 +102,9 @@ void interactiveHelp() << " listaccounts List the accounts on the network." << endl << " listcontracts List the contracts on the network." << endl << " balanceat
Gives the balance of the given account." << endl + << " balanceatblock
Gives the balance of the given account." << endl + << " storageat
Gives the storage of the given account." << endl + << " storageatblock
Gives the storahe of the given account at a given blocknumber." << endl << " codeat
Gives the code of the given account." << endl #endif << " setsigningkey Set the address with which to sign transactions." << endl @@ -168,6 +171,9 @@ void help() << " --port Connect to remote port (default: 30303)." << endl << " --network-id Only connect to other hosts with this network id (default:0)." << endl << " --upnp Use UPnP for NAT (default: on)." << endl + << " --no-discovery Disable Node discovery. (experimental)" << endl + << " --pin Only connect to required (trusted) peers. (experimental)" << endl +// << " --require-peers List of required (trusted) peers. (experimental)" << endl << endl; MinerCLI::streamHelp(cout); cout @@ -304,6 +310,8 @@ int main(int argc, char** argv) unsigned short remotePort = 30303; unsigned peers = 11; bool bootstrap = false; + bool disableDiscovery = false; + bool pinning = false; unsigned networkId = 0; /// Mining params @@ -592,6 +600,10 @@ int main(int argc, char** argv) } else if (arg == "-b" || arg == "--bootstrap") bootstrap = true; + else if (arg == "--no-discovery") + disableDiscovery = true; + else if (arg == "--pin") + pinning = true; else if (arg == "-f" || arg == "--force-mining") forceMining = true; else if (arg == "-i" || arg == "--interactive") @@ -684,6 +696,8 @@ int main(int argc, char** argv) StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat, structuredLoggingURL); VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter); auto netPrefs = publicIP.empty() ? NetworkPreferences(listenIP ,listenPort, upnp) : NetworkPreferences(publicIP, listenIP ,listenPort, upnp); + netPrefs.discovery = !disableDiscovery; + netPrefs.pin = pinning; auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp"); dev::WebThreeDirect web3( WebThreeDirect::composeClientVersion("++eth", clientName), @@ -1360,19 +1374,48 @@ int main(int argc, char** argv) cout << "balance of " << stringHash << " is: " << toString(c->balanceAt(address)) << endl; } } - // TODO implement << operator for std::unorderd_map -// else if (c && cmd == "storageat") -// { -// if (iss.peek() != -1) -// { -// string stringHash; -// iss >> stringHash; + else if (c && cmd == "balanceatblock") + { + if (iss.peek() != -1) + { + string stringHash; + unsigned blocknumber; + iss >> stringHash >> blocknumber; -// Address address = h160(fromHex(stringHash)); + Address address = h160(fromHex(stringHash)); + + cout << "balance of " << stringHash << " is: " << toString(c->balanceAt(address, blocknumber)) << endl; + } + } + else if (c && cmd == "storageat") + { + if (iss.peek() != -1) + { + string stringHash; + iss >> stringHash; -// cout << "storage at " << stringHash << " is: " << c->storageAt(address) << endl; -// } -// } + Address address = h160(fromHex(stringHash)); + + cout << "storage at " << stringHash << " is: " << endl; + for (auto s: c->storageAt(address)) + cout << toHex(s.first) << " : " << toHex(s.second) << endl; + } + } + else if (c && cmd == "storageatblock") + { + if (iss.peek() != -1) + { + string stringHash; + unsigned blocknumber; + iss >> stringHash >> blocknumber; + + Address address = h160(fromHex(stringHash)); + + cout << "storage at " << stringHash << " is: " << endl; + for (auto s: c->storageAt(address, blocknumber)) + cout << "\"0x" << toHex(s.first) << "\" : \"0x" << toHex(s.second) << "\"," << endl; + } + } else if (c && cmd == "codeat") { if (iss.peek() != -1) @@ -1386,6 +1429,7 @@ int main(int argc, char** argv) } } #endif + else if (c && cmd == "send") { if (iss.peek() != -1) diff --git a/ethminer/MinerAux.h b/ethminer/MinerAux.h index 6de3913e4..3351b90de 100644 --- a/ethminer/MinerAux.h +++ b/ethminer/MinerAux.h @@ -410,7 +410,6 @@ private: } catch (...) { - cout << "Error phoning home. ET is sad." << endl; } } #endif diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 2d1822b2c..922a6ec30 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -158,16 +158,17 @@ public: template inline FixedHash& shiftBloom(FixedHash const& _h) { - return (*this |= _h.template bloom()); + return (*this |= _h.template bloomPart()); } template inline bool containsBloom(FixedHash const& _h) { - return contains(_h.template bloom()); + return contains(_h.template bloomPart()); } - template inline FixedHash bloom() const + template inline FixedHash bloomPart() const { + static_assert((M & (M - 1)) == 0, "M must be power-of-two"); static const unsigned c_bloomBits = M * 8; unsigned mask = c_bloomBits - 1; unsigned bloomBytes = (dev::toLog2(c_bloomBits) + 7) / 8; diff --git a/libethash-cl/ethash_cl_miner.cpp b/libethash-cl/ethash_cl_miner.cpp index 6c2f8269a..b160cdd94 100644 --- a/libethash-cl/ethash_cl_miner.cpp +++ b/libethash-cl/ethash_cl_miner.cpp @@ -429,7 +429,8 @@ void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook }; queue pending; - static uint32_t const c_zero = 0; + // this can't be a static because in MacOSX OpenCL implementation a segfault occurs when a static is passed to OpenCL functions + uint32_t const c_zero = 0; // update header constant buffer m_queue.enqueueWriteBuffer(m_header, false, 0, 32, header); diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index bd6996a45..195f65f1d 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -1072,7 +1072,7 @@ VerifiedBlockRef BlockChain::verifyBlock(bytes const& _block, functionrequired) + m_peers.erase(_n); } } @@ -637,27 +638,40 @@ void Host::run(boost::system::error_code const&) // updated. // disconnectLatePeers(); // todo: update peerSlotsAvailable() - unsigned pendingCount = 0; - DEV_GUARDED(x_pendingNodeConns) - pendingCount = m_pendingPeerConns.size(); - int openSlots = m_idealPeerCount - peerCount() - pendingCount; - if (openSlots > 0) + + list> toConnect; + unsigned reqConn = 0; { - list> toConnect; + RecursiveGuard l(x_sessions); + for (auto const& p: m_peers) { - RecursiveGuard l(x_sessions); - for (auto p: m_peers) - if (p.second->shouldReconnect() && !havePeerSession(p.second->id)) - toConnect.push_back(p.second); + bool haveSession = havePeerSession(p.second->id); + bool required = p.second->required; + if (haveSession && required) + reqConn++; + else if (!haveSession && p.second->shouldReconnect() && (!m_netPrefs.pin || required)) + toConnect.push_back(p.second); } + } + + for (auto p: toConnect) + if (p->required && reqConn++ < m_idealPeerCount) + connect(p); + + if (!m_netPrefs.pin) + { + unsigned pendingCount = 0; + DEV_GUARDED(x_pendingNodeConns) + pendingCount = m_pendingPeerConns.size(); + int openSlots = m_idealPeerCount - peerCount() - pendingCount + reqConn; + if (openSlots > 0) + { + for (auto p: toConnect) + if (!p->required && openSlots--) + connect(p); - for (auto p: toConnect) - if (openSlots--) - connect(p); - else - break; - - m_nodeTable->discover(); + m_nodeTable->discover(); + } } auto runcb = [this](boost::system::error_code const& error) { run(error); }; @@ -698,7 +712,7 @@ void Host::startedWorking() else clog(NetP2PNote) << "p2p.start.notice id:" << id() << "TCP Listen port is invalid or unavailable."; - shared_ptr nodeTable(new NodeTable(m_ioService, m_alias, NodeIPEndpoint(bi::address::from_string(listenAddress()), listenPort(), listenPort()))); + shared_ptr nodeTable(new NodeTable(m_ioService, m_alias, NodeIPEndpoint(bi::address::from_string(listenAddress()), listenPort(), listenPort()), m_netPrefs.discovery)); nodeTable->setEventHandler(new HostNodeTableHandler(*this)); m_nodeTable = nodeTable; restoreNetwork(&m_restoreNetwork); diff --git a/libp2p/Network.h b/libp2p/Network.h index d02ce3cbe..e70dd89ea 100644 --- a/libp2p/Network.h +++ b/libp2p/Network.h @@ -52,6 +52,8 @@ struct NetworkPreferences std::string listenIPAddress; unsigned short listenPort = 30303; bool traverseNAT = true; + bool discovery = true; // Discovery is activated with network. + bool pin = false; // Only connect to trusted ("required") peers. }; /** diff --git a/libp2p/NodeTable.cpp b/libp2p/NodeTable.cpp index 6344dc263..1a0b11734 100644 --- a/libp2p/NodeTable.cpp +++ b/libp2p/NodeTable.cpp @@ -40,14 +40,15 @@ const char* NodeTableIngress::name() { return "<connect(); - doRefreshBuckets(boost::system::error_code()); + if (!m_disabled) + { + m_socketPointer->connect(); + doRefreshBuckets(boost::system::error_code()); + } } NodeTable::~NodeTable() diff --git a/libp2p/NodeTable.h b/libp2p/NodeTable.h index 0ec13c828..d31a356ef 100644 --- a/libp2p/NodeTable.h +++ b/libp2p/NodeTable.h @@ -130,7 +130,7 @@ public: enum NodeRelation { Unknown = 0, Known }; /// Constructor requiring host for I/O, credentials, and IP Address and port to listen on. - NodeTable(ba::io_service& _io, KeyPair const& _alias, NodeIPEndpoint const& _endpoint); + NodeTable(ba::io_service& _io, KeyPair const& _alias, NodeIPEndpoint const& _endpoint, bool _enabled = true); ~NodeTable(); /// Returns distance based on xor metric two node ids. Used by NodeEntry and NodeTable. @@ -271,6 +271,8 @@ private: boost::asio::deadline_timer m_bucketRefreshTimer; ///< Timer which schedules and enacts bucket refresh. boost::asio::deadline_timer m_evictionCheckTimer; ///< Timer for handling node evictions. + + bool m_disabled; ///< Disable discovery. }; inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable) diff --git a/libp2p/Peer.cpp b/libp2p/Peer.cpp index 6f368a4b4..a8b4a993d 100644 --- a/libp2p/Peer.cpp +++ b/libp2p/Peer.cpp @@ -38,6 +38,8 @@ bool Peer::shouldReconnect() const unsigned Peer::fallbackSeconds() const { + if (required) + return 5; switch (m_lastDisconnect) { case BadProtocol: diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 5be23b7c8..dbeec858e 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -488,7 +488,7 @@ string FunctionDefinition::externalSignature() const bool VariableDeclaration::isLValue() const { // External function parameters and constant declared variables are Read-Only - return !isExternalFunctionParameter() && !m_isConstant; + return !isExternalCallableParameter() && !m_isConstant; } void VariableDeclaration::checkTypeRequirements() @@ -516,39 +516,41 @@ void VariableDeclaration::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Assignment necessary for type detection.")); m_value->checkTypeRequirements(nullptr); - TypePointer type = m_value->getType(); - if (type->getCategory() == Type::Category::IntegerConstant) - { - auto intType = dynamic_pointer_cast(type)->getIntegerType(); - if (!intType) - BOOST_THROW_EXCEPTION(m_value->createTypeError("Invalid integer constant " + type->toString() + ".")); - type = intType; - } + TypePointer const& type = m_value->getType(); + if ( + type->getCategory() == Type::Category::IntegerConstant && + !dynamic_pointer_cast(type)->getIntegerType() + ) + BOOST_THROW_EXCEPTION(m_value->createTypeError("Invalid integer constant " + type->toString() + ".")); else if (type->getCategory() == Type::Category::Void) BOOST_THROW_EXCEPTION(createTypeError("Variable cannot have void type.")); - m_type = type; + m_type = type->mobileType(); } if (m_isStateVariable && getVisibility() >= Visibility::Public && !FunctionType(*this).externalType()) BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables.")); } -bool VariableDeclaration::isFunctionParameter() const +bool VariableDeclaration::isCallableParameter() const { - auto const* function = dynamic_cast(getScope()); - if (!function) + auto const* callable = dynamic_cast(getScope()); + if (!callable) return false; - for (auto const& variable: function->getParameters() + function->getReturnParameters()) + for (auto const& variable: callable->getParameters()) if (variable.get() == this) return true; + if (callable->getReturnParameterList()) + for (auto const& variable: callable->getReturnParameterList()->getParameters()) + if (variable.get() == this) + return true; return false; } -bool VariableDeclaration::isExternalFunctionParameter() const +bool VariableDeclaration::isExternalCallableParameter() const { - auto const* function = dynamic_cast(getScope()); - if (!function || function->getVisibility() != Declaration::Visibility::External) + auto const* callable = dynamic_cast(getScope()); + if (!callable || callable->getVisibility() != Declaration::Visibility::External) return false; - for (auto const& variable: function->getParameters()) + for (auto const& variable: callable->getParameters()) if (variable.get() == this) return true; return false; diff --git a/libsolidity/AST.h b/libsolidity/AST.h index b3984840f..ff0d708f5 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -406,13 +406,43 @@ private: std::vector> m_parameters; }; -class FunctionDefinition: public Declaration, public VariableScope, public Documented, public ImplementationOptional +/** + * Base class for all nodes that define function-like objects, i.e. FunctionDefinition, + * EventDefinition and ModifierDefinition. + */ +class CallableDeclaration: public Declaration, public VariableScope +{ +public: + CallableDeclaration( + SourceLocation const& _location, + ASTPointer const& _name, + Declaration::Visibility _visibility, + ASTPointer const& _parameters, + ASTPointer const& _returnParameters = ASTPointer() + ): + Declaration(_location, _name, _visibility), + m_parameters(_parameters), + m_returnParameters(_returnParameters) + { + } + + std::vector> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList const& getParameterList() const { return *m_parameters; } + ASTPointer const& getReturnParameterList() const { return m_returnParameters; } + +protected: + ASTPointer m_parameters; + ASTPointer m_returnParameters; +}; + +class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional { public: FunctionDefinition( SourceLocation const& _location, ASTPointer const& _name, - Declaration::Visibility _visibility, bool _isConstructor, + Declaration::Visibility _visibility, + bool _isConstructor, ASTPointer const& _documentation, ASTPointer const& _parameters, bool _isDeclaredConst, @@ -420,14 +450,12 @@ public: ASTPointer const& _returnParameters, ASTPointer const& _body ): - Declaration(_location, _name, _visibility), + CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters), Documented(_documentation), ImplementationOptional(_body != nullptr), m_isConstructor(_isConstructor), - m_parameters(_parameters), m_isDeclaredConst(_isDeclaredConst), m_functionModifiers(_modifiers), - m_returnParameters(_returnParameters), m_body(_body) {} @@ -437,10 +465,7 @@ public: bool isConstructor() const { return m_isConstructor; } bool isDeclaredConst() const { return m_isDeclaredConst; } std::vector> const& getModifiers() const { return m_functionModifiers; } - std::vector> const& getParameters() const { return m_parameters->getParameters(); } - ParameterList const& getParameterList() const { return *m_parameters; } std::vector> const& getReturnParameters() const { return m_returnParameters->getParameters(); } - ASTPointer const& getReturnParameterList() const { return m_returnParameters; } Block const& getBody() const { return *m_body; } virtual bool isVisibleInContract() const override @@ -460,10 +485,8 @@ public: private: bool m_isConstructor; - ASTPointer m_parameters; bool m_isDeclaredConst; std::vector> m_functionModifiers; - ASTPointer m_returnParameters; ASTPointer m_body; }; @@ -512,9 +535,9 @@ public: void checkTypeRequirements(); bool isLocalVariable() const { return !!dynamic_cast(getScope()); } /// @returns true if this variable is a parameter or return parameter of a function. - bool isFunctionParameter() const; + bool isCallableParameter() const; /// @returns true if this variable is a parameter (not return parameter) of an external function. - bool isExternalFunctionParameter() const; + bool isExternalCallableParameter() const; bool isStateVariable() const { return m_isStateVariable; } bool isIndexed() const { return m_isIndexed; } bool isConstant() const { return m_isConstant; } @@ -537,22 +560,25 @@ private: /** * Definition of a function modifier. */ -class ModifierDefinition: public Declaration, public VariableScope, public Documented +class ModifierDefinition: public CallableDeclaration, public Documented { public: - ModifierDefinition(SourceLocation const& _location, - ASTPointer const& _name, - ASTPointer const& _documentation, - ASTPointer const& _parameters, - ASTPointer const& _body): - Declaration(_location, _name), Documented(_documentation), - m_parameters(_parameters), m_body(_body) {} + ModifierDefinition( + SourceLocation const& _location, + ASTPointer const& _name, + ASTPointer const& _documentation, + ASTPointer const& _parameters, + ASTPointer const& _body + ): + CallableDeclaration(_location, _name, Visibility::Default, _parameters), + Documented(_documentation), + m_body(_body) + { + } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - std::vector> const& getParameters() const { return m_parameters->getParameters(); } - ParameterList const& getParameterList() const { return *m_parameters; } Block const& getBody() const { return *m_body; } virtual TypePointer getType(ContractDefinition const* = nullptr) const override; @@ -560,7 +586,6 @@ public: void checkTypeRequirements(); private: - ASTPointer m_parameters; ASTPointer m_body; }; @@ -591,7 +616,7 @@ private: /** * Definition of a (loggable) event. */ -class EventDefinition: public Declaration, public VariableScope, public Documented +class EventDefinition: public CallableDeclaration, public Documented { public: EventDefinition( @@ -601,16 +626,15 @@ public: ASTPointer const& _parameters, bool _anonymous = false ): - Declaration(_location, _name), + CallableDeclaration(_location, _name, Visibility::Default, _parameters), Documented(_documentation), - m_parameters(_parameters), - m_anonymous(_anonymous){} + m_anonymous(_anonymous) + { + } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - std::vector> const& getParameters() const { return m_parameters->getParameters(); } - ParameterList const& getParameterList() const { return *m_parameters; } bool isAnonymous() const { return m_anonymous; } virtual TypePointer getType(ContractDefinition const* = nullptr) const override @@ -621,7 +645,6 @@ public: void checkTypeRequirements(); private: - ASTPointer m_parameters; bool m_anonymous = false; }; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index b2b8bfa4c..82e98dfff 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -30,9 +30,8 @@ #include using namespace std; - -namespace dev { -namespace solidity { +using namespace dev; +using namespace dev::solidity; /** * Simple helper class to ensure that the stack height is the same at certain places in the code. @@ -170,11 +169,17 @@ void Compiler::appendConstructor(FunctionDefinition const& _constructor) if (argumentSize > 0) { - m_context << u256(argumentSize); + CompilerUtils(m_context).fetchFreeMemoryPointer(); + m_context << u256(argumentSize) << eth::Instruction::DUP1; m_context.appendProgramSize(); - m_context << u256(CompilerUtils::dataStartOffset); // copy it to byte four as expected for ABI calls - m_context << eth::Instruction::CODECOPY; - appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true); + m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; + m_context << eth::Instruction::ADD; + CompilerUtils(m_context).storeFreeMemoryPointer(); + appendCalldataUnpacker( + FunctionType(_constructor).getParameterTypes(), + true, + CompilerUtils::freeMemoryPointer + 0x20 + ); } _constructor.accept(*this); } @@ -232,26 +237,35 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) } } -void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory) +void Compiler::appendCalldataUnpacker( + TypePointers const& _typeParameters, + bool _fromMemory, + u256 _startOffset +) { // We do not check the calldata size, everything is zero-paddedd - m_context << u256(CompilerUtils::dataStartOffset); + if (_startOffset == u256(-1)) + _startOffset = u256(CompilerUtils::dataStartOffset); + + m_context << _startOffset; for (TypePointer const& type: _typeParameters) { - if (type->getCategory() == Type::Category::Array) + switch (type->getCategory()) + { + case Type::Category::Array: { auto const& arrayType = dynamic_cast(*type); if (arrayType.location() == ReferenceType::Location::CallData) { + solAssert(!_fromMemory, ""); if (type->isDynamicallySized()) { // put on stack: data_pointer length CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory); // stack: data_offset next_pointer //@todo once we support nested arrays, this offset needs to be dynamic. - m_context << eth::Instruction::SWAP1 << u256(CompilerUtils::dataStartOffset); - m_context << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1 << _startOffset << eth::Instruction::ADD; // stack: next_pointer data_pointer // retrieve length CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); @@ -268,13 +282,15 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool else { solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); - CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).storeInMemoryDynamic(*type); - CompilerUtils(m_context).storeFreeMemoryPointer(); + // compute data pointer + m_context << eth::Instruction::DUP1 << _startOffset << eth::Instruction::ADD; + if (!_fromMemory) + solAssert(false, "Not yet implemented."); + m_context << eth::Instruction::SWAP1 << u256(0x20) << eth::Instruction::ADD; } + break; } - else - { + default: solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString()); CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true); } @@ -284,24 +300,18 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) { - unsigned dataOffset = 0; - unsigned stackDepth = 0; - for (TypePointer const& type: _typeParameters) - stackDepth += type->getSizeOnStack(); - - for (TypePointer const& type: _typeParameters) - { - CompilerUtils(m_context).copyToStackTop(stackDepth, type->getSizeOnStack()); - ExpressionCompiler(m_context, m_optimize).appendTypeConversion(*type, *type, true); - bool const c_padToWords = true; - dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, *type, c_padToWords); - stackDepth -= type->getSizeOnStack(); - } - // note that the stack is not cleaned up here - if (dataOffset == 0) + CompilerUtils utils(m_context); + if (_typeParameters.empty()) m_context << eth::Instruction::STOP; else - m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; + { + utils.fetchFreeMemoryPointer(); + //@todo optimization: if we return a single memory array, there should be enough space before + // its data to add the needed parts and we avoid a memory copy. + utils.encodeToMemory(_typeParameters, _typeParameters); + utils.toSizeAfterFreeMemoryPointer(); + m_context << eth::Instruction::RETURN; + } } void Compiler::registerStateVariables(ContractDefinition const& _contract) @@ -617,8 +627,5 @@ void Compiler::compileExpression(Expression const& _expression, TypePointer cons ExpressionCompiler expressionCompiler(m_context, m_optimize); expressionCompiler.compile(_expression); if (_targetType) - expressionCompiler.appendTypeConversion(*_expression.getType(), *_targetType); -} - -} + CompilerUtils(m_context).convertType(*_expression.getType(), *_targetType); } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 670c74673..60ca00e83 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -73,7 +73,12 @@ private: void appendFunctionSelector(ContractDefinition const& _contract); /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. /// From memory if @a _fromMemory is true, otherwise from call data. - void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false); + /// Expects source offset on the stack. + void appendCalldataUnpacker( + TypePointers const& _typeParameters, + bool _fromMemory = false, + u256 _startOffset = u256(-1) + ); void appendReturnValuePacker(TypePointers const& _typeParameters); void registerStateVariables(ContractDefinition const& _contract); diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 7a96db928..349877a29 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include using namespace std; @@ -33,6 +35,7 @@ namespace solidity const unsigned CompilerUtils::dataStartOffset = 4; const size_t CompilerUtils::freeMemoryPointer = 64; +const unsigned CompilerUtils::identityContractAddress = 4; void CompilerUtils::initialiseFreeMemoryPointer() { @@ -83,8 +86,7 @@ void CompilerUtils::loadFromMemoryDynamic( if (_keepUpdatedMemoryOffset) { // update memory counter - for (unsigned i = 0; i < _type.getSizeOnStack(); ++i) - m_context << eth::swapInstruction(1 + i); + moveToStackTop(_type.getSizeOnStack()); m_context << u256(numBytes) << eth::Instruction::ADD; } } @@ -108,15 +110,80 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound if (type.location() == ReferenceType::Location::CallData) { // stack: target source_offset source_len - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5 + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; // stack: target source_offset source_len source_len source_offset target - << eth::Instruction::CALLDATACOPY - << eth::Instruction::DUP3 << eth::Instruction::ADD - << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + m_context << eth::Instruction::CALLDATACOPY; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + } + else if (type.location() == ReferenceType::Location::Memory) + { + // memcpy using the built-in contract + ArrayUtils(m_context).retrieveLength(type); + if (type.isDynamicallySized()) + { + // change pointer to data part + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + // stack: + // stack for call: outsize target size source value contract gas + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4; + m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; + m_context << u256(0) << u256(identityContractAddress); + //@TODO do not use ::CALL if less than 32 bytes? + //@todo in production, we should not have to pair c_callNewAccountGas. + m_context << u256(eth::c_callGas + 10 + eth::c_callNewAccountGas) << eth::Instruction::GAS; + m_context << eth::Instruction::SUB << eth::Instruction::CALL; + m_context << eth::Instruction::POP; // ignore return value + + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + // stack: + + if (_padToWordBoundaries && (type.isDynamicallySized() || (type.getLength()) % 32 != 0)) + { + // stack: + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: + m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; + // stack: + eth::AssemblyItem skip = m_context.newTag(); + if (type.isDynamicallySized()) + { + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(skip); + } + // round off, load from there. + // stack + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; + m_context << eth::Instruction::SUB; + // stack: target+length remainder + m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; + // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) + m_context << u256(1); + m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; + // stack: ... 1 <32 - remainder> + m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: target+length remainder target+length-remainder + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // stack: target+length remainder target+length-remainder + m_context << u256(32) << eth::Instruction::ADD; + // stack: target+length remainder + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; + + if (type.isDynamicallySized()) + m_context << skip.tag(); + // stack + m_context << eth::Instruction::POP; + } + else + // stack: + m_context << eth::Instruction::ADD; } else { - solAssert(type.location() == ReferenceType::Location::Storage, "Memory arrays not yet implemented."); + solAssert(type.location() == ReferenceType::Location::Storage, ""); m_context << eth::Instruction::POP; // remove offset, arrays always start new slot m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes @@ -133,17 +200,28 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound // stack here: memory_end_offset storage_data_offset memory_offset eth::AssemblyItem loopStart = m_context.newTag(); - m_context << loopStart - // load and store - << eth::Instruction::DUP2 << eth::Instruction::SLOAD - << eth::Instruction::DUP2 << eth::Instruction::MSTORE - // increment storage_data_offset by 1 - << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD - // increment memory offset by 32 - << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD - // check for loop condition - << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; + m_context << loopStart; + // load and store + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // increment storage_data_offset by 1 + m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; + // increment memory offset by 32 + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; m_context.appendConditionalJumpTo(loopStart); + // stack here: memory_end_offset storage_data_offset memory_offset + if (_padToWordBoundaries) + { + // memory_end_offset - start is the actual length (we want to compute the ceil of). + // memory_offset - start is its next multiple of 32, but it might be off by 32. + // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 + m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; + m_context << u256(31) << eth::Instruction::AND; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2; + } m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; } } @@ -159,6 +237,290 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } } +void CompilerUtils::encodeToMemory( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _padToWordBoundaries, + bool _copyDynamicDataInPlace +) +{ + // stack: ... + TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; + solAssert(targetTypes.size() == _givenTypes.size(), ""); + for (TypePointer& t: targetTypes) + t = t->mobileType()->externalType(); + + // Stack during operation: + // ... ... + // The values dyn_head_i are added during the first loop and they point to the head part + // of the ith dynamic parameter, which is filled once the dynamic parts are processed. + + // store memory start pointer + m_context << eth::Instruction::DUP1; + + unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); + unsigned stackPos = 0; // advances through the argument values + unsigned dynPointers = 0; // number of dynamic head pointers on the stack + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + // leave end_of_mem as dyn head pointer + m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; + dynPointers++; + } + else + { + copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->getSizeOnStack()); + if (targetType->isValueType()) + convertType(*_givenTypes[i], *targetType, true); + solAssert(!!targetType, "Externalable type expected."); + storeInMemoryDynamic(*targetType, _padToWordBoundaries); + } + stackPos += _givenTypes[i]->getSizeOnStack(); + } + + // now copy the dynamic part + // Stack: ... ... + stackPos = 0; + unsigned thisDynPointer = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + TypePointer targetType = targetTypes[i]; + solAssert(!!targetType, "Externalable type expected."); + if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) + { + solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); + auto const& arrayType = dynamic_cast(*_givenTypes[i]); + // copy tail pointer (=mem_end - mem_start) to memory + m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; + m_context << eth::Instruction::SUB; + m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); + m_context << eth::Instruction::MSTORE; + // now copy the array + copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack()); + // stack: ... + // copy length to memory + m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); + if (arrayType.location() == ReferenceType::Location::CallData) + m_context << eth::Instruction::DUP2; // length is on stack + else if (arrayType.location() == ReferenceType::Location::Storage) + m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; + else + { + solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); + m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; + } + // stack: ... + storeInMemoryDynamic(IntegerType(256), true); + // stack: ... + // copy the new memory pointer + m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; + // stack: ... + // copy data part + storeInMemoryDynamic(arrayType, true); + // stack: ... + + thisDynPointer++; + } + stackPos += _givenTypes[i]->getSizeOnStack(); + } + + // remove unneeded stack elements (and retain memory pointer) + m_context << eth::swapInstruction(argSize + dynPointers + 1); + popStackSlots(argSize + dynPointers + 1); +} + +void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) +{ + // For a type extension, we need to remove all higher-order bits that we might have ignored in + // previous operations. + // @todo: store in the AST whether the operand might have "dirty" higher order bits + + if (_typeOnStack == _targetType && !_cleanupNeeded) + return; + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + + switch (stackTypeCategory) + { + case Type::Category::FixedBytes: + { + FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); + if (targetTypeCategory == Type::Category::Integer) + { + // conversion from bytes to integer. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast(_targetType); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) + convertType(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); + } + else + { + // clear lower-order bytes for conversion to shorter bytes - we always clean + solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + FixedBytesType const& targetType = dynamic_cast(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + { + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)); + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2; + m_context << eth::Instruction::DIV << eth::Instruction::MUL; + } + } + } + } + break; + case Type::Category::Enum: + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + break; + case Type::Category::Integer: + case Type::Category::Contract: + case Type::Category::IntegerConstant: + if (targetTypeCategory == Type::Category::FixedBytes) + { + solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, + "Invalid conversion to FixedBytesType requested."); + // conversion from bytes to string. no need to clean the high bit + // only to shift left because of opposite alignment + FixedBytesType const& targetBytesType = dynamic_cast(_targetType); + if (auto typeOnStack = dynamic_cast(&_typeOnStack)) + if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) + cleanHigherOrderBits(*typeOnStack); + m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; + } + else if (targetTypeCategory == Type::Category::Enum) + // just clean + convertType(_typeOnStack, *_typeOnStack.mobileType(), true); + else + { + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); + IntegerType addressType(0, IntegerType::Modifier::Address); + IntegerType const& targetType = targetTypeCategory == Type::Category::Integer + ? dynamic_cast(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + cleanHigherOrderBits(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer + ? dynamic_cast(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + cleanHigherOrderBits(typeOnStack); + else if (_cleanupNeeded) + cleanHigherOrderBits(targetType); + } + } + break; + case Type::Category::Array: + { + solAssert(targetTypeCategory == stackTypeCategory, ""); + ArrayType const& typeOnStack = dynamic_cast(_typeOnStack); + ArrayType const& targetType = dynamic_cast(_targetType); + switch (targetType.location()) + { + case ReferenceType::Location::Storage: + // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. + solAssert( + targetType.isPointer() && + typeOnStack.location() == ReferenceType::Location::Storage, + "Invalid conversion to storage type." + ); + break; + case ReferenceType::Location::Memory: + { + // Copy the array to a free position in memory, unless it is already in memory. + if (typeOnStack.location() != ReferenceType::Location::Memory) + { + // stack: (variably sized) + unsigned stackSize = typeOnStack.getSizeOnStack(); + fetchFreeMemoryPointer(); + moveIntoStack(stackSize); + // stack: (variably sized) + if (targetType.isDynamicallySized()) + { + bool fromStorage = (typeOnStack.location() == ReferenceType::Location::Storage); + // store length + if (fromStorage) + { + stackSize--; + // remove storage offset, as requested by ArrayUtils::retrieveLength + m_context << eth::Instruction::POP; + } + ArrayUtils(m_context).retrieveLength(typeOnStack); + // Stack: + m_context << eth::dupInstruction(2 + stackSize) << eth::Instruction::MSTORE; + m_context << eth::dupInstruction(1 + stackSize) << u256(0x20); + m_context << eth::Instruction::ADD; + moveIntoStack(stackSize); + if (fromStorage) + { + m_context << u256(0); + stackSize++; + } + } + else + { + m_context << eth::dupInstruction(1 + stackSize); + moveIntoStack(stackSize); + } + // Stack: + // Store data part. + storeInMemoryDynamic(typeOnStack); + // Stack + storeFreeMemoryPointer(); + } + else if (typeOnStack.location() == ReferenceType::Location::CallData) + { + // Stack: + //@todo + solAssert(false, "Not yet implemented."); + } + // nothing to do for memory to memory + break; + } + default: + solAssert(false, "Invalid type conversion requested."); + } + break; + } + case Type::Category::Struct: + { + //@todo we can probably use some of the code for arrays here. + solAssert(targetTypeCategory == stackTypeCategory, ""); + auto& targetType = dynamic_cast(_targetType); + auto& stackType = dynamic_cast(_typeOnStack); + solAssert( + targetType.location() == ReferenceType::Location::Storage && + stackType.location() == ReferenceType::Location::Storage, + "Non-storage structs not yet implemented." + ); + solAssert( + targetType.isPointer(), + "Type conversion to non-pointer struct requested." + ); + break; + } + default: + // All other types should not be convertible to non-equal types. + solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); + break; + } +} + void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); @@ -189,6 +551,13 @@ void CompilerUtils::moveToStackTop(unsigned _stackDepth) m_context << eth::swapInstruction(1 + i); } +void CompilerUtils::moveIntoStack(unsigned _stackDepth) +{ + solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); + for (unsigned i = _stackDepth; i > 0; --i) + m_context << eth::swapInstruction(i); +} + void CompilerUtils::popStackElement(Type const& _type) { popStackSlots(_type.getSizeOnStack()); @@ -238,6 +607,16 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } +void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) +{ + if (_typeOnStack.getNumBits() == 256) + return; + else if (_typeOnStack.isSigned()) + m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; + else + m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; +} + unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 27c46ba11..32dc93a2c 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -81,6 +81,30 @@ public: /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); + /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given + /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. + /// Removes the values from the stack and leaves the updated memory pointer. + /// Stack pre: ... + /// Stack post: + /// Does not touch the memory-free pointer. + /// @param _padToWordBoundaries if false, all values are concatenated without padding. + /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) + /// together with fixed-length data. + /// @note the locations of target reference types are ignored, because it will always be + /// memory. + void encodeToMemory( + TypePointers const& _givenTypes = {}, + TypePointers const& _targetTypes = {}, + bool _padToWordBoundaries = true, + bool _copyDynamicDataInPlace = false + ); + + /// Appends code for an implicit or explicit type conversion. For now this comprises only erasing + /// higher-order bits (@see appendHighBitCleanup) when widening integer. + /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be + /// necessary. + void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); + /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); /// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth @@ -88,6 +112,8 @@ public: void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); /// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack. void moveToStackTop(unsigned _stackDepth); + /// Moves a single stack element past @a _stackDepth other stack elements + void moveIntoStack(unsigned _stackDepth); /// Removes the current value from the top of the stack. void popStackElement(Type const& _type); /// Removes element from the top of the stack _amount times. @@ -110,6 +136,12 @@ public: static const size_t freeMemoryPointer; private: + /// Address of the precompiled identity contract. + static const unsigned identityContractAddress; + + //// Appends code that cleans higher-order bits for integer types. + void cleanHigherOrderBits(IntegerType const& _typeOnStack); + /// Prepares the given type for storing in memory by shifting it if necessary. unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; /// Loads type from memory assuming memory offset is on stack top. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index d9b6da14e..12274c7ab 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -51,7 +51,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.getValue()->accept(*this); - appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); + utils().convertType(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); } @@ -77,10 +77,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // pop offset m_context << eth::Instruction::POP; // move storage offset to memory. - CompilerUtils(m_context).storeInMemory(32); + utils().storeInMemory(32); // move key to memory. - CompilerUtils(m_context).copyToStackTop(paramTypes.size() - i, 1); - CompilerUtils(m_context).storeInMemory(0); + utils().copyToStackTop(paramTypes.size() - i, 1); + utils().storeInMemory(0); m_context << u256(64) << u256(0) << eth::Instruction::SHA3; // push offset m_context << u256(0); @@ -90,7 +90,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // pop offset m_context << eth::Instruction::POP; - CompilerUtils(m_context).copyToStackTop(paramTypes.size() - i + 1, 1); + utils().copyToStackTop(paramTypes.size() - i + 1, 1); ArrayUtils(m_context).accessIndex(*arrayType); returnType = arrayType->getBaseType(); } @@ -105,7 +105,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << eth::swapInstruction(paramTypes.size()); m_context << eth::Instruction::POP; m_context << eth::swapInstruction(paramTypes.size()); - CompilerUtils(m_context).popStackSlots(paramTypes.size() - 1); + utils().popStackSlots(paramTypes.size() - 1); } unsigned retSizeOnStack = 0; solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); @@ -142,109 +142,14 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); } -void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) -{ - // For a type extension, we need to remove all higher-order bits that we might have ignored in - // previous operations. - // @todo: store in the AST whether the operand might have "dirty" higher order bits - - if (_typeOnStack == _targetType && !_cleanupNeeded) - return; - Type::Category stackTypeCategory = _typeOnStack.getCategory(); - Type::Category targetTypeCategory = _targetType.getCategory(); - - switch (stackTypeCategory) - { - case Type::Category::FixedBytes: - { - FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); - if (targetTypeCategory == Type::Category::Integer) - { - // conversion from bytes to integer. no need to clean the high bit - // only to shift right because of opposite alignment - IntegerType const& targetIntegerType = dynamic_cast(_targetType); - m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) - appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); - } - else - { - // clear lower-order bytes for conversion to shorter bytes - we always clean - solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - FixedBytesType const& targetType = dynamic_cast(_targetType); - if (targetType.getNumBytes() < typeOnStack.getNumBytes()) - { - if (targetType.getNumBytes() == 0) - m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; - else - m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) - << eth::Instruction::DUP1 << eth::Instruction::SWAP2 - << eth::Instruction::DIV << eth::Instruction::MUL; - } - } - } - break; - case Type::Category::Enum: - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); - break; - case Type::Category::Integer: - case Type::Category::Contract: - case Type::Category::IntegerConstant: - if (targetTypeCategory == Type::Category::FixedBytes) - { - solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, - "Invalid conversion to FixedBytesType requested."); - // conversion from bytes to string. no need to clean the high bit - // only to shift left because of opposite alignment - FixedBytesType const& targetBytesType = dynamic_cast(_targetType); - if (auto typeOnStack = dynamic_cast(&_typeOnStack)) - if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) - appendHighBitsCleanup(*typeOnStack); - m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; - } - else if (targetTypeCategory == Type::Category::Enum) - // just clean - appendTypeConversion(_typeOnStack, *_typeOnStack.getRealType(), true); - else - { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); - IntegerType addressType(0, IntegerType::Modifier::Address); - IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast(_targetType) : addressType; - if (stackTypeCategory == Type::Category::IntegerConstant) - { - IntegerConstantType const& constType = dynamic_cast(_typeOnStack); - // We know that the stack is clean, we only have to clean for a narrowing conversion - // where cleanup is forced. - if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) - appendHighBitsCleanup(targetType); - } - else - { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast(_typeOnStack) : addressType; - // Widening: clean up according to source type width - // Non-widening and force: clean up according to target type bits - if (targetType.getNumBits() > typeOnStack.getNumBits()) - appendHighBitsCleanup(typeOnStack); - else if (_cleanupNeeded) - appendHighBitsCleanup(targetType); - } - } - break; - default: - // All other types should not be convertible to non-equal types. - solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); - break; - } -} - bool ExpressionCompiler::visit(Assignment const& _assignment) { CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); if (_assignment.getType()->isValueType()) - appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + utils().convertType(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + // We need this conversion mostly in the case of compound assignments. For non-value types + // the conversion is done in LValue::storeValue. _assignment.getLeftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -256,8 +161,8 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) unsigned itemSize = _assignment.getType()->getSizeOnStack(); if (lvalueSize > 0) { - CompilerUtils(m_context).copyToStackTop(lvalueSize + itemSize, itemSize); - CompilerUtils(m_context).copyToStackTop(itemSize + lvalueSize, lvalueSize); + utils().copyToStackTop(lvalueSize + itemSize, itemSize); + utils().copyToStackTop(itemSize + lvalueSize, lvalueSize); // value lvalue_ref value lvalue_ref } m_currentLValue->retrieveValue(_assignment.getLocation(), true); @@ -372,16 +277,16 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) if (swap) { leftExpression.accept(*this); - appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded); rightExpression.accept(*this); - appendTypeConversion(*rightExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded); } else { rightExpression.accept(*this); - appendTypeConversion(*rightExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*rightExpression.getType(), commonType, cleanupNeeded); leftExpression.accept(*this); - appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); + utils().convertType(*leftExpression.getType(), commonType, cleanupNeeded); } if (Token::isCompareOp(c_op)) appendCompareOperatorCode(c_op, commonType); @@ -404,7 +309,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(_functionCall.getNames().empty(), ""); Expression const& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); - appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + utils().convertType(*firstArgument.getType(), *_functionCall.getType()); } else { @@ -442,7 +347,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) for (unsigned i = 0; i < arguments.size(); ++i) { arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i]); + utils().convertType(*arguments[i]->getType(), *function.getParameterTypes()[i]); } _functionCall.getExpression().accept(*this); @@ -456,7 +361,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // @todo for now, the return value of a function is its first return value, so remove // all others for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) - CompilerUtils(m_context).popStackElement(*function.getReturnParameterTypes()[i]); + utils().popStackElement(*function.getReturnParameterTypes()[i]); break; } case Location::External: @@ -481,7 +386,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) *function.getReturnParameterTypes().front()).getContractDefinition(); // copy the contract's code into memory bytes const& bytecode = m_context.getCompiledContract(contract); - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); m_context << u256(bytecode.size()) << eth::Instruction::DUP1; //@todo could be done by actually appending the Assembly, but then we probably need to compile // multiple times. Will revisit once external fuctions are inlined. @@ -489,10 +394,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY; m_context << eth::Instruction::ADD; - encodeToMemory(argumentTypes, function.getParameterTypes()); + utils().encodeToMemory(argumentTypes, function.getParameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); if (function.valueSet()) m_context << eth::dupInstruction(3); else @@ -508,7 +413,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.getExpression().accept(*this); arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), IntegerType(256), true); + utils().convertType(*arguments.front()->getType(), IntegerType(256), true); // Note that function is not the original function, but the ".gas" function. // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); @@ -531,8 +436,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.getExpression().accept(*this); m_context << u256(0); // do not send gas (there still is the stipend) arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), - *function.getParameterTypes().front(), true); + utils().convertType( + *arguments.front()->getType(), + *function.getParameterTypes().front(), true + ); appendExternalFunctionCall( FunctionType( TypePointers{}, @@ -549,7 +456,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::Suicide: arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); + utils().convertType(*arguments.front()->getType(), *function.getParameterTypes().front(), true); m_context << eth::Instruction::SUICIDE; break; case Location::SHA3: @@ -560,9 +467,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arg->accept(*this); argumentTypes.push_back(arg->getType()); } - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::Instruction::SHA3; break; } @@ -576,16 +483,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) for (unsigned arg = logNumber; arg > 0; --arg) { arguments[arg]->accept(*this); - appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); + utils().convertType(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); } arguments.front()->accept(*this); - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory( + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory( {arguments.front()->getType()}, {function.getParameterTypes().front()}, false, true); - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::logInstruction(logNumber); break; } @@ -600,7 +507,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { ++numIndexed; arguments[arg - 1]->accept(*this); - appendTypeConversion( + utils().convertType( *arguments[arg - 1]->getType(), *function.getParameterTypes()[arg - 1], true @@ -623,17 +530,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) nonIndexedArgTypes.push_back(arguments[arg]->getType()); nonIndexedParamTypes.push_back(function.getParameterTypes()[arg]); } - CompilerUtils(m_context).fetchFreeMemoryPointer(); - encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); + utils().fetchFreeMemoryPointer(); + utils().encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes); // need: topic1 ... topicn memsize memstart - CompilerUtils(m_context).toSizeAfterFreeMemoryPointer(); + utils().toSizeAfterFreeMemoryPointer(); m_context << eth::logInstruction(numIndexed); break; } case Location::BlockHash: { arguments[0]->accept(*this); - appendTypeConversion(*arguments[0]->getType(), *function.getParameterTypes()[0], true); + utils().convertType(*arguments[0]->getType(), *function.getParameterTypes()[0], true); m_context << eth::Instruction::BLOCKHASH; break; } @@ -694,7 +601,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType(type, IntegerType(0, IntegerType::Modifier::Address), true); m_context << identifier; } else @@ -707,13 +614,19 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) case Type::Category::Integer: if (member == "balance") { - appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType( + *_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::Address), + true + ); m_context << eth::Instruction::BALANCE; } else if ((set{"send", "call", "callcode"}).count(member)) - appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::Address), true); + utils().convertType( + *_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::Address), + true + ); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); break; @@ -771,7 +684,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) TypeType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); solAssert( !type.getMembers().membersByName(_memberAccess.getMemberName()).empty(), - "Invalid member access to " + type.toString() + "Invalid member access to " + type.toString(false) ); if (dynamic_cast(type.getActualType().get())) @@ -790,7 +703,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) auto const& type = dynamic_cast(*_memberAccess.getExpression().getType()); if (!type.isDynamicallySized()) { - CompilerUtils(m_context).popStackElement(type); + utils().popStackElement(type); m_context << type.getLength(); } else @@ -831,7 +744,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); m_context << eth::Instruction::SWAP1; solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - appendTypeMoveToMemory(IntegerType(256)); + utils().storeInMemoryDynamic(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; m_context << u256(0); setLValueToStorageItem(_indexAccess); @@ -1052,16 +965,6 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) } } -void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) -{ - if (_typeOnStack.getNumBits() == 256) - return; - else if (_typeOnStack.isSigned()) - m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND; - else - m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; -} - void ExpressionCompiler::appendExternalFunctionCall( FunctionType const& _functionType, vector> const& _arguments @@ -1101,14 +1004,14 @@ void ExpressionCompiler::appendExternalFunctionCall( bool manualFunctionId = (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) && !_arguments.empty() && - _arguments.front()->getType()->getRealType()->getCalldataEncodedSize(false) == + _arguments.front()->getType()->mobileType()->getCalldataEncodedSize(false) == CompilerUtils::dataStartOffset; if (manualFunctionId) { // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as // function identifier. _arguments.front()->accept(*this); - appendTypeConversion( + utils().convertType( *_arguments.front()->getType(), IntegerType(8 * CompilerUtils::dataStartOffset), true @@ -1125,16 +1028,16 @@ void ExpressionCompiler::appendExternalFunctionCall( } // Copy function identifier to memory. - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall() || manualFunctionId) { m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::getSizeOnStack(argumentTypes)); - appendTypeMoveToMemory(IntegerType(8 * CompilerUtils::dataStartOffset), false); + utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false); } // If the function takes arbitrary parameters, copy dynamic length data in place. // Move argumenst to memory, will not update the free memory pointer (but will update the memory // pointer on the stack). - encodeToMemory( + utils().encodeToMemory( argumentTypes, _functionType.getParameterTypes(), _functionType.padArguments(), @@ -1152,7 +1055,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // Output data will replace input data. // put on stack: m_context << u256(retSize); - CompilerUtils(m_context).fetchFreeMemoryPointer(); + utils().fetchFreeMemoryPointer(); m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB; m_context << eth::Instruction::DUP2; @@ -1193,7 +1096,7 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context.appendConditionalJumpTo(m_context.errorTag()); } - CompilerUtils(m_context).popStackSlots(remainsSize); + utils().popStackSlots(remainsSize); if (returnSuccessCondition) { @@ -1202,118 +1105,16 @@ void ExpressionCompiler::appendExternalFunctionCall( else if (funKind == FunctionKind::RIPEMD160) { // fix: built-in contract returns right-aligned data - CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(160), false, true, false); - appendTypeConversion(IntegerType(160), FixedBytesType(20)); + utils().fetchFreeMemoryPointer(); + utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); + utils().convertType(IntegerType(160), FixedBytesType(20)); } else if (firstReturnType) { //@todo manually update free memory pointer if we accept returning memory-stored objects - CompilerUtils(m_context).fetchFreeMemoryPointer(); - CompilerUtils(m_context).loadFromMemoryDynamic(*firstReturnType, false, true, false); - } -} - -void ExpressionCompiler::encodeToMemory( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _padToWordBoundaries, - bool _copyDynamicDataInPlace -) -{ - // stack: ... - TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; - solAssert(targetTypes.size() == _givenTypes.size(), ""); - for (TypePointer& t: targetTypes) - t = t->getRealType()->externalType(); - - // Stack during operation: - // ... ... - // The values dyn_head_i are added during the first loop and they point to the head part - // of the ith dynamic parameter, which is filled once the dynamic parts are processed. - - // store memory start pointer - m_context << eth::Instruction::DUP1; - - unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes); - unsigned stackPos = 0; // advances through the argument values - unsigned dynPointers = 0; // number of dynamic head pointers on the stack - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - // leave end_of_mem as dyn head pointer - m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD; - dynPointers++; - } - else - { - CompilerUtils(m_context).copyToStackTop( - argSize - stackPos + dynPointers + 2, - _givenTypes[i]->getSizeOnStack() - ); - if (targetType->isValueType()) - appendTypeConversion(*_givenTypes[i], *targetType, true); - solAssert(!!targetType, "Externalable type expected."); - appendTypeMoveToMemory(*targetType, _padToWordBoundaries); - } - stackPos += _givenTypes[i]->getSizeOnStack(); + utils().fetchFreeMemoryPointer(); + utils().loadFromMemoryDynamic(*firstReturnType, false, true, false); } - - // now copy the dynamic part - // Stack: ... ... - stackPos = 0; - unsigned thisDynPointer = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type."); - auto const& arrayType = dynamic_cast(*_givenTypes[i]); - // copy tail pointer (=mem_end - mem_start) to memory - m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2; - m_context << eth::Instruction::SUB; - m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer); - m_context << eth::Instruction::MSTORE; - // now copy the array - CompilerUtils(m_context).copyToStackTop( - argSize - stackPos + dynPointers + 2, - arrayType.getSizeOnStack() - ); - // copy length to memory - m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack()); - if (arrayType.location() == ReferenceType::Location::CallData) - m_context << eth::Instruction::DUP2; // length is on stack - else if (arrayType.location() == ReferenceType::Location::Storage) - m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; - else - { - solAssert(arrayType.location() == ReferenceType::Location::Memory, ""); - m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; - } - appendTypeMoveToMemory(IntegerType(256), true); - // copy the new memory pointer - m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP; - // copy data part - appendTypeMoveToMemory(arrayType, true); - - thisDynPointer++; - } - stackPos += _givenTypes[i]->getSizeOnStack(); - } - - // remove unneeded stack elements (and retain memory pointer) - m_context << eth::swapInstruction(argSize + dynPointers + 1); - CompilerUtils(m_context).popStackSlots(argSize + dynPointers + 1); -} - -void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries) -{ - CompilerUtils(m_context).storeInMemoryDynamic(_type, _padToWordBoundaries); } void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) @@ -1321,11 +1122,11 @@ void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, _expression.accept(*this); if (_expectedType.isValueType()) { - appendTypeConversion(*_expression.getType(), _expectedType, true); - appendTypeMoveToMemory(_expectedType); + utils().convertType(*_expression.getType(), _expectedType, true); + utils().storeInMemoryDynamic(_expectedType); } else - appendTypeMoveToMemory(*_expression.getType()->getRealType()); + utils().storeInMemoryDynamic(*_expression.getType()->mobileType()); } void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) @@ -1345,5 +1146,10 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue(_expression, *_expression.getType()); } +CompilerUtils ExpressionCompiler::utils() +{ + return CompilerUtils(m_context); +} + } } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 90994dfdb..642560c64 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include namespace dev { namespace eth @@ -39,6 +39,7 @@ namespace solidity { // forward declarations class CompilerContext; +class CompilerUtils; class Type; class IntegerType; class ArrayType; @@ -66,12 +67,6 @@ public: /// Appends code for a State Variable accessor function void appendStateVariableAccessor(VariableDeclaration const& _varDecl); - /// Appends an implicit or explicit type conversion. For now this comprises only erasing - /// higher-order bits (@see appendHighBitCleanup) when widening integer. - /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be - /// necessary. - void appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); - private: virtual bool visit(Assignment const& _assignment) override; virtual bool visit(UnaryOperation const& _unaryOperation) override; @@ -94,33 +89,11 @@ private: void appendShiftOperatorCode(Token::Value _operator); /// @} - //// Appends code that cleans higher-order bits for integer types. - void appendHighBitsCleanup(IntegerType const& _typeOnStack); - /// Appends code to call a function of the given type with the given arguments. void appendExternalFunctionCall( FunctionType const& _functionType, std::vector> const& _arguments ); - /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given - /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. - /// Removes the values from the stack and leaves the updated memory pointer. - /// Stack pre: ... - /// Stack post: - /// Does not touch the memory-free pointer. - /// @param _padToWordBoundaries if false, all values are concatenated without padding. - /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) - /// together with fixed-length data. - void encodeToMemory( - TypePointers const& _givenTypes = {}, - TypePointers const& _targetTypes = {}, - bool _padToWordBoundaries = true, - bool _copyDynamicDataInPlace = false - ); - /// Appends code that moves a stack element of the given type to memory. The memory offset is - /// expected below the stack element and is updated by this call. - /// For arrays, this only copies the data part. - void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true); /// Appends code that evaluates a single expression and moves the result to memory. The memory offset is /// expected to be on the stack and is updated by this call. void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression); @@ -137,9 +110,13 @@ private: template void setLValue(Expression const& _expression, _Arguments const&... _arguments); + /// @returns the CompilerUtils object containing the current context. + CompilerUtils utils(); + bool m_optimize; CompilerContext& m_context; std::unique_ptr m_currentLValue; + }; template diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index a9d54cdc6..23b7ff82f 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -96,7 +96,7 @@ unique_ptr InterfaceHandler::getABIInterface(ContractDefinition const& _ { Json::Value input; input["name"] = p->getName(); - input["type"] = p->getType()->toString(); + input["type"] = p->getType()->toString(true); input["indexed"] = p->isIndexed(); params.append(input); } diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index b684e55a1..1acf0a3e8 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -198,7 +198,11 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack layout: source_ref source_offset target_ref target_offset // note that we have structs, so offsets should be zero and are ignored auto const& structType = dynamic_cast(m_dataType); - solAssert(structType == _sourceType, "Struct assignment with conversion."); + solAssert( + structType.structDefinition() == + dynamic_cast(_sourceType).structDefinition(), + "Struct assignment with conversion." + ); for (auto const& member: structType.getMembers()) { // assign each member that is not a mapping diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 22232014f..e60797967 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -431,7 +431,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) // They default to memory for function parameters and storage for local variables. if (auto ref = dynamic_cast(type.get())) { - if (_variable.isExternalFunctionParameter()) + if (_variable.isExternalCallableParameter()) { // force location of external function parameters (not return) to calldata if (loc != Location::Default) @@ -439,9 +439,9 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) "Location has to be calldata for external functions " "(remove the \"memory\" or \"storage\" keyword)." )); - type = ref->copyForLocation(ReferenceType::Location::CallData); + type = ref->copyForLocation(ReferenceType::Location::CallData, true); } - else if (_variable.isFunctionParameter() && _variable.getScope()->isPublic()) + else if (_variable.isCallableParameter() && _variable.getScope()->isPublic()) { // force locations of public or external function (return) parameters to memory if (loc == VariableDeclaration::Location::Storage) @@ -449,16 +449,18 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) "Location has to be memory for publicly visible functions " "(remove the \"storage\" keyword)." )); - type = ref->copyForLocation(ReferenceType::Location::Memory); + type = ref->copyForLocation(ReferenceType::Location::Memory, true); } else { if (loc == Location::Default) - loc = _variable.isFunctionParameter() ? Location::Memory : Location::Storage; + loc = _variable.isCallableParameter() ? Location::Memory : Location::Storage; + bool isPointer = !_variable.isStateVariable(); type = ref->copyForLocation( loc == Location::Memory ? ReferenceType::Location::Memory : - ReferenceType::Location::Storage + ReferenceType::Location::Storage, + isPointer ); } } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 6f16f5193..bedd2e7b0 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -179,6 +179,8 @@ TypePointer Type::fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType TypePointer valueType = _valueType.toType(); if (!valueType) BOOST_THROW_EXCEPTION(_valueType.createTypeError("Invalid type name.")); + // Convert value type to storage reference. + valueType = ReferenceType::copyForLocationIfReference(ReferenceType::Location::Storage, valueType); return make_shared(keyType, valueType); } @@ -288,7 +290,7 @@ bool IntegerType::operator==(Type const& _other) const return other.m_bits == m_bits && other.m_modifier == m_modifier; } -string IntegerType::toString() const +string IntegerType::toString(bool) const { if (isAddress()) return "address"; @@ -488,7 +490,7 @@ bool IntegerConstantType::operator==(Type const& _other) const return m_value == dynamic_cast(_other).m_value; } -string IntegerConstantType::toString() const +string IntegerConstantType::toString(bool) const { return "int_const " + m_value.str(); } @@ -508,10 +510,10 @@ u256 IntegerConstantType::literalValue(Literal const*) const return value; } -TypePointer IntegerConstantType::getRealType() const +TypePointer IntegerConstantType::mobileType() const { auto intType = getIntegerType(); - solAssert(!!intType, "getRealType called with invalid integer constant " + toString()); + solAssert(!!intType, "mobileType called with invalid integer constant " + toString(false)); return intType; } @@ -668,21 +670,67 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const return _operator == Token::Delete ? make_shared() : TypePointer(); } +TypePointer ReferenceType::copyForLocationIfReference(Location _location, TypePointer const& _type) +{ + if (auto type = dynamic_cast(_type.get())) + return type->copyForLocation(_location, false); + return _type; +} + +TypePointer ReferenceType::copyForLocationIfReference(TypePointer const& _type) const +{ + return copyForLocationIfReference(m_location, _type); +} + +string ReferenceType::stringForReferencePart() const +{ + switch (m_location) + { + case Location::Storage: + return string("storage ") + (m_isPointer ? "pointer" : "ref"); + case Location::CallData: + return "calldata"; + case Location::Memory: + return "memory"; + } + solAssert(false, ""); + return ""; +} + bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { if (_convertTo.getCategory() != getCategory()) return false; auto& convertTo = dynamic_cast(_convertTo); - // let us not allow assignment to memory arrays for now - if (convertTo.location() != Location::Storage) - return false; if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) return false; - if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType())) + // memory/calldata to storage can be converted, but only to a direct storage reference + if (convertTo.location() == Location::Storage && location() != Location::Storage && convertTo.isPointer()) return false; - if (convertTo.isDynamicallySized()) + if (convertTo.location() == Location::CallData && location() != convertTo.location()) + return false; + if (convertTo.location() == Location::Storage && !convertTo.isPointer()) + { + // Less restrictive conversion, since we need to copy anyway. + if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType())) + return false; + if (convertTo.isDynamicallySized()) + return true; + return !isDynamicallySized() && convertTo.getLength() >= getLength(); + } + else + { + // Require that the base type is the same, not only convertible. + // This disallows assignment of nested arrays from storage to memory for now. + if (*getBaseType() != *convertTo.getBaseType()) + return false; + if (isDynamicallySized() != convertTo.isDynamicallySized()) + return false; + // We also require that the size is the same. + if (!isDynamicallySized() && getLength() != convertTo.getLength()) + return false; return true; - return !isDynamicallySized() && convertTo.getLength() >= getLength(); + } } TypePointer ArrayType::unaryOperatorResult(Token::Value _operator) const @@ -698,7 +746,7 @@ bool ArrayType::operator==(Type const& _other) const return false; ArrayType const& other = dynamic_cast(_other); if ( - other.m_location != m_location || + !ReferenceType::operator==(other) || other.isByteArray() != isByteArray() || other.isString() != isString() || other.isDynamicallySized() != isDynamicallySized() @@ -751,16 +799,23 @@ unsigned ArrayType::getSizeOnStack() const return 1; } -string ArrayType::toString() const +string ArrayType::toString(bool _short) const { + string ret; if (isString()) - return "string"; + ret = "string"; else if (isByteArray()) - return "bytes"; - string ret = getBaseType()->toString() + "["; - if (!isDynamicallySized()) - ret += getLength().str(); - return ret + "]"; + ret = "bytes"; + else + { + ret = getBaseType()->toString(_short) + "["; + if (!isDynamicallySized()) + ret += getLength().str(); + ret += "]"; + } + if (!_short) + ret += " " + stringForReferencePart(); + return ret; } TypePointer ArrayType::externalType() const @@ -778,14 +833,12 @@ TypePointer ArrayType::externalType() const return std::make_shared(Location::CallData, m_baseType->externalType(), m_length); } -TypePointer ArrayType::copyForLocation(ReferenceType::Location _location) const +TypePointer ArrayType::copyForLocation(ReferenceType::Location _location, bool _isPointer) const { auto copy = make_shared(_location); + copy->m_isPointer = _isPointer; copy->m_arrayKind = m_arrayKind; - if (auto ref = dynamic_cast(m_baseType.get())) - copy->m_baseType = ref->copyForLocation(_location); - else - copy->m_baseType = m_baseType; + copy->m_baseType = copy->copyForLocationIfReference(m_baseType); copy->m_hasDynamicLength = m_hasDynamicLength; copy->m_length = m_length; return copy; @@ -801,7 +854,7 @@ bool ContractType::operator==(Type const& _other) const return other.m_contract == m_contract && other.m_super == m_super; } -string ContractType::toString() const +string ContractType::toString(bool) const { return "contract " + string(m_super ? "super " : "") + m_contract.getName(); } @@ -890,6 +943,19 @@ vector> ContractType::getState return variablesAndOffsets; } +bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const +{ + if (_convertTo.getCategory() != getCategory()) + return false; + auto& convertTo = dynamic_cast(_convertTo); + // memory/calldata to storage can be converted, but only to a direct storage reference + if (convertTo.location() == Location::Storage && location() != Location::Storage && convertTo.isPointer()) + return false; + if (convertTo.location() == Location::CallData && location() != convertTo.location()) + return false; + return this->m_struct == convertTo.m_struct; +} + TypePointer StructType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared() : TypePointer(); @@ -900,7 +966,7 @@ bool StructType::operator==(Type const& _other) const if (_other.getCategory() != getCategory()) return false; StructType const& other = dynamic_cast(_other); - return other.m_struct == m_struct; + return ReferenceType::operator==(other) && other.m_struct == m_struct; } u256 StructType::getStorageSize() const @@ -916,9 +982,12 @@ bool StructType::canLiveOutsideStorage() const return true; } -string StructType::toString() const +string StructType::toString(bool _short) const { - return string("struct ") + m_struct.getName(); + string ret = "struct " + m_struct.getName(); + if (!_short) + ret += " " + stringForReferencePart(); + return ret; } MemberList const& StructType::getMembers() const @@ -928,16 +997,23 @@ MemberList const& StructType::getMembers() const { MemberList::MemberMap members; for (ASTPointer const& variable: m_struct.getMembers()) - members.push_back(MemberList::Member(variable->getName(), variable->getType(), variable.get())); + { + members.push_back(MemberList::Member( + variable->getName(), + copyForLocationIfReference(variable->getType()), + variable.get()) + ); + } m_members.reset(new MemberList(members)); } return *m_members; } -TypePointer StructType::copyForLocation(ReferenceType::Location _location) const +TypePointer StructType::copyForLocation(ReferenceType::Location _location, bool _isPointer) const { auto copy = make_shared(m_struct); copy->m_location = _location; + copy->m_isPointer = _isPointer; return copy; } @@ -970,7 +1046,7 @@ unsigned EnumType::getStorageBytes() const return dev::bytesRequired(elements - 1); } -string EnumType::toString() const +string EnumType::toString(bool) const { return string("enum ") + m_enum.getName(); } @@ -1114,14 +1190,14 @@ bool FunctionType::operator==(Type const& _other) const return true; } -string FunctionType::toString() const +string FunctionType::toString(bool _short) const { string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) - name += (*it)->toString() + (it + 1 == m_parameterTypes.end() ? "" : ","); + name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); name += ") returns ("; for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) - name += (*it)->toString() + (it + 1 == m_returnParameterTypes.end() ? "" : ","); + name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); return name + ")"; } @@ -1289,7 +1365,7 @@ string FunctionType::externalSignature(std::string const& _name) const for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it) { solAssert(!!(*it), "Parameter should have external type"); - ret += (*it)->toString() + (it + 1 == externalParameterTypes.cend() ? "" : ","); + ret += (*it)->toString(true) + (it + 1 == externalParameterTypes.cend() ? "" : ","); } return ret + ")"; @@ -1327,7 +1403,7 @@ vector const FunctionType::getParameterTypeNames() const { vector names; for (TypePointer const& t: m_parameterTypes) - names.push_back(t->toString()); + names.push_back(t->toString(true)); return names; } @@ -1336,7 +1412,7 @@ vector const FunctionType::getReturnParameterTypeNames() const { vector names; for (TypePointer const& t: m_returnParameterTypes) - names.push_back(t->toString()); + names.push_back(t->toString(true)); return names; } @@ -1358,9 +1434,9 @@ bool MappingType::operator==(Type const& _other) const return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType; } -string MappingType::toString() const +string MappingType::toString(bool _short) const { - return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")"; + return "mapping(" + getKeyType()->toString(_short) + " => " + getValueType()->toString(_short) + ")"; } u256 VoidType::getStorageSize() const @@ -1445,11 +1521,11 @@ bool ModifierType::operator==(Type const& _other) const return true; } -string ModifierType::toString() const +string ModifierType::toString(bool _short) const { string name = "modifier ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) - name += (*it)->toString() + (it + 1 == m_parameterTypes.end() ? "" : ","); + name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); return name + ")"; } @@ -1496,7 +1572,7 @@ bool MagicType::operator==(Type const& _other) const return other.m_kind == m_kind; } -string MagicType::toString() const +string MagicType::toString(bool) const { switch (m_kind) { diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 17d30ea6c..0f86ac95f 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -198,19 +198,24 @@ public: /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } virtual unsigned getSizeOnStack() const { return 1; } - /// @returns the real type of some types, like e.g: IntegerConstant - virtual TypePointer getRealType() const { return shared_from_this(); } + /// @returns the mobile (in contrast to static) type corresponding to the given type. + /// This returns the corresponding integer type for IntegerConstantTypes and the pointer type + /// for storage reference types. + virtual TypePointer mobileType() const { return shared_from_this(); } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& getMembers() const { return EmptyMemberList; } /// Convenience method, returns the type of the given named member or an empty pointer if no such member exists. TypePointer getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } - virtual std::string toString() const = 0; + virtual std::string toString(bool _short) const = 0; + std::string toString() const { return toString(false); } virtual u256 literalValue(Literal const*) const { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested " - "for type without literals.")); + BOOST_THROW_EXCEPTION( + InternalCompilerError() << + errinfo_comment("Literal value requested for type without literals.") + ); } /// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address. @@ -249,7 +254,7 @@ public: virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual TypePointer externalType() const override { return shared_from_this(); } @@ -287,9 +292,9 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 1; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual u256 literalValue(Literal const* _literal) const override; - virtual TypePointer getRealType() const override; + virtual TypePointer mobileType() const override; /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. std::shared_ptr getIntegerType() const; @@ -322,7 +327,7 @@ public: virtual unsigned getStorageBytes() const override { return m_bytes; } virtual bool isValueType() const override { return true; } - virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); } + virtual std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } virtual u256 literalValue(Literal const* _literal) const override; virtual TypePointer externalType() const override { return shared_from_this(); } @@ -348,27 +353,51 @@ public: virtual unsigned getStorageBytes() const override { return 1; } virtual bool isValueType() const override { return true; } - virtual std::string toString() const override { return "bool"; } + virtual std::string toString(bool) const override { return "bool"; } virtual u256 literalValue(Literal const* _literal) const override; virtual TypePointer externalType() const override { return shared_from_this(); } }; /** - * Trait used by types which are not value types and can be stored either in storage, memory + * Base class used by types which are not value types and can be stored either in storage, memory * or calldata. This is currently used by arrays and structs. */ -class ReferenceType +class ReferenceType: public Type { public: enum class Location { Storage, CallData, Memory }; explicit ReferenceType(Location _location): m_location(_location) {} Location location() const { return m_location; } - /// @returns a copy of this type with location (recursively) changed to @a _location. - virtual TypePointer copyForLocation(Location _location) const = 0; + /// @returns a copy of this type with location (recursively) changed to @a _location, + /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. + virtual TypePointer copyForLocation(Location _location, bool _isPointer) const = 0; + + virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } + + /// Storage references can be pointers or bound references. In general, local variables are of + /// pointer type, state variables are bound references. Assignments to pointers or deleting + /// them will not modify storage (that will only change the pointer). Assignment from + /// non-storage objects to a variable of storage pointer type is not possible. + bool isPointer() const { return m_isPointer; } + + bool operator==(ReferenceType const& _other) const + { + return location() == _other.location() && isPointer() == _other.isPointer(); + } + + /// @returns a copy of @a _type having the same location as this (and is not a pointer type) + /// if _type is a reference type and an unmodified copy of _type otherwise. + /// This function is mostly useful to modify inner types appropriately. + static TypePointer copyForLocationIfReference(Location _location, TypePointer const& _type); protected: + TypePointer copyForLocationIfReference(TypePointer const& _type) const; + /// @returns a human-readable description of the reference part of the type. + std::string stringForReferencePart() const; + Location m_location = Location::Storage; + bool m_isPointer = true; }; /** @@ -378,10 +407,9 @@ protected: * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and * thus start on their own slot. */ -class ArrayType: public Type, public ReferenceType +class ArrayType: public ReferenceType { public: - virtual Category getCategory() const override { return Category::Array; } /// Constructor for a byte array ("bytes") and string. @@ -389,16 +417,18 @@ public: ReferenceType(_location), m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), m_baseType(std::make_shared(1)) - {} + { + } /// Constructor for a dynamically sized array type ("type[]") - ArrayType(Location _location, const TypePointer &_baseType): + ArrayType(Location _location, TypePointer const& _baseType): ReferenceType(_location), - m_baseType(_baseType) - {} + m_baseType(copyForLocationIfReference(_baseType)) + { + } /// Constructor for a fixed-size array type ("type[20]") - ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): + ArrayType(Location _location, TypePointer const& _baseType, u256 const& _length): ReferenceType(_location), - m_baseType(_baseType), + m_baseType(copyForLocationIfReference(_baseType)), m_hasDynamicLength(false), m_length(_length) {} @@ -410,7 +440,7 @@ public: virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } virtual u256 getStorageSize() const override; virtual unsigned getSizeOnStack() const override; - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual MemberList const& getMembers() const override { return isString() ? EmptyMemberList : s_arrayTypeMemberList; @@ -424,7 +454,7 @@ public: TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} u256 const& getLength() const { return m_length; } - TypePointer copyForLocation(Location _location) const override; + TypePointer copyForLocation(Location _location, bool _isPointer) const override; private: /// String is interpreted as a subtype of Bytes. @@ -460,7 +490,7 @@ public: virtual unsigned getStorageBytes() const override { return 20; } virtual bool canLiveOutsideStorage() const override { return true; } virtual bool isValueType() const override { return true; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual MemberList const& getMembers() const override; virtual TypePointer externalType() const override @@ -497,26 +527,29 @@ private: /** * The type of a struct instance, there is one distinct type per struct definition. */ -class StructType: public Type, public ReferenceType +class StructType: public ReferenceType { public: virtual Category getCategory() const override { return Category::Struct; } explicit StructType(StructDefinition const& _struct): //@todo only storage until we have non-storage structs ReferenceType(Location::Storage), m_struct(_struct) {} + virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; virtual unsigned getSizeOnStack() const override { return 2; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual MemberList const& getMembers() const override; - TypePointer copyForLocation(Location _location) const override; + TypePointer copyForLocation(Location _location, bool _isPointer) const override; std::pair const& getStorageOffsetsOfMember(std::string const& _name) const; + StructDefinition const& structDefinition() const { return m_struct; } + private: StructDefinition const& m_struct; /// List of member types, will be lazy-initialized because of recursive references. @@ -540,7 +573,7 @@ public: virtual unsigned getSizeOnStack() const override { return 1; } virtual unsigned getStorageBytes() const override; virtual bool canLiveOutsideStorage() const override { return true; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual bool isValueType() const override { return true; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -649,7 +682,7 @@ public: std::vector const getReturnParameterTypeNames() const; virtual bool operator==(Type const& _other) const override; - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } @@ -721,7 +754,7 @@ public: m_keyType(_keyType), m_valueType(_valueType) {} virtual bool operator==(Type const& _other) const override; - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; virtual unsigned getSizeOnStack() const override { return 2; } virtual bool canLiveOutsideStorage() const override { return false; } @@ -744,7 +777,7 @@ public: VoidType() {} virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } - virtual std::string toString() const override { return "void"; } + virtual std::string toString(bool) const override { return "void"; } virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } @@ -769,7 +802,7 @@ public: virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } - virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } + virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } virtual MemberList const& getMembers() const override; private: @@ -796,7 +829,7 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual bool operator==(Type const& _other) const override; - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; private: TypePointers m_parameterTypes; @@ -826,7 +859,7 @@ public: virtual unsigned getSizeOnStack() const override { return 0; } virtual MemberList const& getMembers() const override { return m_members; } - virtual std::string toString() const override; + virtual std::string toString(bool _short) const override; private: Kind m_kind; diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 4f5eff8b6..befd1d056 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -218,6 +218,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Not enough gas")); case TransactionException::BlockGasLimitReached: BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Block gas limit reached")); + case TransactionException::BadJumpDestination: + BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Solidity exception (bad jump)")); case TransactionException::OutOfStack: BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Out of stack")); case TransactionException::StackUnderflow: @@ -225,7 +227,6 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c //these should not happen in mix case TransactionException::Unknown: case TransactionException::BadInstruction: - case TransactionException::BadJumpDestination: case TransactionException::InvalidSignature: case TransactionException::InvalidNonce: case TransactionException::BadRLP: diff --git a/test/libevm/vm.cpp b/test/libevm/vm.cpp index 5bbab2e1c..ea9339f05 100644 --- a/test/libevm/vm.cpp +++ b/test/libevm/vm.cpp @@ -323,6 +323,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin) fev.thisTxCode = get<3>(fev.addresses.at(fev.myAddress)); fev.code = fev.thisTxCode; } + fev.codeHash = sha3(fev.code); bytes output; bool vmExceptionOccured = false; diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index fd4bbcf6d..8d316a977 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(location_test) AssemblyItems items = compileContract(sourceCode); vector locations = vector(17, SourceLocation(2, 75, n)) + - vector(14, SourceLocation(20, 72, n)) + + vector(26, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(4, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index f12abd48e..f4d875e77 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2420,7 +2420,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data_from_storage) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3) + asBytes("ABC")); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3, string("ABC"))); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -4232,6 +4232,31 @@ BOOST_AUTO_TEST_CASE(reusing_memory) BOOST_REQUIRE(callContractFunction("f(uint256)", 0x34) == encodeArgs(dev::sha3(dev::toBigEndian(u256(0x34))))); } +BOOST_AUTO_TEST_CASE(return_string) +{ + char const* sourceCode = R"( + contract Main { + string public s; + function set(string _s) external { + s = _s; + } + function get1() returns (string r) { + return s; + } +// function get2() returns (string r) { +// r = s; +// } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + string s("Julia"); + bytes args = encodeArgs(u256(0x20), u256(s.length()), s); + BOOST_REQUIRE(callContractFunction("set(string)", asString(args)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get1()") == args); +// BOOST_CHECK(callContractFunction("get2()") == args); +// BOOST_CHECK(callContractFunction("s()") == args); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 3948a4a23..fced12848 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1889,6 +1889,93 @@ BOOST_AUTO_TEST_CASE(storage_location_local_variables) BOOST_CHECK_NO_THROW(parseTextAndResolveNames(sourceCode)); } +BOOST_AUTO_TEST_CASE(assignment_mem_to_local_storage_variable) +{ + char const* sourceCode = R"( + contract C { + uint[] data; + function f(uint[] x) { + var dataRef = data; + dataRef = x; + } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + +BOOST_AUTO_TEST_CASE(storage_assign_to_different_local_variable) +{ + char const* sourceCode = R"( + contract C { + uint[] data; + uint8[] otherData; + function f() { + uint8[] storage x = otherData; + uint[] storage y = data; + y = x; + // note that data = otherData works + } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + +BOOST_AUTO_TEST_CASE(assignment_mem_storage_variable_directly) +{ + char const* sourceCode = R"( + contract C { + uint[] data; + function f(uint[] x) { + data = x; + } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(sourceCode)); +} + +BOOST_AUTO_TEST_CASE(function_argument_mem_to_storage) +{ + char const* sourceCode = R"( + contract C { + function f(uint[] storage x) private { + } + function g(uint[] x) { + f(x); + } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + +BOOST_AUTO_TEST_CASE(function_argument_storage_to_mem) +{ + char const* sourceCode = R"( + contract C { + function f(uint[] storage x) private { + g(x); + } + function g(uint[] x) { + } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(sourceCode)); +} + +BOOST_AUTO_TEST_CASE(mem_array_assignment_changes_base_type) +{ + // Such an assignment is possible in storage, but not in memory + // (because it would incur an otherwise unnecessary copy). + // This requirement might be lifted, though. + char const* sourceCode = R"( + contract C { + function f(uint8[] memory x) private { + uint[] memory y = x; + } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/solidityExecutionFramework.h b/test/libsolidity/solidityExecutionFramework.h index 44590b1c8..4ba229815 100644 --- a/test/libsolidity/solidityExecutionFramework.h +++ b/test/libsolidity/solidityExecutionFramework.h @@ -174,11 +174,11 @@ protected: BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); BOOST_REQUIRE(!executive.call(m_contractAddress, m_sender, _value, m_gasPrice, &_data, m_gas)); } - BOOST_REQUIRE(executive.go()); + BOOST_REQUIRE(executive.go(/* DEBUG eth::Executive::simpleTrace() */)); m_state.noteSending(m_sender); executive.finalize(); - m_gasUsed = executive.gasUsed(); - m_output = std::move(res.output); // FIXME: Looks like Framework needs ExecutiveResult embedded + m_gasUsed = res.gasUsed; + m_output = std::move(res.output); m_logs = executive.logs(); }