diff options
author | chriseth <chris@ethereum.org> | 2016-11-18 00:32:21 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-18 00:32:21 +0800 |
commit | b46a14f4a8e128c08336763abf8bbf7c111f464d (patch) | |
tree | ee20fa35cbc19eddcddd1013e745b387e7371be3 | |
parent | c811691861eb51520d9fd51d56770f14990b0320 (diff) | |
parent | 2c14a96820233809db4360b39f5f02039be5730a (diff) | |
download | dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar.gz dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar.bz2 dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar.lz dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar.xz dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.tar.zst dexon-solidity-b46a14f4a8e128c08336763abf8bbf7c111f464d.zip |
Merge pull request #1122 from ethereum/firstClassFunctions
Functions as first-class types.
49 files changed, 2231 insertions, 321 deletions
diff --git a/Changelog.md b/Changelog.md index a392ec01..ce3343d8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.5 (unreleased) Features: + * Function types * Do-while loops: support for a C-style do{<block>}while(<expr>); control structure * Inline assembly: support ``invalidJumpLabel`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types diff --git a/docs/types.rst b/docs/types.rst index 9ec9e526..b22ad7d4 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -234,7 +234,7 @@ Hexademical Literals behave like String Literals and have the same convertibilit .. _enums: Enums -===== +----- Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversions @@ -267,6 +267,128 @@ check the value ranges at runtime and a failure causes an exception. Enums need } } +.. index:: ! function type, ! type; function + +.. _function_types: + +Function Types +-------------- + +Function types are the types of functions. Variables of function type +can be assigned from functions and function parameters of function type +can be used to pass functions to and return functions from function calls. +Function types come in two flavours - *internal* and *external* functions: + +Internal functions can only be used inside the current contract (more specifically, +inside the current code unit, which also includes internal library functions +and inherited functions) because they cannot be executed outside of the +context of the current contract. Calling an internal function is realized +by jumping to its entry label, just like when calling a function of the current +contract internally. + +External functions consist of an address and a function signature and they can +be passed via and returned from external function calls. + +Function types are notated as follows:: + + function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)] + +In contrast to the parameter types, the return types cannot be empty - if the +function type should not return anything, the whole ``returns (<return types>)`` +part has to be omitted. + +By default, function types are internal, so the ``internal`` keyword can be +omitted. + +There are two ways to access a function in the current contract: Either directly +by its name, ``f``, or using ``this.f``. The former will result in an internal +function, the latter in an external function. + +If a function type variable is not initialized, calling it will result +in an exception. The same happens if you call a function after using ``delete`` +on it. + +If external function types are used outside of the context of Solidity, +they are treated as the ``function`` type, which encodes the address +followed by the function identifier together in a single ``bytes24`` type. + +Example that shows how to use internal function types:: + + library ArrayUtils { + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) returns (uint) f) + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + function reduce( + uint[] memory self, + function (uint) returns (uint) f + ) + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + function range(uint length) returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } + } + + contract Pyramid { + using ArrayUtils for *; + function pyramid(uint l) return (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + function square(uint x) internal returns (uint) { + return x * x; + } + function sum(uint x, uint y) internal returns (uint) { + return x + y; + } + } + +Another example that uses external function types:: + + contract Oracle { + struct Request { + bytes data; + function(bytes) external callback; + } + Request[] requests; + event NewRequest(uint); + function query(bytes data, function(bytes) external callback) { + requests.push(Request(data, callback)); + NewRequest(requests.length - 1); + } + function reply(uint requestID, bytes response) { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } + } + + contract OracleUser { + Oracle constant oracle = 0x1234567; // known contract + function buySomething() { + oracle.query("USD", oracleResponse); + } + function oracleResponse(bytes response) { + if (msg.sender != oracle) throw; + // Use the data + } + } + +Note that lambda or inline functions are planned but not yet supported. + .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct Reference Types diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e881c1e2..a3037456 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -20,13 +20,17 @@ */ #include "Assembly.h" -#include <fstream> + #include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/ControlFlowGraph.h> +#include <libevmasm/PeepholeOptimiser.h> #include <libevmasm/BlockDeduplicator.h> #include <libevmasm/ConstantOptimiser.h> #include <libevmasm/GasMeter.h> + +#include <fstream> #include <json/json.h> + using namespace std; using namespace dev; using namespace dev::eth; @@ -75,17 +79,17 @@ string Assembly::out() const return ret.str(); } -unsigned Assembly::bytesRequired() const +unsigned Assembly::bytesRequired(unsigned subTagSize) const { - for (unsigned br = 1;; ++br) + for (unsigned tagSize = subTagSize; true; ++tagSize) { unsigned ret = 1; for (auto const& i: m_data) ret += i.second.size(); for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(br); - if (dev::bytesRequired(ret) <= br) + ret += i.bytesRequired(tagSize); + if (dev::bytesRequired(ret) <= tagSize) return ret; } } @@ -132,13 +136,19 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con if (i.data() == 0) _out << " PUSH [ErrorTag]"; else - _out << " PUSH [tag" << dec << i.data() << "]"; + { + size_t subId = i.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]"; + else + _out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]"; + } break; case PushSub: - _out << " PUSH [$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH [$" << size_t(i.data()) << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH #[$" << size_t(i.data()) << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; @@ -167,7 +177,7 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con for (size_t i = 0; i < m_subs.size(); ++i) { _out << _prefix << " " << hex << i << ": " << endl; - m_subs[i].stream(_out, _prefix + " ", _sourceCodes); + m_subs[i]->stream(_out, _prefix + " ", _sourceCodes); } } return _out; @@ -266,7 +276,7 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes { std::stringstream hexStr; hexStr << hex << i; - data[hexStr.str()] = m_subs[i].stream(_out, "", _sourceCodes, true); + data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true); } root[".data"] = data; _out << root; @@ -308,41 +318,67 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - if (!_enable) - return *this; + optimiseInternal(_enable, _isCreation, _runs); + return *this; +} +map<u256, u256> Assembly::optimiseInternal(bool _enable, bool _isCreation, size_t _runs) +{ + for (size_t subId = 0; subId < m_subs.size(); ++subId) + { + map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(_enable, false, _runs); + BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); + } + + map<u256, u256> tagReplacements; unsigned total = 0; for (unsigned count = 1; count > 0; total += count) { count = 0; + PeepholeOptimiser peepOpt(m_items); + if (peepOpt.optimise()) + count++; + + if (!_enable) + continue; + // This only modifies PushTags, we have to run again to actually remove code. BlockDeduplicator dedup(m_items); if (dedup.deduplicate()) + { + tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); count++; + } { - // Control flow graph that resets knowledge at path joins. - ControlFlowGraph cfg(m_items, false); + // Control flow graph optimization has been here before but is disabled because it + // assumes we only jump to tags that are pushed. This is not the case anymore with + // function types that can be stored in storage. AssemblyItems optimisedItems; - for (BasicBlock const& block: cfg.optimisedBlocks()) + + auto iter = m_items.begin(); + while (iter != m_items.end()) { - // We used to start with the block's initial state but it caused - // too many inconsistencies. + auto end = iter; + while (end != m_items.end()) + if (SemanticInformation::altersControlFlow(*end++)) + break; + KnownState emptyState; CommonSubexpressionEliminator eliminator(emptyState); - auto iter = m_items.begin() + block.begin; - auto const end = m_items.begin() + block.end; - while (iter < end) + auto blockIter = iter; + auto const blockEnd = end; + while (blockIter < blockEnd) { - auto orig = iter; - iter = eliminator.feedItems(iter, end); + auto orig = blockIter; + blockIter = eliminator.feedItems(blockIter, blockEnd); bool shouldReplace = false; AssemblyItems optimisedChunk; try { optimisedChunk = eliminator.getOptimizedItems(); - shouldReplace = (optimisedChunk.size() < size_t(iter - orig)); + shouldReplace = (optimisedChunk.size() < size_t(blockIter - orig)); } catch (StackTooDeepException const&) { @@ -361,10 +397,10 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) optimisedItems += optimisedChunk; } else - copy(orig, iter, back_inserter(optimisedItems)); + copy(orig, blockIter, back_inserter(optimisedItems)); } + iter = end; } - if (optimisedItems.size() < m_items.size()) { m_items = move(optimisedItems); @@ -373,17 +409,15 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) } } - total += ConstantOptimisationMethod::optimiseConstants( - _isCreation, - _isCreation ? 1 : _runs, - *this, - m_items - ); - - for (auto& sub: m_subs) - sub.optimise(true, false, _runs); + if (_enable) + total += ConstantOptimisationMethod::optimiseConstants( + _isCreation, + _isCreation ? 1 : _runs, + *this, + m_items + ); - return *this; + return tagReplacements; } LinkerObject const& Assembly::assemble() const @@ -391,20 +425,28 @@ LinkerObject const& Assembly::assemble() const if (!m_assembledObject.bytecode.empty()) return m_assembledObject; + size_t subTagSize = 1; + for (auto const& sub: m_subs) + { + sub->assemble(); + if (!sub->m_tagPositionsInBytecode.empty()) + subTagSize = max(subTagSize, *max_element(sub->m_tagPositionsInBytecode.begin(), sub->m_tagPositionsInBytecode.end())); + } + LinkerObject& ret = m_assembledObject; - unsigned totalBytes = bytesRequired(); - vector<unsigned> tagPos(m_usedTags); - map<unsigned, unsigned> tagRef; + size_t bytesRequiredForCode = bytesRequired(subTagSize); + m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1); + map<size_t, pair<size_t, size_t>> tagRef; multimap<h256, unsigned> dataRef; multimap<size_t, size_t> subRef; vector<unsigned> sizeRef; ///< Pointers to code locations where the size of the program is inserted - unsigned bytesPerTag = dev::bytesRequired(totalBytes); + unsigned bytesPerTag = dev::bytesRequired(bytesRequiredForCode); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; - unsigned bytesRequiredIncludingData = bytesRequired(); + unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1; for (auto const& sub: m_subs) - bytesRequiredIncludingData += sub.assemble().bytecode.size(); + bytesRequiredIncludingData += sub->assemble().bytecode.size(); unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); byte dataRefPush = (byte)Instruction::PUSH1 - 1 + bytesPerDataRef; @@ -413,8 +455,8 @@ LinkerObject const& Assembly::assemble() const for (AssemblyItem const& i: m_items) { // store position of the invalid jump destination - if (i.type() != Tag && tagPos[0] == 0) - tagPos[0] = ret.bytecode.size(); + if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1)) + m_tagPositionsInBytecode[0] = ret.bytecode.size(); switch (i.type()) { @@ -446,7 +488,7 @@ LinkerObject const& Assembly::assemble() const case PushTag: { ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = (unsigned)i.data(); + tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); break; } @@ -462,7 +504,7 @@ LinkerObject const& Assembly::assemble() const break; case PushSubSize: { - auto s = m_subs.at(size_t(i.data())).assemble().bytecode.size(); + auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); byte b = max<unsigned>(1, dev::bytesRequired(s)); ret.bytecode.push_back((byte)Instruction::PUSH1 - 1 + b); @@ -484,25 +526,16 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + 20); break; case Tag: - tagPos[(unsigned)i.data()] = ret.bytecode.size(); assertThrow(i.data() != 0, AssemblyException, ""); + assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag."); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); + m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size(); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } } - for (auto const& i: tagRef) - { - bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); - auto tag = i.second; - if (tag >= tagPos.size()) - tag = 0; - if (tag == 0) - assertThrow(tagPos[tag] != 0, AssemblyException, ""); - - toBigEndian(tagPos[tag], r); - } if (!dataRef.empty() && !subRef.empty()) ret.bytecode.push_back(0); @@ -516,7 +549,24 @@ LinkerObject const& Assembly::assemble() const bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); toBigEndian(ret.bytecode.size(), r); } - ret.append(m_subs[i].assemble()); + ret.append(m_subs[i]->assemble()); + } + for (auto const& i: tagRef) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = i.second; + assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id"); + std::vector<size_t> const& tagPositions = + subId == size_t(-1) ? + m_tagPositionsInBytecode : + m_subs[subId]->m_tagPositionsInBytecode; + assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); + size_t pos = tagPositions[tagId]; + assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); + assertThrow(dev::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); + bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); + toBigEndian(pos, r); } for (auto const& dataItem: m_data) { diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index dae1e1da..f3c56610 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -14,30 +14,32 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. */ -/** @file Assembly.h - * @author Gav Wood <i@gavwood.com> - * @date 2014 - */ #pragma once -#include <iostream> -#include <sstream> -#include <libdevcore/Common.h> -#include <libdevcore/Assertions.h> -#include <libdevcore/SHA3.h> #include <libevmasm/Instruction.h> #include <libevmasm/SourceLocation.h> #include <libevmasm/AssemblyItem.h> #include <libevmasm/LinkerObject.h> -#include "Exceptions.h" +#include <libevmasm/Exceptions.h> + +#include <libdevcore/Common.h> +#include <libdevcore/Assertions.h> +#include <libdevcore/SHA3.h> + #include <json/json.h> +#include <iostream> +#include <sstream> +#include <memory> + namespace dev { namespace eth { +using AssemblyPointer = std::shared_ptr<Assembly>; + class Assembly { public: @@ -46,20 +48,18 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } - AssemblyItem newSub(Assembly const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } - Assembly const& sub(size_t _sub) const { return m_subs.at(_sub); } - Assembly& sub(size_t _sub) { return m_subs.at(_sub); } + AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } + Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } + Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } AssemblyItem newPushString(std::string const& _data) { h256 h(dev::keccak256(_data)); m_strings[h] = _data; return AssemblyItem(PushString, h); } AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); - AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); void append(Assembly const& _a, int _deposit); AssemblyItem const& append(AssemblyItem const& _i); AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } - AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } /// Pushes the final size of the current assembly itself. Use this when the code is modified /// after compilation and CODESIZE is not an option. void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } @@ -101,6 +101,7 @@ public: /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. + /// If @a _enable is not set, will perform some simple peephole optimizations. Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); Json::Value stream( std::ostream& _out, @@ -110,9 +111,13 @@ public: ) const; protected: + /// Does the same operations as @a optimise, but should only be applied to a sub and + /// returns the replaced tags. + std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs); + std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } - unsigned bytesRequired() const; + unsigned bytesRequired(unsigned subTagSize) const; private: Json::Value streamAsmJson(std::ostream& _out, StringMap const& _sourceCodes) const; @@ -124,11 +129,12 @@ protected: unsigned m_usedTags = 1; AssemblyItems m_items; std::map<h256, bytes> m_data; - std::vector<Assembly> m_subs; + std::vector<std::shared_ptr<Assembly>> m_subs; std::map<h256, std::string> m_strings; std::map<h256, std::string> m_libraries; ///< Identifiers of libraries to be linked. mutable LinkerObject m_assembledObject; + mutable std::vector<size_t> m_tagPositionsInBytecode; int m_deposit = 0; int m_baseDeposit = 0; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 599ded85..7bd93eaf 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -26,6 +26,29 @@ using namespace std; using namespace dev; using namespace dev::eth; +AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const +{ + assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set."); + + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + AssemblyItem r = *this; + r.m_type = PushTag; + r.setPushTagSubIdAndTag(_subId, size_t(m_data)); + return r; +} + +pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data)); +} + +void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + setData(_tag + (u256(_subId + 1) << 64)); +} + unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const { switch (m_type) @@ -104,8 +127,14 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushString" << hex << (unsigned)_item.data(); break; case PushTag: - _out << " PushTag " << _item.data(); + { + size_t subId = _item.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PushTag " << _item.splitForeignPushTag().second; + else + _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; break; + } case Tag: _out << " Tag " << _item.data(); break; @@ -113,10 +142,10 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushData " << hex << (unsigned)_item.data(); break; case PushSub: - _out << " PushSub " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSub " << hex << size_t(_item.data()); break; case PushSubSize: - _out << " PushSubSize " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSubSize " << hex << size_t(_item.data()); break; case PushProgramSize: _out << " PushProgramSize"; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 1c3d9789..1a2fb1e6 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -69,6 +69,14 @@ public: AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } + /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. + /// @param _subId the identifier of the subassembly the tag is taken from. + AssemblyItem toSubAssemblyTag(size_t _subId) const; + /// @returns splits the data of the push tag into sub assembly id and actual tag id. + /// The sub assembly id of non-foreign push tags is -1. + std::pair<size_t, size_t> splitForeignPushTag() const; + /// Sets sub-assembly part and tag for a push tag. + void setPushTagSubIdAndTag(size_t _subId, size_t _tag); AssemblyItemType type() const { return m_type; } u256 const& data() const { return m_data; } diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index 3bb7a797..18b595cd 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -77,7 +77,6 @@ bool BlockDeduplicator::deduplicate() { //@todo this should probably be optimized. set<size_t, function<bool(size_t, size_t)>> blocksSeen(comparator); - map<u256, u256> tagReplacement; for (size_t i = 0; i < m_items.size(); ++i) { if (m_items.at(i).type() != Tag) @@ -86,22 +85,40 @@ bool BlockDeduplicator::deduplicate() if (it == blocksSeen.end()) blocksSeen.insert(i); else - tagReplacement[m_items.at(i).data()] = m_items.at(*it).data(); + m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data(); } - bool changed = false; - for (AssemblyItem& item: m_items) - if (item.type() == PushTag && tagReplacement.count(item.data())) - { - changed = true; - item.setData(tagReplacement.at(item.data())); - } - if (!changed) + if (!applyTagReplacement(m_items, m_replacedTags)) break; } return iterations > 0; } +bool BlockDeduplicator::applyTagReplacement( + AssemblyItems& _items, + map<u256, u256> const& _replacements, + size_t _subId +) +{ + bool changed = false; + for (AssemblyItem& item: _items) + if (item.type() == PushTag) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = item.splitForeignPushTag(); + if (subId != _subId) + continue; + auto it = _replacements.find(tagId); + if (it != _replacements.end()) + { + changed = true; + item.setPushTagSubIdAndTag(subId, size_t(it->second)); + } + } + return changed; +} + BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() { if (it == end) diff --git a/libevmasm/BlockDeduplicator.h b/libevmasm/BlockDeduplicator.h index c48835fd..5ecab7f3 100644 --- a/libevmasm/BlockDeduplicator.h +++ b/libevmasm/BlockDeduplicator.h @@ -23,9 +23,12 @@ #pragma once +#include <libdevcore/Common.h> + #include <cstddef> #include <vector> #include <functional> +#include <map> namespace dev { @@ -45,6 +48,18 @@ public: BlockDeduplicator(AssemblyItems& _items): m_items(_items) {} /// @returns true if something was changed bool deduplicate(); + /// @returns the tags that were replaced. + std::map<u256, u256> const& replacedTags() const { return m_replacedTags; } + + /// Replaces all PushTag operations insied @a _items that match a key in + /// @a _replacements by the respective value. If @a _subID is not -1, only + /// apply the replacement for foreign tags from this sub id. + /// @returns true iff a replacement was performed. + static bool applyTagReplacement( + AssemblyItems& _items, + std::map<u256, u256> const& _replacements, + size_t _subID = size_t(-1) + ); private: /// Iterator that skips tags and skips to the end if (all branches of) the control @@ -70,6 +85,7 @@ private: AssemblyItem const* replaceWith; }; + std::map<u256, u256> m_replacedTags; AssemblyItems& m_items; }; diff --git a/libevmasm/ControlFlowGraph.h b/libevmasm/ControlFlowGraph.h index 03a1f717..78998262 100644 --- a/libevmasm/ControlFlowGraph.h +++ b/libevmasm/ControlFlowGraph.h @@ -89,6 +89,11 @@ struct BasicBlock using BasicBlocks = std::vector<BasicBlock>; +/** + * Control flow graph optimizer. + * ASSUMES THAT WE ONLY JUMP TO TAGS THAT WERE PREVIOUSLY PUSHED. THIS IS NOT TRUE ANYMORE + * NOW THAT FUNCTION TAGS CAN BE STORED IN STORAGE. + */ class ControlFlowGraph { public: diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp new file mode 100644 index 00000000..f42dba48 --- /dev/null +++ b/libevmasm/PeepholeOptimiser.cpp @@ -0,0 +1,148 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ + +#include "PeepholeOptimiser.h" + +#include <libevmasm/AssemblyItem.h> +#include <libevmasm/SemanticInformation.h> + +using namespace std; +using namespace dev::eth; +using namespace dev; + +// TODO: Extend this to use the tools from ExpressionClasses.cpp + +struct Identity +{ + static size_t windowSize() { return 1; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out) + { + *_out = *_in; + return true; + } +}; + +struct PushPop +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems>) + { + auto t = _in[0].type(); + if (_in[1] == Instruction::POP && ( + SemanticInformation::isDupInstruction(_in[0]) || + t == Push || t == PushString || t == PushTag || t == PushSub || + t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress + )) + return true; + else + return false; + } +}; + +struct DoubleSwap +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems>) + { + if (_in[0] == _in[1] && SemanticInformation::isSwapInstruction(_in[0])) + return true; + else + return false; + } +}; + +struct JumpToNext +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out) + { + if ( + _in[0].type() == PushTag && + (_in[1] == Instruction::JUMP || _in[1] == Instruction::JUMPI) && + _in[2].type() == Tag && + _in[0].data() == _in[2].data() + ) + { + if (_in[1] == Instruction::JUMPI) + *_out = AssemblyItem(Instruction::POP, _in[1].location()); + *_out = _in[2]; + return true; + } + else + return false; + } +}; + +struct TagConjunctions +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out) + { + if ( + _in[0].type() == PushTag && + _in[2] == Instruction::AND && + _in[1].type() == Push && + (_in[1].data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF) + ) + { + *_out = _in[0]; + return true; + } + else + return false; + } +}; + +struct OptimiserState +{ + AssemblyItems const& items; + size_t i; + std::back_insert_iterator<AssemblyItems> out; +}; + +void applyMethods(OptimiserState&) +{ + assertThrow(false, OptimizerException, "Peephole optimizer failed to apply identity."); +} + +template <typename Method, typename... OtherMethods> +void applyMethods(OptimiserState& _state, Method, OtherMethods... _other) +{ + if (_state.i + Method::windowSize() <= _state.items.size() && Method::apply(_state.items.begin() + _state.i, _state.out)) + _state.i += Method::windowSize(); + else + applyMethods(_state, _other...); +} + +bool PeepholeOptimiser::optimise() +{ + OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; + while (state.i < m_items.size()) + applyMethods(state, PushPop(), DoubleSwap(), JumpToNext(), TagConjunctions(), Identity()); + if (m_optimisedItems.size() < m_items.size()) + { + m_items = std::move(m_optimisedItems); + return true; + } + else + return false; + +} diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h new file mode 100644 index 00000000..372e49c5 --- /dev/null +++ b/libevmasm/PeepholeOptimiser.h @@ -0,0 +1,54 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ +#pragma once + +#include <vector> +#include <cstddef> +#include <iterator> + +namespace dev +{ +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector<AssemblyItem>; + +class PeepholeOptimisationMethod +{ +public: + virtual size_t windowSize() const; + virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out); +}; + +class PeepholeOptimiser +{ +public: + explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} + + bool optimise(); + +private: + AssemblyItems& m_items; + AssemblyItems m_optimisedItems; +}; + +} +} diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 39b6376c..d757dcf1 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -474,7 +474,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireSize(2); requireDeposit(0, 1); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[0].m_asm); if (us == "WHILE") m_asm.append(Instruction::ISZERO); @@ -489,7 +489,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireDeposit(1, 1); m_asm.append(code[0].m_asm, 0); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[1].m_asm); m_asm.append(Instruction::ISZERO); auto end = m_asm.appendJumpI(); @@ -520,7 +520,8 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireMaxSize(3); requireDeposit(1, 1); - auto subPush = m_asm.appendSubSize(code[0].assembly(ns)); + auto subPush = m_asm.newSub(make_shared<Assembly>(code[0].assembly(ns))); + m_asm.append(m_asm.newPushSubSize(subPush.data())); m_asm.append(Instruction::DUP1); if (code.size() == 3) { diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index a7b9e8b8..a0768a34 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -83,6 +83,31 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } +void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) +{ + switch (_typeName.visibility()) + { + case VariableDeclaration::Visibility::Default: + case VariableDeclaration::Visibility::Internal: + case VariableDeclaration::Visibility::External: + break; + default: + typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + } + + if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) + fatalTypeError(_typeName.location(), "Only external function types can be payable."); + if (_typeName.visibility() == VariableDeclaration::Visibility::External) + for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + if (!t->annotation().type->canBeUsedExternally(false)) + fatalTypeError(t->location(), "Internal type cannot be used for external function type."); + } + + _typeName.annotation().type = make_shared<FunctionType>(_typeName); +} + void ReferencesResolver::endVisit(Mapping const& _typeName) { TypePointer keyType = _typeName.keyType().annotation().type; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 1986b2bb..bfaef2e1 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -62,6 +62,7 @@ private: virtual bool visit(Identifier const& _identifier) override; virtual bool visit(ElementaryTypeName const& _typeName) override; virtual void endVisit(UserDefinedTypeName const& _typeName) override; + virtual void endVisit(FunctionTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; virtual void endVisit(ArrayTypeName const& _typeName) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index f934b2c8..8b6d45e2 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -574,6 +574,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return false; } +void TypeChecker::endVisit(FunctionTypeName const& _funType) +{ + FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type); + if (fun.location() == FunctionType::Location::External) + if (!fun.canBeUsedExternally(false)) + typeError(_funType.location(), "External function type uses internal types."); +} + bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { // Inline assembly does not have its own type-checking phase, so we just run the diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index be1e02be..5874bb50 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -87,6 +87,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); virtual bool visit(EventDefinition const& _eventDef) override; + virtual void endVisit(FunctionTypeName const& _funType) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 1b42c499..a2b70fe9 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -823,6 +823,41 @@ private: }; /** + * A literal function type. Its source form is "function (paramType1, paramType2) internal / external returns (retType1, retType2)" + */ +class FunctionTypeName: public TypeName +{ +public: + FunctionTypeName( + SourceLocation const& _location, + ASTPointer<ParameterList> const& _parameterTypes, + ASTPointer<ParameterList> const& _returnTypes, + Declaration::Visibility _visibility, + bool _isDeclaredConst, + bool _isPayable + ): + TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + m_visibility(_visibility), m_isDeclaredConst(_isDeclaredConst), m_isPayable(_isPayable) + {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); } + std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); } + + Declaration::Visibility visibility() const { return m_visibility; } + bool isDeclaredConst() const { return m_isDeclaredConst; } + bool isPayable() const { return m_isPayable; } + +private: + ASTPointer<ParameterList> m_parameterTypes; + ASTPointer<ParameterList> m_returnTypes; + Declaration::Visibility m_visibility; + bool m_isDeclaredConst; + bool m_isPayable; +}; + +/** * A mapping type. Its source form is "mapping('keyType' => 'valueType')" */ class Mapping: public TypeName diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 59fc1b57..52bbf396 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -54,6 +54,7 @@ class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; class UserDefinedTypeName; +class FunctionTypeName; class Mapping; class ArrayTypeName; class Statement; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 3fce1180..37dfd3c6 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -226,6 +226,20 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) return true; } +bool ASTJsonConverter::visit(FunctionTypeName const& _node) +{ + string visibility = "internal"; + if (_node.visibility() == Declaration::Visibility::External) + visibility = "external"; + + addJsonNode(_node, "FunctionTypeName", { + make_pair("payable", _node.isPayable()), + make_pair("visibility", visibility), + make_pair("constant", _node.isDeclaredConst()) + }, true); + return true; +} + bool ASTJsonConverter::visit(Mapping const& _node) { addJsonNode(_node, "Mapping", {}, true); @@ -507,6 +521,11 @@ void ASTJsonConverter::endVisit(UserDefinedTypeName const&) { } +void ASTJsonConverter::endVisit(FunctionTypeName const&) +{ + goUp(); +} + void ASTJsonConverter::endVisit(Mapping const&) { goUp(); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 7c7b37f8..0a71779c 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -69,6 +69,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -114,6 +115,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 27266968..053b9b82 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -164,6 +164,13 @@ bool ASTPrinter::visit(UserDefinedTypeName const& _node) return goDeeper(); } +bool ASTPrinter::visit(FunctionTypeName const& _node) +{ + writeLine("FunctionTypeName"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Mapping const& _node) { writeLine("Mapping"); @@ -442,6 +449,11 @@ void ASTPrinter::endVisit(UserDefinedTypeName const&) m_indentation--; } +void ASTPrinter::endVisit(FunctionTypeName const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Mapping const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index f0ab1098..9f88a1fd 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -63,6 +63,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -106,6 +107,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 3a1b55d3..e72afe69 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -61,6 +61,7 @@ public: virtual bool visit(TypeName& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); } virtual bool visit(Mapping& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } virtual bool visit(InlineAssembly& _node) { return visitNode(_node); } @@ -106,6 +107,7 @@ public: virtual void endVisit(TypeName& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); } virtual void endVisit(Mapping& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } virtual void endVisit(InlineAssembly& _node) { endVisitNode(_node); } @@ -163,6 +165,7 @@ public: virtual bool visit(TypeName const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); } virtual bool visit(Mapping const& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName const& _node) { return visitNode(_node); } virtual bool visit(Block const& _node) { return visitNode(_node); } @@ -208,6 +211,7 @@ public: virtual void endVisit(TypeName const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Mapping const& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Block const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index b5a3806b..f521e092 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -327,6 +327,26 @@ void UserDefinedTypeName::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void FunctionTypeName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void FunctionTypeName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Mapping::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6ad74d28..4488398f 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1265,6 +1265,7 @@ TypePointer ArrayType::decodingType() const TypePointer ArrayType::interfaceType(bool _inLibrary) const { + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) return shared_from_this(); @@ -1282,6 +1283,21 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const return make_shared<ArrayType>(DataLocation::Memory, baseExt, m_length); } +bool ArrayType::canBeUsedExternally(bool _inLibrary) const +{ + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) + if (_inLibrary && location() == DataLocation::Storage) + return true; + else if (m_arrayKind != ArrayKind::Ordinary) + return true; + else if (!m_baseType->canBeUsedExternally(_inLibrary)) + return false; + else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) + return false; + else + return true; +} + u256 ArrayType::memorySize() const { solAssert(!isDynamicallySized(), ""); @@ -1704,7 +1720,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): m_location(_isInternal ? Location::Internal : Location::External), m_isConstant(_function.isDeclaredConst()), - m_isPayable(_function.isPayable()), + m_isPayable(_isInternal ? false : _function.isPayable()), m_declaration(&_function) { TypePointers params; @@ -1805,6 +1821,38 @@ FunctionType::FunctionType(EventDefinition const& _event): swap(paramNames, m_parameterNames); } +FunctionType::FunctionType(FunctionTypeName const& _typeName): + m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), + m_isConstant(_typeName.isDeclaredConst()), + m_isPayable(_typeName.isPayable()) +{ + if (_typeName.isPayable()) + { + solAssert(m_location == Location::External, "Internal payable function type used."); + solAssert(!m_isConstant, "Payable constant function"); + } + for (auto const& t: _typeName.parameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as parameter for external function." + ); + m_parameterTypes.push_back(t->annotation().type); + } + for (auto const& t: _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for return parameter."); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as return parameter for external function." + ); + m_returnParameterTypes.push_back(t->annotation().type); + } +} + FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _contract) { FunctionDefinition const* constructor = _contract.constructor(); @@ -1880,22 +1928,69 @@ bool FunctionType::operator==(Type const& _other) const return true; } +TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Value::Delete) + return make_shared<TupleType>(); + return TypePointer(); +} + +string FunctionType::canonicalName(bool) const +{ + solAssert(m_location == Location::External, ""); + return "function"; +} + string FunctionType::toString(bool _short) const { string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) 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(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); - return name + ")"; + name += ")"; + if (m_isConstant) + name += " constant"; + if (m_isPayable) + name += " payable"; + if (m_location == Location::External) + name += " external"; + if (!m_returnParameterTypes.empty()) + { + name += " returns ("; + for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) + name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); + name += ")"; + } + return name; +} + +unsigned FunctionType::calldataEncodedSize(bool _padded) const +{ + unsigned size = storageBytes(); + if (_padded) + size = ((size + 31) / 32) * 32; + return size; } u256 FunctionType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable function type requested.")); + if (m_location == Location::External || m_location == Location::Internal) + return 1; + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + +unsigned FunctionType::storageBytes() const +{ + if (m_location == Location::External) + return 20 + 4; + else if (m_location == Location::Internal) + return 8; // it should really not be possible to create larger programs + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); } unsigned FunctionType::sizeOnStack() const @@ -2018,6 +2113,23 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con } } +TypePointer FunctionType::encodingType() const +{ + // Only external functions can be encoded, internal functions cannot leave code boundaries. + if (m_location == Location::External) + return shared_from_this(); + else + return TypePointer(); +} + +TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const +{ + if (m_location == Location::External) + return shared_from_this(); + else + return TypePointer(); +} + bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const { solAssert(!bound() || _selfType, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 082e16a6..34fcfc82 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -254,6 +254,9 @@ public: /// @param _inLibrary if set, returns types as used in a library, e.g. struct and contract types /// are returned without modification. virtual TypePointer interfaceType(bool /*_inLibrary*/) const { return TypePointer(); } + /// @returns true iff this type can be passed on via calls (to libraries if _inLibrary is true), + /// should be have identical to !!interfaceType(_inLibrary) but might do optimizations. + virtual bool canBeUsedExternally(bool _inLibrary) const { return !!interfaceType(_inLibrary); } private: /// @returns a member list containing all members added to this type by `using for` directives. @@ -580,6 +583,7 @@ public: virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; + virtual bool canBeUsedExternally(bool _inLibrary) const override; /// @returns true if this is a byte array or a string bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } @@ -821,6 +825,8 @@ public: explicit FunctionType(VariableDeclaration const& _varDecl); /// Creates the function type of an event. explicit FunctionType(EventDefinition const& _event); + /// Creates the type of a function type name. + explicit FunctionType(FunctionTypeName const& _typeName); /// Function type constructor to be used for a plain type (not derived from a declaration). FunctionType( strings const& _parameterTypes, @@ -890,12 +896,19 @@ public: TypePointer selfType() const; virtual bool operator==(Type const& _other) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; - virtual bool canBeStored() const override { return false; } + virtual unsigned calldataEncodedSize(bool _padded) const override; + virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned storageBytes() const override; + virtual bool isValueType() const override { return true; } + virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + virtual TypePointer encodingType() const override; + virtual TypePointer interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an /// appropriate external types (i.e. the interfaceType()s) of input/return parameters of diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 1ae5dda9..54639515 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,14 +33,15 @@ void Compiler::compileContract( std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts ) { - ContractCompiler runtimeCompiler(m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(m_context, m_optimize); - m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); + // This might modify m_runtimeContext because it can access runtime functions at + // creation time. + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); if (_contract.isLibrary()) { @@ -54,11 +55,11 @@ void Compiler::compileClone( map<ContractDefinition const*, eth::Assembly const*> const& _contracts ) { - ContractCompiler cloneCompiler(m_context, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); + ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); } eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fccb68a9..4a87de0e 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -35,7 +35,9 @@ class Compiler public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), - m_optimizeRuns(_runs) + m_optimizeRuns(_runs), + m_runtimeContext(), + m_context(&m_runtimeContext) { } void compileContract( @@ -69,9 +71,9 @@ public: private: bool const m_optimize; unsigned const m_optimizeRuns; - CompilerContext m_context; - size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_runtimeContext; + size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. + CompilerContext m_context; }; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3ac5bd3c..c76db2a6 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -60,8 +60,8 @@ void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { - solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); - m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; + solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } void CompilerContext::removeVariable(VariableDeclaration const& _declaration) @@ -92,22 +92,22 @@ eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& return m_functionCompilationQueue.entryLabelIfExists(_declaration); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function) +FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function) { // Libraries do not allow inheritance and their functions can be inlined, so we should not // search the inheritance hierarchy (which will be the wrong one in case the function // is inlined). if (auto scope = dynamic_cast<ContractDefinition const*>(_function.scope())) if (scope->isLibrary()) - return functionEntryLabel(_function); + return _function; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); + return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin()); } -eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) +FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base) { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, superContract(_base)); + return resolveVirtualFunction(_function, superContract(_base)); } FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const @@ -145,12 +145,12 @@ unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declarat unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return m_asm.deposit() - _baseOffset - 1; + return m_asm->deposit() - _baseOffset - 1; } unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const { - return m_asm.deposit() - _offset - 1; + return m_asm->deposit() - _offset - 1; } pair<u256, unsigned> CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const @@ -217,17 +217,17 @@ void CompilerContext::appendInlineAssembly( return true; }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, m_asm, identifierAccess), ""); + solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), ""); } void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) { - eth::Assembly& sub = m_asm.sub(_subIndex); + eth::Assembly& sub = m_asm->sub(_subIndex); sub.injectStart(Instruction::POP); sub.injectStart(fromBigEndian<u256>(binaryVersion())); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( +FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector<ContractDefinition const*>::const_iterator _searchStart ) @@ -242,9 +242,9 @@ eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( !function->isConstructor() && FunctionType(*function).hasEqualArgumentTypes(functionType) ) - return functionEntryLabel(*function); + return *function; solAssert(false, "Super function " + name + " not found."); - return m_asm.newTag(); // not reached + return _function; // not reached } vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const @@ -257,7 +257,7 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::superContract void CompilerContext::updateSourceLocation() { - m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); + m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 0c1500b0..8ccbddfd 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,6 +44,14 @@ namespace solidity { class CompilerContext { public: + CompilerContext(CompilerContext* _runtimeContext = nullptr): + m_asm(std::make_shared<eth::Assembly>()), + m_runtimeContext(_runtimeContext) + { + if (m_runtimeContext) + m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); + } + void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); @@ -52,9 +60,9 @@ public: void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; - void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } - void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } - unsigned stackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } + void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } + void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } + unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; @@ -67,10 +75,10 @@ public: eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. - eth::AssemblyItem virtualFunctionEntryLabel(FunctionDefinition const& _function); - /// @returns the entry label of a function that overrides the given declaration from the most derived class just + FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); + /// @returns the function that overrides the given declaration from the most derived class just /// above _base in the current inheritance hierarchy. - eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); + FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; /// @returns the next function in the queue of functions that are still to be compiled @@ -95,30 +103,33 @@ public: std::pair<u256, unsigned> storageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag - eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } /// Appends a JUMPI instruction to @a _tag - CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJumpI(_tag); return *this; } /// Appends a JUMP to a new tag and @returns the tag - eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } + eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Returns an "ErrorTag" - eth::AssemblyItem errorTag() { return m_asm.errorTag(); } + eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. - eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data - eth::AssemblyItem newTag() { return m_asm.newTag(); } + eth::AssemblyItem newTag() { return m_asm->newTag(); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) - /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. - eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } + /// on the stack. @returns the pushsub assembly item. + eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; } + void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); } + /// Pushes the offset of the subroutine. + void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { return m_asm.appendProgramSize(); } + void appendProgramSize() { m_asm->appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack - eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } + eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. - void appendLibraryAddress(std::string const& _identifier) { m_asm.appendLibraryAddress(_identifier); } + void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes @@ -127,10 +138,10 @@ public: void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm.append(_instruction); return *this; } - CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } - CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } + CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } /// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// prior to parsing the inline assembly. @@ -144,22 +155,27 @@ public: /// Prepends "PUSH <compiler version number> POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } + + /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. + CompilerContext* runtimeContext() { return m_runtimeContext; } + /// @returns the identifier of the runtime subroutine. + size_t runtimeSub() const { return m_runtimeSub; } - eth::Assembly const& assembly() const { return m_asm; } + eth::Assembly const& assembly() const { return *m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return m_asm; } + eth::Assembly& nonConstAssembly() { return *m_asm; } /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFormat shows whether the out should be in Json format Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const { - return m_asm.stream(_stream, "", _sourceCodes, _inJsonFormat); + return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); } - eth::LinkerObject const& assembledObject() { return m_asm.assemble(); } - eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm.sub(_subIndex).assemble(); } + eth::LinkerObject const& assembledObject() { return m_asm->assemble(); } + eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); } /** * Helper class to pop the visited nodes stack when a scope closes @@ -172,9 +188,9 @@ public: }; private: - /// @returns the entry label of the given function - searches the inheritance hierarchy - /// startig from the given point towards the base. - eth::AssemblyItem virtualFunctionEntryLabel( + /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns + /// the first function definition that is overwritten by _function. + FunctionDefinition const& resolveVirtualFunction( FunctionDefinition const& _function, std::vector<ContractDefinition const*>::const_iterator _searchStart ); @@ -215,7 +231,7 @@ private: mutable std::queue<Declaration const*> m_functionsToCompile; } m_functionCompilationQueue; - eth::Assembly m_asm; + eth::AssemblyPointer m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set<Declaration const*> m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. @@ -228,6 +244,10 @@ private: std::vector<ContractDefinition const*> m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack<ASTNode const*> m_visitedNodes; + /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. + CompilerContext *m_runtimeContext; + /// The index of the runtime subroutine. + size_t m_runtimeSub = -1; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 58d1caa9..bfe5386b 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -133,6 +133,16 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << u256(str->value().size()); m_context << Instruction::ADD; } + else if ( + _type.category() == Type::Category::Function && + dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External + ) + { + solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + combineExternalFunctionType(true); + m_context << Instruction::DUP2 << Instruction::MSTORE; + m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; + } else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); @@ -206,7 +216,8 @@ void CompilerUtils::encodeToMemory( else if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::CallData) || - _givenTypes[i]->category() == Type::Category::StringLiteral + _givenTypes[i]->category() == Type::Category::StringLiteral || + _givenTypes[i]->category() == Type::Category::Function ) type = _givenTypes[i]; // delay conversion else @@ -304,6 +315,49 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } +void CompilerUtils::splitExternalFunctionType(bool _leftAligned) +{ + // We have to split the left-aligned <address><function identifier> into two stack slots: + // address (right aligned), function identifier (right aligned) + if (_leftAligned) + { + m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV; + // <input> <address> + m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; + } + else + { + m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + } + m_context << u256(0xffffffffUL) << Instruction::AND; +} + +void CompilerUtils::combineExternalFunctionType(bool _leftAligned) +{ + // <address> <function_id> + m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; + if (!_leftAligned) + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << (u256(1) << 32) << Instruction::MUL; + m_context << Instruction::OR; + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::MUL; +} + +void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) +{ + m_context << m_context.functionEntryLabel(_function).pushTag(); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + m_context << + (u256(1) << 32) << + Instruction::MUL << + rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << + Instruction::OR; +} + 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 @@ -678,6 +732,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp void CompilerUtils::pushZeroValue(Type const& _type) { + if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + m_context << m_context.errorTag(); + return; + } + } auto const* referenceType = dynamic_cast<ReferenceType const*>(&_type); if (!referenceType || referenceType->location() == DataLocation::Storage) { @@ -822,21 +884,27 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.category() == Type::Category::FixedBytes; + bool isExternalFunctionType = false; + if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) + if (funType->location() == FunctionType::Location::External) + isExternalFunctionType = true; if (numBytes == 0) + { m_context << Instruction::POP << u256(0); - else + return numBytes; + } + solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); + m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); + if (isExternalFunctionType) + splitExternalFunctionType(true); + else if (numBytes != 32) { - solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); - m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); - if (numBytes != 32) - { - // add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); - m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; - if (leftAligned) - m_context << shiftFactor << Instruction::MUL; - } + bool leftAligned = _type.category() == Type::Category::FixedBytes; + // add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); + m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; + if (leftAligned) + m_context << shiftFactor << Instruction::MUL; } return numBytes; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index da74dc90..8e4033d6 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,6 +114,17 @@ public: /// Stack post: void memoryCopy(); + /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) + /// external function type <address><function identifier> into two stack slots: + /// address (right aligned), function identifier (right aligned) + void splitExternalFunctionType(bool _rightAligned); + /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) + void combineExternalFunctionType(bool _rightAligned); + /// Appends code that combines the construction-time (if available) and runtime function + /// entry label of the given function into a single stack slot. + /// Note: This might cause the compilation queue of the runtime context to be extended. + void pushCombinedFunctionEntryLabel(Declaration const& _function); + /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory /// if a reference type is converted from calldata or storage to memory. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 2aec3055..2e3f5d7f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,14 +60,13 @@ void ContractCompiler::compileContract( } size_t ContractCompiler::compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract, _runtimeContext); + return packIntoContractCreator(_contract); } size_t ContractCompiler::compileClone( @@ -80,7 +79,7 @@ size_t ContractCompiler::compileClone( appendInitAndConstructorCode(_contract); //@todo determine largest return size of all runtime functions - eth::AssemblyItem runtimeSub = m_context.addSubroutine(cloneRuntime()); + auto runtimeSub = m_context.addSubroutine(cloneRuntime()); // stack contains sub size m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; @@ -88,7 +87,6 @@ size_t ContractCompiler::compileClone( appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); return size_t(runtimeSub.data()); } @@ -141,21 +139,31 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendBaseConstructor(*c); } -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { + solAssert(!!m_runtimeCompiler, ""); + appendInitAndConstructorCode(_contract); - eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + // We jump to the deploy routine because we first have to append all missing functions, + // which can cause further functions to be added to the runtime context. + eth::AssemblyItem deployRoutine = m_context.appendJumpToNew(); - // stack contains sub size - m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; - m_context << u256(0) << Instruction::RETURN; - - // note that we have to include the functions again because of absolute jump labels + // We have to include copies of functions in the construction time and runtime context + // because of absolute jumps. appendMissingFunctions(); + m_runtimeCompiler->appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), ""); - return size_t(runtimeSub.data()); + m_context << deployRoutine; + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.pushSubroutineSize(m_context.runtimeSub()); + m_context << Instruction::DUP1; + m_context.pushSubroutineOffset(m_context.runtimeSub()); + m_context << u256(0) << Instruction::CODECOPY; + m_context << u256(0) << Instruction::RETURN; + + return m_context.runtimeSub(); } void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -293,6 +301,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); + solAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast<ArrayType const&>(*type); @@ -515,7 +524,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) - _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + { + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + { + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); + } + } else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) { solAssert(!variable->isConstant(), ""); @@ -871,7 +892,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::Assembly ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -889,5 +910,5 @@ eth::Assembly ContractCompiler::cloneRuntime() a.appendJumpI(a.errorTag()); //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; - return a; + return make_shared<eth::Assembly>(a); } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 0799a543..2244a3be 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,12 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): m_optimise(_optimise), + m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(); + m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -52,7 +53,6 @@ public: /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts ); @@ -74,7 +74,7 @@ private: /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. /// @returns the identifier of the runtime sub assembly - size_t packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + size_t packIntoContractCreator(ContractDefinition const& _contract); /// Appends state variable initialisation and constructor code. void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); @@ -114,9 +114,11 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::Assembly cloneRuntime(); + static eth::AssemblyPointer cloneRuntime(); bool const m_optimise; + /// Pointer to the runtime compiler in case this is a creation compiler. + ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index e3f05c21..7a328528 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -488,6 +488,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) parameterSize += function.selfType()->sizeOnStack(); } + if (m_context.runtimeContext()) + // We have a runtime context, so we need the creation part. + m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + else + // Extract the runtime part. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; @@ -521,8 +528,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // copy the contract's code into memory eth::Assembly const& assembly = m_context.compiledContract(contract); utils().fetchFreeMemoryPointer(); + // TODO we create a copy here, which is actually what we want. + // This should be revisited at the point where we fix + // https://github.com/ethereum/solidity/issues/1092 // pushes size - eth::AssemblyItem subroutine = m_context.addSubroutine(assembly); + auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly)); m_context << Instruction::DUP1 << subroutine; m_context << Instruction::DUP4 << Instruction::CODECOPY; @@ -845,9 +855,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.functionEntryLabel( - dynamic_cast<FunctionDefinition const&>(funType->declaration()) - ).pushTag(); + FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration()); + utils().pushCombinedFunctionEntryLabel(funDef); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); } else @@ -883,7 +892,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // us to link against it although we actually do not need it. auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration); solAssert(!!function, "Function not found in member access"); - m_context << m_context.functionEntryLabel(*function).pushTag(); + utils().pushCombinedFunctionEntryLabel(*function); } } else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type.get())) @@ -915,10 +924,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (type.isSuper()) { solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); - m_context << m_context.superFunctionEntryLabel( + utils().pushCombinedFunctionEntryLabel(m_context.superFunction( dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration), type.contractDefinition() - ).pushTag(); + )); } else { @@ -1203,7 +1212,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) appendVariable(*variable, static_cast<Expression const&>(_identifier)); else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) @@ -1266,6 +1275,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (_operator == Token::Equal || _operator == Token::NotEqual) { + if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + // We have to remove the upper bits (construction time value) because they might + // be "unknown" in one of the operands and not in the other. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + } + } m_context << Instruction::EQ; if (_operator == Token::NotEqual) m_context << Instruction::ISZERO; diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 69a80b6a..2ec7f800 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -153,7 +153,8 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): { if (m_dataType->isValueType()) { - solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + if (m_dataType->category() != Type::Category::Function) + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } @@ -176,6 +177,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::POP << Instruction::SLOAD; else { + bool cleaned = false; m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; @@ -183,14 +185,31 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const // implementation should be very similar to the integer case. solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) + { m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; + cleaned = true; + } else if ( m_dataType->category() == Type::Category::Integer && dynamic_cast<IntegerType const&>(*m_dataType).isSigned() ) + { m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; - else + cleaned = true; + } + else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + { + CompilerUtils(m_context).splitExternalFunctionType(false); + cleaned = true; + } + } + if (!cleaned) + { + solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; + } } } @@ -204,6 +223,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); if (m_dataType->storageBytes() == 32) { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size."); // offset should be zero m_context << Instruction::POP; if (!_move) @@ -222,16 +242,27 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::MUL; - m_context << Instruction::NOT << Instruction::AND; - // stack: value storage_ref multiplier cleared_value - m_context - << Instruction::SWAP1 << Instruction::DUP4; + m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; + // stack: value storage_ref cleared_value multiplier + utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if (m_dataType->category() == Type::Category::FixedBytes) + if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(false); + else + m_context << + ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << + Instruction::AND; + } + else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) << Instruction::SWAP1 << Instruction::DIV; else + { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type."); // remove the higher order bits m_context << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) @@ -239,11 +270,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::DUP2 << Instruction::MUL << Instruction::DIV; + } m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; if (_move) - m_context << Instruction::POP; + utils.popStackElement(*m_dataType); } } else diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 6ad8994a..c8bc3aed 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -22,7 +22,7 @@ StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* )? '}' ModifierDefinition = 'modifier' Identifier ParameterList? Block FunctionDefinition = 'function' Identifier? ParameterList - ( FunctionCall | Identifier | 'constant' | 'external' | 'public' | 'internal' | 'private' )* + ( FunctionCall | Identifier | 'constant' |' payable' | 'external' | 'public' | 'internal' | 'private' )* ( 'returns' ParameterList )? Block EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';' @@ -31,13 +31,16 @@ EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}' IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')' ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')' +TypeNameList = '(' ( TypeName (',' TypeName )* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists VariableDeclaration = TypeName Identifier -TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName +TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' +FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )* + ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' Block = '{' Statement* '}' diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 519027bc..f1eb2614 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -37,6 +37,7 @@ #include <libsolidity/interface/InterfaceHandler.h> #include <libsolidity/formal/Why3Translator.h> +#include <libevmasm/Exceptions.h> #include <libdevcore/SHA3.h> #include <boost/algorithm/string.hpp> @@ -590,9 +591,19 @@ void CompilerStack::compileContract( compiledContract.runtimeObject = compiler->runtimeObject(); _compiledContracts[compiledContract.contract] = &compiler->assembly(); - Compiler cloneCompiler(_optimize, _runs); - cloneCompiler.compileClone(_contract, _compiledContracts); - compiledContract.cloneObject = cloneCompiler.assembledObject(); + try + { + Compiler cloneCompiler(_optimize, _runs); + cloneCompiler.compileClone(_contract, _compiledContracts); + compiledContract.cloneObject = cloneCompiler.assembledObject(); + } + catch (eth::AssemblyException const&) + { + // In some cases (if the constructor requests a runtime function), it is not + // possible to compile the clone. + + // TODO: Report error / warning + } } std::string CompilerStack::defaultContractName() const diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 5705856c..0f7b6c35 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -66,7 +66,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe { Json::Value method; method["type"] = "constructor"; - auto externalFunction = FunctionType(*_contractDef.constructor()).interfaceFunctionType(); + auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["inputs"] = populateParameters( externalFunction->parameterNames(), @@ -76,7 +76,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe } if (_contractDef.fallbackFunction()) { - auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction()).interfaceFunctionType(); + auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "fallback"; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index df3ed7b2..02b7d5e0 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -217,7 +217,9 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) if (currentTokenValue == Token::RBrace) break; else if (currentTokenValue == Token::Function) - subNodes.push_back(parseFunctionDefinition(name.get())); + // This can be a function or a state variable of function type (especially + // complicated to distinguish fallback function from function type state variable) + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); else if (currentTokenValue == Token::Struct) subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) @@ -288,81 +290,121 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) return visibility; } -ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* _contractName) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) { - ASTNodeFactory nodeFactory(*this); - ASTPointer<ASTString> docstring; - if (m_scanner->currentCommentLiteral() != "") - docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - + FunctionHeaderParserResult result; expectToken(Token::Function); - ASTPointer<ASTString> name; - if (m_scanner->currentToken() == Token::LParen) - name = make_shared<ASTString>(); // anonymous function + if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared<ASTString>(); // anonymous function else - name = expectIdentifierToken(); + result.name = expectIdentifierToken(); VarDeclParserOptions options; options.allowLocationSpecifier = true; - ASTPointer<ParameterList> parameters(parseParameterList(options)); - bool isDeclaredConst = false; - bool isPayable = false; - Declaration::Visibility visibility(Declaration::Visibility::Default); - vector<ASTPointer<ModifierInvocation>> modifiers; + result.parameters = parseParameterList(options); while (true) { Token::Value token = m_scanner->currentToken(); if (token == Token::Const) { - isDeclaredConst = true; + result.isDeclaredConst = true; m_scanner->next(); } else if (m_scanner->currentToken() == Token::Payable) { - isPayable = true; + result.isPayable = true; m_scanner->next(); } - else if (token == Token::Identifier) - modifiers.push_back(parseModifierInvocation()); + else if (_allowModifiers && token == Token::Identifier) + { + // This can either be a modifier (function declaration) or the name of the + // variable (function type name plus variable). + if ( + m_scanner->peekNextToken() == Token::Semicolon || + m_scanner->peekNextToken() == Token::Assign + ) + // Variable declaration, break here. + break; + else + result.modifiers.push_back(parseModifierInvocation()); + } else if (Token::isVisibilitySpecifier(token)) { - if (visibility != Declaration::Visibility::Default) + if (result.visibility != Declaration::Visibility::Default) fatalParserError(string("Multiple visibility specifiers.")); - visibility = parseVisibilitySpecifier(token); + result.visibility = parseVisibilitySpecifier(token); } else break; } - ASTPointer<ParameterList> returnParameters; if (m_scanner->currentToken() == Token::Returns) { bool const permitEmptyParameterList = false; m_scanner->next(); - returnParameters = parseParameterList(options, permitEmptyParameterList); + result.returnParameters = parseParameterList(options, permitEmptyParameterList); } else - returnParameters = createEmptyParameterList(); - ASTPointer<Block> block = ASTPointer<Block>(); - nodeFactory.markEndPosition(); - if (m_scanner->currentToken() != Token::Semicolon) + result.returnParameters = createEmptyParameterList(); + return result; +} + +ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<ASTString> docstring; + if (m_scanner->currentCommentLiteral() != "") + docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); + + FunctionHeaderParserResult header = parseFunctionHeader(false, true); + + if ( + !header.modifiers.empty() || + !header.name->empty() || + m_scanner->currentToken() == Token::Semicolon || + m_scanner->currentToken() == Token::LBrace + ) { - block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + // this has to be a function + ASTPointer<Block> block = ASTPointer<Block>(); + nodeFactory.markEndPosition(); + if (m_scanner->currentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' + bool const c_isConstructor = (_contractName && *header.name == *_contractName); + return nodeFactory.createNode<FunctionDefinition>( + header.name, + header.visibility, + c_isConstructor, + docstring, + header.parameters, + header.isDeclaredConst, + header.modifiers, + header.returnParameters, + header.isPayable, + block + ); } else - m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *name == *_contractName); - return nodeFactory.createNode<FunctionDefinition>( - name, - visibility, - c_isConstructor, - docstring, - parameters, - isDeclaredConst, - modifiers, - returnParameters, - isPayable, - block - ); + { + // this has to be a state variable + ASTPointer<TypeName> type = nodeFactory.createNode<FunctionTypeName>( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); + type = parseTypeNameSuffix(type, nodeFactory); + VarDeclParserOptions options; + options.isStateVariable = true; + options.allowInitialValue = true; + auto node = parseVariableDeclaration(options, type); + expectToken(Token::Semicolon); + return node; + } } ASTPointer<StructDefinition> Parser::parseStructDefinition() @@ -611,6 +653,21 @@ ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName() return nodeFactory.createNode<UserDefinedTypeName>(identifierPath); } +ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTNodeFactory& nodeFactory) +{ + while (m_scanner->currentToken() == Token::LBrack) + { + m_scanner->next(); + ASTPointer<Expression> length; + if (m_scanner->currentToken() != Token::RBrack) + length = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + type = nodeFactory.createNode<ArrayTypeName>(type, length); + } + return type; +} + ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) { ASTNodeFactory nodeFactory(*this); @@ -631,6 +688,8 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) fatalParserError(string("Expected explicit type name.")); m_scanner->next(); } + else if (token == Token::Function) + type = parseFunctionType(); else if (token == Token::Mapping) type = parseMapping(); else if (token == Token::Identifier) @@ -640,19 +699,23 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) if (type) // Parse "[...]" postfixes for arrays. - while (m_scanner->currentToken() == Token::LBrack) - { - m_scanner->next(); - ASTPointer<Expression> length; - if (m_scanner->currentToken() != Token::RBrack) - length = parseExpression(); - nodeFactory.markEndPosition(); - expectToken(Token::RBrack); - type = nodeFactory.createNode<ArrayTypeName>(type, length); - } + type = parseTypeNameSuffix(type, nodeFactory); return type; } +ASTPointer<FunctionTypeName> Parser::parseFunctionType() +{ + ASTNodeFactory nodeFactory(*this); + FunctionHeaderParserResult header = parseFunctionHeader(true, false); + return nodeFactory.createNode<FunctionTypeName>( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); +} + ASTPointer<Mapping> Parser::parseMapping() { ASTNodeFactory nodeFactory(*this); @@ -1278,7 +1341,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const Token::Value token(m_scanner->currentToken()); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); - if (token == Token::Mapping || token == Token::Var) + if (token == Token::Mapping || token == Token::Function || token == Token::Var) return LookAheadInfo::VariableDeclarationStatement; if (mightBeTypeName) { diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 26f347cb..9295a7fa 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -53,6 +53,18 @@ private: bool allowLocationSpecifier = false; }; + /// This struct is shared for parsing a function header and a function type. + struct FunctionHeaderParserResult + { + ASTPointer<ASTString> name; + ASTPointer<ParameterList> parameters; + ASTPointer<ParameterList> returnParameters; + Declaration::Visibility visibility = Declaration::Visibility::Default; + bool isDeclaredConst = false; + bool isPayable = false; + std::vector<ASTPointer<ModifierInvocation>> modifiers; + }; + ///@{ ///@name Parsing functions for the AST nodes ASTPointer<PragmaDirective> parsePragmaDirective(); @@ -60,6 +72,8 @@ private: ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<StructDefinition> parseStructDefinition(); ASTPointer<EnumDefinition> parseEnumDefinition(); @@ -74,7 +88,9 @@ private: ASTPointer<ModifierInvocation> parseModifierInvocation(); ASTPointer<Identifier> parseIdentifier(); ASTPointer<UserDefinedTypeName> parseUserDefinedTypeName(); + ASTPointer<TypeName> parseTypeNameSuffix(ASTPointer<TypeName> type, ASTNodeFactory& nodeFactory); ASTPointer<TypeName> parseTypeName(bool _allowVar); + ASTPointer<FunctionTypeName> parseFunctionType(); ASTPointer<Mapping> parseMapping(); ASTPointer<ParameterList> parseParameterList( VarDeclParserOptions const& _options, diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 6c062ee8..b88218e7 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -197,6 +197,37 @@ BOOST_AUTO_TEST_CASE(non_utf8) BOOST_CHECK(literal["attributes"]["type"].asString().find("invalid") != string::npos); } +BOOST_AUTO_TEST_CASE(function_type) +{ + CompilerStack c; + c.addSource("a", + "contract C { function f(function() external payable returns (uint) x) " + "returns (function() external constant returns (uint)) {} }" + ); + c.parse(); + map<string, unsigned> sourceIndices; + sourceIndices["a"] = 1; + Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); + Json::Value fun = astJson["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(fun["name"], "FunctionDefinition"); + Json::Value argument = fun["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(argument["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(argument["attributes"]["name"], "x"); + BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () payable external returns (uint256)"); + Json::Value funType = argument["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], false); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); + Json::Value retval = fun["children"][1]["children"][0]; + BOOST_CHECK_EQUAL(retval["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(retval["attributes"]["name"], ""); + BOOST_CHECK_EQUAL(retval["attributes"]["type"], "function () constant external returns (uint256)"); + funType = retval["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], false); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index eddba5e1..e5ce691b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -116,10 +116,10 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr<string const> n = make_shared<string>(""); AssemblyItems items = compileContract(sourceCode); vector<SourceLocation> locations = - vector<SourceLocation>(18, SourceLocation(2, 75, n)) + - vector<SourceLocation>(31, SourceLocation(20, 72, n)) + + vector<SourceLocation>(16, SourceLocation(2, 75, n)) + + vector<SourceLocation>(27, SourceLocation(20, 72, n)) + vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + - vector<SourceLocation>(4, SourceLocation(58, 67, n)) + + vector<SourceLocation>(2, SourceLocation(58, 67, n)) + vector<SourceLocation>(3, SourceLocation(20, 72, n)); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 0ad9e928..5cabb7fa 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -684,7 +684,7 @@ BOOST_AUTO_TEST_CASE(payable_function) checkInterface(sourceCode, interface); } -BOOST_AUTO_TEST_CASE(payable_fallback_unction) +BOOST_AUTO_TEST_CASE(payable_fallback_function) { char const* sourceCode = R"( contract test { @@ -703,6 +703,32 @@ BOOST_AUTO_TEST_CASE(payable_fallback_unction) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* sourceCode = R"( + contract test { + function g(function(uint) external returns (uint) x) {} + } + )"; + + char const* interface = R"( + [ + { + "constant" : false, + "payable": false, + "inputs": [{ + "name": "x", + "type": "function" + }], + "name": "g", + "outputs": [], + "type" : "function" + } + ] + )"; + checkInterface(sourceCode, interface); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d8924250..ed95d687 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7606,6 +7606,568 @@ BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function) +{ + char const* sourceCode = R"( + contract C { + function intern() returns (uint) { + function (uint) internal returns (uint) x; + x(2); + return 7; + } + function extern() returns (uint) { + function (uint) external returns (uint) x; + x(2); + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + // This should throw exceptions + BOOST_CHECK(callContractFunction("intern()") == encodeArgs()); + BOOST_CHECK(callContractFunction("extern()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) +{ + char const* sourceCode = R"( + contract C { + function() internal returns (uint) x; + int mutex; + function t() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // Avoid re-executing this function if we jump somewhere. + x(); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(pass_function_types_internally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return eval(g, x); + } + function eval(function(uint) returns (uint) x, uint a) internal returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); +} + +BOOST_AUTO_TEST_CASE(pass_function_types_externally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return this.eval(this.g, x); + } + function f2(uint x) returns (uint) { + return eval(this.g, x); + } + function eval(function(uint) external returns (uint) x, uint a) returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); +} + +BOOST_AUTO_TEST_CASE(receive_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() returns (uint) { return 7; } + function f(function() external returns (uint) g) returns (uint) { + return g(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction( + "f(function)", + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) + ) == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(return_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() {} + function f() returns (function() external) { + return this.g; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK( + callContractFunction("f()") == + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) + ); +} + +BOOST_AUTO_TEST_CASE(store_function) +{ + char const* sourceCode = R"( + contract Other { + function addTwo(uint x) returns (uint) { return x + 2; } + } + contract C { + function (function (uint) external returns (uint)) returns (uint) ev; + function (uint) external returns (uint) x; + function store(function(uint) external returns (uint) y) { + x = y; + } + function eval(function(uint) external returns (uint) y) returns (uint) { + return y(7); + } + function t() returns (uint) { + ev = eval; + this.store((new Other()).addTwo); + return ev(x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); +} + +BOOST_AUTO_TEST_CASE(store_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + uint public result_in_constructor; + function (uint) internal returns (uint) x; + function C () { + x = double; + result_in_constructor = use(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function use(uint _arg) returns (uint) { + return x(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("use(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4))); +} + +// TODO: store bound internal library functions + +BOOST_AUTO_TEST_CASE(store_internal_unused_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + function () internal returns (uint) x; + function C () { + x = unused; + } + function unused() internal returns (uint) { + return 7; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(store_internal_unused_library_function_in_constructor) +{ + char const* sourceCode = R"( + library L { function x() internal returns (uint) { return 7; } } + contract C { + function () internal returns (uint) x; + function C () { + x = L.x; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) +{ + char const* sourceCode = R"( + contract C { + uint public initial; + function C() { + initial = double(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function runtime(uint _arg) returns (uint) { + return double(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("runtime(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4))); +} + +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime_equality_check) +{ + char const* sourceCode = R"( + contract C { + function (uint) internal returns (uint) x; + function C() { + x = double; + } + function test() returns (bool) { + return x == double; + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(function_type_library_internal) +{ + char const* sourceCode = R"( + library Utils { + function reduce(uint[] memory array, function(uint, uint) returns (uint) f, uint init) internal returns (uint) { + for (uint i = 0; i < array.length; i++) { + init = f(array[i], init); + } + return init; + } + function sum(uint a, uint b) internal returns (uint) { + return a + b; + } + } + contract C { + function f(uint[] x) returns (uint) { + return Utils.reduce(x, Utils.sum, 0); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256[])", 0x20, 3, u256(1), u256(7), u256(3)) == encodeArgs(u256(11))); +} + + +BOOST_AUTO_TEST_CASE(call_function_returning_function) +{ + char const* sourceCode = R"( + contract test { + function f0() returns (uint) { + return 2; + } + function f1() internal returns (function() returns (uint)) { + return f0; + } + function f2() internal returns (function() returns (function () returns (uint))) { + return f1; + } + function f3() internal returns (function() returns (function () returns (function () returns (uint)))) + { + return f2; + } + function f() returns (uint) { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + x = f3; + return x()()()(); + } + } + )"; + + compileAndRun(sourceCode, 0, "test"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); +} + +BOOST_AUTO_TEST_CASE(mapping_of_functions) +{ + char const* sourceCode = R"( + contract Flow { + bool public success; + + mapping (address => function () internal) stages; + + function stage0() internal { + stages[msg.sender] = stage1; + } + + function stage1() internal { + stages[msg.sender] = stage2; + } + + function stage2() internal { + success = true; + } + + function Flow() { + stages[msg.sender] = stage0; + } + + function f() returns (uint) { + stages[msg.sender](); + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "Flow"); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(packed_functions) +{ + char const* sourceCode = R"( + contract C { + // these should take the same slot + function() returns (uint) a; + function() external returns (uint) b; + function() external returns (uint) c; + function() returns (uint) d; + uint8 public x; + + function set() { + x = 2; + d = g; + c = this.h; + b = this.h; + a = g; + } + function t1() returns (uint) { + return a(); + } + function t2() returns (uint) { + return b(); + } + function t3() returns (uint) { + return a(); + } + function t4() returns (uint) { + return b(); + } + function g() returns (uint) { + return 7; + } + function h() returns (uint) { + return 8; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs()); + BOOST_CHECK(callContractFunction("t1()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("t3()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t4()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(2))); +} + +BOOST_AUTO_TEST_CASE(function_memory_array) +{ + char const* sourceCode = R"( + contract C { + function a(uint x) returns (uint) { return x + 1; } + function b(uint x) returns (uint) { return x + 2; } + function c(uint x) returns (uint) { return x + 3; } + function d(uint x) returns (uint) { return x + 5; } + function e(uint x) returns (uint) { return x + 8; } + function test(uint x, uint i) returns (uint) { + function(uint) internal returns (uint)[] memory arr = + new function(uint) internal returns (uint)[](10); + arr[0] = a; + arr[1] = b; + arr[2] = c; + arr[3] = d; + arr[4] = e; + return arr[i](x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(0)) == encodeArgs(u256(11))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(1)) == encodeArgs(u256(12))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(2)) == encodeArgs(u256(13))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(3)) == encodeArgs(u256(15))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(4)) == encodeArgs(u256(18))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(5)) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(function_delete_storage) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function() internal returns (uint) y; + function set() returns (uint) { + y = a; + return y(); + } + function d() returns (uint) { + delete y; + return 1; + } + function ca() returns (uint) { + return y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("d()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(function_delete_stack) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function test() returns (uint) { + var y = a; + delete y; + y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(copy_function_storage_array) +{ + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[] x; + function() internal returns (uint)[] y; + function test() returns (uint) { + x.length = 10; + x[9] = a; + y = x; + return y[9](); + } + function a() returns (uint) { + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(function_array_cross_calls) +{ + char const* sourceCode = R"( + contract D { + function f(function() external returns (function() external returns (uint))[] x) + returns (function() external returns (uint)[3] r) + { + r[0] = x[0](); + r[1] = x[1](); + r[2] = x[2](); + } + } + contract C { + function test() returns (uint, uint, uint) { + function() external returns (function() external returns (uint))[] memory x = + new function() external returns (function() external returns (uint))[](10); + for (uint i = 0; i < x.length; i ++) + x[i] = this.h; + x[0] = this.htwo; + var y = (new D()).f(x); + return (y[0](), y[1](), y[2]()); + } + function e() returns (uint) { return 5; } + function f() returns (uint) { return 6; } + function g() returns (uint) { return 7; } + uint counter; + function h() returns (function() external returns (uint)) { + return counter++ == 0 ? this.f : this.g; + } + function htwo() returns (function() external returns (uint)) { + return this.e; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(5), u256(6), u256(7))); +} + +BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) +{ + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[20] x; + int mutex; + function one() returns (uint) { + function() internal returns (uint)[20] xmem; + x = xmem; + return 3; + } + function two() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // If this test fails, it might re-execute this function. + x[0](); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("one()") == encodeArgs(u256(3))); + BOOST_CHECK(callContractFunction("two()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index e9a05745..91edfefd 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -325,12 +325,12 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::ADD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::MOD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::DIV), byte(Instruction::MUL)}); @@ -425,39 +425,6 @@ BOOST_AUTO_TEST_CASE(assignment) BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(function_call) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" - " function g(uint a, uint b) returns (uint c) {}\n" - "}\n"; - bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, - {{"test", "f", "a"}, {"test", "f", "b"}}); - - // Stack: a, b - bytes expectation({byte(Instruction::PUSH1), 0x02, - byte(Instruction::PUSH1), 0x0c, - byte(Instruction::PUSH1), 0x01, - byte(Instruction::DUP5), - byte(Instruction::ADD), - // Stack here: a b 2 <ret label> (a+1) - byte(Instruction::DUP4), - byte(Instruction::PUSH1), 0x13, - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), - // Stack here: a b 2 g(a+1, b) - byte(Instruction::MUL), - // Stack here: a b g(a+1, b)*2 - byte(Instruction::DUP3), - byte(Instruction::ADD), - // Stack here: a b a+g(a+1, b)*2 - byte(Instruction::SWAP2), - byte(Instruction::POP), - byte(Instruction::DUP2), - byte(Instruction::JUMPDEST)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - BOOST_AUTO_TEST_CASE(negative_literals_8bits) { char const* sourceCode = "contract test {\n" diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index f498a0b9..865eb7ce 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4132,6 +4132,231 @@ BOOST_AUTO_TEST_CASE(using_directive_for_missing_selftype) BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) returns (uint) x; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_parameter) +{ + char const* text = R"( + contract C { + function f(function(uint) external returns (uint) g) returns (function(uint) external returns (uint)) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_returned) +{ + char const* text = R"( + contract C { + function f() returns (function(uint) external returns (uint) g) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(private_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) private returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(public_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) public returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(payable_internal_function_type) +{ + char const* text = R"( + contract C { + function (uint) internal payable returns (uint) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external returns (uint) x; + function f() { + x.value(2)(); + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(external_function_type_returning_internal) +{ + char const* text = R"( + contract C { + function() external returns (function () internal) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(external_function_type_taking_internal) +{ + char const* text = R"( + contract C { + function(function () internal) external x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external payable returns (uint) x; + function f() { + x.value(2)(1); + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) +{ + // It should not be possible to give internal functions + // as parameters to external functions. + char const* text = R"( + contract C { + function f(function(uint) internal returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) +{ + // It should not be possible to return internal functions from external functions. + char const* text = R"( + contract C { + function f() returns (function(uint) internal returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal) +{ + char const* text = R"( + library L { + function f(function(uint) internal returns (uint) x) internal { + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external) +{ + char const* text = R"( + library L { + function f(function(uint) internal returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(function_type_arrays) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint)[] public x; + function(uint) internal returns (uint)[10] y; + function f() { + function(uint) returns (uint)[10] memory a; + function(uint) returns (uint)[10] storage b = y; + function(uint) external returns (uint)[] memory c; + c = new function(uint) external returns (uint)[](200); + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(delete_function_type) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint) x; + function(uint) internal returns (uint) y; + function f() { + delete x; + var a = y; + delete a; + delete y; + var c = f; + delete c; + function(uint) internal returns (uint) g; + delete g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(delete_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete this.f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 562b7859..4991cf24 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -365,16 +365,6 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions) // BOOST_CHECK_EQUAL(2, numSHA3s); } -BOOST_AUTO_TEST_CASE(successor_not_found_bug) -{ - // This bug was caused because MSVC chose to use the u256->bool conversion - // instead of u256->unsigned - char const* sourceCode = R"( - contract greeter { function greeter() {} } - )"; - compileBothVersions(sourceCode); -} - BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug) { // This bug appeared because a sha3 operation with too low sequence number was used, @@ -1238,6 +1228,24 @@ BOOST_AUTO_TEST_CASE(inconsistency) compareVersions("trigger()"); } +BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies) +{ + // This tests that a runtime-function that is stored in storage in the constructor + // is not removed as part of dead code elimination. + char const* sourceCode = R"( + contract DCE { + function () internal returns (uint) stored; + function DCE() { + stored = f; + } + function f() internal returns (uint) { return 7; } + function test() returns (uint) { return stored(); } + } + )"; + compileBothVersions(sourceCode); + compareVersions("test()"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index ec23d5fd..914dbc30 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1241,6 +1241,115 @@ BOOST_AUTO_TEST_CASE(payable_accessor) BOOST_CHECK(!successParse(text)); } +BOOST_AUTO_TEST_CASE(function_type_in_expression) +{ + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function g() { + function (uint, uint) internal returns (uint) f1 = f; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) +{ + char const* text = R"( + contract test { + function (uint, uint) internal returns (uint) f1; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_modifiers) +{ + char const* text = R"( + contract test { + function (uint, uint) modifier1() returns (uint) f1; + } + )"; + BOOST_CHECK(!successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_assignment) +{ + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function (uint, uint) internal returns (uint) f1 = f; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_in_struct) +{ + char const* text = R"( + contract test { + struct S { + function (uint x, uint y) internal returns (uint a) f; + function (uint, uint) external returns (uint) g; + uint d; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_parameter) +{ + char const* text = R"( + contract test { + function f(function(uint) external returns (uint) g) internal returns (uint a) { + return g(1); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(calling_function) +{ + char const* text = R"( + contract test { + function f() { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + uint y; + y = x()()()(); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(mapping_and_array_of_functions) +{ + char const* text = R"( + contract test { + mapping (address => function() internal returns (uint)) a; + mapping (address => function() external) b; + mapping (address => function() external[]) c; + function() external[] d; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_state_variable) +{ + char const* text = R"( + contract test { + function() x; + function() y = x; + } + )"; + BOOST_CHECK(successParse(text)); +} + + BOOST_AUTO_TEST_SUITE_END() } |