diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 61a3dee1f..717757c70 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -422,7 +422,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) //try to resolve contract for source level debugging auto nameIter = m_contractNames.find(code.address); CompiledContract const* compilerRes = nullptr; - if (nameIter != m_contractNames.end() && (compilerRes = m_codeModel->tryGetContract(nameIter->second))) + if (nameIter != m_contractNames.end() && (compilerRes = m_codeModel->tryGetContract(nameIter->second))) //returned object is guaranteed to live till the end of event handler in main thread { eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes->assemblyItems() : compilerRes->constructorAssemblyItems(); codes.back()->setDocument(compilerRes->documentId()); @@ -467,9 +467,9 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) { //track calls into functions AssemblyItem const& prevInstruction = codeItems[s.codeIndex][prevInstructionIndex]; - auto functionIter = contract->functions().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); - if (functionIter != contract->functions().end() && ((prevInstruction.getJumpType() == AssemblyItem::JumpType::IntoFunction) || solCallStack.empty())) - solCallStack.push_front(QVariant::fromValue(functionIter.value())); + QString functionName = m_codeModel->resolveFunctionName(instruction.getLocation()); + if (!functionName.isEmpty() && ((prevInstruction.getJumpType() == AssemblyItem::JumpType::IntoFunction) || solCallStack.empty())) + solCallStack.push_front(QVariant::fromValue(functionName)); else if (prevInstruction.getJumpType() == AssemblyItem::JumpType::OutOfFunction && !solCallStack.empty()) solCallStack.pop_front(); } @@ -521,11 +521,13 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) prevInstructionIndex = instructionIndex; + // filter out locations that match whole function or contract SourceLocation location = instruction.getLocation(); - if (contract->contract()->location() == location || contract->functions().contains(LocationPair(location.start, location.end))) + QString source = QString::fromUtf8(location.sourceName->c_str()); + if (m_codeModel->isContractOrFunctionLocation(location)) location = dev::SourceLocation(-1, -1, location.sourceName); - solState = new QSolState(debugData, move(storage), move(solCallStack), move(locals), location.start, location.end, QString::fromUtf8(location.sourceName->c_str())); + solState = new QSolState(debugData, move(storage), move(solCallStack), move(locals), location.start, location.end, source); } states.append(QVariant::fromValue(new QMachineState(debugData, instructionIndex, s, codes[s.codeIndex], data[s.dataIndex], solState))); diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 357ce88b9..515123de5 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -51,30 +51,31 @@ const std::set c_predefinedContracts = namespace { using namespace dev::solidity; -class CollectDeclarationsVisitor: public ASTConstVisitor + +class CollectLocalsVisitor: public ASTConstVisitor { public: - CollectDeclarationsVisitor(QHash* _functions, QHash* _locals): - m_functions(_functions), m_locals(_locals), m_functionScope(false) {} + CollectLocalsVisitor(QHash* _locals): + m_locals(_locals), m_functionScope(false) {} + private: LocationPair nodeLocation(ASTNode const& _node) { return LocationPair(_node.getLocation().start, _node.getLocation().end); } - virtual bool visit(FunctionDefinition const& _node) + virtual bool visit(FunctionDefinition const&) { - m_functions->insert(nodeLocation(_node), QString::fromStdString(_node.getName())); m_functionScope = true; return true; } - virtual void endVisit(FunctionDefinition const&) + virtual void endVisit(FunctionDefinition const&) override { m_functionScope = false; } - virtual bool visit(VariableDeclaration const& _node) + virtual bool visit(VariableDeclaration const& _node) override { SolidityDeclaration decl; decl.type = CodeModel::nodeType(_node.getType().get()); @@ -87,11 +88,38 @@ private: } private: - QHash* m_functions; QHash* m_locals; bool m_functionScope; }; +class CollectLocationsVisitor: public ASTConstVisitor +{ +public: + CollectLocationsVisitor(SourceMap* _sourceMap): + m_sourceMap(_sourceMap) {} + +private: + LocationPair nodeLocation(ASTNode const& _node) + { + return LocationPair(_node.getLocation().start, _node.getLocation().end); + } + + virtual bool visit(FunctionDefinition const& _node) override + { + m_sourceMap->functions.insert(nodeLocation(_node), QString::fromStdString(_node.getName())); + return true; + } + + virtual bool visit(ContractDefinition const& _node) override + { + m_sourceMap->contracts.insert(nodeLocation(_node), QString::fromStdString(_node.getName())); + return true; + } + +private: + SourceMap* m_sourceMap; +}; + QHash collectStorage(dev::solidity::ContractDefinition const& _contract) { QHash result; @@ -132,7 +160,7 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler if (contractDefinition.getLocation().sourceName.get()) m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName); - CollectDeclarationsVisitor visitor(&m_functions, &m_locals); + CollectLocalsVisitor visitor(&m_locals); m_storage = collectStorage(contractDefinition); contractDefinition.accept(visitor); m_assemblyItems = *_compiler.getRuntimeAssemblyItems(name); @@ -243,6 +271,7 @@ void CodeModel::releaseContracts() for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); ++c) c.value()->deleteLater(); m_contractMap.clear(); + m_sourceMaps.clear(); } void CodeModel::runCompilationJob(int _jobId) @@ -253,13 +282,17 @@ void CodeModel::runCompilationJob(int _jobId) try { cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})"); + std::vector sourceNames; { Guard l(x_pendingContracts); for (auto const& c: m_pendingContracts) + { cs.addSource(c.first.toStdString(), c.second.toStdString()); + sourceNames.push_back(c.first.toStdString()); + } } cs.compile(false); - collectContracts(cs); + collectContracts(cs, sourceNames); } catch (dev::Exception const& _exception) { @@ -281,11 +314,20 @@ void CodeModel::runCompilationJob(int _jobId) emit stateChanged(); } -void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs) +void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs, std::vector const& _sourceNames) { Guard pl(x_pendingContracts); Guard l(x_contractMap); ContractMap result; + SourceMaps sourceMaps; + for (std::string const& sourceName: _sourceNames) + { + dev::solidity::SourceUnit const& source = _cs.getAST(sourceName); + SourceMap sourceMap; + CollectLocationsVisitor collector(&sourceMap); + source.accept(collector); + sourceMaps.insert(QString::fromStdString(sourceName), std::move(sourceMap)); + } for (std::string n: _cs.getContractNames()) { if (c_predefinedContracts.count(n) != 0) @@ -326,6 +368,7 @@ void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs) } releaseContracts(); m_contractMap.swap(result); + m_sourceMaps.swap(sourceMaps); emit codeChanged(); emit compilationComplete(); } @@ -431,3 +474,32 @@ SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) return r; } +bool CodeModel::isContractOrFunctionLocation(dev::SourceLocation const& _location) +{ + if (!_location.sourceName) + return false; + Guard l(x_contractMap); + auto sourceMapIter = m_sourceMaps.find(QString::fromStdString(*_location.sourceName)); + if (sourceMapIter != m_sourceMaps.cend()) + { + LocationPair location(_location.start, _location.end); + return sourceMapIter.value().contracts.contains(location) || sourceMapIter.value().functions.contains(location); + } + return false; +} + +QString CodeModel::resolveFunctionName(dev::SourceLocation const& _location) +{ + if (!_location.sourceName) + return QString(); + Guard l(x_contractMap); + auto sourceMapIter = m_sourceMaps.find(QString::fromStdString(*_location.sourceName)); + if (sourceMapIter != m_sourceMaps.cend()) + { + LocationPair location(_location.start, _location.end); + auto functionNameIter = sourceMapIter.value().functions.find(location); + if (functionNameIter != sourceMapIter.value().functions.cend()) + return functionNameIter.value(); + } + return QString(); +} diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 0572483c0..3c6c3abdf 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -97,7 +97,6 @@ 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; } @@ -110,7 +109,6 @@ private: QString m_documentId; eth::AssemblyItems m_assemblyItems; eth::AssemblyItems m_constructorAssemblyItems; - QHash m_functions; QHash m_locals; QHash m_storage; @@ -119,6 +117,17 @@ private: using ContractMap = QMap; //needs to be sorted +/// Source map +using LocationMap = QHash; + +struct SourceMap +{ + LocationMap contracts; + LocationMap functions; +}; + +using SourceMaps = QMap; //by source id + /// Code compilation model. Compiles contracts in background an provides compiled contract data class CodeModel: public QObject { @@ -153,6 +162,10 @@ public: Q_INVOKABLE void reset() { reset(QVariantMap()); } /// Convert solidity type info to mix type static SolidityType nodeType(dev::solidity::Type const* _type); + /// Check if given location belongs to contract or function + bool isContractOrFunctionLocation(dev::SourceLocation const& _location); + /// Get funciton name by location + QString resolveFunctionName(dev::SourceLocation const& _location); signals: /// Emited on compilation state change @@ -182,11 +195,12 @@ private: void runCompilationJob(int _jobId); void stop(); void releaseContracts(); - void collectContracts(solidity::CompilerStack const& _cs); + void collectContracts(dev::solidity::CompilerStack const& _cs, std::vector const& _sourceNames); std::atomic m_compiling; mutable dev::Mutex x_contractMap; ContractMap m_contractMap; + SourceMaps m_sourceMaps; std::unique_ptr m_codeHighlighterSettings; QThread m_backgroundThread; BackgroundWorker m_backgroundWorker; diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index 3a3a060c3..13d01d474 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -158,7 +158,7 @@ Dialog { id: importJsonFileDialog visible: false title: qsTr("Select State File") - nameFilters: [qsTr("JSON files (*.json)", "All files (*)")] + nameFilters: Qt.platform.os === "osx" ? [] : [qsTr("JSON files (*.json)", "All files (*)")] //qt 5.4 segfaults with filter string on OSX onAccepted: { var path = importJsonFileDialog.fileUrl.toString() var jsonData = fileIo.readFile(path) diff --git a/mix/qml/html/codeeditor.js b/mix/qml/html/codeeditor.js index e8504fee0..d25fbd091 100644 --- a/mix/qml/html/codeeditor.js +++ b/mix/qml/html/codeeditor.js @@ -126,7 +126,10 @@ highlightExecution = function(start, end) { executionMark.clear(); if (debugWarning) debugWarning.clear(); - executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" }); + if (start > 0 && end > start) { + executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" }); + editor.scrollIntoView(editor.posFromIndex(start)); + } } var changeId; diff --git a/mix/qml/js/Debugger.js b/mix/qml/js/Debugger.js index 3f5742fde..f6900489a 100644 --- a/mix/qml/js/Debugger.js +++ b/mix/qml/js/Debugger.js @@ -183,7 +183,8 @@ function selectState(stateIndex) function highlightSelection(index) { - statesList.positionViewAtRow(index, ListView.Center); + if (statesList.visible) + statesList.positionViewAtRow(index, ListView.Visible); statesList.selection.clear(); statesList.selection.select(index); } diff --git a/mix/test/qml/js/TestTutorial.js b/mix/test/qml/js/TestTutorial.js index 7f366f23b..895b5c9c1 100644 --- a/mix/test/qml/js/TestTutorial.js +++ b/mix/test/qml/js/TestTutorial.js @@ -52,7 +52,7 @@ function test_tutorial() transactionDialog.selectFunction("setRating"); clickElement(transactionDialog, 200, 310); ts.typeString("Titanic", transactionDialog); - clickElement(transactionDialog, 200, 350); + clickElement(transactionDialog, 200, 330); ts.typeString("2", transactionDialog); transactionDialog.acceptAndClose(); mainApplication.projectModel.stateDialog.acceptAndClose();