mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1122 from ethereum/firstClassFunctions
Functions as first-class types.
This commit is contained in:
commit
b46a14f4a8
@ -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
|
||||
|
124
docs/types.rst
124
docs/types.rst
@ -234,7 +234,7 @@ Hexademical Literals behave like String Literals and have the same convertibilit
|
||||
.. _enums:
|
||||
|
||||
Enums
|
||||
=====
|
||||
-----
|
||||
|
||||
Enums are one way to create a user-defined type in Solidity. They are explicitly convertible
|
||||
to and from all integer types but implicit conversion is not allowed. The explicit conversions
|
||||
@ -267,6 +267,128 @@ check the value ranges at runtime and a failure causes an exception. Enums need
|
||||
}
|
||||
}
|
||||
|
||||
.. index:: ! function type, ! type; function
|
||||
|
||||
.. _function_types:
|
||||
|
||||
Function Types
|
||||
--------------
|
||||
|
||||
Function types are the types of functions. Variables of function type
|
||||
can be assigned from functions and function parameters of function type
|
||||
can be used to pass functions to and return functions from function calls.
|
||||
Function types come in two flavours - *internal* and *external* functions:
|
||||
|
||||
Internal functions can only be used inside the current contract (more specifically,
|
||||
inside the current code unit, which also includes internal library functions
|
||||
and inherited functions) because they cannot be executed outside of the
|
||||
context of the current contract. Calling an internal function is realized
|
||||
by jumping to its entry label, just like when calling a function of the current
|
||||
contract internally.
|
||||
|
||||
External functions consist of an address and a function signature and they can
|
||||
be passed via and returned from external function calls.
|
||||
|
||||
Function types are notated as follows::
|
||||
|
||||
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]
|
||||
|
||||
In contrast to the parameter types, the return types cannot be empty - if the
|
||||
function type should not return anything, the whole ``returns (<return types>)``
|
||||
part has to be omitted.
|
||||
|
||||
By default, function types are internal, so the ``internal`` keyword can be
|
||||
omitted.
|
||||
|
||||
There are two ways to access a function in the current contract: Either directly
|
||||
by its name, ``f``, or using ``this.f``. The former will result in an internal
|
||||
function, the latter in an external function.
|
||||
|
||||
If a function type variable is not initialized, calling it will result
|
||||
in an exception. The same happens if you call a function after using ``delete``
|
||||
on it.
|
||||
|
||||
If external function types are used outside of the context of Solidity,
|
||||
they are treated as the ``function`` type, which encodes the address
|
||||
followed by the function identifier together in a single ``bytes24`` type.
|
||||
|
||||
Example that shows how to use internal function types::
|
||||
|
||||
library ArrayUtils {
|
||||
// internal functions can be used in internal library functions because
|
||||
// they will be part of the same code context
|
||||
function map(uint[] memory self, function (uint) returns (uint) f)
|
||||
returns (uint[] memory r)
|
||||
{
|
||||
r = new uint[](self.length);
|
||||
for (uint i = 0; i < self.length; i++) {
|
||||
r[i] = f(self[i]);
|
||||
}
|
||||
}
|
||||
function reduce(
|
||||
uint[] memory self,
|
||||
function (uint) returns (uint) f
|
||||
)
|
||||
returns (uint r)
|
||||
{
|
||||
r = self[0];
|
||||
for (uint i = 1; i < self.length; i++) {
|
||||
r = f(r, self[i]);
|
||||
}
|
||||
}
|
||||
function range(uint length) returns (uint[] memory r) {
|
||||
r = new uint[](length);
|
||||
for (uint i = 0; i < r.length; i++) {
|
||||
r[i] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract Pyramid {
|
||||
using ArrayUtils for *;
|
||||
function pyramid(uint l) return (uint) {
|
||||
return ArrayUtils.range(l).map(square).reduce(sum);
|
||||
}
|
||||
function square(uint x) internal returns (uint) {
|
||||
return x * x;
|
||||
}
|
||||
function sum(uint x, uint y) internal returns (uint) {
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
||||
Another example that uses external function types::
|
||||
|
||||
contract Oracle {
|
||||
struct Request {
|
||||
bytes data;
|
||||
function(bytes) external callback;
|
||||
}
|
||||
Request[] requests;
|
||||
event NewRequest(uint);
|
||||
function query(bytes data, function(bytes) external callback) {
|
||||
requests.push(Request(data, callback));
|
||||
NewRequest(requests.length - 1);
|
||||
}
|
||||
function reply(uint requestID, bytes response) {
|
||||
// Here goes the check that the reply comes from a trusted source
|
||||
requests[requestID].callback(response);
|
||||
}
|
||||
}
|
||||
|
||||
contract OracleUser {
|
||||
Oracle constant oracle = 0x1234567; // known contract
|
||||
function buySomething() {
|
||||
oracle.query("USD", oracleResponse);
|
||||
}
|
||||
function oracleResponse(bytes response) {
|
||||
if (msg.sender != oracle) throw;
|
||||
// Use the data
|
||||
}
|
||||
}
|
||||
|
||||
Note that lambda or inline functions are planned but not yet supported.
|
||||
|
||||
.. index:: ! type;reference, ! reference type, storage, memory, location, array, struct
|
||||
|
||||
Reference Types
|
||||
|
@ -20,13 +20,17 @@
|
||||
*/
|
||||
|
||||
#include "Assembly.h"
|
||||
#include <fstream>
|
||||
|
||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||
#include <libevmasm/ControlFlowGraph.h>
|
||||
#include <libevmasm/PeepholeOptimiser.h>
|
||||
#include <libevmasm/BlockDeduplicator.h>
|
||||
#include <libevmasm/ConstantOptimiser.h>
|
||||
#include <libevmasm/GasMeter.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <json/json.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
@ -75,17 +79,17 @@ string Assembly::out() const
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
unsigned Assembly::bytesRequired() const
|
||||
unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
||||
{
|
||||
for (unsigned br = 1;; ++br)
|
||||
for (unsigned tagSize = subTagSize; true; ++tagSize)
|
||||
{
|
||||
unsigned ret = 1;
|
||||
for (auto const& i: m_data)
|
||||
ret += i.second.size();
|
||||
|
||||
for (AssemblyItem const& i: m_items)
|
||||
ret += i.bytesRequired(br);
|
||||
if (dev::bytesRequired(ret) <= br)
|
||||
ret += i.bytesRequired(tagSize);
|
||||
if (dev::bytesRequired(ret) <= tagSize)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -132,13 +136,19 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
|
||||
if (i.data() == 0)
|
||||
_out << " PUSH [ErrorTag]";
|
||||
else
|
||||
_out << " PUSH [tag" << dec << i.data() << "]";
|
||||
{
|
||||
size_t subId = i.splitForeignPushTag().first;
|
||||
if (subId == size_t(-1))
|
||||
_out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]";
|
||||
else
|
||||
_out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]";
|
||||
}
|
||||
break;
|
||||
case PushSub:
|
||||
_out << " PUSH [$" << h256(i.data()).abridgedMiddle() << "]";
|
||||
_out << " PUSH [$" << size_t(i.data()) << "]";
|
||||
break;
|
||||
case PushSubSize:
|
||||
_out << " PUSH #[$" << h256(i.data()).abridgedMiddle() << "]";
|
||||
_out << " PUSH #[$" << size_t(i.data()) << "]";
|
||||
break;
|
||||
case PushProgramSize:
|
||||
_out << " PUSHSIZE";
|
||||
@ -167,7 +177,7 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
|
||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||
{
|
||||
_out << _prefix << " " << hex << i << ": " << endl;
|
||||
m_subs[i].stream(_out, _prefix + " ", _sourceCodes);
|
||||
m_subs[i]->stream(_out, _prefix + " ", _sourceCodes);
|
||||
}
|
||||
}
|
||||
return _out;
|
||||
@ -266,7 +276,7 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes
|
||||
{
|
||||
std::stringstream hexStr;
|
||||
hexStr << hex << i;
|
||||
data[hexStr.str()] = m_subs[i].stream(_out, "", _sourceCodes, true);
|
||||
data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true);
|
||||
}
|
||||
root[".data"] = data;
|
||||
_out << root;
|
||||
@ -308,41 +318,67 @@ void Assembly::injectStart(AssemblyItem const& _i)
|
||||
|
||||
Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
{
|
||||
if (!_enable)
|
||||
return *this;
|
||||
optimiseInternal(_enable, _isCreation, _runs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
map<u256, u256> Assembly::optimiseInternal(bool _enable, bool _isCreation, size_t _runs)
|
||||
{
|
||||
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
||||
{
|
||||
map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(_enable, false, _runs);
|
||||
BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId);
|
||||
}
|
||||
|
||||
map<u256, u256> tagReplacements;
|
||||
unsigned total = 0;
|
||||
for (unsigned count = 1; count > 0; total += count)
|
||||
{
|
||||
count = 0;
|
||||
|
||||
PeepholeOptimiser peepOpt(m_items);
|
||||
if (peepOpt.optimise())
|
||||
count++;
|
||||
|
||||
if (!_enable)
|
||||
continue;
|
||||
|
||||
// This only modifies PushTags, we have to run again to actually remove code.
|
||||
BlockDeduplicator dedup(m_items);
|
||||
if (dedup.deduplicate())
|
||||
{
|
||||
tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end());
|
||||
count++;
|
||||
}
|
||||
|
||||
{
|
||||
// Control flow graph that resets knowledge at path joins.
|
||||
ControlFlowGraph cfg(m_items, false);
|
||||
// Control flow graph optimization has been here before but is disabled because it
|
||||
// assumes we only jump to tags that are pushed. This is not the case anymore with
|
||||
// function types that can be stored in storage.
|
||||
AssemblyItems optimisedItems;
|
||||
for (BasicBlock const& block: cfg.optimisedBlocks())
|
||||
|
||||
auto iter = m_items.begin();
|
||||
while (iter != m_items.end())
|
||||
{
|
||||
// We used to start with the block's initial state but it caused
|
||||
// too many inconsistencies.
|
||||
auto end = iter;
|
||||
while (end != m_items.end())
|
||||
if (SemanticInformation::altersControlFlow(*end++))
|
||||
break;
|
||||
|
||||
KnownState emptyState;
|
||||
CommonSubexpressionEliminator eliminator(emptyState);
|
||||
auto iter = m_items.begin() + block.begin;
|
||||
auto const end = m_items.begin() + block.end;
|
||||
while (iter < end)
|
||||
auto blockIter = iter;
|
||||
auto const blockEnd = end;
|
||||
while (blockIter < blockEnd)
|
||||
{
|
||||
auto orig = iter;
|
||||
iter = eliminator.feedItems(iter, end);
|
||||
auto orig = blockIter;
|
||||
blockIter = eliminator.feedItems(blockIter, blockEnd);
|
||||
bool shouldReplace = false;
|
||||
AssemblyItems optimisedChunk;
|
||||
try
|
||||
{
|
||||
optimisedChunk = eliminator.getOptimizedItems();
|
||||
shouldReplace = (optimisedChunk.size() < size_t(iter - orig));
|
||||
shouldReplace = (optimisedChunk.size() < size_t(blockIter - orig));
|
||||
}
|
||||
catch (StackTooDeepException const&)
|
||||
{
|
||||
@ -361,10 +397,10 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
optimisedItems += optimisedChunk;
|
||||
}
|
||||
else
|
||||
copy(orig, iter, back_inserter(optimisedItems));
|
||||
copy(orig, blockIter, back_inserter(optimisedItems));
|
||||
}
|
||||
iter = end;
|
||||
}
|
||||
|
||||
if (optimisedItems.size() < m_items.size())
|
||||
{
|
||||
m_items = move(optimisedItems);
|
||||
@ -373,17 +409,15 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
||||
}
|
||||
}
|
||||
|
||||
total += ConstantOptimisationMethod::optimiseConstants(
|
||||
_isCreation,
|
||||
_isCreation ? 1 : _runs,
|
||||
*this,
|
||||
m_items
|
||||
);
|
||||
if (_enable)
|
||||
total += ConstantOptimisationMethod::optimiseConstants(
|
||||
_isCreation,
|
||||
_isCreation ? 1 : _runs,
|
||||
*this,
|
||||
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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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; }
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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:
|
||||
|
148
libevmasm/PeepholeOptimiser.cpp
Normal file
148
libevmasm/PeepholeOptimiser.cpp
Normal 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;
|
||||
|
||||
}
|
54
libevmasm/PeepholeOptimiser.h
Normal file
54
libevmasm/PeepholeOptimiser.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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')"
|
||||
*/
|
||||
|
@ -54,6 +54,7 @@ class MagicVariableDeclaration;
|
||||
class TypeName;
|
||||
class ElementaryTypeName;
|
||||
class UserDefinedTypeName;
|
||||
class FunctionTypeName;
|
||||
class Mapping;
|
||||
class ArrayTypeName;
|
||||
class Statement;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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--;
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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))
|
||||
|
@ -1265,6 +1265,7 @@ TypePointer ArrayType::decodingType() const
|
||||
|
||||
TypePointer ArrayType::interfaceType(bool _inLibrary) const
|
||||
{
|
||||
// Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return shared_from_this();
|
||||
|
||||
@ -1282,6 +1283,21 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const
|
||||
return make_shared<ArrayType>(DataLocation::Memory, baseExt, m_length);
|
||||
}
|
||||
|
||||
bool ArrayType::canBeUsedExternally(bool _inLibrary) const
|
||||
{
|
||||
// Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
return true;
|
||||
else if (m_arrayKind != ArrayKind::Ordinary)
|
||||
return true;
|
||||
else if (!m_baseType->canBeUsedExternally(_inLibrary))
|
||||
return false;
|
||||
else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized())
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
u256 ArrayType::memorySize() const
|
||||
{
|
||||
solAssert(!isDynamicallySized(), "");
|
||||
@ -1704,7 +1720,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
|
||||
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
|
||||
m_location(_isInternal ? Location::Internal : Location::External),
|
||||
m_isConstant(_function.isDeclaredConst()),
|
||||
m_isPayable(_function.isPayable()),
|
||||
m_isPayable(_isInternal ? false : _function.isPayable()),
|
||||
m_declaration(&_function)
|
||||
{
|
||||
TypePointers params;
|
||||
@ -1805,6 +1821,38 @@ FunctionType::FunctionType(EventDefinition const& _event):
|
||||
swap(paramNames, m_parameterNames);
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
||||
m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal),
|
||||
m_isConstant(_typeName.isDeclaredConst()),
|
||||
m_isPayable(_typeName.isPayable())
|
||||
{
|
||||
if (_typeName.isPayable())
|
||||
{
|
||||
solAssert(m_location == Location::External, "Internal payable function type used.");
|
||||
solAssert(!m_isConstant, "Payable constant function");
|
||||
}
|
||||
for (auto const& t: _typeName.parameterTypes())
|
||||
{
|
||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||
if (m_location == Location::External)
|
||||
solAssert(
|
||||
t->annotation().type->canBeUsedExternally(false),
|
||||
"Internal type used as parameter for external function."
|
||||
);
|
||||
m_parameterTypes.push_back(t->annotation().type);
|
||||
}
|
||||
for (auto const& t: _typeName.returnParameterTypes())
|
||||
{
|
||||
solAssert(t->annotation().type, "Type not set for return parameter.");
|
||||
if (m_location == Location::External)
|
||||
solAssert(
|
||||
t->annotation().type->canBeUsedExternally(false),
|
||||
"Internal type used as return parameter for external function."
|
||||
);
|
||||
m_returnParameterTypes.push_back(t->annotation().type);
|
||||
}
|
||||
}
|
||||
|
||||
FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _contract)
|
||||
{
|
||||
FunctionDefinition const* constructor = _contract.constructor();
|
||||
@ -1880,22 +1928,69 @@ bool FunctionType::operator==(Type const& _other) const
|
||||
return true;
|
||||
}
|
||||
|
||||
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
|
||||
{
|
||||
if (_operator == Token::Value::Delete)
|
||||
return make_shared<TupleType>();
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
string FunctionType::canonicalName(bool) const
|
||||
{
|
||||
solAssert(m_location == Location::External, "");
|
||||
return "function";
|
||||
}
|
||||
|
||||
string FunctionType::toString(bool _short) const
|
||||
{
|
||||
string name = "function (";
|
||||
for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it)
|
||||
name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ",");
|
||||
name += ") returns (";
|
||||
for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it)
|
||||
name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ",");
|
||||
return name + ")";
|
||||
name += ")";
|
||||
if (m_isConstant)
|
||||
name += " constant";
|
||||
if (m_isPayable)
|
||||
name += " payable";
|
||||
if (m_location == Location::External)
|
||||
name += " external";
|
||||
if (!m_returnParameterTypes.empty())
|
||||
{
|
||||
name += " returns (";
|
||||
for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it)
|
||||
name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ",");
|
||||
name += ")";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
unsigned FunctionType::calldataEncodedSize(bool _padded) const
|
||||
{
|
||||
unsigned size = storageBytes();
|
||||
if (_padded)
|
||||
size = ((size + 31) / 32) * 32;
|
||||
return size;
|
||||
}
|
||||
|
||||
u256 FunctionType::storageSize() const
|
||||
{
|
||||
BOOST_THROW_EXCEPTION(
|
||||
InternalCompilerError()
|
||||
<< errinfo_comment("Storage size of non-storable function type requested."));
|
||||
if (m_location == Location::External || m_location == Location::Internal)
|
||||
return 1;
|
||||
else
|
||||
BOOST_THROW_EXCEPTION(
|
||||
InternalCompilerError()
|
||||
<< errinfo_comment("Storage size of non-storable function type requested."));
|
||||
}
|
||||
|
||||
unsigned FunctionType::storageBytes() const
|
||||
{
|
||||
if (m_location == Location::External)
|
||||
return 20 + 4;
|
||||
else if (m_location == Location::Internal)
|
||||
return 8; // it should really not be possible to create larger programs
|
||||
else
|
||||
BOOST_THROW_EXCEPTION(
|
||||
InternalCompilerError()
|
||||
<< errinfo_comment("Storage size of non-storable function type requested."));
|
||||
}
|
||||
|
||||
unsigned FunctionType::sizeOnStack() const
|
||||
@ -2018,6 +2113,23 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
||||
}
|
||||
}
|
||||
|
||||
TypePointer FunctionType::encodingType() const
|
||||
{
|
||||
// Only external functions can be encoded, internal functions cannot leave code boundaries.
|
||||
if (m_location == Location::External)
|
||||
return shared_from_this();
|
||||
else
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const
|
||||
{
|
||||
if (m_location == Location::External)
|
||||
return shared_from_this();
|
||||
else
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const
|
||||
{
|
||||
solAssert(!bound() || _selfType, "");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -133,6 +133,16 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
|
||||
m_context << u256(str->value().size());
|
||||
m_context << Instruction::ADD;
|
||||
}
|
||||
else if (
|
||||
_type.category() == Type::Category::Function &&
|
||||
dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External
|
||||
)
|
||||
{
|
||||
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
|
||||
combineExternalFunctionType(true);
|
||||
m_context << Instruction::DUP2 << Instruction::MSTORE;
|
||||
m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
|
||||
@ -206,7 +216,8 @@ void CompilerUtils::encodeToMemory(
|
||||
else if (
|
||||
_givenTypes[i]->dataStoredIn(DataLocation::Storage) ||
|
||||
_givenTypes[i]->dataStoredIn(DataLocation::CallData) ||
|
||||
_givenTypes[i]->category() == Type::Category::StringLiteral
|
||||
_givenTypes[i]->category() == Type::Category::StringLiteral ||
|
||||
_givenTypes[i]->category() == Type::Category::Function
|
||||
)
|
||||
type = _givenTypes[i]; // delay conversion
|
||||
else
|
||||
@ -304,6 +315,49 @@ void CompilerUtils::memoryCopy()
|
||||
m_context << Instruction::POP; // ignore return value
|
||||
}
|
||||
|
||||
void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
|
||||
{
|
||||
// We have to split the left-aligned <address><function identifier> into two stack slots:
|
||||
// address (right aligned), function identifier (right aligned)
|
||||
if (_leftAligned)
|
||||
{
|
||||
m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV;
|
||||
// <input> <address>
|
||||
m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
|
||||
m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1;
|
||||
}
|
||||
m_context << u256(0xffffffffUL) << Instruction::AND;
|
||||
}
|
||||
|
||||
void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
|
||||
{
|
||||
// <address> <function_id>
|
||||
m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1;
|
||||
if (!_leftAligned)
|
||||
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
|
||||
m_context << (u256(1) << 32) << Instruction::MUL;
|
||||
m_context << Instruction::OR;
|
||||
if (_leftAligned)
|
||||
m_context << (u256(1) << 64) << Instruction::MUL;
|
||||
}
|
||||
|
||||
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
|
||||
{
|
||||
m_context << m_context.functionEntryLabel(_function).pushTag();
|
||||
// If there is a runtime context, we have to merge both labels into the same
|
||||
// stack slot in case we store it in storage.
|
||||
if (CompilerContext* rtc = m_context.runtimeContext())
|
||||
m_context <<
|
||||
(u256(1) << 32) <<
|
||||
Instruction::MUL <<
|
||||
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
|
||||
Instruction::OR;
|
||||
}
|
||||
|
||||
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded)
|
||||
{
|
||||
// For a type extension, we need to remove all higher-order bits that we might have ignored in
|
||||
@ -678,6 +732,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
||||
|
||||
void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
{
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
{
|
||||
if (funType->location() == FunctionType::Location::Internal)
|
||||
{
|
||||
m_context << m_context.errorTag();
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto const* referenceType = dynamic_cast<ReferenceType const*>(&_type);
|
||||
if (!referenceType || referenceType->location() == DataLocation::Storage)
|
||||
{
|
||||
@ -822,21 +884,27 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
|
||||
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
|
||||
{
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries);
|
||||
bool leftAligned = _type.category() == Type::Category::FixedBytes;
|
||||
bool isExternalFunctionType = false;
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
if (funType->location() == FunctionType::Location::External)
|
||||
isExternalFunctionType = true;
|
||||
if (numBytes == 0)
|
||||
m_context << Instruction::POP << u256(0);
|
||||
else
|
||||
{
|
||||
solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested.");
|
||||
m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD);
|
||||
if (numBytes != 32)
|
||||
{
|
||||
// add leading or trailing zeros by dividing/multiplying depending on alignment
|
||||
u256 shiftFactor = u256(1) << ((32 - numBytes) * 8);
|
||||
m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV;
|
||||
if (leftAligned)
|
||||
m_context << shiftFactor << Instruction::MUL;
|
||||
}
|
||||
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 (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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -153,7 +153,8 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
|
||||
{
|
||||
if (m_dataType->isValueType())
|
||||
{
|
||||
solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), "");
|
||||
if (m_dataType->category() != Type::Category::Function)
|
||||
solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), "");
|
||||
solAssert(m_dataType->storageSize() == 1, "Invalid storage size.");
|
||||
}
|
||||
}
|
||||
@ -176,6 +177,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
||||
m_context << Instruction::POP << Instruction::SLOAD;
|
||||
else
|
||||
{
|
||||
bool cleaned = false;
|
||||
m_context
|
||||
<< Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1
|
||||
<< u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV;
|
||||
@ -183,14 +185,31 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
||||
// implementation should be very similar to the integer case.
|
||||
solUnimplemented("Not yet implemented - FixedPointType.");
|
||||
if (m_dataType->category() == Type::Category::FixedBytes)
|
||||
{
|
||||
m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL;
|
||||
cleaned = true;
|
||||
}
|
||||
else if (
|
||||
m_dataType->category() == Type::Category::Integer &&
|
||||
dynamic_cast<IntegerType const&>(*m_dataType).isSigned()
|
||||
)
|
||||
{
|
||||
m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND;
|
||||
else
|
||||
cleaned = true;
|
||||
}
|
||||
else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
|
||||
{
|
||||
if (fun->location() == FunctionType::Location::External)
|
||||
{
|
||||
CompilerUtils(m_context).splitExternalFunctionType(false);
|
||||
cleaned = true;
|
||||
}
|
||||
}
|
||||
if (!cleaned)
|
||||
{
|
||||
solAssert(m_dataType->sizeOnStack() == 1, "");
|
||||
m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +223,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size.");
|
||||
if (m_dataType->storageBytes() == 32)
|
||||
{
|
||||
solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size.");
|
||||
// offset should be zero
|
||||
m_context << Instruction::POP;
|
||||
if (!_move)
|
||||
@ -222,16 +242,27 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
m_context
|
||||
<< Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1)
|
||||
<< Instruction::MUL;
|
||||
m_context << Instruction::NOT << Instruction::AND;
|
||||
// stack: value storage_ref multiplier cleared_value
|
||||
m_context
|
||||
<< Instruction::SWAP1 << Instruction::DUP4;
|
||||
m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1;
|
||||
// stack: value storage_ref cleared_value multiplier
|
||||
utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack());
|
||||
// stack: value storage_ref cleared_value multiplier value
|
||||
if (m_dataType->category() == Type::Category::FixedBytes)
|
||||
if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
|
||||
{
|
||||
if (fun->location() == FunctionType::Location::External)
|
||||
// Combine the two-item function type into a single stack slot.
|
||||
utils.combineExternalFunctionType(false);
|
||||
else
|
||||
m_context <<
|
||||
((u256(1) << (8 * m_dataType->storageBytes())) - 1) <<
|
||||
Instruction::AND;
|
||||
}
|
||||
else if (m_dataType->category() == Type::Category::FixedBytes)
|
||||
m_context
|
||||
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()))
|
||||
<< Instruction::SWAP1 << Instruction::DIV;
|
||||
else
|
||||
{
|
||||
solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type.");
|
||||
// remove the higher order bits
|
||||
m_context
|
||||
<< (u256(1) << (8 * (32 - m_dataType->storageBytes())))
|
||||
@ -239,11 +270,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
<< Instruction::DUP2
|
||||
<< Instruction::MUL
|
||||
<< Instruction::DIV;
|
||||
}
|
||||
m_context << Instruction::MUL << Instruction::OR;
|
||||
// stack: value storage_ref updated_value
|
||||
m_context << Instruction::SWAP1 << Instruction::SSTORE;
|
||||
if (_move)
|
||||
m_context << Instruction::POP;
|
||||
utils.popStackElement(*m_dataType);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -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* '}'
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <libsolidity/interface/InterfaceHandler.h>
|
||||
#include <libsolidity/formal/Why3Translator.h>
|
||||
|
||||
#include <libevmasm/Exceptions.h>
|
||||
#include <libdevcore/SHA3.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -590,9 +591,19 @@ void CompilerStack::compileContract(
|
||||
compiledContract.runtimeObject = compiler->runtimeObject();
|
||||
_compiledContracts[compiledContract.contract] = &compiler->assembly();
|
||||
|
||||
Compiler cloneCompiler(_optimize, _runs);
|
||||
cloneCompiler.compileClone(_contract, _compiledContracts);
|
||||
compiledContract.cloneObject = cloneCompiler.assembledObject();
|
||||
try
|
||||
{
|
||||
Compiler cloneCompiler(_optimize, _runs);
|
||||
cloneCompiler.compileClone(_contract, _compiledContracts);
|
||||
compiledContract.cloneObject = cloneCompiler.assembledObject();
|
||||
}
|
||||
catch (eth::AssemblyException const&)
|
||||
{
|
||||
// In some cases (if the constructor requests a runtime function), it is not
|
||||
// possible to compile the clone.
|
||||
|
||||
// TODO: Report error / warning
|
||||
}
|
||||
}
|
||||
|
||||
std::string CompilerStack::defaultContractName() const
|
||||
|
@ -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";
|
||||
|
@ -217,7 +217,9 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
|
||||
if (currentTokenValue == Token::RBrace)
|
||||
break;
|
||||
else if (currentTokenValue == Token::Function)
|
||||
subNodes.push_back(parseFunctionDefinition(name.get()));
|
||||
// This can be a function or a state variable of function type (especially
|
||||
// complicated to distinguish fallback function from function type state variable)
|
||||
subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get()));
|
||||
else if (currentTokenValue == Token::Struct)
|
||||
subNodes.push_back(parseStructDefinition());
|
||||
else if (currentTokenValue == Token::Enum)
|
||||
@ -288,81 +290,121 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
|
||||
return visibility;
|
||||
}
|
||||
|
||||
ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* _contractName)
|
||||
Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers)
|
||||
{
|
||||
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)
|
||||
// this has to be a function
|
||||
ASTPointer<Block> block = ASTPointer<Block>();
|
||||
nodeFactory.markEndPosition();
|
||||
if (m_scanner->currentToken() != Token::Semicolon)
|
||||
{
|
||||
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);
|
||||
block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
ASTPointer<ParameterList> returnParameters;
|
||||
if (m_scanner->currentToken() == Token::Returns)
|
||||
{
|
||||
bool const permitEmptyParameterList = false;
|
||||
m_scanner->next();
|
||||
returnParameters = parseParameterList(options, permitEmptyParameterList);
|
||||
m_scanner->next(); // just consume the ';'
|
||||
bool const c_isConstructor = (_contractName && *header.name == *_contractName);
|
||||
return nodeFactory.createNode<FunctionDefinition>(
|
||||
header.name,
|
||||
header.visibility,
|
||||
c_isConstructor,
|
||||
docstring,
|
||||
header.parameters,
|
||||
header.isDeclaredConst,
|
||||
header.modifiers,
|
||||
header.returnParameters,
|
||||
header.isPayable,
|
||||
block
|
||||
);
|
||||
}
|
||||
else
|
||||
returnParameters = createEmptyParameterList();
|
||||
ASTPointer<Block> block = ASTPointer<Block>();
|
||||
nodeFactory.markEndPosition();
|
||||
if (m_scanner->currentToken() != Token::Semicolon)
|
||||
{
|
||||
block = parseBlock();
|
||||
nodeFactory.setEndPositionFromNode(block);
|
||||
// this has to be a state variable
|
||||
ASTPointer<TypeName> type = nodeFactory.createNode<FunctionTypeName>(
|
||||
header.parameters,
|
||||
header.returnParameters,
|
||||
header.visibility,
|
||||
header.isDeclaredConst,
|
||||
header.isPayable
|
||||
);
|
||||
type = parseTypeNameSuffix(type, nodeFactory);
|
||||
VarDeclParserOptions options;
|
||||
options.isStateVariable = true;
|
||||
options.allowInitialValue = true;
|
||||
auto node = parseVariableDeclaration(options, type);
|
||||
expectToken(Token::Semicolon);
|
||||
return node;
|
||||
}
|
||||
else
|
||||
m_scanner->next(); // just consume the ';'
|
||||
bool const c_isConstructor = (_contractName && *name == *_contractName);
|
||||
return nodeFactory.createNode<FunctionDefinition>(
|
||||
name,
|
||||
visibility,
|
||||
c_isConstructor,
|
||||
docstring,
|
||||
parameters,
|
||||
isDeclaredConst,
|
||||
modifiers,
|
||||
returnParameters,
|
||||
isPayable,
|
||||
block
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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"(
|
||||
|
@ -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"
|
||||
|
@ -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"(
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user