Merge pull request #1122 from ethereum/firstClassFunctions

Functions as first-class types.
This commit is contained in:
chriseth 2016-11-17 17:32:21 +01:00 committed by GitHub
commit b46a14f4a8
49 changed files with 2250 additions and 340 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
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,6 +409,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
}
}
if (_enable)
total += ConstantOptimisationMethod::optimiseConstants(
_isCreation,
_isCreation ? 1 : _runs,
@ -380,10 +417,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
m_items
);
for (auto& sub: m_subs)
sub.optimise(true, false, _runs);
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)
{

View File

@ -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;

View File

@ -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";

View File

@ -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; }

View File

@ -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)

View File

@ -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;
};

View File

@ -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:

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -822,6 +822,41 @@ private:
std::vector<ASTString> m_namePath;
};
/**
* 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')"
*/

View File

@ -54,6 +54,7 @@ class MagicVariableDeclaration;
class TypeName;
class ElementaryTypeName;
class UserDefinedTypeName;
class FunctionTypeName;
class Mapping;
class ArrayTypeName;
class Statement;

View File

@ -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();

View File

@ -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;

View File

@ -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--;

View File

@ -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;

View File

@ -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); }

View File

@ -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))

View File

@ -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,19 +1928,66 @@ 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 (";
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() ? "" : ",");
return name + ")";
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
{
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."));
@ -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, "");

View File

@ -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

View File

@ -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

View File

@ -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;
};
}

View File

@ -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(

View File

@ -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); }
eth::Assembly const& assembly() const { return m_asm; }
/// @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; }
/// @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;
};
}

View File

@ -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,22 +884,28 @@ 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
{
m_context << Instruction::POP << u256(0);
return numBytes;
}
solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested.");
m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD);
if (numBytes != 32)
if (isExternalFunctionType)
splitExternalFunctionType(true);
else if (numBytes != 32)
{
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;
}

View File

@ -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.

View File

@ -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;
// We have to include copies of functions in the construction time and runtime context
// because of absolute jumps.
appendMissingFunctions();
m_runtimeCompiler->appendMissingFunctions();
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;
// note that we have to include the functions again because of absolute jump labels
appendMissingFunctions();
solAssert(runtimeSub.data() < numeric_limits<size_t>::max(), "");
return size_t(runtimeSub.data());
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);
}

View File

@ -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

View File

@ -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;

View File

@ -153,6 +153,7 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
{
if (m_dataType->isValueType())
{
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,15 +185,32 @@ 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;
}
}
}
void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const
@ -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

View File

@ -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* '}'

View File

@ -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();
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

View File

@ -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";

View File

@ -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,59 +290,80 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
return visibility;
}
ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* _contractName)
Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers)
{
FunctionHeaderParserResult result;
expectToken(Token::Function);
if (_forceEmptyName || m_scanner->currentToken() == Token::LParen)
result.name = make_shared<ASTString>(); // anonymous function
else
result.name = expectIdentifierToken();
VarDeclParserOptions options;
options.allowLocationSpecifier = true;
result.parameters = parseParameterList(options);
while (true)
{
Token::Value token = m_scanner->currentToken();
if (token == Token::Const)
{
result.isDeclaredConst = true;
m_scanner->next();
}
else if (m_scanner->currentToken() == Token::Payable)
{
result.isPayable = true;
m_scanner->next();
}
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 (result.visibility != Declaration::Visibility::Default)
fatalParserError(string("Multiple visibility specifiers."));
result.visibility = parseVisibilitySpecifier(token);
}
else
break;
}
if (m_scanner->currentToken() == Token::Returns)
{
bool const permitEmptyParameterList = false;
m_scanner->next();
result.returnParameters = parseParameterList(options, permitEmptyParameterList);
}
else
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());
expectToken(Token::Function);
ASTPointer<ASTString> name;
if (m_scanner->currentToken() == Token::LParen)
name = make_shared<ASTString>(); // anonymous function
else
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;
while (true)
FunctionHeaderParserResult header = parseFunctionHeader(false, true);
if (
!header.modifiers.empty() ||
!header.name->empty() ||
m_scanner->currentToken() == Token::Semicolon ||
m_scanner->currentToken() == Token::LBrace
)
{
Token::Value token = m_scanner->currentToken();
if (token == Token::Const)
{
isDeclaredConst = true;
m_scanner->next();
}
else if (m_scanner->currentToken() == Token::Payable)
{
isPayable = true;
m_scanner->next();
}
else if (token == Token::Identifier)
modifiers.push_back(parseModifierInvocation());
else if (Token::isVisibilitySpecifier(token))
{
if (visibility != Declaration::Visibility::Default)
fatalParserError(string("Multiple visibility specifiers."));
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);
}
else
returnParameters = createEmptyParameterList();
// this has to be a function
ASTPointer<Block> block = ASTPointer<Block>();
nodeFactory.markEndPosition();
if (m_scanner->currentToken() != Token::Semicolon)
@ -350,19 +373,38 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const*
}
else
m_scanner->next(); // just consume the ';'
bool const c_isConstructor = (_contractName && *name == *_contractName);
bool const c_isConstructor = (_contractName && *header.name == *_contractName);
return nodeFactory.createNode<FunctionDefinition>(
name,
visibility,
header.name,
header.visibility,
c_isConstructor,
docstring,
parameters,
isDeclaredConst,
modifiers,
returnParameters,
isPayable,
header.parameters,
header.isDeclaredConst,
header.modifiers,
header.returnParameters,
header.isPayable,
block
);
}
else
{
// 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)
{

View File

@ -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,

View File

@ -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()
}

View File

@ -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);
}

View File

@ -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()
}

View File

@ -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"(

View File

@ -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"

View File

@ -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"(

View File

@ -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()
}

View File

@ -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()
}