Browse Source

Merge remote-tracking branch 'upstream/develop' into evmjit

cl-refactor
Paweł Bylica 10 years ago
parent
commit
06965dec1f
  1. 9
      alethzero/MainWin.cpp
  2. 20
      docker/Dockerfile
  3. 28
      libethereum/Client.cpp
  4. 4
      libethereum/Client.h
  5. 3
      libevm/ExtVMFace.h
  6. 58
      libsolidity/AST.cpp
  7. 16
      libsolidity/AST.h
  8. 304
      libsolidity/ArrayUtils.cpp
  9. 78
      libsolidity/ArrayUtils.h
  10. 173
      libsolidity/Compiler.cpp
  11. 7
      libsolidity/Compiler.h
  12. 33
      libsolidity/CompilerContext.cpp
  13. 8
      libsolidity/CompilerContext.h
  14. 10
      libsolidity/CompilerStack.cpp
  15. 18
      libsolidity/CompilerStack.h
  16. 170
      libsolidity/CompilerUtils.cpp
  17. 13
      libsolidity/CompilerUtils.h
  18. 21
      libsolidity/ExpressionCompiler.cpp
  19. 108
      libsolidity/LValue.cpp
  20. 44
      libsolidity/LValue.h
  21. 10
      libsolidity/SourceReferenceFormatter.cpp
  22. 42
      libsolidity/Types.cpp
  23. 1
      libsolidity/Types.h
  24. 1
      libweb3jsonrpc/WebThreeStubServerBase.cpp
  25. 4
      libwhisper/WhisperHost.cpp
  26. 18
      mix/ClientModel.cpp
  27. 2
      mix/CodeModel.cpp
  28. 10
      mix/CodeModel.h
  29. 9
      mix/DebuggingStateWrapper.cpp
  30. 30
      mix/DebuggingStateWrapper.h
  31. 13
      mix/MachineStates.h
  32. 6
      mix/MixClient.cpp
  33. 43
      mix/qml/CodeEditorView.qml
  34. 50
      mix/qml/Debugger.qml
  35. 25
      mix/qml/MainContent.qml
  36. 47
      mix/qml/StatusPane.qml
  37. 28
      mix/qml/WebCodeEditor.qml
  38. 4
      mix/qml/html/cm/codemirror.css
  39. 6
      mix/qml/html/cm/solarized.css
  40. 50
      mix/qml/html/codeeditor.js
  41. BIN
      mix/qml/img/search_filled.png
  42. 147
      mix/qml/js/Debugger.js
  43. 4
      mix/qml/js/ProjectModel.js
  44. 3
      mix/qml/js/TransactionHelper.js
  45. 24
      mix/qml/main.qml
  46. 1
      mix/res.qrc
  47. 261
      test/SolidityEndToEndTest.cpp
  48. 109
      test/SolidityNameAndTypeResolution.cpp

9
alethzero/MainWin.cpp

@ -1382,11 +1382,16 @@ void Main::on_blocks_currentItemChanged()
s << "<br/>Pre: <b>" << BlockInfo(ethereum()->blockChain().block(info.parentHash)).stateRoot << "</b>"; s << "<br/>Pre: <b>" << BlockInfo(ethereum()->blockChain().block(info.parentHash)).stateRoot << "</b>";
else else
s << "<br/>Pre: <i>Nothing is before Phil</i>"; s << "<br/>Pre: <i>Nothing is before Phil</i>";
BlockReceipts receipts = ethereum()->blockChain().receipts(h);
unsigned ii = 0;
for (auto const& i: block[1]) for (auto const& i: block[1])
s << "<br/>" << sha3(i.data()).abridged();// << ": <b>" << i[1].toHash<h256>() << "</b> [<b>" << i[2].toInt<u256>() << "</b> used]"; {
s << "<br/>" << sha3(i.data()).abridged() << ": <b>" << receipts.receipts[ii].stateRoot() << "</b> [<b>" << receipts.receipts[ii].gasUsed() << "</b> used]";
++ii;
}
s << "<br/>Post: <b>" << info.stateRoot << "</b>"; s << "<br/>Post: <b>" << info.stateRoot << "</b>";
s << "<br/>Dump: " Span(Mono) << toHex(block[0].data()) << "</span>"; s << "<br/>Dump: " Span(Mono) << toHex(block[0].data()) << "</span>";
s << "<div>Receipts-Hex: " Span(Mono) << toHex(ethereum()->blockChain().receipts(h).rlp()) << "</span></div>"; s << "<div>Receipts-Hex: " Span(Mono) << toHex(receipts.rlp()) << "</span></div>";
} }
else else
{ {

20
docker/Dockerfile

@ -8,23 +8,29 @@ RUN apt-get upgrade -y
RUN apt-get install -qy build-essential g++-4.8 git cmake libboost-all-dev libcurl4-openssl-dev wget RUN apt-get install -qy build-essential g++-4.8 git cmake libboost-all-dev libcurl4-openssl-dev wget
RUN apt-get install -qy automake unzip libgmp-dev libtool libleveldb-dev yasm libminiupnpc-dev libreadline-dev scons RUN apt-get install -qy automake unzip libgmp-dev libtool libleveldb-dev yasm libminiupnpc-dev libreadline-dev scons
RUN apt-get install -qy libjsoncpp-dev libargtable2-dev RUN apt-get install -qy libjsoncpp-dev libargtable2-dev
RUN apt-get install -qy libncurses5-dev libcurl4-openssl-dev wget
# NCurses based GUI (not optional though for a succesful compilation, see https://github.com/ethereum/cpp-ethereum/issues/452 ) RUN apt-get install -qy libjsoncpp-dev libargtable2-dev libmicrohttpd-dev
RUN apt-get install -qy libncurses5-dev
# Qt-based GUI
# RUN apt-get install -qy qtbase5-dev qt5-default qtdeclarative5-dev libqt5webkit5-dev
# Ethereum PPA # Ethereum PPA
RUN apt-get install -qy software-properties-common RUN apt-get install -qy software-properties-common
RUN add-apt-repository ppa:ethereum/ethereum RUN add-apt-repository ppa:ethereum/ethereum
RUN add-apt-repository ppa:ethereum/ethereum-dev
RUN apt-get update RUN apt-get update
RUN apt-get install -qy libcryptopp-dev libjson-rpc-cpp-dev RUN apt-get install -qy libcryptopp-dev libjson-rpc-cpp-dev
# LLVM-3.5
RUN wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add -
RUN echo "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.5 main\ndeb-src http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.5 main" > /etc/apt/sources.list.d/llvm-trusty.list
RUN apt-get update
RUN apt-get install -qy llvm-3.5 libedit-dev
# Fix llvm-3.5 cmake paths
RUN mkdir -p /usr/lib/llvm-3.5/share/llvm && ln -s /usr/share/llvm-3.5/cmake /usr/lib/llvm-3.5/share/llvm/cmake
# Build Ethereum (HEADLESS) # Build Ethereum (HEADLESS)
RUN git clone --depth=1 https://github.com/ethereum/cpp-ethereum RUN git clone --depth=1 https://github.com/ethereum/cpp-ethereum
RUN mkdir -p cpp-ethereum/build RUN mkdir -p cpp-ethereum/build
RUN cd cpp-ethereum/build && cmake .. -DCMAKE_BUILD_TYPE=Release -DHEADLESS=1 && make -j $(cat /proc/cpuinfo | grep processor | wc -l) && make install RUN cd cpp-ethereum/build && cmake .. -DHEADLESS=1 -DLLVM_DIR=/usr/share/llvm-3.5/cmake -DEVMJIT=1 && make -j $(cat /proc/cpuinfo | grep processor | wc -l) && make install
RUN ldconfig RUN ldconfig
ENTRYPOINT ["/usr/local/bin/eth"] ENTRYPOINT ["/usr/local/bin/eth"]

28
libethereum/Client.cpp

@ -291,7 +291,7 @@ LocalisedLogEntries Client::checkWatch(unsigned _watchId)
return ret; return ret;
} }
void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed) void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3)
{ {
Guard l(m_filterLock); Guard l(m_filterLock);
for (pair<h256 const, InstalledFilter>& i: m_filters) for (pair<h256 const, InstalledFilter>& i: m_filters)
@ -303,7 +303,7 @@ void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& i
{ {
// filter catches them // filter catches them
for (LogEntry const& l: m) for (LogEntry const& l: m)
i.second.changes.push_back(LocalisedLogEntry(l, m_bc.number() + 1)); i.second.changes.push_back(LocalisedLogEntry(l, m_bc.number() + 1, _sha3));
io_changed.insert(i.first); io_changed.insert(i.first);
} }
} }
@ -319,14 +319,16 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed)
for (pair<h256 const, InstalledFilter>& i: m_filters) for (pair<h256 const, InstalledFilter>& i: m_filters)
if ((unsigned)i.second.filter.latest() >= d.number && (unsigned)i.second.filter.earliest() <= d.number && i.second.filter.matches(d.logBloom)) if ((unsigned)i.second.filter.latest() >= d.number && (unsigned)i.second.filter.earliest() <= d.number && i.second.filter.matches(d.logBloom))
// acceptable number & looks like block may contain a matching log entry. // acceptable number & looks like block may contain a matching log entry.
for (TransactionReceipt const& tr: br.receipts) for (size_t j = 0; j < br.receipts.size(); j++)
{ {
auto tr = br.receipts[j];
auto m = i.second.filter.matches(tr); auto m = i.second.filter.matches(tr);
if (m.size()) if (m.size())
{ {
auto sha3 = transaction(d.hash, j).sha3();
// filter catches them // filter catches them
for (LogEntry const& l: m) for (LogEntry const& l: m)
i.second.changes.push_back(LocalisedLogEntry(l, (unsigned)d.number)); i.second.changes.push_back(LocalisedLogEntry(l, (unsigned)d.number, sha3));
io_changed.insert(i.first); io_changed.insert(i.first);
} }
} }
@ -582,8 +584,9 @@ void Client::doWork()
TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq); TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq);
if (newPendingReceipts.size()) if (newPendingReceipts.size())
{ {
for (auto i: newPendingReceipts) for (size_t i = 0; i < newPendingReceipts.size(); i++)
appendFromNewPending(i, changeds); appendFromNewPending(newPendingReceipts[i], changeds, m_postMine.pending()[i].sha3());
changeds.insert(PendingChangedFilter); changeds.insert(PendingChangedFilter);
if (isMining()) if (isMining())
@ -756,6 +759,7 @@ LocalisedLogEntries Client::logs(LogFilter const& _f) const
{ {
// Might have a transaction that contains a matching log. // Might have a transaction that contains a matching log.
TransactionReceipt const& tr = m_postMine.receipt(i); TransactionReceipt const& tr = m_postMine.receipt(i);
auto sha3 = m_postMine.pending()[i].sha3();
LogEntries le = _f.matches(tr); LogEntries le = _f.matches(tr);
if (le.size()) if (le.size())
{ {
@ -763,7 +767,7 @@ LocalisedLogEntries Client::logs(LogFilter const& _f) const
if (s) if (s)
s--; s--;
else else
ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin)); ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, sha3));
} }
} }
begin = m_bc.number(); begin = m_bc.number();
@ -782,11 +786,15 @@ LocalisedLogEntries Client::logs(LogFilter const& _f) const
int total = 0; int total = 0;
#endif #endif
// check block bloom // check block bloom
if (_f.matches(m_bc.info(h).logBloom)) auto info = m_bc.info(h);
for (TransactionReceipt receipt: m_bc.receipts(h).receipts) auto receipts = m_bc.receipts(h).receipts;
if (_f.matches(info.logBloom))
for (size_t i = 0; i < receipts.size(); i++)
{ {
TransactionReceipt receipt = receipts[i];
if (_f.matches(receipt.bloom())) if (_f.matches(receipt.bloom()))
{ {
auto h = transaction(info.hash, i).sha3();
LogEntries le = _f.matches(receipt); LogEntries le = _f.matches(receipt);
if (le.size()) if (le.size())
{ {
@ -798,7 +806,7 @@ LocalisedLogEntries Client::logs(LogFilter const& _f) const
if (s) if (s)
s--; s--;
else else
ret.insert(ret.begin(), LocalisedLogEntry(le[j], n)); ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, h));
} }
} }
} }

4
libethereum/Client.h

@ -220,7 +220,7 @@ public:
/// @returns the length of the chain. /// @returns the length of the chain.
virtual unsigned number() const { return m_bc.number(); } virtual unsigned number() const { return m_bc.number(); }
/// Get a map containing each of the pending transactions. /// Get the list of pending transactions.
/// @TODO: Remove in favour of transactions(). /// @TODO: Remove in favour of transactions().
virtual Transactions pending() const { return m_postMine.pending(); } virtual Transactions pending() const { return m_postMine.pending(); }
@ -311,7 +311,7 @@ public:
protected: protected:
/// Collate the changed filters for the bloom filter of the given pending transaction. /// Collate the changed filters for the bloom filter of the given pending transaction.
/// Insert any filters that are activated into @a o_changed. /// Insert any filters that are activated into @a o_changed.
void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed); void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3);
/// Collate the changed filters for the hash of the given block. /// Collate the changed filters for the hash of the given block.
/// Insert any filters that are activated into @a o_changed. /// Insert any filters that are activated into @a o_changed.

3
libevm/ExtVMFace.h

@ -63,9 +63,10 @@ using LogEntries = std::vector<LogEntry>;
struct LocalisedLogEntry: public LogEntry struct LocalisedLogEntry: public LogEntry
{ {
LocalisedLogEntry() {} LocalisedLogEntry() {}
LocalisedLogEntry(LogEntry const& _le, unsigned _number): LogEntry(_le), number(_number) {} LocalisedLogEntry(LogEntry const& _le, unsigned _number, h256 _sha3 = {}): LogEntry(_le), number(_number), sha3(_sha3) {}
unsigned number = 0; unsigned number = 0;
h256 sha3;
}; };
using LocalisedLogEntries = std::vector<LocalisedLogEntry>; using LocalisedLogEntries = std::vector<LocalisedLogEntry>;

58
libsolidity/AST.cpp

@ -209,6 +209,33 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
return *m_interfaceFunctionList; return *m_interfaceFunctionList;
} }
vector<Declaration const*> const& ContractDefinition::getInheritableMembers() const
{
if (!m_inheritableMembers)
{
set<string> memberSeen;
m_inheritableMembers.reset(new vector<Declaration const*>());
auto addInheritableMember = [&](Declaration const* _decl)
{
if (memberSeen.count(_decl->getName()) == 0 && _decl->isVisibleInDerivedContracts())
{
memberSeen.insert(_decl->getName());
m_inheritableMembers->push_back(_decl);
}
};
for (ASTPointer<FunctionDefinition> const& f: getDefinedFunctions())
addInheritableMember(f.get());
for (ASTPointer<VariableDeclaration> const& v: getStateVariables())
addInheritableMember(v.get());
for (ASTPointer<StructDefinition> const& s: getDefinedStructs())
addInheritableMember(s.get());
}
return *m_inheritableMembers;
}
TypePointer EnumValue::getType(ContractDefinition const*) const TypePointer EnumValue::getType(ContractDefinition const*) const
{ {
EnumDefinition const* parentDef = dynamic_cast<EnumDefinition const*>(getScope()); EnumDefinition const* parentDef = dynamic_cast<EnumDefinition const*>(getScope());
@ -281,7 +308,9 @@ void FunctionDefinition::checkTypeRequirements()
if (!var->getType()->canLiveOutsideStorage()) if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers) for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
modifier->checkTypeRequirements(); modifier->checkTypeRequirements(isConstructor() ?
dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() :
vector<ASTPointer<InheritanceSpecifier>>());
m_body->checkTypeRequirements(); m_body->checkTypeRequirements();
} }
@ -324,19 +353,34 @@ void ModifierDefinition::checkTypeRequirements()
m_body->checkTypeRequirements(); m_body->checkTypeRequirements();
} }
void ModifierInvocation::checkTypeRequirements() void ModifierInvocation::checkTypeRequirements(vector<ASTPointer<InheritanceSpecifier>> const& _bases)
{ {
m_modifierName->checkTypeRequirements(); m_modifierName->checkTypeRequirements();
for (ASTPointer<Expression> const& argument: m_arguments) for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements(); argument->checkTypeRequirements();
ModifierDefinition const* modifier = dynamic_cast<ModifierDefinition const*>(m_modifierName->getReferencedDeclaration()); auto declaration = m_modifierName->getReferencedDeclaration();
solAssert(modifier, "Function modifier not found."); vector<ASTPointer<VariableDeclaration>> emptyParameterList;
vector<ASTPointer<VariableDeclaration>> const& parameters = modifier->getParameters(); vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
if (parameters.size() != m_arguments.size()) if (auto modifier = dynamic_cast<ModifierDefinition const*>(declaration))
parameters = &modifier->getParameters();
else
// check parameters for Base constructors
for (auto const& base: _bases)
if (declaration == base->getName()->getReferencedDeclaration())
{
if (auto referencedConstructor = dynamic_cast<ContractDefinition const&>(*declaration).getConstructor())
parameters = &referencedConstructor->getParameters();
else
parameters = &emptyParameterList;
break;
}
if (!parameters)
BOOST_THROW_EXCEPTION(createTypeError("Referenced declaration is neither modifier nor base class."));
if (parameters->size() != m_arguments.size())
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for modifier invocation.")); BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for modifier invocation."));
for (size_t i = 0; i < m_arguments.size(); ++i) for (size_t i = 0; i < m_arguments.size(); ++i)
if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*(*parameters)[i]->getType()))
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in modifier invocation.")); BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in modifier invocation."));
} }

16
libsolidity/AST.h

@ -144,7 +144,7 @@ public:
Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; } Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; }
bool isPublic() const { return getVisibility() >= Visibility::Public; } bool isPublic() const { return getVisibility() >= Visibility::Public; }
bool isVisibleInContract() const { return getVisibility() != Visibility::External; } bool isVisibleInContract() const { return getVisibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Internal; } virtual bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Internal; }
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step. /// Available only after name and type resolution step.
@ -247,6 +247,9 @@ public:
/// as intended for use by the ABI. /// as intended for use by the ABI.
std::map<FixedHash<4>, FunctionTypePointer> getInterfaceFunctions() const; std::map<FixedHash<4>, FunctionTypePointer> getInterfaceFunctions() const;
/// @returns a list of the inheritable members of this contract
std::vector<Declaration const*> const& getInheritableMembers() const;
/// List of all (direct and indirect) base contracts in order from derived to base, including /// List of all (direct and indirect) base contracts in order from derived to base, including
/// the contract itself. Available after name resolution /// the contract itself. Available after name resolution
std::vector<ContractDefinition const*> const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } std::vector<ContractDefinition const*> const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; }
@ -273,6 +276,7 @@ private:
std::vector<ContractDefinition const*> m_linearizedBaseContracts; std::vector<ContractDefinition const*> m_linearizedBaseContracts;
mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<ASTPointer<EventDefinition>>> m_interfaceEvents; mutable std::unique_ptr<std::vector<ASTPointer<EventDefinition>>> m_interfaceEvents;
mutable std::unique_ptr<std::vector<Declaration const*>> m_inheritableMembers;
}; };
class InheritanceSpecifier: public ASTNode class InheritanceSpecifier: public ASTNode
@ -405,6 +409,11 @@ public:
ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; } ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; }
Block const& getBody() const { return *m_body; } Block const& getBody() const { return *m_body; }
virtual bool isVisibleInDerivedContracts() const override
{
return !isConstructor() && !getName().empty() && isVisibleInContract() &&
getVisibility() >= Visibility::Internal;
}
virtual TypePointer getType(ContractDefinition const*) const override; virtual TypePointer getType(ContractDefinition const*) const override;
/// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
@ -501,7 +510,7 @@ private:
}; };
/** /**
* Invocation/usage of a modifier in a function header. * Invocation/usage of a modifier in a function header or a base constructor call.
*/ */
class ModifierInvocation: public ASTNode class ModifierInvocation: public ASTNode
{ {
@ -516,7 +525,8 @@ public:
ASTPointer<Identifier> const& getName() const { return m_modifierName; } ASTPointer<Identifier> const& getName() const { return m_modifierName; }
std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; } std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; }
void checkTypeRequirements(); /// @param _bases is the list of base contracts for base constructor calls. For modifiers an empty vector should be passed.
void checkTypeRequirements(std::vector<ASTPointer<InheritanceSpecifier>> const& _bases);
private: private:
ASTPointer<Identifier> m_modifierName; ASTPointer<Identifier> m_modifierName;

304
libsolidity/ArrayUtils.cpp

@ -0,0 +1,304 @@
/*
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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Code generation utils that handle arrays.
*/
#include <libsolidity/ArrayUtils.h>
#include <libevmcore/Instruction.h>
#include <libsolidity/CompilerContext.h>
#include <libsolidity/CompilerUtils.h>
#include <libsolidity/Types.h>
#include <libsolidity/Utils.h>
#include <libsolidity/LValue.h>
using namespace std;
using namespace dev;
using namespace solidity;
void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const
{
// stack layout: [source_ref] target_ref (top)
// need to leave target_ref on the stack at the end
solAssert(_targetType.getLocation() == ArrayType::Location::Storage, "");
IntegerType uint256(256);
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType());
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType());
switch (_sourceType.getLocation())
{
case ArrayType::Location::CallData:
{
solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here.");
solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here.");
// This also assumes that after "length" we only have zeros, i.e. it cannot be used to
// slice a byte array from calldata.
// stack: source_offset source_len target_ref
// fetch old length and convert to words
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
convertLengthToSize(_targetType);
// stack here: source_offset source_len target_ref target_length_words
// actual array data is stored at SHA3(storage_offset)
m_context << eth::Instruction::DUP2;
CompilerUtils(m_context).computeHashStatic();
// compute target_data_end
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
// stack here: source_offset source_len target_ref target_data_end target_data_ref
// store length (in bytes)
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5
<< eth::Instruction::SSTORE;
// jump to end if length is zero
m_context << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// store start offset
m_context << eth::Instruction::DUP5;
// stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart
// copy from calldata and store
<< eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE
// increment target_data_ref by 1
<< eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD
// increment calldata_offset by 32
<< eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD
// check for loop condition
<< eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT;
m_context.appendConditionalJumpTo(copyLoopStart);
m_context << eth::Instruction::POP;
m_context << copyLoopEnd;
// now clear leftover bytes of the old value
// stack now: source_offset source_len target_ref target_data_end target_data_ref
clearStorageLoop(IntegerType(256));
// stack now: source_offset source_len target_ref target_data_end
m_context << eth::Instruction::POP << eth::Instruction::SWAP2
<< eth::Instruction::POP << eth::Instruction::POP;
break;
}
case ArrayType::Location::Storage:
{
// this copies source to target and also clears target if it was larger
solAssert(sourceBaseType->getStorageSize() == targetBaseType->getStorageSize(),
"Copying with different storage sizes not yet implemented.");
// stack: source_ref target_ref
// store target_ref
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
// stack: target_ref source_ref target_ref
// fetch lengthes
retrieveLength(_targetType);
m_context << eth::Instruction::SWAP2;
// stack: target_ref target_len target_ref source_ref
retrieveLength(_sourceType);
// stack: target_ref target_len target_ref source_ref source_len
if (_targetType.isDynamicallySized())
// store new target length
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE;
// compute hashes (data positions)
m_context << eth::Instruction::SWAP2;
if (_targetType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic();
m_context << eth::Instruction::SWAP1;
if (_sourceType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic();
// stack: target_ref target_len source_len target_data_pos source_data_pos
m_context << eth::Instruction::DUP4;
convertLengthToSize(_sourceType);
m_context << eth::Instruction::DUP4;
convertLengthToSize(_sourceType);
// stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size
// @todo we might be able to go without a third counter
m_context << u256(0);
// stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// copy
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD;
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
m_context << eth::dupInstruction(5 + sourceBaseType->getSizeOnStack())
<< eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << eth::Instruction::ADD;
StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
// increment
m_context << targetBaseType->getStorageSize() << eth::Instruction::ADD;
m_context.appendJumpTo(copyLoopStart);
m_context << copyLoopEnd;
// zero-out leftovers in target
// stack: target_ref target_len source_len target_data_pos source_data_pos target_size source_size counter
// add counter to target_data_pos
m_context << eth::Instruction::DUP5 << eth::Instruction::ADD
<< eth::Instruction::SWAP5 << eth::Instruction::POP;
// stack: target_ref target_len target_data_pos_updated target_data_pos source_data_pos target_size source_size
// add size to target_data_pos to get target_data_end
m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SWAP4
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
// stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop(*targetBaseType);
m_context << eth::Instruction::POP;
break;
}
default:
solAssert(false, "Given byte array location not implemented.");
}
}
void ArrayUtils::clearArray(ArrayType const& _type) const
{
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
if (_type.isDynamicallySized())
clearDynamicArray(_type);
else if (_type.getLength() == 0)
m_context << eth::Instruction::POP;
else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value
{
for (unsigned i = 1; i < _type.getLength(); ++i)
{
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false);
m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD;
}
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true);
}
else
{
m_context
<< eth::Instruction::DUP1 << u256(_type.getLength())
<< u256(_type.getBaseType()->getStorageSize())
<< eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1;
clearStorageLoop(*_type.getBaseType());
m_context << eth::Instruction::POP;
}
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
{
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), "");
// fetch length
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// set length to zero
m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE;
// stack: ref old_length
convertLengthToSize(_type);
// compute data positions
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
// stack: len data_pos (len is in slots for byte array and in items for other arrays)
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
// stack: data_pos_end data_pos
if (_type.isByteArray())
clearStorageLoop(IntegerType(256));
else
clearStorageLoop(*_type.getBaseType());
// cleanup
m_context << eth::Instruction::POP;
}
void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
{
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), "");
eth::AssemblyItem resizeEnd = m_context.newTag();
// stack: ref new_length
// fetch old length
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
// stack: ref new_length old_length
// store new length
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP4 << eth::Instruction::SSTORE;
// skip if size is not reduced
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP2
<< eth::Instruction::ISZERO << eth::Instruction::GT;
m_context.appendConditionalJumpTo(resizeEnd);
// size reduced, clear the end of the array
// stack: ref new_length old_length
convertLengthToSize(_type);
m_context << eth::Instruction::DUP2;
convertLengthToSize(_type);
// stack: ref new_length old_size new_size
// compute data positions
m_context << eth::Instruction::DUP4;
CompilerUtils(m_context).computeHashStatic();
// stack: ref new_length old_size new_size data_pos
m_context << eth::Instruction::SWAP2 << eth::Instruction::DUP3 << eth::Instruction::ADD;
// stack: ref new_length data_pos new_size delete_end
m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD;
// stack: ref new_length delete_end delete_start
if (_type.isByteArray())
clearStorageLoop(IntegerType(256));
else
clearStorageLoop(*_type.getBaseType());
m_context << resizeEnd;
// cleanup
m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
}
void ArrayUtils::clearStorageLoop(Type const& _type) const
{
// stack: end_pos pos
eth::AssemblyItem loopStart = m_context.newTag();
m_context << loopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd);
// delete
StorageItem(m_context, _type).setToZero(SourceLocation(), false);
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(loopStart);
// cleanup
m_context << zeroLoopEnd;
m_context << eth::Instruction::POP;
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType) const
{
if (_arrayType.isByteArray())
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
else if (_arrayType.getBaseType()->getStorageSize() > 1)
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
}
void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
{
if (_arrayType.isDynamicallySized())
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
else
m_context << _arrayType.getLength();
}

78
libsolidity/ArrayUtils.h

@ -0,0 +1,78 @@
/*
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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Code generation utils that handle arrays.
*/
#pragma once
namespace dev
{
namespace solidity
{
class CompilerContext;
class Type;
class ArrayType;
/**
* Class that provides code generation for handling arrays.
*/
class ArrayUtils
{
public:
ArrayUtils(CompilerContext& _context): m_context(_context) {}
/// Copies an array to an array in storage. The arrays can be of different types only if
/// their storage representation is the same.
/// Stack pre: [source_reference] target_reference
/// Stack post: target_reference
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
/// Clears the given dynamic or static array.
/// Stack pre: reference
/// Stack post:
void clearArray(ArrayType const& _type) const;
/// Clears the length and data elements of the array referenced on the stack.
/// Stack pre: reference
/// Stack post:
void clearDynamicArray(ArrayType const& _type) const;
/// Changes the size of a dynamic array and clears the tail if it is shortened.
/// Stack pre: reference new_length
/// Stack post:
void resizeDynamicArray(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
void clearStorageLoop(Type const& _type) const;
/// Converts length to size (multiplies by size on stack), rounds up for byte arrays.
/// Stack pre: length
/// Stack post: size
void convertLengthToSize(ArrayType const& _arrayType) const;
/// Retrieves the length (number of elements) of the array ref on the stack. This also
/// works for statically-sized arrays.
/// Stack pre: reference
/// Stack post: reference length
void retrieveLength(ArrayType const& _arrayType) const;
private:
CompilerContext& m_context;
};
}
}

173
libsolidity/Compiler.cpp

@ -58,7 +58,10 @@ void Compiler::compileContract(ContractDefinition const& _contract,
while (!functions.empty()) while (!functions.empty())
{ {
for (Declaration const* function: functions) for (Declaration const* function: functions)
{
m_context.setStackOffset(0);
function->accept(*this); function->accept(*this);
}
functions = m_context.getFunctionsWithoutCode(); functions = m_context.getFunctionsWithoutCode();
} }
@ -79,37 +82,38 @@ void Compiler::initializeContext(ContractDefinition const& _contract,
void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext)
{ {
// arguments for base constructors, filled in derived-to-base order
map<ContractDefinition const*, vector<ASTPointer<Expression>> const*> baseArguments;
// Determine the arguments that are used for the base constructors. // Determine the arguments that are used for the base constructors.
std::vector<ContractDefinition const*> const& bases = _contract.getLinearizedBaseContracts(); std::vector<ContractDefinition const*> const& bases = _contract.getLinearizedBaseContracts();
for (ContractDefinition const* contract: bases) for (ContractDefinition const* contract: bases)
{
if (FunctionDefinition const* constructor = contract->getConstructor())
for (auto const& modifier: constructor->getModifiers())
{
auto baseContract = dynamic_cast<ContractDefinition const*>(
modifier->getName()->getReferencedDeclaration());
if (baseContract)
if (m_baseArguments.count(baseContract->getConstructor()) == 0)
m_baseArguments[baseContract->getConstructor()] = &modifier->getArguments();
}
for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts()) for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts())
{ {
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>( ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
base->getName()->getReferencedDeclaration()); base->getName()->getReferencedDeclaration());
solAssert(baseContract, ""); solAssert(baseContract, "");
if (baseArguments.count(baseContract) == 0)
baseArguments[baseContract] = &base->getArguments();
}
// Call constructors in base-to-derived order. if (m_baseArguments.count(baseContract->getConstructor()) == 0)
// The Constructor for the most derived contract is called later. m_baseArguments[baseContract->getConstructor()] = &base->getArguments();
for (unsigned i = 1; i < bases.size(); i++)
{
ContractDefinition const* base = bases[bases.size() - i];
solAssert(base, "");
initializeStateVariables(*base);
FunctionDefinition const* baseConstructor = base->getConstructor();
if (!baseConstructor)
continue;
solAssert(baseArguments[base], "");
appendBaseConstructorCall(*baseConstructor, *baseArguments[base]);
} }
initializeStateVariables(_contract); }
if (_contract.getConstructor()) // Initialization of state variables in base-to-derived order.
appendConstructorCall(*_contract.getConstructor()); for (ContractDefinition const* contract: boost::adaptors::reverse(bases))
initializeStateVariables(*contract);
if (FunctionDefinition const* constructor = _contract.getConstructor())
appendConstructor(*constructor);
else if (auto c = m_context.getNextConstructor(_contract))
appendBaseConstructor(*c);
eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly()); eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly());
// stack contains sub size // stack contains sub size
@ -126,22 +130,23 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp
} }
} }
void Compiler::appendBaseConstructorCall(FunctionDefinition const& _constructor, void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor)
vector<ASTPointer<Expression>> const& _arguments)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_constructor); CompilerContext::LocationSetter locationSetter(m_context, &_constructor);
FunctionType constructorType(_constructor); FunctionType constructorType(_constructor);
eth::AssemblyItem returnLabel = m_context.pushNewTag(); if (!constructorType.getParameterTypes().empty())
for (unsigned i = 0; i < _arguments.size(); ++i) {
compileExpression(*_arguments[i], constructorType.getParameterTypes()[i]); std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor];
m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); solAssert(arguments, "");
m_context << returnLabel; for (unsigned i = 0; i < arguments->size(); ++i)
compileExpression(*(arguments->at(i)), constructorType.getParameterTypes()[i]);
}
_constructor.accept(*this);
} }
void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) void Compiler::appendConstructor(FunctionDefinition const& _constructor)
{ {
CompilerContext::LocationSetter locationSetter(m_context, &_constructor); CompilerContext::LocationSetter locationSetter(m_context, &_constructor);
eth::AssemblyItem returnTag = m_context.pushNewTag();
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
unsigned argumentSize = 0; unsigned argumentSize = 0;
for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters()) for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters())
@ -155,8 +160,7 @@ void Compiler::appendConstructorCall(FunctionDefinition const& _constructor)
m_context << eth::Instruction::CODECOPY; m_context << eth::Instruction::CODECOPY;
appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true); appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true);
} }
m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); _constructor.accept(*this);
m_context << returnTag;
} }
void Compiler::appendFunctionSelector(ContractDefinition const& _contract) void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
@ -201,42 +205,49 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool
{ {
// We do not check the calldata size, everything is zero-padded. // We do not check the calldata size, everything is zero-padded.
unsigned offset(CompilerUtils::dataStartOffset); unsigned offset(CompilerUtils::dataStartOffset);
bool const c_padToWords = true;
unsigned dynamicParameterCount = 0; bigint parameterHeadEnd = offset;
for (TypePointer const& type: _typeParameters) for (TypePointer const& type: _typeParameters)
if (type->isDynamicallySized()) parameterHeadEnd += type->isDynamicallySized() ? 32 :
dynamicParameterCount++; CompilerUtils::getPaddedSize(type->getCalldataEncodedSize());
offset += dynamicParameterCount * 32; solAssert(parameterHeadEnd <= numeric_limits<unsigned>::max(), "Arguments too large.");
unsigned currentDynamicParameter = 0;
unsigned stackHeightOfPreviousDynamicArgument = 0;
ArrayType const* previousDynamicType = nullptr;
for (TypePointer const& type: _typeParameters) for (TypePointer const& type: _typeParameters)
{
switch (type->getCategory())
{
case Type::Category::Array:
if (type->isDynamicallySized()) if (type->isDynamicallySized())
{ {
// value on stack: [calldata_offset] (only if we are already in dynamic mode) // put on stack: data_offset length
if (currentDynamicParameter == 0) unsigned newStackHeight = m_context.getStackHeight();
// switch from static to dynamic if (previousDynamicType)
m_context << u256(offset); {
// retrieve length // Retrieve data start offset by adding length to start offset of previous dynamic type
CompilerUtils(m_context).loadFromMemory( unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument;
CompilerUtils::dataStartOffset + currentDynamicParameter * 32, m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth);
IntegerType(256), !_fromMemory, c_padToWords); ArrayUtils(m_context).convertLengthToSize(*previousDynamicType);
// stack: offset length m_context << u256(32) << eth::Instruction::MUL << eth::Instruction::ADD;
// add 32-byte padding to copy of length
m_context << u256(32) << eth::Instruction::DUP1 << u256(31)
<< eth::Instruction::DUP4 << eth::Instruction::ADD
<< eth::Instruction::DIV << eth::Instruction::MUL;
// stack: offset length padded_length
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
currentDynamicParameter++;
// stack: offset length next_calldata_offset
} }
else if (currentDynamicParameter == 0)
// we can still use static load
offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, c_padToWords);
else else
CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, c_padToWords); m_context << u256(parameterHeadEnd);
if (dynamicParameterCount > 0) stackHeightOfPreviousDynamicArgument = newStackHeight;
m_context << eth::Instruction::POP; previousDynamicType = &dynamic_cast<ArrayType const&>(*type);
offset += CompilerUtils(m_context).loadFromMemory(offset, IntegerType(256), !_fromMemory);
}
else
{
m_context << u256(offset);
offset += CompilerUtils::getPaddedSize(type->getCalldataEncodedSize());
}
break;
default:
solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString());
offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, true);
}
}
} }
void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
@ -296,28 +307,36 @@ bool Compiler::visit(FunctionDefinition const& _function)
// although note that this reduces the size of the visible stack // although note that this reduces the size of the visible stack
m_context.startFunction(_function); m_context.startFunction(_function);
m_returnTag = m_context.newTag();
m_breakTags.clear();
m_continueTags.clear();
m_stackCleanupForReturn = 0;
m_currentFunction = &_function;
m_modifierDepth = 0;
// stack upon entry: [return address] [arg0] [arg1] ... [argn] // stack upon entry: [return address] [arg0] [arg1] ... [argn]
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters());
m_context.adjustStackOffset(parametersSize); if (!_function.isConstructor())
// adding 1 for return address.
m_context.adjustStackOffset(parametersSize + 1);
for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters()) for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters())
{ {
m_context.addVariable(*variable, parametersSize); m_context.addVariable(*variable, parametersSize);
parametersSize -= variable->getType()->getSizeOnStack(); parametersSize -= variable->getType()->getSizeOnStack();
} }
for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters()) for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters())
m_context.addAndInitializeVariable(*variable); m_context.addAndInitializeVariable(*variable);
for (VariableDeclaration const* localVariable: _function.getLocalVariables()) for (VariableDeclaration const* localVariable: _function.getLocalVariables())
m_context.addAndInitializeVariable(*localVariable); m_context.addAndInitializeVariable(*localVariable);
if (_function.isConstructor())
if (auto c = m_context.getNextConstructor(dynamic_cast<ContractDefinition const&>(*_function.getScope())))
appendBaseConstructor(*c);
m_returnTag = m_context.newTag();
m_breakTags.clear();
m_continueTags.clear();
m_stackCleanupForReturn = 0;
m_currentFunction = &_function;
m_modifierDepth = 0;
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
m_context << m_returnTag; m_context << m_returnTag;
@ -352,8 +371,14 @@ bool Compiler::visit(FunctionDefinition const& _function)
} }
//@todo assert that everything is in place now //@todo assert that everything is in place now
m_context << eth::Instruction::JUMP; for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters() + _function.getReturnParameters())
m_context.removeVariable(*variable);
for (VariableDeclaration const* localVariable: _function.getLocalVariables())
m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-c_returnValuesSize);
if (!_function.isConstructor())
m_context << eth::Instruction::JUMP;
return false; return false;
} }
@ -515,6 +540,16 @@ void Compiler::appendModifierOrFunctionCode()
else else
{ {
ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth];
// constructor call should be excluded
if (dynamic_cast<ContractDefinition const*>(modifierInvocation->getName()->getReferencedDeclaration()))
{
++m_modifierDepth;
appendModifierOrFunctionCode();
--m_modifierDepth;
return;
}
ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName());
CompilerContext::LocationSetter locationSetter(m_context, &modifier); CompilerContext::LocationSetter locationSetter(m_context, &modifier);
solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), "");

7
libsolidity/Compiler.h

@ -55,9 +55,8 @@ private:
/// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// 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. /// with a new and initialized context. Adds the constructor code.
void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext);
void appendBaseConstructorCall(FunctionDefinition const& _constructor, void appendBaseConstructor(FunctionDefinition const& _constructor);
std::vector<ASTPointer<Expression>> const& _arguments); void appendConstructor(FunctionDefinition const& _constructor);
void appendConstructorCall(FunctionDefinition const& _constructor);
void appendFunctionSelector(ContractDefinition const& _contract); void appendFunctionSelector(ContractDefinition const& _contract);
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data. /// From memory if @a _fromMemory is true, otherwise from call data.
@ -94,6 +93,8 @@ private:
unsigned m_modifierDepth = 0; unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction; FunctionDefinition const* m_currentFunction;
unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments;
}; };
} }

33
libsolidity/CompilerContext.cpp

@ -51,8 +51,6 @@ void CompilerContext::addStateVariable(VariableDeclaration const& _declaration)
void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::startFunction(Declaration const& _function)
{ {
m_functionsWithCode.insert(&_function); m_functionsWithCode.insert(&_function);
m_localVariables.clear();
m_asm.setDeposit(0);
*this << getFunctionEntryLabel(_function); *this << getFunctionEntryLabel(_function);
} }
@ -63,6 +61,12 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent;
} }
void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
{
solAssert(m_localVariables.count(&_declaration), "");
m_localVariables.erase(&_declaration);
}
void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration)
{ {
LocationSetter locationSetter(*this, &_declaration); LocationSetter locationSetter(*this, &_declaration);
@ -110,11 +114,8 @@ eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefiniti
eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base) eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base)
{ {
// search for first contract after _base auto it = getSuperContract(_base);
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); for (; it != m_inheritanceHierarchy.end(); ++it)
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_base);
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy.");
for (++it; it != m_inheritanceHierarchy.end(); ++it)
for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions())
if (!function->isConstructor() && function->getName() == _name) if (!function->isConstructor() && function->getName() == _name)
return getFunctionEntryLabel(*function); return getFunctionEntryLabel(*function);
@ -122,6 +123,16 @@ eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _nam
return m_asm.newTag(); // not reached return m_asm.newTag(); // not reached
} }
FunctionDefinition const* CompilerContext::getNextConstructor(ContractDefinition const& _contract) const
{
vector<ContractDefinition const*>::const_iterator it = getSuperContract(_contract);
for (; it != m_inheritanceHierarchy.end(); ++it)
if ((*it)->getConstructor())
return (*it)->getConstructor();
return nullptr;
}
set<Declaration const*> CompilerContext::getFunctionsWithoutCode() set<Declaration const*> CompilerContext::getFunctionsWithoutCode()
{ {
set<Declaration const*> functions; set<Declaration const*> functions;
@ -201,5 +212,13 @@ CompilerContext& CompilerContext::operator<<(bytes const& _data)
return *this; return *this;
} }
vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract);
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy.");
return ++it;
}
} }
} }

8
libsolidity/CompilerContext.h

@ -43,11 +43,13 @@ public:
void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addMagicGlobal(MagicVariableDeclaration const& _declaration);
void addStateVariable(VariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration);
void addAndInitializeVariable(VariableDeclaration const& _declaration); void addAndInitializeVariable(VariableDeclaration const& _declaration);
void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; } void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; }
bytes const& getCompiledContract(ContractDefinition const& _contract) const; bytes const& getCompiledContract(ContractDefinition const& _contract) const;
void setStackOffset(int _offset) { m_asm.setDeposit(_offset); }
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); }
unsigned getStackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } unsigned getStackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); }
@ -62,6 +64,8 @@ public:
/// @returns the entry label of function with the given name from the most derived class just /// @returns the entry label of function with the given name from the most derived class just
/// above _base in the current inheritance hierarchy. /// above _base in the current inheritance hierarchy.
eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base); eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base);
FunctionDefinition const* getNextConstructor(ContractDefinition const& _contract) const;
/// @returns the set of functions for which we still need to generate code /// @returns the set of functions for which we still need to generate code
std::set<Declaration const*> getFunctionsWithoutCode(); std::set<Declaration const*> getFunctionsWithoutCode();
/// Resets function specific members, inserts the function entry label and marks the function /// Resets function specific members, inserts the function entry label and marks the function
@ -126,9 +130,11 @@ public:
LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node):
ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); }
}; };
eth::Assembly m_asm;
private: private:
std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const;
eth::Assembly m_asm;
/// Magic global variables like msg, tx or this, distinguished by type. /// Magic global variables like msg, tx or this, distinguished by type.
std::set<Declaration const*> m_magicGlobals; std::set<Declaration const*> m_magicGlobals;
/// Other already compiled contracts to be used in contract creation calls. /// Other already compiled contracts to be used in contract creation calls.

10
libsolidity/CompilerStack.cpp

@ -155,6 +155,16 @@ bytes const& CompilerStack::compile(string const& _sourceCode, bool _optimize)
return getBytecode(); return getBytecode();
} }
eth::AssemblyItems const& CompilerStack::getAssemblyItems(string const& _contractName) const
{
return getContract(_contractName).compiler->getAssemblyItems();
}
eth::AssemblyItems const& CompilerStack::getRuntimeAssemblyItems(string const& _contractName) const
{
return getContract(_contractName).compiler->getRuntimeAssemblyItems();
}
bytes const& CompilerStack::getBytecode(string const& _contractName) const bytes const& CompilerStack::getBytecode(string const& _contractName) const
{ {
return getContract(_contractName).bytecode; return getContract(_contractName).bytecode;

18
libsolidity/CompilerStack.h

@ -26,12 +26,22 @@
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <libdevcore/FixedHash.h> #include <libdevcore/FixedHash.h>
namespace dev { namespace dev
namespace solidity { {
namespace eth
{
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
}
namespace solidity
{
// forward declarations // forward declarations
class Scanner; class Scanner;
@ -85,6 +95,10 @@ public:
bytes const& getBytecode(std::string const& _contractName = "") const; bytes const& getBytecode(std::string const& _contractName = "") const;
/// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor. /// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
bytes const& getRuntimeBytecode(std::string const& _contractName = "") const; bytes const& getRuntimeBytecode(std::string const& _contractName = "") const;
/// @returns normal contract assembly items
eth::AssemblyItems const& getAssemblyItems(std::string const& _contractName = "") const;
/// @returns runtime contract assembly items
eth::AssemblyItems const& getRuntimeAssemblyItems(std::string const& _contractName = "") const;
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor. /// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
dev::h256 getContractCodeHash(std::string const& _contractName = "") const; dev::h256 getContractCodeHash(std::string const& _contractName = "") const;

170
libsolidity/CompilerUtils.cpp

@ -164,134 +164,6 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari
m_context << u256(length) << u256(0) << eth::Instruction::SHA3; m_context << u256(length) << u256(0) << eth::Instruction::SHA3;
} }
void CompilerUtils::copyByteArrayToStorage(
ArrayType const& _targetType, ArrayType const& _sourceType) const
{
// stack layout: [source_ref] target_ref (top)
// need to leave target_ref on the stack at the end
solAssert(_targetType.getLocation() == ArrayType::Location::Storage, "");
solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here.");
solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here.");
switch (_sourceType.getLocation())
{
case ArrayType::Location::CallData:
{
// This also assumes that after "length" we only have zeros, i.e. it cannot be used to
// slice a byte array from calldata.
// stack: source_offset source_len target_ref
// fetch old length and convert to words
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
// stack here: source_offset source_len target_ref target_length_words
// actual array data is stored at SHA3(storage_offset)
m_context << eth::Instruction::DUP2;
CompilerUtils(m_context).computeHashStatic();
// compute target_data_end
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
// stack here: source_offset source_len target_ref target_data_end target_data_ref
// store length (in bytes)
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5
<< eth::Instruction::SSTORE;
// jump to end if length is zero
m_context << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// store start offset
m_context << eth::Instruction::DUP5;
// stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart
// copy from calldata and store
<< eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE
// increment target_data_ref by 1
<< eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD
// increment calldata_offset by 32
<< eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD
// check for loop condition
<< eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT;
m_context.appendConditionalJumpTo(copyLoopStart);
m_context << eth::Instruction::POP;
m_context << copyLoopEnd;
// now clear leftover bytes of the old value
// stack now: source_offset source_len target_ref target_data_end target_data_ref
clearStorageLoop();
// stack now: source_offset source_len target_ref target_data_end
m_context << eth::Instruction::POP << eth::Instruction::SWAP2
<< eth::Instruction::POP << eth::Instruction::POP;
break;
}
case ArrayType::Location::Storage:
{
// this copies source to target and also clears target if it was larger
// stack: source_ref target_ref
// store target_ref
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
// fetch lengthes
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2
<< eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack: target_ref target_len_bytes target_ref source_ref source_len_bytes
// store new target length
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE;
// compute hashes (data positions)
m_context << eth::Instruction::SWAP2;
CompilerUtils(m_context).computeHashStatic();
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos
// convert lengthes from bytes to storage slots
m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV
<< eth::Instruction::SWAP2
<< eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV;
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len
// @todo we might be able to go without a third counter
m_context << u256(0);
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// copy
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SLOAD
<< eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SSTORE;
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(copyLoopStart);
m_context << copyLoopEnd;
// zero-out leftovers in target
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter
// add counter to target_data_pos
m_context << eth::Instruction::DUP5 << eth::Instruction::ADD
<< eth::Instruction::SWAP5 << eth::Instruction::POP;
// stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len
// add length to target_data_pos to get target_data_end
m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SWAP4
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
// stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop();
m_context << eth::Instruction::POP;
break;
}
default:
solAssert(false, "Given byte array location not implemented.");
}
}
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{ {
unsigned _encodedSize = _type.getCalldataEncodedSize(); unsigned _encodedSize = _type.getCalldataEncodedSize();
@ -316,28 +188,6 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
return numBytes; return numBytes;
} }
void CompilerUtils::clearByteArray(ArrayType const& _type) const
{
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isByteArray(), "Non byte arrays not yet implemented here.");
// fetch length
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// set length to zero
m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE;
// convert length from bytes to storage slots
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
// compute data positions
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
// stack: len data_pos
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
clearStorageLoop();
// cleanup
m_context << eth::Instruction::POP;
}
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{ {
@ -356,25 +206,5 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBou
return numBytes; return numBytes;
} }
void CompilerUtils::clearStorageLoop() const
{
// stack: end_pos pos
eth::AssemblyItem loopStart = m_context.newTag();
m_context << loopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd);
// zero out
m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE;
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(loopStart);
// cleanup
m_context << zeroLoopEnd;
m_context << eth::Instruction::POP;
}
} }
} }

13
libsolidity/CompilerUtils.h

@ -79,15 +79,6 @@ public:
/// @note Only works for types of fixed size. /// @note Only works for types of fixed size.
void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false); void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false);
/// Copies a byte array to a byte array in storage.
/// Stack pre: [source_reference] target_reference
/// Stack post: target_reference
void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
/// Clears the length and data elements of the byte array referenced on the stack.
/// Stack pre: reference
/// Stack post:
void clearByteArray(ArrayType const& _type) const;
/// Bytes we need to the start of call data. /// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier. /// - The size in bytes of the function (hash) identifier.
static const unsigned int dataStartOffset; static const unsigned int dataStartOffset;
@ -97,10 +88,6 @@ private:
unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const;
/// Loads type from memory assuming memory offset is on stack top. /// Loads type from memory assuming memory offset is on stack top.
unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries);
/// Appends a loop that clears a sequence of storage slots (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
void clearStorageLoop() const;
CompilerContext& m_context; CompilerContext& m_context;
}; };

21
libsolidity/ExpressionCompiler.cpp

@ -93,7 +93,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
m_context << eth::Instruction::DUP1 m_context << eth::Instruction::DUP1
<< structType->getStorageOffsetOfMember(names[i]) << structType->getStorageOffsetOfMember(names[i])
<< eth::Instruction::ADD; << eth::Instruction::ADD;
StorageItem(m_context, types[i]).retrieveValue(SourceLocation(), true); StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true);
solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented.");
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
retSizeOnStack += types[i]->getSizeOnStack(); retSizeOnStack += types[i]->getSizeOnStack();
@ -104,7 +104,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{ {
// simple value // simple value
solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); solAssert(accessorType.getReturnParameterTypes().size() == 1, "");
StorageItem(m_context, returnType).retrieveValue(SourceLocation(), true); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
retSizeOnStack = returnType->getSizeOnStack(); retSizeOnStack = returnType->getSizeOnStack();
} }
solAssert(retSizeOnStack <= 15, "Stack too deep."); solAssert(retSizeOnStack <= 15, "Stack too deep.");
@ -680,7 +680,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
break; break;
case ArrayType::Location::Storage: case ArrayType::Location::Storage:
setLValueToStorageItem(_memberAccess); setLValue<StorageArrayLength>(_memberAccess, type);
break; break;
default: default:
solAssert(false, "Unsupported array location."); solAssert(false, "Unsupported array location.");
@ -984,9 +984,10 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
else else
// send all gas except for the 21 needed to execute "SUB" and "CALL" // send all gas except for the 21 needed to execute "SUB" and "CALL"
m_context << u256(21) << eth::Instruction::GAS << eth::Instruction::SUB; m_context << u256(_functionType.valueSet() ? 6741 : 41) << eth::Instruction::GAS << eth::Instruction::SUB;
m_context << eth::Instruction::CALL m_context << eth::Instruction::CALL;
<< eth::Instruction::POP; // @todo do not ignore failure indicator auto tag = m_context.appendConditionalJump();
m_context << eth::Instruction::STOP << tag; // STOP if CALL leaves 0.
if (_functionType.valueSet()) if (_functionType.valueSet())
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
if (_functionType.gasSet()) if (_functionType.gasSet())
@ -999,10 +1000,12 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true); CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true);
} }
void ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expression const>> const& _arguments, void ExpressionCompiler::appendArgumentsCopyToMemory(
vector<ASTPointer<Expression const>> const& _arguments,
TypePointers const& _types, TypePointers const& _types,
bool _padToWordBoundaries, bool _padToWordBoundaries,
bool _padExceptionIfFourBytes) bool _padExceptionIfFourBytes
)
{ {
solAssert(_types.empty() || _types.size() == _arguments.size(), ""); solAssert(_types.empty() || _types.size() == _arguments.size(), "");
for (size_t i = 0; i < _arguments.size(); ++i) for (size_t i = 0; i < _arguments.size(); ++i)
@ -1044,7 +1047,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio
void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
{ {
setLValue<StorageItem>(_expression, _expression.getType()); setLValue<StorageItem>(_expression, *_expression.getType());
} }
} }

108
libsolidity/LValue.cpp

@ -32,15 +32,14 @@ using namespace solidity;
StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration): StackVariable::StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration):
LValue(_compilerContext, _declaration.getType()), LValue(_compilerContext, *_declaration.getType()),
m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)), m_baseStackOffset(m_context.getBaseStackOffsetOfVariable(_declaration)),
m_size(m_dataType->getSizeOnStack()) m_size(m_dataType.getSizeOnStack())
{ {
} }
void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove) const void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
{ {
(void)_remove;
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION(CompilerError() BOOST_THROW_EXCEPTION(CompilerError()
@ -49,9 +48,8 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool _remove)
m_context << eth::dupInstruction(stackPos + 1); m_context << eth::dupInstruction(stackPos + 1);
} }
void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const
{ {
(void)_sourceType;
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
if (stackDiff > 16) if (stackDiff > 16)
BOOST_THROW_EXCEPTION(CompilerError() BOOST_THROW_EXCEPTION(CompilerError()
@ -63,7 +61,7 @@ void StackVariable::storeValue(Type const& _sourceType, SourceLocation const& _l
retrieveValue(_location); retrieveValue(_location);
} }
void StackVariable::setToZero(SourceLocation const& _location) const void StackVariable::setToZero(SourceLocation const& _location, bool) const
{ {
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset); unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackDiff > 16) if (stackDiff > 16)
@ -77,20 +75,20 @@ void StackVariable::setToZero(SourceLocation const& _location) const
StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration):
StorageItem(_compilerContext, _declaration.getType()) StorageItem(_compilerContext, *_declaration.getType())
{ {
m_context << m_context.getStorageLocationOfVariable(_declaration); m_context << m_context.getStorageLocationOfVariable(_declaration);
} }
StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _type): StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
LValue(_compilerContext, _type) LValue(_compilerContext, _type)
{ {
if (m_dataType->isValueType()) if (m_dataType.isValueType())
{ {
solAssert(m_dataType->getStorageSize() == m_dataType->getSizeOnStack(), ""); solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), "");
solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), solAssert(m_dataType.getStorageSize() <= numeric_limits<unsigned>::max(),
"The storage size of " + m_dataType->toString() + " should fit in an unsigned"); "The storage size of " + m_dataType.toString() + " should fit in an unsigned");
m_size = unsigned(m_dataType->getStorageSize()); m_size = unsigned(m_dataType.getStorageSize());
} }
else else
m_size = 0; // unused m_size = 0; // unused
@ -98,7 +96,7 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, TypePointer const& _
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
{ {
if (!m_dataType->isValueType()) if (!m_dataType.isValueType())
return; // no distinction between value and reference for non-value types return; // no distinction between value and reference for non-value types
if (!_remove) if (!_remove)
m_context << eth::Instruction::DUP1; m_context << eth::Instruction::DUP1;
@ -118,7 +116,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const
{ {
// stack layout: value value ... value target_ref // stack layout: value value ... value target_ref
if (m_dataType->isValueType()) if (m_dataType.isValueType())
{ {
if (!_move) // copy values if (!_move) // copy values
{ {
@ -143,20 +141,20 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
} }
else else
{ {
solAssert(_sourceType.getCategory() == m_dataType->getCategory(), solAssert(_sourceType.getCategory() == m_dataType.getCategory(),
"Wrong type conversation for assignment."); "Wrong type conversation for assignment.");
if (m_dataType->getCategory() == Type::Category::Array) if (m_dataType.getCategory() == Type::Category::Array)
{ {
CompilerUtils(m_context).copyByteArrayToStorage( ArrayUtils(m_context).copyArrayToStorage(
dynamic_cast<ArrayType const&>(*m_dataType), dynamic_cast<ArrayType const&>(m_dataType),
dynamic_cast<ArrayType const&>(_sourceType)); dynamic_cast<ArrayType const&>(_sourceType));
if (_move) if (_move)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
} }
else if (m_dataType->getCategory() == Type::Category::Struct) else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: source_ref target_ref // stack layout: source_ref target_ref
auto const& structType = dynamic_cast<StructType const&>(*m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
solAssert(structType == _sourceType, "Struct assignment with conversion."); solAssert(structType == _sourceType, "Struct assignment with conversion.");
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
@ -167,12 +165,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
m_context << structType.getStorageOffsetOfMember(member.first) m_context << structType.getStorageOffsetOfMember(member.first)
<< eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD;
// stack: source_ref target_ref member_offset source_member_ref // stack: source_ref target_ref member_offset source_member_ref
StorageItem(m_context, memberType).retrieveValue(_location, true); StorageItem(m_context, *memberType).retrieveValue(_location, true);
// stack: source_ref target_ref member_offset source_value... // stack: source_ref target_ref member_offset source_value...
m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) m_context << eth::dupInstruction(2 + memberType->getSizeOnStack())
<< eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD;
// stack: source_ref target_ref member_offset source_value... target_member_ref // stack: source_ref target_ref member_offset source_value... target_member_ref
StorageItem(m_context, memberType).storeValue(*memberType, _location, true); StorageItem(m_context, *memberType).storeValue(*memberType, _location, true);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
} }
if (_move) if (_move)
@ -187,16 +185,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
} }
} }
void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
void StorageItem::setToZero(SourceLocation const& _location) const
{ {
(void)_location; if (m_dataType.getCategory() == Type::Category::Array)
if (m_dataType->getCategory() == Type::Category::Array) {
CompilerUtils(m_context).clearByteArray(dynamic_cast<ArrayType const&>(*m_dataType)); if (!_removeReference)
else if (m_dataType->getCategory() == Type::Category::Struct) m_context << eth::Instruction::DUP1;
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType));
}
else if (m_dataType.getCategory() == Type::Category::Struct)
{ {
// stack layout: ref // stack layout: ref
auto const& structType = dynamic_cast<StructType const&>(*m_dataType); auto const& structType = dynamic_cast<StructType const&>(m_dataType);
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
// zero each member that is not a mapping // zero each member that is not a mapping
@ -205,14 +205,23 @@ void StorageItem::setToZero(SourceLocation const& _location) const
continue; continue;
m_context << structType.getStorageOffsetOfMember(member.first) m_context << structType.getStorageOffsetOfMember(member.first)
<< eth::Instruction::DUP2 << eth::Instruction::ADD; << eth::Instruction::DUP2 << eth::Instruction::ADD;
StorageItem(m_context, memberType).setToZero(); StorageItem(m_context, *memberType).setToZero();
} }
if (_removeReference)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
} }
else else
{ {
if (m_size == 0) if (m_size == 0 && _removeReference)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
else if (m_size == 1)
m_context
<< u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2)
<< eth::Instruction::SSTORE;
else
{
if (!_removeReference)
m_context << eth::Instruction::DUP1;
for (unsigned i = 0; i < m_size; ++i) for (unsigned i = 0; i < m_size; ++i)
if (i + 1 >= m_size) if (i + 1 >= m_size)
m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
@ -220,4 +229,37 @@ void StorageItem::setToZero(SourceLocation const& _location) const
m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::ADD; << u256(1) << eth::Instruction::ADD;
} }
}
} }
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType):
LValue(_compilerContext, *_arrayType.getMemberType("length")),
m_arrayType(_arrayType)
{
solAssert(m_arrayType.isDynamicallySized(), "");
}
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const
{
if (!_remove)
m_context << eth::Instruction::DUP1;
m_context << eth::Instruction::SLOAD;
}
void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const
{
if (_move)
m_context << eth::Instruction::SWAP1;
else
m_context << eth::Instruction::DUP2;
ArrayUtils(m_context).resizeDynamicArray(m_arrayType);
}
void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const
{
if (!_removeReference)
m_context << eth::Instruction::DUP1;
ArrayUtils(m_context).clearDynamicArray(m_arrayType);
}

44
libsolidity/LValue.h

@ -24,6 +24,7 @@
#include <memory> #include <memory>
#include <libevmcore/SourceLocation.h> #include <libevmcore/SourceLocation.h>
#include <libsolidity/ArrayUtils.h>
namespace dev namespace dev
{ {
@ -32,6 +33,7 @@ namespace solidity
class Declaration; class Declaration;
class Type; class Type;
class ArrayType;
class CompilerContext; class CompilerContext;
/** /**
@ -40,7 +42,7 @@ class CompilerContext;
class LValue class LValue
{ {
protected: protected:
LValue(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _dataType): LValue(CompilerContext& _compilerContext, Type const& _dataType):
m_context(_compilerContext), m_dataType(_dataType) {} m_context(_compilerContext), m_dataType(_dataType) {}
public: public:
@ -56,13 +58,14 @@ public:
/// Stack post: if !_move: value_of(lvalue_ref) /// Stack post: if !_move: value_of(lvalue_ref)
virtual void storeValue(Type const& _sourceType, virtual void storeValue(Type const& _sourceType,
SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0;
/// Stores zero in the lvalue. /// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true.
/// @a _location is the source location of the requested operation /// @a _location is the source location of the requested operation
virtual void setToZero(SourceLocation const& _location = SourceLocation()) const = 0; virtual void setToZero(
SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const = 0;
protected: protected:
CompilerContext& m_context; CompilerContext& m_context;
std::shared_ptr<Type const> m_dataType; Type const& m_dataType;
}; };
/** /**
@ -71,13 +74,14 @@ protected:
class StackVariable: public LValue class StackVariable: public LValue
{ {
public: public:
explicit StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration); StackVariable(CompilerContext& _compilerContext, Declaration const& _declaration);
virtual bool storesReferenceOnStack() const { return false; } virtual bool storesReferenceOnStack() const { return false; }
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(Type const& _sourceType, virtual void storeValue(Type const& _sourceType,
SourceLocation const& _location = SourceLocation(), bool _move = false) const override; SourceLocation const& _location = SourceLocation(), bool _move = false) const override;
virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; virtual void setToZero(
SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override;
private: private:
/// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable. /// Base stack offset (@see CompilerContext::getBaseStackOffsetOfVariable) of the local variable.
@ -93,14 +97,15 @@ class StorageItem: public LValue
{ {
public: public:
/// Constructs the LValue and pushes the location of @a _declaration onto the stack. /// Constructs the LValue and pushes the location of @a _declaration onto the stack.
explicit StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration);
/// Constructs the LValue and assumes that the storage reference is already on the stack. /// Constructs the LValue and assumes that the storage reference is already on the stack.
explicit StorageItem(CompilerContext& _compilerContext, std::shared_ptr<Type const> const& _type); StorageItem(CompilerContext& _compilerContext, Type const& _type);
virtual bool storesReferenceOnStack() const { return true; } virtual bool storesReferenceOnStack() const { return true; }
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(Type const& _sourceType, virtual void storeValue(Type const& _sourceType,
SourceLocation const& _location = SourceLocation(), bool _move = false) const override; SourceLocation const& _location = SourceLocation(), bool _move = false) const override;
virtual void setToZero(SourceLocation const& _location = SourceLocation()) const override; virtual void setToZero(
SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override;
private: private:
/// Number of stack elements occupied by the value (not the reference). /// Number of stack elements occupied by the value (not the reference).
@ -108,5 +113,26 @@ private:
unsigned m_size; unsigned m_size;
}; };
/**
* Reference to the "length" member of a dynamically-sized array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus arrays members have to be
* deleted.
*/
class StorageArrayLength: public LValue
{
public:
/// Constructs the LValue, assumes that the reference to the array head is already on the stack.
StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType);
virtual bool storesReferenceOnStack() const { return true; }
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(Type const& _sourceType,
SourceLocation const& _location = SourceLocation(), bool _move = false) const override;
virtual void setToZero(
SourceLocation const& _location = SourceLocation(), bool _removeReference = true) const override;
private:
ArrayType const& m_arrayType;
};
} }
} }

10
libsolidity/SourceReferenceFormatter.cpp

@ -44,8 +44,14 @@ void SourceReferenceFormatter::printSourceLocation(ostream& _stream,
tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end); tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end);
if (startLine == endLine) if (startLine == endLine)
{ {
_stream << _scanner.getLineAtPosition(_location.start) << endl string line = _scanner.getLineAtPosition(_location.start);
<< string(startColumn, ' ') << "^"; _stream << line << endl;
std::for_each(line.cbegin(), line.cbegin() + startColumn,
[&_stream](char const& ch)
{
_stream << (ch == '\t' ? '\t' : ' ');
});
_stream << "^";
if (endColumn > startColumn + 2) if (endColumn > startColumn + 2)
_stream << string(endColumn - startColumn - 2, '-'); _stream << string(endColumn - startColumn - 2, '-');
if (endColumn > startColumn + 1) if (endColumn > startColumn + 1)

42
libsolidity/Types.cpp

@ -537,7 +537,19 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
{ {
return _convertTo.getCategory() == getCategory(); if (_convertTo.getCategory() != getCategory())
return false;
auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
// let us not allow assignment to memory arrays for now
if (convertTo.getLocation() != Location::Storage)
return false;
if (convertTo.isByteArray() != isByteArray())
return false;
if (!getBaseType()->isImplicitlyConvertibleTo(*convertTo.getBaseType()))
return false;
if (convertTo.isDynamicallySized())
return true;
return !isDynamicallySized() && convertTo.getLength() >= getLength();
} }
TypePointer ArrayType::unaryOperatorResult(Token::Value _operator) const TypePointer ArrayType::unaryOperatorResult(Token::Value _operator) const
@ -552,7 +564,19 @@ bool ArrayType::operator==(Type const& _other) const
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
return false; return false;
ArrayType const& other = dynamic_cast<ArrayType const&>(_other); ArrayType const& other = dynamic_cast<ArrayType const&>(_other);
return other.m_location == m_location; if (other.m_location != m_location || other.isByteArray() != isByteArray() ||
other.isDynamicallySized() != isDynamicallySized())
return false;
return isDynamicallySized() || getLength() == other.getLength();
}
unsigned ArrayType::getCalldataEncodedSize() const
{
if (isDynamicallySized())
return 0;
bigint size = bigint(getLength()) * (isByteArray() ? 1 : getBaseType()->getCalldataEncodedSize());
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
return unsigned(size);
} }
u256 ArrayType::getStorageSize() const u256 ArrayType::getStorageSize() const
@ -571,8 +595,8 @@ u256 ArrayType::getStorageSize() const
unsigned ArrayType::getSizeOnStack() const unsigned ArrayType::getSizeOnStack() const
{ {
if (m_location == Location::CallData) if (m_location == Location::CallData)
// offset, length (stack top) // offset [length] (stack top)
return 2; return 1 + (isDynamicallySized() ? 1 : 0);
else else
// offset // offset
return 1; return 1;
@ -628,8 +652,7 @@ MemberList const& ContractType::getMembers() const
{ {
for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts())
for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions())
if (!function->isConstructor() && !function->getName().empty()&& if (function->isVisibleInDerivedContracts())
function->isVisibleInDerivedContracts())
members.push_back(make_pair(function->getName(), make_shared<FunctionType>(*function, true))); members.push_back(make_pair(function->getName(), make_shared<FunctionType>(*function, true)));
} }
else else
@ -1024,10 +1047,9 @@ MemberList const& TypeType::getMembers() const
vector<ContractDefinition const*> currentBases = m_currentContract->getLinearizedBaseContracts(); vector<ContractDefinition const*> currentBases = m_currentContract->getLinearizedBaseContracts();
if (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()) if (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end())
// We are accessing the type of a base contract, so add all public and protected // We are accessing the type of a base contract, so add all public and protected
// functions. Note that this does not add inherited functions on purpose. // members. Note that this does not add inherited functions on purpose.
for (ASTPointer<FunctionDefinition> const& f: contract.getDefinedFunctions()) for (Declaration const* decl: contract.getInheritableMembers())
if (!f->isConstructor() && !f->getName().empty() && f->isVisibleInDerivedContracts()) members.push_back(make_pair(decl->getName(), decl->getType()));
members.push_back(make_pair(f->getName(), make_shared<FunctionType>(*f)));
} }
else if (m_actualType->getCategory() == Category::Enum) else if (m_actualType->getCategory() == Category::Enum)
{ {

1
libsolidity/Types.h

@ -302,6 +302,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(const Type& _other) const override; virtual bool operator==(const Type& _other) const override;
virtual unsigned getCalldataEncodedSize() const override;
virtual bool isDynamicallySized() const { return m_hasDynamicLength; } virtual bool isDynamicallySized() const { return m_hasDynamicLength; }
virtual u256 getStorageSize() const override; virtual u256 getStorageSize() const override;
virtual unsigned getSizeOnStack() const override; virtual unsigned getSizeOnStack() const override;

1
libweb3jsonrpc/WebThreeStubServerBase.cpp

@ -95,6 +95,7 @@ static Json::Value toJson(dev::eth::LocalisedLogEntry const& _e)
for (auto const& t: _e.topics) for (auto const& t: _e.topics)
res["topic"].append(toJS(t)); res["topic"].append(toJS(t));
res["number"] = _e.number; res["number"] = _e.number;
res["hash"] = toJS(_e.sha3);
return res; return res;
} }

4
libwhisper/WhisperHost.cpp

@ -58,7 +58,7 @@ void WhisperHost::inject(Envelope const& _m, WhisperPeer* _p)
{ {
cnote << this << ": inject: " << _m.expiry() << _m.ttl() << _m.topic() << toHex(_m.data()); cnote << this << ": inject: " << _m.expiry() << _m.ttl() << _m.topic() << toHex(_m.data());
if (_m.expiry() <= time(0)) if (_m.expiry() <= (unsigned)time(0))
return; return;
auto h = _m.sha3(); auto h = _m.sha3();
@ -171,7 +171,7 @@ void WhisperHost::cleanup()
{ {
// remove old messages. // remove old messages.
// should be called every now and again. // should be called every now and again.
auto now = time(0); unsigned now = (unsigned)time(0);
WriteGuard l(x_messages); WriteGuard l(x_messages);
for (auto it = m_expiryQueue.begin(); it != m_expiryQueue.end() && it->first <= now; it = m_expiryQueue.erase(it)) for (auto it = m_expiryQueue.begin(); it != m_expiryQueue.end() && it->first <= now; it = m_expiryQueue.erase(it))
m_messages.erase(it->second); m_messages.erase(it->second);

18
mix/ClientModel.cpp

@ -305,8 +305,21 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
QDebugData* debugData = new QDebugData(); QDebugData* debugData = new QDebugData();
QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership); QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership);
QList<QCode*> codes; QList<QCode*> codes;
for (bytes const& code: _t.executionCode) for (MachineCode const& code: _t.executionCode)
codes.push_back(QMachineState::getHumanReadableCode(debugData, code)); {
codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code));
//try to resolve contract for source level debugging
auto nameIter = m_contractNames.find(code.address);
if (nameIter != m_contractNames.end())
{
CompiledContract const& compilerRes = m_context->codeModel()->contract(nameIter->second);
eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes.assemblyItems() : compilerRes.constructorAssemblyItems();
QVariantList locations;
for (eth::AssemblyItem const& item: assemblyItems)
locations.push_back(QVariant::fromValue(new QSourceLocation(debugData, item.getLocation().start, item.getLocation().end)));
codes.back()->setLocations(compilerRes.documentId(), std::move(locations));
}
}
QList<QCallData*> data; QList<QCallData*> data;
for (bytes const& d: _t.transactionData) for (bytes const& d: _t.transactionData)
@ -330,7 +343,6 @@ void ClientModel::emptyRecord()
debugDataReady(new QDebugData()); debugDataReady(new QDebugData());
} }
void ClientModel::debugRecord(unsigned _index) void ClientModel::debugRecord(unsigned _index)
{ {
ExecutionResult const& e = m_client->executions().at(_index); ExecutionResult const& e = m_client->executions().at(_index);

2
mix/CodeModel.cpp

@ -56,6 +56,8 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler
m_contract.reset(new QContractDefinition(&contractDefinition)); m_contract.reset(new QContractDefinition(&contractDefinition));
QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership);
m_bytes = _compiler.getBytecode(_contractName.toStdString()); m_bytes = _compiler.getBytecode(_contractName.toStdString());
m_assemblyItems = _compiler.getRuntimeAssemblyItems(_contractName.toStdString());
m_constructorAssemblyItems = _compiler.getAssemblyItems(_contractName.toStdString());
dev::solidity::InterfaceHandler interfaceHandler; dev::solidity::InterfaceHandler interfaceHandler;
m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition));
if (m_contractInterface.isEmpty()) if (m_contractInterface.isEmpty())

10
mix/CodeModel.h

@ -30,6 +30,7 @@
#include <QHash> #include <QHash>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <libdevcore/Guards.h> #include <libdevcore/Guards.h>
#include <libevmcore/Assembly.h>
class QTextDocument; class QTextDocument;
@ -70,7 +71,7 @@ class CompiledContract: public QObject
Q_PROPERTY(QContractDefinition* contract READ contract) Q_PROPERTY(QContractDefinition* contract READ contract)
Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT) Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT)
Q_PROPERTY(QString codeHex READ codeHex CONSTANT) Q_PROPERTY(QString codeHex READ codeHex CONSTANT)
Q_PROPERTY(QString documentId MEMBER m_documentId CONSTANT) Q_PROPERTY(QString documentId READ documentId CONSTANT)
public: public:
/// Successful compilation result constructor /// Successful compilation result constructor
@ -86,6 +87,11 @@ public:
QString codeHex() const; QString codeHex() const;
/// @returns contract definition in JSON format /// @returns contract definition in JSON format
QString contractInterface() const { return m_contractInterface; } QString contractInterface() const { return m_contractInterface; }
/// @return assebly item locations
eth::AssemblyItems const& assemblyItems() const { return m_assemblyItems; }
eth::AssemblyItems const& constructorAssemblyItems() const { return m_constructorAssemblyItems; }
/// @returns contract source Id
QString documentId() const { return m_documentId; }
private: private:
uint m_sourceHash; uint m_sourceHash;
@ -94,6 +100,8 @@ private:
dev::bytes m_bytes; dev::bytes m_bytes;
QString m_contractInterface; QString m_contractInterface;
QString m_documentId; QString m_documentId;
eth::AssemblyItems m_assemblyItems;
eth::AssemblyItems m_constructorAssemblyItems;
friend class CodeModel; friend class CodeModel;
}; };

9
mix/DebuggingStateWrapper.cpp

@ -69,7 +69,7 @@ namespace
} }
} }
QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code) QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code)
{ {
QVariantList codeStr; QVariantList codeStr;
for (unsigned i = 0; i <= _code.size(); ++i) for (unsigned i = 0; i <= _code.size(); ++i)
@ -96,7 +96,7 @@ QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code)
break; // probably hit data segment break; // probably hit data segment
} }
} }
return new QCode(_owner, std::move(codeStr)); return new QCode(_owner, QString::fromStdString(toString(_address)), std::move(codeStr));
} }
QBigInt* QMachineState::gasCost() QBigInt* QMachineState::gasCost()
@ -152,11 +152,6 @@ QVariantList QMachineState::levels()
return levelList; return levelList;
} }
QString QMachineState::address()
{
return QString::fromStdString(toString(m_state.address));
}
QString QMachineState::instruction() QString QMachineState::instruction()
{ {
return QString::fromStdString(dev::eth::instructionInfo(m_state.inst).name); return QString::fromStdString(dev::eth::instructionInfo(m_state.inst).name);

30
mix/DebuggingStateWrapper.h

@ -53,6 +53,22 @@ private:
int m_processIndex; int m_processIndex;
}; };
class QSourceLocation: public QObject
{
Q_OBJECT
Q_PROPERTY(int start MEMBER m_start CONSTANT)
Q_PROPERTY(int end MEMBER m_end CONSTANT)
public:
QSourceLocation(QObject* _owner, int _start, int _end): QObject(_owner), m_start(_start), m_end(_end) {}
private:
int m_start;
int m_end;
};
/** /**
* @brief Shared container for lines * @brief Shared container for lines
*/ */
@ -60,12 +76,19 @@ class QCode: public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT) Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT)
Q_PROPERTY(QVariantList locations MEMBER m_locations CONSTANT)
Q_PROPERTY(QString address MEMBER m_address CONSTANT)
Q_PROPERTY(QString documentId MEMBER m_document CONSTANT)
public: public:
QCode(QObject* _owner, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions) {} QCode(QObject* _owner, QString const& _address, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions), m_address(_address) {}
void setLocations(QString const& _document, QVariantList&& _locations) { m_document = _document; m_locations = _locations; }
private: private:
QVariantList m_instructions; QVariantList m_instructions;
QString m_address;
QString m_document;
QVariantList m_locations;
}; };
/** /**
@ -110,7 +133,6 @@ class QMachineState: public QObject
Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT) Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT)
Q_PROPERTY(QBigInt* gas READ gas CONSTANT) Q_PROPERTY(QBigInt* gas READ gas CONSTANT)
Q_PROPERTY(QString instruction READ instruction CONSTANT) Q_PROPERTY(QString instruction READ instruction CONSTANT)
Q_PROPERTY(QString address READ address CONSTANT)
Q_PROPERTY(QStringList debugStack READ debugStack CONSTANT) Q_PROPERTY(QStringList debugStack READ debugStack CONSTANT)
Q_PROPERTY(QStringList debugStorage READ debugStorage CONSTANT) Q_PROPERTY(QStringList debugStorage READ debugStorage CONSTANT)
Q_PROPERTY(QVariantList debugMemory READ debugMemory CONSTANT) Q_PROPERTY(QVariantList debugMemory READ debugMemory CONSTANT)
@ -133,8 +155,6 @@ public:
unsigned codeIndex() { return m_state.codeIndex; } unsigned codeIndex() { return m_state.codeIndex; }
/// Get the call data id /// Get the call data id
unsigned dataIndex() { return m_state.dataIndex; } unsigned dataIndex() { return m_state.dataIndex; }
/// Get address for call stack
QString address();
/// Get gas cost. /// Get gas cost.
QBigInt* gasCost(); QBigInt* gasCost();
/// Get gas used. /// Get gas used.
@ -158,7 +178,7 @@ public:
/// Set the current processed machine state. /// Set the current processed machine state.
void setState(MachineState _state) { m_state = _state; } void setState(MachineState _state) { m_state = _state; }
/// Convert all machine states in human readable code. /// Convert all machine states in human readable code.
static QCode* getHumanReadableCode(QObject* _owner, bytes const& _code); static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code);
/// Convert call data into human readable form /// Convert call data into human readable form
static QCallData* getDebugCallData(QObject* _owner, bytes const& _data); static QCallData* getDebugCallData(QObject* _owner, bytes const& _data);

13
mix/MachineStates.h

@ -42,7 +42,6 @@ namespace mix
struct MachineState struct MachineState
{ {
uint64_t steps; uint64_t steps;
dev::Address address;
dev::u256 curPC; dev::u256 curPC;
dev::eth::Instruction inst; dev::eth::Instruction inst;
dev::bigint newMemSize; dev::bigint newMemSize;
@ -56,6 +55,15 @@ namespace mix
unsigned dataIndex; unsigned dataIndex;
}; };
/**
* @brief Executed conract code info
*/
struct MachineCode
{
dev::Address address;
bytes code;
};
/** /**
* @brief Store information about a machine states. * @brief Store information about a machine states.
*/ */
@ -65,7 +73,7 @@ namespace mix
std::vector<MachineState> machineStates; std::vector<MachineState> machineStates;
std::vector<bytes> transactionData; std::vector<bytes> transactionData;
std::vector<bytes> executionCode; std::vector<MachineCode> executionCode;
bytes returnValue; bytes returnValue;
dev::Address address; dev::Address address;
dev::Address sender; dev::Address sender;
@ -74,6 +82,7 @@ namespace mix
unsigned transactionIndex; unsigned transactionIndex;
bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); } bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; }
}; };
using ExecutionResults = std::vector<ExecutionResult>; using ExecutionResults = std::vector<ExecutionResult>;

6
mix/MixClient.cpp

@ -107,7 +107,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
execution.setup(&rlp); execution.setup(&rlp);
std::vector<MachineState> machineStates; std::vector<MachineState> machineStates;
std::vector<unsigned> levels; std::vector<unsigned> levels;
std::vector<bytes> codes; std::vector<MachineCode> codes;
std::map<bytes const*, unsigned> codeIndexes; std::map<bytes const*, unsigned> codeIndexes;
std::vector<bytes> data; std::vector<bytes> data;
std::map<bytesConstRef const*, unsigned> dataIndexes; std::map<bytesConstRef const*, unsigned> dataIndexes;
@ -127,7 +127,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
else else
{ {
codeIndex = codes.size(); codeIndex = codes.size();
codes.push_back(ext.code); codes.push_back(MachineCode({ext.myAddress, ext.code}));
codeIndexes[&ext.code] = codeIndex; codeIndexes[&ext.code] = codeIndex;
} }
lastCode = &ext.code; lastCode = &ext.code;
@ -152,7 +152,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
else else
levels.resize(ext.depth); levels.resize(ext.depth);
machineStates.emplace_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), machineStates.emplace_back(MachineState({steps, vm.curPC(), inst, newMemSize, vm.gas(),
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels, codeIndex, dataIndex})); vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels, codeIndex, dataIndex}));
}; };

43
mix/qml/CodeEditorView.qml

@ -7,6 +7,7 @@ Item {
id: codeEditorView id: codeEditorView
property string currentDocumentId: "" property string currentDocumentId: ""
signal documentEdit(string documentId) signal documentEdit(string documentId)
signal breakpointsChanged(string documentId)
function getDocumentText(documentId) { function getDocumentText(documentId) {
for (var i = 0; i < editorListModel.count; i++) { for (var i = 0; i < editorListModel.count; i++) {
@ -45,9 +46,51 @@ Item {
if (document.isContract) if (document.isContract)
codeModel.registerCodeChange(document.documentId, editor.getText()); codeModel.registerCodeChange(document.documentId, editor.getText());
}); });
editor.onBreakpointsChanged.connect(function() {
if (document.isContract)
breakpointsChanged(document.documentId);
});
editor.setText(data, document.syntaxMode); editor.setText(data, document.syntaxMode);
} }
function getEditor(documentId) {
for (var i = 0; i < editorListModel.count; i++)
if (editorListModel.get(i).documentId === documentId)
return editors.itemAt(i).item;
return null;
}
function highlightExecution(documentId, location) {
var editor = getEditor(documentId);
if (editor)
editor.highlightExecution(location);
}
function editingContract() {
for (var i = 0; i < editorListModel.count; i++)
if (editorListModel.get(i).documentId === currentDocumentId)
return editorListModel.get(i).isContract;
return false;
}
function getBreakpoints() {
var bpMap = {};
for (var i = 0; i < editorListModel.count; i++) {
var documentId = editorListModel.get(i).documentId;
var editor = editors.itemAt(i).item;
if (editor) {
bpMap[documentId] = editor.getBreakpoints();
}
}
return bpMap;
}
function toggleBreakpoint() {
var editor = getEditor(currentDocumentId);
if (editor)
editor.toggleBreakpoint();
}
Component.onCompleted: projectModel.codeEditor = codeEditorView; Component.onCompleted: projectModel.codeEditor = codeEditorView;
Connections { Connections {

50
mix/qml/Debugger.qml

@ -11,8 +11,11 @@ import "."
Rectangle { Rectangle {
id: debugPanel id: debugPanel
property alias transactionLog : transactionLog property alias transactionLog: transactionLog
signal debugExecuteLocation(string documentId, var location)
property string compilationErrorMessage property string compilationErrorMessage
property bool assemblyMode: false
objectName: "debugPanel" objectName: "debugPanel"
color: "#ededed" color: "#ededed"
clip: true clip: true
@ -23,6 +26,11 @@ Rectangle {
forceActiveFocus(); forceActiveFocus();
} }
onAssemblyModeChanged:
{
Debugger.updateMode();
}
function displayCompilationErrorIfAny() function displayCompilationErrorIfAny()
{ {
debugScrollArea.visible = false; debugScrollArea.visible = false;
@ -51,6 +59,11 @@ Rectangle {
forceActiveFocus(); forceActiveFocus();
} }
function setBreakpoints(bp)
{
Debugger.setBreakpoints(bp);
}
Connections { Connections {
target: clientModel target: clientModel
onDebugDataReady: { onDebugDataReady: {
@ -60,10 +73,8 @@ Rectangle {
Connections { Connections {
target: codeModel target: codeModel
onCompilationComplete: onCompilationComplete: {
{
debugPanel.compilationErrorMessage = ""; debugPanel.compilationErrorMessage = "";
update(null, false);
} }
onCompilationError: { onCompilationError: {
@ -199,11 +210,23 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
color: "transparent" color: "transparent"
width: stateListContainer.width width: parent.width * 0.4
RowLayout { RowLayout {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
id: jumpButtons id: jumpButtons
spacing: 3 spacing: 3
StepActionImage
{
id: runBackAction;
enabledStateImg: "qrc:/qml/img/jumpoutback.png"
disableStateImg: "qrc:/qml/img/jumpoutbackdisabled.png"
onClicked: Debugger.runBack()
width: 30
height: 30
buttonShortcut: "Ctrl+Shift+F5"
buttonTooltip: qsTr("Run Back")
}
StepActionImage StepActionImage
{ {
id: jumpOutBackAction; id: jumpOutBackAction;
@ -275,6 +298,20 @@ Rectangle {
buttonShortcut: "Shift+F11" buttonShortcut: "Shift+F11"
buttonTooltip: qsTr("Step Out Forward") buttonTooltip: qsTr("Step Out Forward")
} }
StepActionImage
{
id: runForwardAction
enabledStateImg: "qrc:/qml/img/jumpoutforward.png"
disableStateImg: "qrc:/qml/img/jumpoutforwarddisabled.png"
onClicked: Debugger.runForward()
width: 30
height: 30
buttonShortcut: "Ctrl+F5"
buttonTooltip: qsTr("Run Forward")
}
} }
} }
@ -282,7 +319,7 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
width: debugInfoContainer.width width: parent.width * 0.6
color: "transparent" color: "transparent"
Slider { Slider {
id: statesSlider id: statesSlider
@ -317,6 +354,7 @@ Rectangle {
height: 405 height: 405
implicitHeight: 405 implicitHeight: 405
color: "transparent" color: "transparent"
visible: assemblyMode
Rectangle Rectangle
{ {

25
mix/qml/MainContent.qml

@ -26,6 +26,7 @@ Rectangle {
property alias projectViewVisible: projectList.visible property alias projectViewVisible: projectList.visible
property alias runOnProjectLoad: mainSettings.runOnProjectLoad property alias runOnProjectLoad: mainSettings.runOnProjectLoad
property alias rightPane: rightView property alias rightPane: rightView
property alias codeEditor: codeEditor
property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally
property bool firstCompile: true property bool firstCompile: true
@ -40,6 +41,20 @@ Rectangle {
} }
} }
Connections {
target: rightView
onDebugExecuteLocation: {
codeEditor.highlightExecution(documentId, location);
}
}
Connections {
target: codeEditor
onBreakpointsChanged: {
rightPane.setBreakpoints(codeEditor.getBreakpoints());
}
}
function startQuickDebugging() function startQuickDebugging()
{ {
ensureRightView(); ensureRightView();
@ -75,6 +90,11 @@ Rectangle {
codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical); codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical);
} }
//TODO: move this to debugger.js after refactoring, introduce events
function toggleBreakpoint() {
codeEditor.toggleBreakpoint();
}
function displayCompilationErrorIfAny() function displayCompilationErrorIfAny()
{ {
rightView.visible = true; rightView.visible = true;
@ -174,6 +194,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
orientation: Qt.Vertical orientation: Qt.Vertical
CodeEditorView { CodeEditorView {
id: codeEditor
height: parent.height * 0.6 height: parent.height * 0.6
anchors.top: parent.top anchors.top: parent.top
Layout.fillWidth: true Layout.fillWidth: true
@ -202,7 +223,3 @@ Rectangle {
} }
} }
} }

47
mix/qml/StatusPane.qml

@ -75,7 +75,6 @@ Rectangle {
height: 30 height: 30
color: "#fcfbfc" color: "#fcfbfc"
Text { Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -116,27 +115,6 @@ Rectangle {
} }
} }
Button
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.right
anchors.leftMargin: 10
width: 38
height: 28
visible: false
text: qsTr("Log")
objectName: "status"
id: logslink
action: displayLogAction
}
Action {
id: displayLogAction
onTriggered: {
mainContent.displayCompilationErrorIfAny();
}
}
Button Button
{ {
anchors.fill: parent anchors.fill: parent
@ -157,6 +135,25 @@ Rectangle {
} }
} }
Button
{
id: logslink
anchors.left: statusContainer.right
anchors.leftMargin: 9
visible: false
anchors.verticalCenter: parent.verticalCenter
action: displayLogAction
iconSource: "qrc:/qml/img/search_filled.png"
}
Action {
id: displayLogAction
tooltip: qsTr("Display Log")
onTriggered: {
mainContent.displayCompilationErrorIfAny();
}
}
Rectangle Rectangle
{ {
color: "transparent" color: "transparent"
@ -167,9 +164,13 @@ Rectangle {
RowLayout RowLayout
{ {
anchors.fill: parent anchors.fill: parent
Rectangle { anchors.top: statusHeader.top
anchors.right: statusHeader.right
Rectangle
{
color: "transparent" color: "transparent"
anchors.fill: parent anchors.fill: parent
Button Button
{ {
anchors.right: parent.right anchors.right: parent.right

28
mix/qml/WebCodeEditor.qml

@ -2,15 +2,16 @@ import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import QtQuick.Layouts 1.0 import QtQuick.Layouts 1.0
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
import CodeEditorExtensionManager 1.0
import QtWebEngine 1.0 import QtWebEngine 1.0
import QtWebEngine.experimental 1.0 import QtWebEngine.experimental 1.0
Item { Item {
signal editorTextChanged signal editorTextChanged;
signal breakpointsChanged;
property string currentText: "" property string currentText: ""
property string currentMode: "" property string currentMode: ""
property bool initialized: false property bool initialized: false
property var currentBreakpoints: [];
function setText(text, mode) { function setText(text, mode) {
currentText = text; currentText = text;
@ -37,6 +38,18 @@ Item {
} }
} }
function highlightExecution(location) {
editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")");
}
function getBreakpoints() {
return currentBreakpoints;
}
function toggleBreakpoint() {
editorBrowser.runJavaScript("toggleBreakpoint()");
}
Connections { Connections {
target: appContext target: appContext
onClipboardChanged: syncClipboard() onClipboardChanged: syncClipboard()
@ -80,6 +93,17 @@ Item {
}); });
} }
}); });
editorBrowser.runJavaScript("getBreakpointsChanged()", function(result) {
if (result === true) {
editorBrowser.runJavaScript("getBreakpoints()" , function(bp) {
if (currentBreakpoints !== bp) {
currentBreakpoints = bp;
breakpointsChanged();
}
});
}
});
} }
} }
} }

4
mix/qml/html/cm/codemirror.css

@ -8,6 +8,10 @@
font-size:12px font-size:12px
} }
/* BREAKPOINTS */
.breakpoints {width: .8em;}
.breakpoint { color: #822; }
/* PADDING */ /* PADDING */
.CodeMirror-lines { .CodeMirror-lines {

6
mix/qml/html/cm/solarized.css

@ -163,3 +163,9 @@ view-port
.cm-s-solarized.cm-s-light .CodeMirror-activeline-background { .cm-s-solarized.cm-s-light .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.10); background: rgba(0, 0, 0, 0.10);
} }
/* Code execution */
.CodeMirror-exechighlight {
background: rgba(255, 255, 255, 0.10);
}

50
mix/qml/html/codeeditor.js

@ -4,6 +4,7 @@ var editor = CodeMirror(document.body, {
//styleActiveLine: true, //styleActiveLine: true,
matchBrackets: true, matchBrackets: true,
autofocus: true, autofocus: true,
gutters: ["CodeMirror-linenumbers", "breakpoints"]
}); });
@ -13,6 +14,7 @@ editor.setOption("indentWithTabs", true);
editor.setOption("fullScreen", true); editor.setOption("fullScreen", true);
editor.changeRegistered = false; editor.changeRegistered = false;
editor.breakpointsChangeRegistered = false;
editor.on("change", function(eMirror, object) { editor.on("change", function(eMirror, object) {
editor.changeRegistered = true; editor.changeRegistered = true;
@ -33,6 +35,28 @@ editor.setOption("extraKeys", {
}}); }});
} }
makeMarker = function() {
var marker = document.createElement("div");
marker.style.color = "#822";
marker.innerHTML = "●";
return marker;
};
toggleBreakpointLine = function(n) {
var info = editor.lineInfo(n);
editor.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker());
editor.breakpointsChangeRegistered = true;
}
editor.on("gutterClick", function(cm, n) {
toggleBreakpointLine(n);
});
toggleBreakpoint = function() {
var line = editor.getCursor().line;
toggleBreakpointLine(line);
}
getTextChanged = function() { getTextChanged = function() {
return editor.changeRegistered; return editor.changeRegistered;
}; };
@ -42,6 +66,25 @@ getText = function() {
return editor.getValue(); return editor.getValue();
}; };
getBreakpointsChanged = function() {
return editor.changeRegistered || editor.breakpointsChangeRegistered; //TODO: track new lines
};
getBreakpoints = function() {
var locations = [];
editor.breakpointsChangeRegistered = false;
var doc = editor.doc;
doc.iter(function(line) {
if (line.gutterMarkers && line.gutterMarkers["breakpoints"]) {
var l = doc.getLineNumber(line);
locations.push({
start: editor.indexFromPos({ line: l, ch: 0}),
end: editor.indexFromPos({ line: l + 1, ch: 0})
});;
}
});
return locations;
};
setTextBase64 = function(text) { setTextBase64 = function(text) {
editor.setValue(window.atob(text)); editor.setValue(window.atob(text));
@ -60,3 +103,10 @@ setMode = function(mode) {
setClipboardBase64 = function(text) { setClipboardBase64 = function(text) {
clipboard = window.atob(text); clipboard = window.atob(text);
}; };
var executionMark;
highlightExecution = function(start, end) {
if (executionMark)
executionMark.clear();
executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" });
}

BIN
mix/qml/img/search_filled.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

147
mix/qml/js/Debugger.js

@ -5,6 +5,9 @@ var currentSelectedState = null;
var currentDisplayedState = null; var currentDisplayedState = null;
var debugData = null; var debugData = null;
var codeMap = null; var codeMap = null;
var locations = [];
var locationMap = {};
var breakpoints = {};
function init(data) function init(data)
{ {
@ -22,6 +25,8 @@ function init(data)
currentSelectedState = null; currentSelectedState = null;
currentDisplayedState = null; currentDisplayedState = null;
debugData = null; debugData = null;
locations = [];
locationMap = {};
return; return;
} }
@ -30,9 +35,59 @@ function init(data)
currentDisplayedState = 0; currentDisplayedState = 0;
setupInstructions(currentSelectedState); setupInstructions(currentSelectedState);
setupCallData(currentSelectedState); setupCallData(currentSelectedState);
statesSlider.maximumValue = data.states.length - 1; initLocations();
initSlider();
selectState(currentSelectedState);
}
function updateMode()
{
initSlider();
}
function initLocations()
{
locations = [];
if (debugData.states.length === 0)
return;
var nullLocation = { start: -1, end: -1, documentId: "", state: 0 };
var prevLocation = nullLocation;
for (var i = 0; i < debugData.states.length - 1; i++) {
var code = debugData.states[i].code;
var location = code.documentId ? code.locations[codeStr(i)] : nullLocation;
if (location.start !== prevLocation.start || location.end !== prevLocation.end || code.documentId !== prevLocation.documentId)
{
prevLocation = { start: location.start, end: location.end, documentId: code.documentId, state: i };
locations.push(prevLocation);
}
locationMap[i] = locations.length - 1;
}
locations.push({ start: -1, end: -1, documentId: code.documentId, state: i });
locationMap[debugData.states.length - 1] = locations.length - 1;
}
function setBreakpoints(bp)
{
breakpoints = bp;
}
function srcMode()
{
return !assemblyMode && locations.length;
}
function initSlider()
{
if (!debugData)
statesSlider.maximumValue = 0;
else if (srcMode()) {
statesSlider.maximumValue = locations.length - 1;
} else {
statesSlider.maximumValue = debugData.states.length - 1;
}
statesSlider.value = 0; statesSlider.value = 0;
select(currentSelectedState);
} }
function setupInstructions(stateIndex) function setupInstructions(stateIndex)
@ -54,11 +109,13 @@ function setupCallData(stateIndex)
function moveSelection(incr) function moveSelection(incr)
{ {
var prevState = currentSelectedState; if (srcMode()) {
if (currentSelectedState + incr >= 0) var locationIndex = locationMap[currentSelectedState];
{ if (locationIndex + incr >= 0 && locationIndex + incr < locations.length)
if (currentSelectedState + incr < debugData.states.length) selectState(locations[locationIndex + incr].state);
select(currentSelectedState + incr); } else {
if (currentSelectedState + incr >= 0 && currentSelectedState + incr < debugData.states.length)
selectState(currentSelectedState + incr);
} }
} }
@ -67,7 +124,7 @@ function display(stateIndex)
if (stateIndex < 0) if (stateIndex < 0)
stateIndex = 0; stateIndex = 0;
if (stateIndex >= debugData.states.length) if (stateIndex >= debugData.states.length)
stateIndex = debugData.state.length - 1; stateIndex = debugData.states.length - 1;
if (debugData.states[stateIndex].codeIndex !== debugData.states[currentDisplayedState].codeIndex) if (debugData.states[stateIndex].codeIndex !== debugData.states[currentDisplayedState].codeIndex)
setupInstructions(stateIndex); setupInstructions(stateIndex);
if (debugData.states[stateIndex].dataIndex !== debugData.states[currentDisplayedState].dataIndex) if (debugData.states[stateIndex].dataIndex !== debugData.states[currentDisplayedState].dataIndex)
@ -77,6 +134,9 @@ function display(stateIndex)
highlightSelection(codeLine); highlightSelection(codeLine);
completeCtxInformation(state); completeCtxInformation(state);
currentDisplayedState = stateIndex; currentDisplayedState = stateIndex;
var docId = debugData.states[stateIndex].code.documentId;
if (docId)
debugExecuteLocation(docId, locations[locationMap[stateIndex]]);
} }
function displayFrame(frameIndex) function displayFrame(frameIndex)
@ -88,26 +148,39 @@ function displayFrame(frameIndex)
display(state.levels[frameIndex - 1]); display(state.levels[frameIndex - 1]);
} }
function select(stateIndex) function select(index)
{
if (srcMode())
selectState(locations[index].state);
else
selectState(index);
}
function selectState(stateIndex)
{ {
display(stateIndex); display(stateIndex);
currentSelectedState = stateIndex; currentSelectedState = stateIndex;
var state = debugData.states[stateIndex]; var state = debugData.states[stateIndex];
statesSlider.value = stateIndex;
jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1) jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1)
jumpIntoBackAction.enabled(stateIndex > 0); jumpIntoBackAction.enabled(stateIndex > 0);
jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1); jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1);
jumpOverBackAction.enabled(stateIndex > 0); jumpOverBackAction.enabled(stateIndex > 0);
jumpOutBackAction.enabled(state.levels.length > 1); jumpOutBackAction.enabled(state.levels.length > 1);
jumpOutForwardAction.enabled(state.levels.length > 1); jumpOutForwardAction.enabled(state.levels.length > 1);
runForwardAction.enabled(stateIndex < debugData.states.length - 1)
runBackAction.enabled(stateIndex > 0);
var callStackData = []; var callStackData = [];
for (var l = 0; l < state.levels.length; l++) { for (var l = 0; l < state.levels.length; l++) {
var address = debugData.states[state.levels[l] + 1].address; var address = debugData.states[state.levels[l] + 1].code.address;
callStackData.push(address); callStackData.push(address);
} }
callStackData.push(debugData.states[0].address); callStackData.push(debugData.states[0].code.address);
callStack.listModel = callStackData; callStack.listModel = callStackData;
if (srcMode())
statesSlider.value = locationMap[stateIndex];
else
statesSlider.value = stateIndex;
} }
function codeStr(stateIndex) function codeStr(stateIndex)
@ -147,6 +220,24 @@ function isReturnInstruction(index)
return state.instruction === "RETURN" return state.instruction === "RETURN"
} }
function locationsIntersect(l1, l2)
{
return l1.start <= l2.end && l1.end >= l2.start;
}
function breakpointHit(i)
{
var bpLocations = breakpoints[debugData.states[i].code.documentId];
if (bpLocations) {
var location = locations[locationMap[i]];
if (location.start >= 0 && location.end >= location.start)
for (var b = 0; b < bpLocations.length; b++)
if (locationsIntersect(location, bpLocations[b]))
return true;
}
return false;
}
function stepIntoBack() function stepIntoBack()
{ {
moveSelection(-1); moveSelection(-1);
@ -173,25 +264,48 @@ function stepIntoForward()
moveSelection(1); moveSelection(1);
} }
function runBack()
{
var i = currentSelectedState - 1;
while (i > 0 && !breakpointHit(i)) {
--i;
}
selectState(i);
}
function runForward()
{
var i = currentSelectedState + 1;
while (i < debugData.states.length - 1 && !breakpointHit(i)) {
++i;
}
selectState(i);
}
function stepOutBack() function stepOutBack()
{ {
var i = currentSelectedState - 1; var i = currentSelectedState - 1;
var depth = 0; var depth = 0;
while (--i >= 0) while (--i >= 0) {
if (breakpointHit(i))
break;
if (isCallInstruction(i)) if (isCallInstruction(i))
if (depth == 0) if (depth == 0)
break; break;
else depth--; else depth--;
else if (isReturnInstruction(i)) else if (isReturnInstruction(i))
depth++; depth++;
select(i); }
selectState(i);
} }
function stepOutForward() function stepOutForward()
{ {
var i = currentSelectedState; var i = currentSelectedState;
var depth = 0; var depth = 0;
while (++i < debugData.states.length) while (++i < debugData.states.length) {
if (breakpointHit(i))
break;
if (isReturnInstruction(i)) if (isReturnInstruction(i))
if (depth == 0) if (depth == 0)
break; break;
@ -199,7 +313,8 @@ function stepOutForward()
depth--; depth--;
else if (isCallInstruction(i)) else if (isCallInstruction(i))
depth++; depth++;
select(i + 1); }
selectState(i + 1);
} }
function jumpTo(value) function jumpTo(value)

4
mix/qml/js/ProjectModel.js

@ -66,7 +66,7 @@ function saveProject() {
function loadProject(path) { function loadProject(path) {
closeProject(); closeProject();
console.log("loading project at " + path); console.log("Loading project at " + path);
var projectFile = path + projectFileName; var projectFile = path + projectFileName;
var json = fileIo.readFile(projectFile); var json = fileIo.readFile(projectFile);
var projectData = JSON.parse(json); var projectData = JSON.parse(json);
@ -189,7 +189,7 @@ function doCloseProject() {
function doCreateProject(title, path) { function doCreateProject(title, path) {
closeProject(); closeProject();
console.log("creating project " + title + " at " + path); console.log("Creating project " + title + " at " + path);
if (path[path.length - 1] !== "/") if (path[path.length - 1] !== "/")
path += "/"; path += "/";
var dirPath = path + title + "/"; var dirPath = path + title + "/";

3
mix/qml/js/TransactionHelper.js

@ -22,12 +22,13 @@ function rpcCall(requests, callBack)
httpRequest.setRequestHeader("Connection", "close"); httpRequest.setRequestHeader("Connection", "close");
httpRequest.onreadystatechange = function() { httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === XMLHttpRequest.DONE) { if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status !== 200) if (httpRequest.status !== 200 || httpRequest.responseText === "")
{ {
var errorText = qsTr("Deployment error: Error while registering Dapp ") + httpRequest.status; var errorText = qsTr("Deployment error: Error while registering Dapp ") + httpRequest.status;
console.log(errorText); console.log(errorText);
deploymentError(errorText); deploymentError(errorText);
} }
else
callBack(httpRequest.status, httpRequest.responseText) callBack(httpRequest.status, httpRequest.responseText)
} }
} }

24
mix/qml/main.qml

@ -37,7 +37,6 @@ ApplicationWindow {
} }
Menu { Menu {
title: qsTr("Deploy") title: qsTr("Deploy")
MenuItem { action: debugRunAction }
MenuItem { action: mineAction } MenuItem { action: mineAction }
MenuSeparator {} MenuSeparator {}
MenuItem { action: editStatesAction } MenuItem { action: editStatesAction }
@ -46,6 +45,12 @@ ApplicationWindow {
MenuSeparator {} MenuSeparator {}
MenuItem { action: toggleRunOnLoadAction } MenuItem { action: toggleRunOnLoadAction }
} }
Menu {
title: qsTr("Debug")
MenuItem { action: debugRunAction }
MenuSeparator {}
MenuItem { action: toggleAssemblyDebuggingAction }
}
Menu { Menu {
title: qsTr("Windows") title: qsTr("Windows")
MenuItem { action: openNextDocumentAction } MenuItem { action: openNextDocumentAction }
@ -129,6 +134,15 @@ ApplicationWindow {
enabled: codeModel.hasContract && !clientModel.running enabled: codeModel.hasContract && !clientModel.running
} }
Action {
id: toggleAssemblyDebuggingAction
text: qsTr("Show VM Code")
shortcut: "Ctrl+Alt+V"
onTriggered: mainContent.rightPane.assemblyMode = !mainContent.rightPane.assemblyMode;
checked: mainContent.rightPane.assemblyMode;
enabled: true
}
Action { Action {
id: toggleWebPreviewAction id: toggleWebPreviewAction
text: qsTr("Show Web View") text: qsTr("Show Web View")
@ -294,6 +308,14 @@ ApplicationWindow {
onTriggered: projectModel.openPrevDocument(); onTriggered: projectModel.openPrevDocument();
} }
Action {
id: toggleBreakpointAction
text: qsTr("Toggle Breakpoint")
shortcut: "F9"
enabled: mainContent.codeEditor.editingContract();
onTriggered: mainContent.toggleBreakpoint();
}
Action { Action {
id: deployViaRpcAction id: deployViaRpcAction
text: qsTr("Deploy to Network") text: qsTr("Deploy to Network")

1
mix/res.qrc

@ -102,5 +102,6 @@
<file>qml/WebPreviewStyle.qml</file> <file>qml/WebPreviewStyle.qml</file>
<file>qml/img/available_updates.png</file> <file>qml/img/available_updates.png</file>
<file>qml/DeploymentDialog.qml</file> <file>qml/DeploymentDialog.qml</file>
<file>qml/img/search_filled.png</file>
</qresource> </qresource>
</RCC> </RCC>

261
test/SolidityEndToEndTest.cpp

@ -1619,9 +1619,11 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic)
function sendAmount(uint amount) returns (uint256 bal) { function sendAmount(uint amount) returns (uint256 bal) {
return h.getBalance.value(amount)(); return h.getBalance.value(amount)();
} }
function outOfGas() returns (bool flagBefore, bool flagAfter, uint myBal) { function outOfGas() returns (bool ret) {
flagBefore = h.getFlag(); h.setFlag.gas(2)(); // should fail due to OOG
h.setFlag.gas(2)(); // should fail due to OOG, return value can be garbage return true;
}
function checkState() returns (bool flagAfter, uint myBal) {
flagAfter = h.getFlag(); flagAfter = h.getFlag();
myBal = this.balance; myBal = this.balance;
} }
@ -1630,7 +1632,8 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic)
compileAndRun(sourceCode, 20); compileAndRun(sourceCode, 20);
BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(5)); BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(5));
// call to helper should not succeed but amount should be transferred anyway // call to helper should not succeed but amount should be transferred anyway
BOOST_REQUIRE(callContractFunction("outOfGas()", 5) == encodeArgs(false, false, 20 - 5)); BOOST_REQUIRE(callContractFunction("outOfGas()", 5) == bytes());
BOOST_REQUIRE(callContractFunction("checkState()", 5) == encodeArgs(false, 20 - 5));
} }
BOOST_AUTO_TEST_CASE(value_complex) BOOST_AUTO_TEST_CASE(value_complex)
@ -2504,11 +2507,11 @@ BOOST_AUTO_TEST_CASE(struct_containing_bytes_copy_and_delete)
compileAndRun(sourceCode); compileAndRun(sourceCode);
string data = "123456789012345678901234567890123"; string data = "123456789012345678901234567890123";
BOOST_CHECK(m_state.storage(m_contractAddress).empty()); BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("set(uint256,bytes,uint256)", u256(data.length()), 12, data, 13) == encodeArgs(true)); BOOST_CHECK(callContractFunction("set(uint256,bytes,uint256)", 12, u256(data.length()), 13, data) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty()); BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("copy()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("copy()") == encodeArgs(true));
BOOST_CHECK(m_state.storage(m_contractAddress).empty()); BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("set(uint256,bytes,uint256)", u256(data.length()), 12, data, 13) == encodeArgs(true)); BOOST_CHECK(callContractFunction("set(uint256,bytes,uint256)", 12, u256(data.length()), 13, data) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty()); BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("del()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("del()") == encodeArgs(true));
BOOST_CHECK(m_state.storage(m_contractAddress).empty()); BOOST_CHECK(m_state.storage(m_contractAddress).empty());
@ -2661,8 +2664,8 @@ BOOST_AUTO_TEST_CASE(bytes_in_arguments)
bytes calldata1 = encodeArgs(u256(innercalldata1.length()), 12, innercalldata1, 13); bytes calldata1 = encodeArgs(u256(innercalldata1.length()), 12, innercalldata1, 13);
string innercalldata2 = asString(FixedHash<4>(dev::sha3("g(uint256)")).asBytes() + encodeArgs(3)); string innercalldata2 = asString(FixedHash<4>(dev::sha3("g(uint256)")).asBytes() + encodeArgs(3));
bytes calldata = encodeArgs( bytes calldata = encodeArgs(
u256(innercalldata1.length()), u256(innercalldata2.length()), 12, u256(innercalldata1.length()), u256(innercalldata2.length()), 13,
12, innercalldata1, innercalldata2, 13); innercalldata1, innercalldata2);
BOOST_CHECK(callContractFunction("test(uint256,bytes,bytes,uint256)", calldata) BOOST_CHECK(callContractFunction("test(uint256,bytes,bytes,uint256)", calldata)
== encodeArgs(12, (8 + 9) * 3, 13, u256(innercalldata1.length()))); == encodeArgs(12, (8 + 9) * 3, 13, u256(innercalldata1.length())));
} }
@ -2768,6 +2771,248 @@ BOOST_AUTO_TEST_CASE(dynamic_out_of_bounds_array_access)
BOOST_CHECK(callContractFunction("length()") == encodeArgs(4)); BOOST_CHECK(callContractFunction("length()") == encodeArgs(4));
} }
BOOST_AUTO_TEST_CASE(fixed_array_cleanup)
{
char const* sourceCode = R"(
contract c {
uint spacer1;
uint spacer2;
uint[20] data;
function fill() {
for (uint i = 0; i < data.length; ++i) data[i] = i+1;
}
function clear() { delete data; }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("fill()") == bytes());
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("clear()") == bytes());
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(short_fixed_array_cleanup)
{
char const* sourceCode = R"(
contract c {
uint spacer1;
uint spacer2;
uint[3] data;
function fill() {
for (uint i = 0; i < data.length; ++i) data[i] = i+1;
}
function clear() { delete data; }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("fill()") == bytes());
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("clear()") == bytes());
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(dynamic_array_cleanup)
{
char const* sourceCode = R"(
contract c {
uint[20] spacer;
uint[] dynamic;
function fill() {
dynamic.length = 21;
for (uint i = 0; i < dynamic.length; ++i) dynamic[i] = i+1;
}
function halfClear() { dynamic.length = 5; }
function fullClear() { delete dynamic; }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("fill()") == bytes());
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("halfClear()") == bytes());
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("fullClear()") == bytes());
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(dynamic_multi_array_cleanup)
{
char const* sourceCode = R"(
contract c {
struct s { uint[][] d; }
s[] data;
function fill() returns (uint) {
data.length = 3;
data[2].d.length = 4;
data[2].d[3].length = 5;
data[2].d[3][4] = 8;
return data[2].d[3][4];
}
function clear() { delete data; }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("fill()") == encodeArgs(8));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("clear()") == bytes());
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(array_copy_storage_storage_dyn_dyn)
{
char const* sourceCode = R"(
contract c {
uint[] data1;
uint[] data2;
function setData1(uint length, uint index, uint value) {
data1.length = length; if (index < length) data1[index] = value;
}
function copyStorageStorage() { data2 = data1; }
function getData2(uint index) returns (uint len, uint val) {
len = data2.length; if (index < len) val = data2[index];
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("setData1(uint256,uint256,uint256)", 10, 5, 4) == bytes());
BOOST_CHECK(callContractFunction("copyStorageStorage()") == bytes());
BOOST_CHECK(callContractFunction("getData2(uint256)", 5) == encodeArgs(10, 4));
BOOST_CHECK(callContractFunction("setData1(uint256,uint256,uint256)", 0, 0, 0) == bytes());
BOOST_CHECK(callContractFunction("copyStorageStorage()") == bytes());
BOOST_CHECK(callContractFunction("getData2(uint256)", 0) == encodeArgs(0, 0));
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(array_copy_storage_storage_static_static)
{
char const* sourceCode = R"(
contract c {
uint[40] data1;
uint[20] data2;
function test() returns (uint x, uint y){
data1[30] = 4;
data1[2] = 7;
data1[3] = 9;
data2[3] = 8;
data1 = data2;
x = data1[3];
y = data1[30]; // should be cleared
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("test()") == encodeArgs(8, 0));
}
BOOST_AUTO_TEST_CASE(array_copy_storage_storage_static_dynamic)
{
char const* sourceCode = R"(
contract c {
uint[9] data1;
uint[] data2;
function test() returns (uint x, uint y){
data1[8] = 4;
data2 = data1;
x = data2.length;
y = data2[8];
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("test()") == encodeArgs(9, 4));
}
BOOST_AUTO_TEST_CASE(array_copy_storage_storage_struct)
{
char const* sourceCode = R"(
contract c {
struct Data { uint x; uint y; }
Data[] data1;
Data[] data2;
function test() returns (uint x, uint y) {
data1.length = 9;
data1[8].x = 4;
data1[8].y = 5;
data2 = data1;
x = data2[8].x;
y = data2[8].y;
data1.length = 0;
data2 = data1;
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("test()") == encodeArgs(4, 5));
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base)
{
char const* sourceCode = R"(
contract Base {
function Base(uint i)
{
m_i = i;
}
uint public m_i;
}
contract Derived is Base(2) {
function Derived(uint i) Base(i)
{}
}
contract Final is Derived(4) {
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("m_i()") == encodeArgs(4));
}
BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base_base)
{
char const* sourceCode = R"(
contract Base {
function Base(uint j)
{
m_i = j;
}
uint public m_i;
}
contract Base1 is Base(3) {
function Base1(uint k) Base(k*k) {}
}
contract Derived is Base(3), Base1(2) {
function Derived(uint i) Base(i) Base1(i)
{}
}
contract Final is Derived(4) {
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("m_i()") == encodeArgs(4));
}
BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base_base_with_gap)
{
char const* sourceCode = R"(
contract Base {
function Base(uint i)
{
m_i = i;
}
uint public m_i;
}
contract Base1 is Base(3) {}
contract Derived is Base(2), Base1 {
function Derived(uint i) Base(i) {}
}
contract Final is Derived(4) {
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("m_i()") == encodeArgs(4));
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} }

109
test/SolidityNameAndTypeResolution.cpp

@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(inheritance_diamond_basic)
function g() { f(); rootFunction(); } function g() { f(); rootFunction(); }
} }
)"; )";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); BOOST_CHECK_NO_THROW(parseTextAndResolveNamesWithChecks(text));
} }
BOOST_AUTO_TEST_CASE(cyclic_inheritance) BOOST_AUTO_TEST_CASE(cyclic_inheritance)
@ -720,6 +720,58 @@ BOOST_AUTO_TEST_CASE(private_state_variable)
BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of an internal variable should not exist"); BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of an internal variable should not exist");
} }
BOOST_AUTO_TEST_CASE(base_class_state_variable_accessor)
{
// test for issue #1126 https://github.com/ethereum/cpp-ethereum/issues/1126
char const* text = "contract Parent {\n"
" uint256 public m_aMember;\n"
"}\n"
"contract Child is Parent{\n"
" function foo() returns (uint256) { return Parent.m_aMember; }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextAndResolveNamesWithChecks(text));
}
BOOST_AUTO_TEST_CASE(base_class_state_variable_internal_member)
{
char const* text = "contract Parent {\n"
" uint256 internal m_aMember;\n"
"}\n"
"contract Child is Parent{\n"
" function foo() returns (uint256) { return Parent.m_aMember; }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextAndResolveNamesWithChecks(text));
}
BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class1)
{
char const* text = "contract Parent1 {\n"
" uint256 internal m_aMember1;\n"
"}\n"
"contract Parent2 is Parent1{\n"
" uint256 internal m_aMember2;\n"
"}\n"
"contract Child is Parent2{\n"
" function foo() returns (uint256) { return Parent2.m_aMember1; }\n"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class2)
{
char const* text = "contract Parent1 {\n"
" uint256 internal m_aMember1;\n"
"}\n"
"contract Parent2 is Parent1{\n"
" uint256 internal m_aMember2;\n"
"}\n"
"contract Child is Parent2{\n"
" function foo() returns (uint256) { return Child.m_aMember2; }\n"
" uint256 public m_aMember3;\n"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(fallback_function) BOOST_AUTO_TEST_CASE(fallback_function)
{ {
char const* text = R"( char const* text = R"(
@ -1185,6 +1237,61 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length)
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
} }
BOOST_AUTO_TEST_CASE(array_copy_with_different_types1)
{
char const* text = R"(
contract c {
bytes a;
uint[] b;
function f() { b = a; }
})";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(array_copy_with_different_types2)
{
char const* text = R"(
contract c {
uint32[] a;
uint8[] b;
function f() { b = a; }
})";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(array_copy_with_different_types_conversion_possible)
{
char const* text = R"(
contract c {
uint32[] a;
uint8[] b;
function f() { a = b; }
})";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(array_copy_with_different_types_static_dynamic)
{
char const* text = R"(
contract c {
uint32[] a;
uint8[80] b;
function f() { a = b; }
})";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(array_copy_with_different_types_dynamic_static)
{
char const* text = R"(
contract c {
uint[] a;
uint[80] b;
function f() { b = a; }
})";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} }

Loading…
Cancel
Save