diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index 5e9d2fcd3..be35b62c2 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -99,6 +99,8 @@ void Assembly::append(Assembly const& _a) { if (i.type() == Tag || i.type() == PushTag) i.m_data += m_usedTags; + else if (i.type() == PushSub || i.type() == PushSubSize) + i.m_data += m_subs.size(); append(i); } m_deposit = newDeposit; @@ -108,7 +110,7 @@ void Assembly::append(Assembly const& _a) for (auto const& i: _a.m_strings) m_strings.insert(i); for (auto const& i: _a.m_subs) - m_subs.insert(i); + m_subs.push_back(i); assert(!_a.m_baseDeposit); assert(!_a.m_totalDeposit); @@ -199,7 +201,6 @@ ostream& Assembly::streamRLP(ostream& _out, string const& _prefix, StringMap con _out << _prefix << ".code:" << endl; for (AssemblyItem const& i: m_items) { - string sourceLine = getLocationFromSources(_sourceCodes, i.getLocation()); _out << _prefix; switch (i.m_type) { @@ -239,29 +240,30 @@ ostream& Assembly::streamRLP(ostream& _out, string const& _prefix, StringMap con default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } - _out << string("\t\t") << sourceLine << endl; + _out << string("\t\t") << getLocationFromSources(_sourceCodes, i.getLocation()) << endl; } if (!m_data.empty() || !m_subs.empty()) { _out << _prefix << ".data:" << endl; for (auto const& i: m_data) - if (!m_subs.count(i.first)) + if (u256(i.first) >= m_subs.size()) _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << toHex(i.second) << endl; - for (auto const& i: m_subs) + for (size_t i = 0; i < m_subs.size(); ++i) { - _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << endl; - i.second.streamRLP(_out, _prefix + " "); + _out << _prefix << " " << hex << i << ": " << endl; + m_subs[i].streamRLP(_out, _prefix + " ", _sourceCodes); } } return _out; } -AssemblyItem const& Assembly::append(AssemblyItem const& _i, SourceLocation const& _location) +AssemblyItem const& Assembly::append(AssemblyItem const& _i) { m_deposit += _i.deposit(); m_items.push_back(_i); - m_items.back().setLocation(_location); + if (m_items.back().getLocation().isEmpty() && !m_currentSourceLocation.isEmpty()) + m_items.back().setLocation(m_currentSourceLocation); return back(); } @@ -493,8 +495,8 @@ Assembly& Assembly::optimise(bool _enable) copt << total << " optimisations done."; - for (auto& i: m_subs) - i.second.optimise(true); + for (auto& sub: m_subs) + sub.optimise(true); return *this; } @@ -511,8 +513,8 @@ bytes Assembly::assemble() const unsigned bytesPerTag = dev::bytesRequired(totalBytes); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; - for (auto const& i: m_subs) - m_data[i.first] = i.second.assemble(); + for (size_t i = 0; i < m_subs.size(); ++i) + m_data[u256(i)] = m_subs[i].assemble(); unsigned bytesRequiredIncludingData = bytesRequired(); unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); diff --git a/libevmcore/Assembly.h b/libevmcore/Assembly.h index 4b818e624..cd71db747 100644 --- a/libevmcore/Assembly.h +++ b/libevmcore/Assembly.h @@ -81,16 +81,16 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h = (u256)std::hash()(asString(_data)); m_data[h] = _data; return AssemblyItem(PushData, h); } - AssemblyItem newSub(Assembly const& _sub) { h256 h = h256::random(s_fixedHashEngine); m_subs[h] = _sub; return AssemblyItem(PushSub, h); } + AssemblyItem newSub(Assembly const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } AssemblyItem newPushString(std::string const& _data) { h256 h = (u256)std::hash()(_data); m_strings[h] = _data; return AssemblyItem(PushString, h); } - AssemblyItem newPushSubSize(h256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } + AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); void append(Assembly const& _a, int _deposit); - AssemblyItem const& append(AssemblyItem const& _i, SourceLocation const& _location = SourceLocation()); - AssemblyItem const& append(std::string const& _data, SourceLocation const& _location = SourceLocation()) { return append(newPushString(_data), _location); } - AssemblyItem const& append(bytes const& _data, SourceLocation const& _location = SourceLocation()) { return append(newData(_data), _location); } + AssemblyItem const& append(AssemblyItem const& _i); + AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } + AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } /// Pushes the final size of the current assembly itself. Use this when the code is modified /// after compilation and CODESIZE is not an option. @@ -119,6 +119,9 @@ public: void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } + /// Changes the source location used for each appended item. + void setSourceLocation(SourceLocation const& _location) { m_currentSourceLocation = _location; } + bytes assemble() const; Assembly& optimise(bool _enable); std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "", const StringMap &_sourceCodes = StringMap()) const; @@ -131,12 +134,14 @@ protected: unsigned m_usedTags = 0; AssemblyItems m_items; mutable std::map m_data; - std::map m_subs; + std::vector m_subs; std::map m_strings; int m_deposit = 0; int m_baseDeposit = 0; int m_totalDeposit = 0; + + SourceLocation m_currentSourceLocation; }; inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 5eeb0c3e2..7ff846bdb 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -132,7 +132,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) { - CompilerContext::LocationSetter locationSetter(m_context, &_constructor); + CompilerContext::LocationSetter locationSetter(m_context, _constructor); FunctionType constructorType(_constructor); if (!constructorType.getParameterTypes().empty()) { @@ -146,7 +146,7 @@ void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) void Compiler::appendConstructor(FunctionDefinition const& _constructor) { - CompilerContext::LocationSetter locationSetter(m_context, &_constructor); + CompilerContext::LocationSetter locationSetter(m_context, _constructor); // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program unsigned argumentSize = 0; for (ASTPointer const& var: _constructor.getParameters()) @@ -192,10 +192,12 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) for (auto const& it: interfaceFunctions) { FunctionTypePointer const& functionType = it.second; + solAssert(functionType->hasDeclaration(), ""); + CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration()); m_context << callDataUnpackerEntryPoints.at(it.first); eth::AssemblyItem returnTag = m_context.pushNewTag(); appendCalldataUnpacker(functionType->getParameterTypes()); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration())); m_context << returnTag; appendReturnValuePacker(functionType->getReturnParameterTypes()); } @@ -286,7 +288,7 @@ void Compiler::initializeStateVariables(ContractDefinition const& _contract) bool Compiler::visit(VariableDeclaration const& _variableDeclaration) { solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclaration); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration); m_context.startFunction(_variableDeclaration); m_breakTags.clear(); @@ -300,7 +302,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration) bool Compiler::visit(FunctionDefinition const& _function) { - CompilerContext::LocationSetter locationSetter(m_context, &_function); + CompilerContext::LocationSetter locationSetter(m_context, _function); //@todo to simplify this, the calling convention could by changed such that // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack @@ -384,7 +386,7 @@ bool Compiler::visit(FunctionDefinition const& _function) bool Compiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_ifStatement); + CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); compileExpression(_ifStatement.getCondition()); eth::AssemblyItem trueTag = m_context.appendConditionalJump(); if (_ifStatement.getFalseStatement()) @@ -401,7 +403,7 @@ bool Compiler::visit(IfStatement const& _ifStatement) bool Compiler::visit(WhileStatement const& _whileStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_whileStatement); + CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -427,7 +429,7 @@ bool Compiler::visit(WhileStatement const& _whileStatement) bool Compiler::visit(ForStatement const& _forStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_forStatement); + CompilerContext::LocationSetter locationSetter(m_context, _forStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -464,7 +466,7 @@ bool Compiler::visit(ForStatement const& _forStatement) bool Compiler::visit(Continue const& _continueStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_continueStatement); + CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); if (!m_continueTags.empty()) m_context.appendJumpTo(m_continueTags.back()); return false; @@ -472,7 +474,7 @@ bool Compiler::visit(Continue const& _continueStatement) bool Compiler::visit(Break const& _breakStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_breakStatement); + CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); if (!m_breakTags.empty()) m_context.appendJumpTo(m_breakTags.back()); return false; @@ -480,7 +482,7 @@ bool Compiler::visit(Break const& _breakStatement) bool Compiler::visit(Return const& _return) { - CompilerContext::LocationSetter locationSetter(m_context, &_return); + CompilerContext::LocationSetter locationSetter(m_context, _return); //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.getExpression()) { @@ -499,7 +501,7 @@ bool Compiler::visit(Return const& _return) bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); if (Expression const* expression = _variableDeclarationStatement.getExpression()) { compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); @@ -512,7 +514,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta bool Compiler::visit(ExpressionStatement const& _expressionStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_expressionStatement); + CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); Expression const& expression = _expressionStatement.getExpression(); compileExpression(expression); CompilerUtils(m_context).popStackElement(*expression.getType()); @@ -523,7 +525,7 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement) bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_placeholderStatement); + CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); ++m_modifierDepth; appendModifierOrFunctionCode(); --m_modifierDepth; @@ -550,7 +552,7 @@ void Compiler::appendModifierOrFunctionCode() } 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(), ""); for (unsigned i = 0; i < modifier.getParameters().size(); ++i) { diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index ee8d3e00c..1dea62e93 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -69,7 +69,7 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) { - LocationSetter locationSetter(*this, &_declaration); + LocationSetter locationSetter(*this, _declaration); addVariable(_declaration); int const size = _declaration.getType()->getSizeOnStack(); for (int i = 0; i < size; ++i) @@ -182,34 +182,7 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node) stack newStack; newStack.push(_node); std::swap(m_visitedNodes, newStack); -} - -CompilerContext& CompilerContext::operator<<(eth::AssemblyItem const& _item) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_item, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(eth::Instruction _instruction) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_instruction, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(u256 const& _value) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_value, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(bytes const& _data) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_data, m_visitedNodes.top()->getLocation()); - return *this; + updateSourceLocation(); } vector::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const @@ -220,5 +193,10 @@ vector::const_iterator CompilerContext::getSuperContr return ++it; } +void CompilerContext::updateSourceLocation() +{ + m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation()); +} + } } diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index e42e7c76c..4d63d8ba0 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -108,15 +108,15 @@ public: /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes - void popVisitedNodes() { m_visitedNodes.pop(); } + void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); } /// Pushes an ASTNode to the stack of visited nodes - void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); } + void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item); - CompilerContext& operator<<(eth::Instruction _instruction); - CompilerContext& operator<<(u256 const& _value); - CompilerContext& operator<<(bytes const& _data); + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } + CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } eth::Assembly const& getAssembly() const { return m_asm; } /// @arg _sourceCodes is the map of input files to source code strings @@ -130,12 +130,14 @@ public: class LocationSetter: public ScopeGuard { public: - LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): - ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } + LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): + ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } }; private: std::vector::const_iterator getSuperContract(const ContractDefinition &_contract) const; + /// Updates source location set in the assembly. + void updateSourceLocation(); eth::Assembly m_asm; /// Magic global variables like msg, tx or this, distinguished by type. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 3d7a25311..b02aecf5f 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -48,7 +48,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c if (!_varDecl.getValue()) return; solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.getValue()->accept(*this); appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); @@ -57,7 +57,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) { - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); FunctionType accessorType(_varDecl); unsigned length = 0; @@ -204,7 +204,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con bool ExpressionCompiler::visit(Assignment const& _assignment) { - CompilerContext::LocationSetter locationSetter(m_context, &_assignment); + CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); if (_assignment.getType()->isValueType()) appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); @@ -237,7 +237,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { - CompilerContext::LocationSetter locationSetter(m_context, &_unaryOperation); + CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); //@todo type checking and creating code for an operator should be in the same place: // the operator should know how to convert itself and to which types it applies, so // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that @@ -307,7 +307,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) { - CompilerContext::LocationSetter locationSetter(m_context, &_binaryOperation); + CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation); Expression const& leftExpression = _binaryOperation.getLeftExpression(); Expression const& rightExpression = _binaryOperation.getRightExpression(); Type const& commonType = _binaryOperation.getCommonType(); @@ -354,7 +354,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { - CompilerContext::LocationSetter locationSetter(m_context, &_functionCall); + CompilerContext::LocationSetter locationSetter(m_context, _functionCall); using Location = FunctionType::Location; if (_functionCall.isTypeConversion()) { @@ -572,7 +572,7 @@ bool ExpressionCompiler::visit(NewExpression const&) void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_memberAccess); + CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); ASTString const& member = _memberAccess.getMemberName(); switch (_memberAccess.getExpression().getType()->getCategory()) { @@ -707,7 +707,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_indexAccess); + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); _indexAccess.getBaseExpression().accept(*this); Type const& baseType = *_indexAccess.getBaseExpression().getType(); @@ -821,6 +821,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) void ExpressionCompiler::endVisit(Identifier const& _identifier) { + CompilerContext::LocationSetter locationSetter(m_context, _identifier); Declaration const* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) { @@ -853,6 +854,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Literal const& _literal) { + CompilerContext::LocationSetter locationSetter(m_context, _literal); switch (_literal.getType()->getCategory()) { case Type::Category::IntegerConstant: diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 809d2dfd6..4e099b05b 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -37,7 +37,6 @@ #include "QVariableDefinition.h" #include "ContractCallDataEncoder.h" #include "CodeModel.h" -#include "ClientModel.h" #include "QEther.h" #include "Web3Server.h" #include "ClientModel.h" @@ -318,19 +317,28 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QDebugData* debugData = new QDebugData(); QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership); QList codes; + QList> codeMaps; + QList codeItems; + QList contracts; for (MachineCode const& code: _t.executionCode) { - codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code)); + QHash codeMap; + codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code, codeMap)); + codeMaps.push_back(std::move(codeMap)); //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)); + codes.back()->setDocument(compilerRes.documentId()); + codeItems.push_back(std::move(assemblyItems)); + contracts.push_back(&compilerRes); + } + else + { + codeItems.push_back(AssemblyItems()); + contracts.push_back(nullptr); } } @@ -339,18 +347,77 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) data.push_back(QMachineState::getDebugCallData(debugData, d)); QVariantList states; + QStringList solCallStack; + std::map solLocals; // + QList returnStack; + + unsigned prevInstructionIndex = 0; for (MachineState const& s: _t.machineStates) - states.append(QVariant::fromValue(new QMachineState(debugData, s, codes[s.codeIndex], data[s.dataIndex]))); + { + int instructionIndex = codeMaps[s.codeIndex][static_cast(s.curPC)]; + QSolState* solState = nullptr; + if (!codeItems[s.codeIndex].empty() && contracts[s.codeIndex]) + { + CompiledContract const* contract = contracts[s.codeIndex]; + AssemblyItem const& instruction = codeItems[s.codeIndex][instructionIndex]; - debugData->setStates(std::move(states)); + if (instruction.type() == dev::eth::Push && !instruction.data()) + { + //register new local variable initialization + auto localIter = contract->locals().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); + if (localIter != contract->locals().end()) + solLocals[s.stack.size()] = localIter.value(); + } - //QList returnParameters; - //returnParameters = encoder.decode(f->returnParameters(), debuggingContent.returnValue); + if (instruction.type() == dev::eth::Tag) //TODO: use annotations + { + //track calls into functions + auto functionIter = contract->functions().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); + if (functionIter != contract->functions().end()) + { + QString functionName = functionIter.value(); + solCallStack.push_back(functionName); + returnStack.push_back(prevInstructionIndex + 1); + } + else if (!returnStack.empty() && instructionIndex == returnStack.back()) + { + returnStack.pop_back(); + solCallStack.pop_back(); + } + } + + //format solidity context values + QStringList locals; + for(auto l: solLocals) + if (l.first < (int)s.stack.size()) + locals.push_back(l.second.name + "\t" + formatValue(l.second.type, s.stack[l.first])); + + QStringList storage; + for(auto st: s.storage) + { + if (st.first < std::numeric_limits::max()) + { + auto storageIter = contract->storage().find(static_cast(st.first)); + if (storageIter != contract->storage().end()) + storage.push_back(storageIter.value().name + "\t" + formatValue(storageIter.value().type, st.second)); + } + } + prevInstructionIndex = instructionIndex; + solState = new QSolState(debugData, storage, solCallStack, locals, instruction.getLocation().start, instruction.getLocation().end); + } + + states.append(QVariant::fromValue(new QMachineState(debugData, instructionIndex, s, codes[s.codeIndex], data[s.dataIndex], solState))); + } - //collect states for last transaction + debugData->setStates(std::move(states)); debugDataReady(debugData); } +QString ClientModel::formatValue(SolidityType const&, dev::u256 const& _value) +{ + return QString::fromStdString(prettyU256(_value)); +} + void ClientModel::emptyRecord() { debugDataReady(new QDebugData()); diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 8810502bd..a12fe22e6 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -41,6 +41,7 @@ class QEther; class QDebugData; class MixClient; class QVariableDefinition; +struct SolidityType; /// Backend transaction config class struct TransactionSettings @@ -198,6 +199,7 @@ private: void onNewTransaction(); void onStateReset(); void showDebuggerForTransaction(ExecutionResult const& _t); + QString formatValue(SolidityType const& _type, dev::u256 const& _value); AppContext* m_context; std::atomic m_running; diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index cc2a29b42..bb258334c 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -25,7 +25,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -43,6 +46,91 @@ using namespace dev::mix; const std::set c_predefinedContracts = { "Config", "Coin", "CoinReg", "coin", "service", "owned", "mortal", "NameReg", "named", "std", "configUser" }; + +namespace +{ + using namespace dev::solidity; + class CollectDeclarationsVisitor: public ASTConstVisitor + { + public: + CollectDeclarationsVisitor(QHash* _functions, QHash* _locals, QHash* _storage): + m_functions(_functions), m_locals(_locals), m_storage(_storage), m_functionScope(false), m_storageSlot(0) {} + private: + QHash* m_functions; + QHash* m_locals; + QHash* m_storage; + bool m_functionScope; + uint m_storageSlot; + + LocationPair nodeLocation(ASTNode const& _node) + { + return LocationPair(_node.getLocation().start, _node.getLocation().end); + } + + SolidityType nodeType(Type const* _type) + { + if (!_type) + return SolidityType { SolidityType::Type::UnsignedInteger, 32 }; + switch (_type->getCategory()) + { + case Type::Category::Integer: + { + IntegerType const* it = dynamic_cast(_type); + unsigned size = it->getNumBits() / 8; + SolidityType::Type typeCode = it->isAddress() ? SolidityType::Type::Address : it->isHash() ? SolidityType::Type::Hash : it->isSigned() ? SolidityType::Type::SignedInteger : SolidityType::Type::UnsignedInteger; + return SolidityType { typeCode, size }; + } + case Type::Category::Bool: + return SolidityType { SolidityType::Type::Bool, _type->getSizeOnStack() * 32 }; + case Type::Category::String: + { + StaticStringType const* s = dynamic_cast(_type); + return SolidityType { SolidityType::Type::String, static_cast(s->getNumBytes()) }; + } + case Type::Category::Contract: + return SolidityType { SolidityType::Type::Address, _type->getSizeOnStack() * 32 }; + case Type::Category::Array: + case Type::Category::Enum: + case Type::Category::Function: + case Type::Category::IntegerConstant: + case Type::Category::Magic: + case Type::Category::Mapping: + case Type::Category::Modifier: + case Type::Category::Real: + case Type::Category::Struct: + case Type::Category::TypeType: + case Type::Category::Void: + default: + return SolidityType { SolidityType::Type::UnsignedInteger, 32 }; + } + } + + virtual bool visit(FunctionDefinition const& _node) + { + m_functions->insert(nodeLocation(_node), QString::fromStdString(_node.getName())); + m_functionScope = true; + return true; + } + + virtual void endVisit(FunctionDefinition const&) + { + m_functionScope = false; + } + + virtual bool visit(VariableDeclaration const& _node) + { + SolidityDeclaration decl; + decl.type = nodeType(_node.getType().get()); + decl.name = QString::fromStdString(_node.getName()); + if (m_functionScope) + m_locals->insert(nodeLocation(_node), decl); + else + m_storage->insert(m_storageSlot++, decl); + return true; + } + }; +} + void BackgroundWorker::queueCodeChange(int _jobId) { m_model->runCompilationJob(_jobId); @@ -52,18 +140,23 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler QObject(nullptr), m_sourceHash(qHash(_source)) { - auto const& contractDefinition = _compiler.getContractDefinition(_contractName.toStdString()); + std::string name = _contractName.toStdString(); + auto const& contractDefinition = _compiler.getContractDefinition(name); m_contract.reset(new QContractDefinition(&contractDefinition)); QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership); m_bytes = _compiler.getBytecode(_contractName.toStdString()); - m_assemblyItems = _compiler.getRuntimeAssemblyItems(_contractName.toStdString()); - m_constructorAssemblyItems = _compiler.getAssemblyItems(_contractName.toStdString()); + m_assemblyItems = _compiler.getRuntimeAssemblyItems(name); + m_constructorAssemblyItems = _compiler.getAssemblyItems(name); + dev::solidity::InterfaceHandler interfaceHandler; m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); if (m_contractInterface.isEmpty()) m_contractInterface = "[]"; if (contractDefinition.getLocation().sourceName.get()) m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName); + + CollectDeclarationsVisitor visitor(&m_functions, &m_locals, &m_storage); + contractDefinition.accept(visitor); } QString CompiledContract::codeHex() const @@ -121,12 +214,11 @@ void CodeModel::reset(QVariantMap const& _documents) void CodeModel::registerCodeChange(QString const& _documentId, QString const& _code) { - { - Guard l(x_contractMap); - CompiledContract* contract = m_contractMap.value(_documentId); - if (contract != nullptr && contract->m_sourceHash == qHash(_code)) - return; + CompiledContract* contract = contractByDocumentId(_documentId); + if (contract != nullptr && contract->m_sourceHash == qHash(_code)) + return; + { Guard pl(x_pendingContracts); m_pendingContracts[_documentId] = _code; } @@ -196,7 +288,8 @@ void CodeModel::runCompilationJob(int _jobId) if (c_predefinedContracts.count(n) != 0) continue; QString name = QString::fromStdString(n); - auto sourceIter = m_pendingContracts.find(name); + QString sourceName = QString::fromStdString(*cs.getContractDefinition(n).getLocation().sourceName); + auto sourceIter = m_pendingContracts.find(sourceName); QString source = sourceIter != m_pendingContracts.end() ? sourceIter->second : QString(); CompiledContract* contract = new CompiledContract(cs, name, source); QQmlEngine::setObjectOwnership(contract, QQmlEngine::CppOwnership); diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 511d2e01f..de9a07bab 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -64,6 +64,29 @@ private: CodeModel* m_model; }; +using LocationPair = QPair; + +struct SolidityType +{ + enum class Type //TODO: arrays and structs + { + SignedInteger, + UnsignedInteger, + Hash, + Bool, + Address, + String, + }; + Type type; + unsigned size; //bytes +}; + +struct SolidityDeclaration +{ + QString name; + SolidityType type; +}; + ///Compilation result model. Contains all the compiled contract data required by UI class CompiledContract: public QObject { @@ -93,6 +116,10 @@ public: /// @returns contract source Id QString documentId() const { return m_documentId; } + QHash const& functions() const { return m_functions; } + QHash const& locals() const { return m_locals; } + QHash const& storage() const { return m_storage; } + private: uint m_sourceHash; std::shared_ptr m_contract; @@ -102,11 +129,13 @@ private: QString m_documentId; eth::AssemblyItems m_assemblyItems; eth::AssemblyItems m_constructorAssemblyItems; + QHash m_functions; + QHash m_locals; + QHash m_storage; friend class CodeModel; }; - using ContractMap = QHash; /// Code compilation model. Compiles contracts in background an provides compiled contract data diff --git a/mix/DebuggingStateWrapper.cpp b/mix/DebuggingStateWrapper.cpp index 6cb29bbae..b8fbdef30 100644 --- a/mix/DebuggingStateWrapper.cpp +++ b/mix/DebuggingStateWrapper.cpp @@ -69,7 +69,7 @@ namespace } } -QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code) +QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code, QHash& o_codeMap) { QVariantList codeStr; for (unsigned i = 0; i <= _code.size(); ++i) @@ -80,14 +80,15 @@ QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _addr QString s = QString::fromStdString(instructionInfo((Instruction)b).name); std::ostringstream out; out << std::hex << std::setw(4) << std::setfill('0') << i; - int line = i; + int offset = i; if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32) { unsigned bc = getPushNumber((Instruction)b); s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&_code[i + 1], bc))); i += bc; } - codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s, line))); + o_codeMap[offset] = codeStr.size(); + codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s))); } catch (...) { diff --git a/mix/DebuggingStateWrapper.h b/mix/DebuggingStateWrapper.h index 7a34d6493..7eb8c932c 100644 --- a/mix/DebuggingStateWrapper.h +++ b/mix/DebuggingStateWrapper.h @@ -26,12 +26,12 @@ #include #include -#include +#include #include #include #include +#include "MachineStates.h" #include "QVariableDefinition.h" -#include "MixClient.h" #include "QBigInt.h" namespace dev @@ -46,32 +46,39 @@ class QInstruction: public QObject { Q_OBJECT Q_PROPERTY(QString line MEMBER m_line CONSTANT) - Q_PROPERTY(int processIndex MEMBER m_processIndex CONSTANT) public: - QInstruction(QObject* _owner, QString _line, int _processIndex): QObject(_owner), m_line(_line), m_processIndex(_processIndex) {} + QInstruction(QObject* _owner, QString _line): QObject(_owner), m_line(_line) {} private: QString m_line; - int m_processIndex; }; - -class QSourceLocation: public QObject +/** + * @brief Solidity state + */ +class QSolState: public QObject { Q_OBJECT + Q_PROPERTY(QStringList storage MEMBER m_storage CONSTANT) + Q_PROPERTY(QStringList callStack MEMBER m_callStack CONSTANT) + Q_PROPERTY(QStringList locals MEMBER m_locals CONSTANT) 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) {} + QSolState(QObject* _parent, QStringList const& _storage, QStringList const& _callStack, QStringList const& _locals, int _start, int _end): + QObject(_parent), m_storage(_storage), m_callStack(_callStack), m_locals(_locals), m_start(_start), m_end(_end) + { } private: + QStringList m_storage; + QStringList m_callStack; + QStringList m_locals; int m_start; int m_end; }; - /** * @brief Shared container for lines */ @@ -79,19 +86,17 @@ class QCode: public QObject { Q_OBJECT 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: 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; } + void setDocument(QString const& _documentId) { m_document = _documentId; } private: QVariantList m_instructions; QString m_address; QString m_document; - QVariantList m_locations; }; /** @@ -133,6 +138,7 @@ class QMachineState: public QObject Q_OBJECT Q_PROPERTY(int step READ step CONSTANT) Q_PROPERTY(int curPC READ curPC CONSTANT) + Q_PROPERTY(int instructionIndex MEMBER m_instructionIndex CONSTANT) Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT) Q_PROPERTY(QBigInt* gas READ gas CONSTANT) Q_PROPERTY(QString instruction READ instruction CONSTANT) @@ -146,10 +152,11 @@ class QMachineState: public QObject Q_PROPERTY(QVariantList levels READ levels CONSTANT) Q_PROPERTY(unsigned codeIndex READ codeIndex CONSTANT) Q_PROPERTY(unsigned dataIndex READ dataIndex CONSTANT) + Q_PROPERTY(QObject* solidity MEMBER m_solState CONSTANT) public: - QMachineState(QObject* _owner, MachineState const& _state, QCode* _code, QCallData* _callData): - QObject(_owner), m_state(_state), m_code(_code), m_callData(_callData) {} + QMachineState(QObject* _owner, int _instructionIndex, MachineState const& _state, QCode* _code, QCallData* _callData, QSolState* _solState): + QObject(_owner), m_instructionIndex(_instructionIndex), m_state(_state), m_code(_code), m_callData(_callData), m_solState(_solState) { } /// Get the step of this machine states. int step() { return (int)m_state.steps; } /// Get the proccessed code index. @@ -168,7 +175,7 @@ public: QStringList debugStorage(); /// Get memory. QVariantList debugMemory(); - /// get end of debug information. + /// Get end of debug information. QString endOfDebug(); /// Get the new memory size. QBigInt* newMemSize(); @@ -177,18 +184,18 @@ public: /// Get all previous steps. QVariantList levels(); /// Get the current processed machine state. - MachineState state() { return m_state; } - /// Set the current processed machine state. - void setState(MachineState _state) { m_state = _state; } + MachineState const& state() const { return m_state; } /// Convert all machine states in human readable code. - static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code); + static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code, QHash& o_codeMap); /// Convert call data into human readable form static QCallData* getDebugCallData(QObject* _owner, bytes const& _data); private: + int m_instructionIndex; MachineState m_state; QCode* m_code; QCallData* m_callData; + QSolState* m_solState; }; } diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 781924db7..061057bb4 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -41,7 +41,7 @@ namespace mix { const Secret c_defaultUserAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074"); -const u256 c_mixGenesisDifficulty = (u256) 1 << 4; +const u256 c_mixGenesisDifficulty = c_minimumDifficulty; //TODO: make it lower for Mix somehow class MixBlockChain: public dev::eth::BlockChain { diff --git a/mix/qml/CallStack.qml b/mix/qml/CallStack.qml new file mode 100644 index 000000000..c9e22532d --- /dev/null +++ b/mix/qml/CallStack.qml @@ -0,0 +1,74 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 +import "." + +DebugInfoList +{ + id: callStack + collapsible: true + title : qsTr("Call Stack") + enableSelection: true + itemDelegate: + Item { + anchors.fill: parent + + Rectangle { + anchors.fill: parent + color: "#4A90E2" + visible: styleData.selected; + } + + RowLayout + { + id: row + anchors.fill: parent + Rectangle + { + color: "#f7f7f7" + Layout.fillWidth: true + Layout.minimumWidth: 30 + Layout.maximumWidth: 30 + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + font.family: "monospace" + anchors.leftMargin: 5 + color: "#4a4a4a" + text: styleData.row; + font.pointSize: DebuggerPaneStyle.general.basicFontSize + width: parent.width - 5 + elide: Text.ElideRight + } + } + Rectangle + { + color: "transparent" + Layout.fillWidth: true + Layout.minimumWidth: parent.width - 30 + Layout.maximumWidth: parent.width - 30 + Text { + anchors.leftMargin: 5 + width: parent.width - 5 + wrapMode: Text.NoWrap + anchors.left: parent.left + font.family: "monospace" + anchors.verticalCenter: parent.verticalCenter + color: "#4a4a4a" + text: styleData.value; + elide: Text.ElideRight + font.pointSize: DebuggerPaneStyle.general.basicFontSize + } + } + } + + Rectangle { + anchors.top: row.bottom + width: parent.width; + height: 1; + color: "#cccccc" + anchors.bottom: parent.bottom + } + } +} diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index af46bee1c..732d02f75 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -29,6 +29,7 @@ Rectangle { onAssemblyModeChanged: { Debugger.updateMode(); + machineStates.updateHeight(); } function displayCompilationErrorIfAny() @@ -90,6 +91,9 @@ Rectangle { property alias memoryDumpHeightSettings: memoryRect.height property alias callDataHeightSettings: callDataRect.height property alias transactionLogVisible: transactionLog.visible + property alias solCallStackHeightSettings: solStackRect.height + property alias solStorageHeightSettings: solStorageRect.height + property alias solLocalsHeightSettings: solLocalsRect.height } Rectangle @@ -183,8 +187,12 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true function updateHeight() { - statesLayout.height = buttonRow.childrenRect.height + assemblyCodeRow.childrenRect.height + - callStackRect.childrenRect.height + storageRect.childrenRect.height + memoryRect.childrenRect.height + callDataRect.childrenRect.height + 120; + var h = buttonRow.childrenRect.height; + if (assemblyMode) + h += assemblyCodeRow.childrenRect.height + callStackRect.childrenRect.height + storageRect.childrenRect.height + memoryRect.childrenRect.height + callDataRect.childrenRect.height; + else + h += solStackRect.childrenRect.height + solLocalsRect.childrenRect.height + solStorageRect.childrenRect.height; + statesLayout.height = h + 120; } Component.onCompleted: updateHeight(); @@ -546,83 +554,66 @@ Rectangle { Rectangle { - id: callStackRect; + id: solStackRect; color: "transparent" Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); - DebugInfoList - { - id: callStack - collapsible: true + visible: !assemblyMode + CallStack { anchors.fill: parent - title : qsTr("Call Stack") - enableSelection: true - onRowActivated: Debugger.displayFrame(index); - itemDelegate: - Item { - anchors.fill: parent + id: solCallStack + } + } - Rectangle { - anchors.fill: parent - color: "#4A90E2" - visible: styleData.selected; - } + Rectangle + { + id: solLocalsRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: !assemblyMode + StorageView { + title : qsTr("Locals") + anchors.fill: parent + id: solLocals + } + } - RowLayout - { - id: row - anchors.fill: parent - Rectangle - { - color: "#f7f7f7" - Layout.fillWidth: true - Layout.minimumWidth: 30 - Layout.maximumWidth: 30 - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - font.family: "monospace" - anchors.leftMargin: 5 - color: "#4a4a4a" - text: styleData.row; - font.pointSize: DebuggerPaneStyle.general.basicFontSize - width: parent.width - 5 - elide: Text.ElideRight - } - } - Rectangle - { - color: "transparent" - Layout.fillWidth: true - Layout.minimumWidth: parent.width - 30 - Layout.maximumWidth: parent.width - 30 - Text { - anchors.leftMargin: 5 - width: parent.width - 5 - wrapMode: Text.NoWrap - anchors.left: parent.left - font.family: "monospace" - anchors.verticalCenter: parent.verticalCenter - color: "#4a4a4a" - text: styleData.value; - elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize - } - } - } + Rectangle + { + id: solStorageRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: !assemblyMode + StorageView { + title : qsTr("Members") + anchors.fill: parent + id: solStorage + } + } - Rectangle { - anchors.top: row.bottom - width: parent.width; - height: 1; - color: "#cccccc" - anchors.bottom: parent.bottom - } - } + Rectangle + { + id: callStackRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode + CallStack { + anchors.fill: parent + id: callStack + onRowActivated: Debugger.displayFrame(index); } } + + + Rectangle { id: storageRect @@ -631,68 +622,10 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); - DebugInfoList - { - id: storage + visible: assemblyMode + StorageView { anchors.fill: parent - collapsible: true - title : qsTr("Storage") - itemDelegate: - Item { - anchors.fill: parent - RowLayout - { - id: row - anchors.fill: parent - Rectangle - { - color: "#f7f7f7" - Layout.fillWidth: true - Layout.minimumWidth: parent.width / 2 - Layout.maximumWidth: parent.width / 2 - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - font.family: "monospace" - anchors.leftMargin: 5 - color: "#4a4a4a" - text: styleData.value.split('\t')[0]; - font.pointSize: DebuggerPaneStyle.general.basicFontSize - width: parent.width - 5 - elide: Text.ElideRight - } - } - Rectangle - { - color: "transparent" - Layout.fillWidth: true - Layout.minimumWidth: parent.width / 2 - Layout.maximumWidth: parent.width / 2 - Text { - maximumLineCount: 1 - clip: true - anchors.leftMargin: 5 - width: parent.width - 5 - wrapMode: Text.WrapAnywhere - anchors.left: parent.left - font.family: "monospace" - anchors.verticalCenter: parent.verticalCenter - color: "#4a4a4a" - text: styleData.value.split('\t')[1]; - elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize - } - } - } - - Rectangle { - anchors.top: row.bottom - width: parent.width; - height: 1; - color: "#cccccc" - anchors.bottom: parent.bottom - } - } + id: storage } } @@ -704,6 +637,7 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode DebugInfoList { id: memoryDump anchors.fill: parent @@ -726,6 +660,7 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode DebugInfoList { id: callDataDump anchors.fill: parent diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index e526d65bd..60a9a051e 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -159,8 +159,8 @@ Rectangle { color: "transparent" width: 100 height: parent.height - anchors.top: statusHeader.top - anchors.right: statusHeader.right + anchors.top: parent.top + anchors.right: parent.right RowLayout { anchors.fill: parent diff --git a/mix/qml/StorageView.qml b/mix/qml/StorageView.qml new file mode 100644 index 000000000..f4831e3c3 --- /dev/null +++ b/mix/qml/StorageView.qml @@ -0,0 +1,69 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 +import "." + +DebugInfoList +{ + id: storage + collapsible: true + title : qsTr("Storage") + itemDelegate: + Item { + anchors.fill: parent + RowLayout + { + id: row + anchors.fill: parent + Rectangle + { + color: "#f7f7f7" + Layout.fillWidth: true + Layout.minimumWidth: parent.width / 2 + Layout.maximumWidth: parent.width / 2 + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + font.family: "monospace" + anchors.leftMargin: 5 + color: "#4a4a4a" + text: styleData.value.split('\t')[0]; + font.pointSize: DebuggerPaneStyle.general.basicFontSize + width: parent.width - 5 + elide: Text.ElideRight + } + } + Rectangle + { + color: "transparent" + Layout.fillWidth: true + Layout.minimumWidth: parent.width / 2 + Layout.maximumWidth: parent.width / 2 + Text { + maximumLineCount: 1 + clip: true + anchors.leftMargin: 5 + width: parent.width - 5 + wrapMode: Text.WrapAnywhere + anchors.left: parent.left + font.family: "monospace" + anchors.verticalCenter: parent.verticalCenter + color: "#4a4a4a" + text: styleData.value.split('\t')[1]; + elide: Text.ElideRight + font.pointSize: DebuggerPaneStyle.general.basicFontSize + } + } + } + + Rectangle { + anchors.top: row.bottom + width: parent.width; + height: 1; + color: "#cccccc" + anchors.bottom: parent.bottom + } + } +} + diff --git a/mix/qml/js/Debugger.js b/mix/qml/js/Debugger.js index 30dd5489b..23aa81844 100644 --- a/mix/qml/js/Debugger.js +++ b/mix/qml/js/Debugger.js @@ -4,7 +4,6 @@ var currentSelectedState = null; var currentDisplayedState = null; var debugData = null; -var codeMap = null; var locations = []; var locationMap = {}; var breakpoints = {}; @@ -56,7 +55,7 @@ function initLocations() 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; + var location = code.documentId ? debugData.states[i].solidity : 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 }; @@ -65,6 +64,7 @@ function initLocations() locationMap[i] = locations.length - 1; } locations.push({ start: -1, end: -1, documentId: code.documentId, state: i }); + locationMap[debugData.states.length - 1] = locations.length - 1; } @@ -93,12 +93,10 @@ function initSlider() function setupInstructions(stateIndex) { var instructions = debugData.states[stateIndex].code.instructions; - codeMap = {}; statesList.model.clear(); - for (var i = 0; i < instructions.length; i++) { + for (var i = 0; i < instructions.length; i++) statesList.model.append(instructions[i]); - codeMap[instructions[i].processIndex] = i; - } + callDataDump.listModel = debugData.states[stateIndex].callData.items; } @@ -129,14 +127,14 @@ function display(stateIndex) setupInstructions(stateIndex); if (debugData.states[stateIndex].dataIndex !== debugData.states[currentDisplayedState].dataIndex) setupCallData(stateIndex); - var codeLine = codeStr(stateIndex); var state = debugData.states[stateIndex]; + var codeLine = state.instructionIndex; highlightSelection(codeLine); completeCtxInformation(state); currentDisplayedState = stateIndex; var docId = debugData.states[stateIndex].code.documentId; if (docId) - debugExecuteLocation(docId, locations[locationMap[stateIndex]]); + debugExecuteLocation(docId, debugData.states[stateIndex].solidity); } function displayFrame(frameIndex) @@ -183,12 +181,6 @@ function selectState(stateIndex) statesSlider.value = stateIndex; } -function codeStr(stateIndex) -{ - var state = debugData.states[stateIndex]; - return codeMap[state.curPC]; -} - function highlightSelection(index) { statesList.positionViewAtRow(index, ListView.Center); @@ -206,6 +198,15 @@ function completeCtxInformation(state) stack.listModel = state.debugStack; storage.listModel = state.debugStorage; memoryDump.listModel = state.debugMemory; + if (state.solidity) { + solLocals.listModel = state.solidity.locals; + solStorage.listModel = state.solidity.storage; + solCallStack.listModel = state.solidity.callStack; + } else { + solLocals.listModel = []; + solStorage.listModel = []; + solCallStack.listModel = []; + } } function isCallInstruction(index) @@ -229,7 +230,7 @@ function breakpointHit(i) { var bpLocations = breakpoints[debugData.states[i].code.documentId]; if (bpLocations) { - var location = locations[locationMap[i]]; + var location = debugData.states[i].solidity; if (location.start >= 0 && location.end >= location.start) for (var b = 0; b < bpLocations.length; b++) if (locationsIntersect(location, bpLocations[b])) diff --git a/mix/res.qrc b/mix/res.qrc index fc1b2fb23..515c5fadf 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -103,6 +103,8 @@ qml/img/available_updates.png qml/DeploymentDialog.qml qml/img/search_filled.png + qml/StorageView.qml + qml/CallStack.qml qml/img/help.png qml/img/openedfolder.png qml/img/b64.png diff --git a/test/TestHelper.h b/test/TestHelper.h index 91ec977db..0f23f945c 100644 --- a/test/TestHelper.h +++ b/test/TestHelper.h @@ -46,39 +46,55 @@ namespace test /// Make sure that no Exception is thrown during testing. If one is thrown show its info and fail the test. /// Our version of BOOST_REQUIRE_NO_THROW() -/// @param _expression The expression for which to make sure no exceptions are thrown +/// @param _statenent The statement for which to make sure no exceptions are thrown /// @param _message A message to act as a prefix to the expression's error information -#define ETH_TEST_REQUIRE_NO_THROW(_expression, _message) \ +#define ETH_TEST_REQUIRE_NO_THROW(_statement, _message) \ do \ { \ try \ { \ - _expression; \ + BOOST_TEST_PASSPOINT(); \ + _statement; \ } \ catch (boost::exception const& _e) \ { \ - auto msg = std::string(_message"\n") + boost::diagnostic_information(_e); \ - BOOST_FAIL(msg); \ + auto msg = std::string(_message " due to an exception thrown by " \ + BOOST_STRINGIZE(_statement) "\n") + boost::diagnostic_information(_e); \ + BOOST_CHECK_IMPL(false, msg, REQUIRE, CHECK_MSG); \ } \ - } while (0) + catch (...) \ + { \ + BOOST_CHECK_IMPL(false, "Unknown exception thrown by " \ + BOOST_STRINGIZE(_statement), REQUIRE, CHECK_MSG); \ + } \ + } \ + while (0) /// Check if an Exception is thrown during testing. If one is thrown show its info and continue the test /// Our version of BOOST_CHECK_NO_THROW() -/// @param _expression The expression for which to make sure no exceptions are thrown +/// @param _statement The statement for which to make sure no exceptions are thrown /// @param _message A message to act as a prefix to the expression's error information -#define ETH_TEST_CHECK_NO_THROW(_expression, _message) \ +#define ETH_TEST_CHECK_NO_THROW(_statement, _message) \ do \ { \ try \ { \ - _expression; \ + BOOST_TEST_PASSPOINT(); \ + _statement; \ } \ catch (boost::exception const& _e) \ { \ - auto msg = std::string(_message"\n") + boost::diagnostic_information(_e); \ - BOOST_MESSAGE(msg); \ + auto msg = std::string(_message " due to an exception thrown by " \ + BOOST_STRINGIZE(_statement) "\n") + boost::diagnostic_information(_e); \ + BOOST_CHECK_IMPL(false, msg, CHECK, CHECK_MSG); \ + } \ + catch (...) \ + { \ + BOOST_CHECK_IMPL(false, "Unknown exception thrown by " \ + BOOST_STRINGIZE(_statement), CHECK, CHECK_MSG ); \ } \ - } while (0) + } \ + while (0) class ImportTest