Browse Source

improved debugging model

cl-refactor
arkpar 10 years ago
parent
commit
50a1edb23b
  1. 132
      mix/AssemblyDebuggerControl.cpp
  2. 33
      mix/AssemblyDebuggerControl.h
  3. 27
      mix/AssemblyDebuggerModel.cpp
  4. 6
      mix/AssemblyDebuggerModel.h
  5. 4
      mix/CodeEditorExtensionManager.cpp
  6. 12
      mix/CodeModel.cpp
  7. 22
      mix/CodeModel.h
  8. 11
      mix/ConstantCompilationControl.cpp
  9. 1
      mix/ConstantCompilationControl.h
  10. 2
      mix/MixApplication.cpp
  11. 13
      mix/qml/StateList.qml
  12. 7
      mix/qml/main.qml

132
mix/AssemblyDebuggerControl.cpp

@ -22,6 +22,8 @@
#include <libsolidity/Token.h>
#include <libsolidity/Types.h>
#include <utility>
#include <stdexcept>
#include <boost/exception/diagnostic_information.hpp>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QQmlContext>
@ -55,7 +57,8 @@ QString toQString(dev::u256 _value)
return QString::fromStdString(s.str());
}
AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::ModalDialog)
AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context):
Extension(_context, ExtensionDisplayBehavior::ModalDialog), m_running(false)
{
qRegisterMetaType<QVariableDefinition*>("QVariableDefinition*");
qRegisterMetaType<QVariableDefinitionList*>("QVariableDefinitionList*");
@ -63,14 +66,11 @@ AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context): Extensio
qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>");
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
qRegisterMetaType<AssemblyDebuggerData>("AssemblyDebuggerData");
qRegisterMetaType<DebuggingStatusResult>("DebuggingStatusResult");
connect(this, SIGNAL(dataAvailable(bool, DebuggingStatusResult, QList<QVariableDefinition*>, QList<QObject*>, AssemblyDebuggerData)),
this, SLOT(updateGUI(bool, DebuggingStatusResult, QList<QVariableDefinition*>, QList<QObject*>, AssemblyDebuggerData)), Qt::QueuedConnection);
connect(this, &AssemblyDebuggerControl::dataAvailable, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection);
m_modelDebugger = std::unique_ptr<AssemblyDebuggerModel>(new AssemblyDebuggerModel);
_context->appEngine()->rootContext()->setContextProperty("debugModel", this);
m_modelDebugger = std::unique_ptr<AssemblyDebuggerModel>(new AssemblyDebuggerModel);
}
QString AssemblyDebuggerControl::contentUrl() const
@ -80,7 +80,7 @@ QString AssemblyDebuggerControl::contentUrl() const
QString AssemblyDebuggerControl::title() const
{
return QApplication::tr("debugger");
return QApplication::tr("Debugger");
}
void AssemblyDebuggerControl::start() const
@ -89,7 +89,7 @@ void AssemblyDebuggerControl::start() const
void AssemblyDebuggerControl::debugDeployment()
{
deployContract();
executeSequence(std::vector<TransactionSettings>(), 0);
}
void AssemblyDebuggerControl::debugState(QVariantMap _state)
@ -97,8 +97,7 @@ void AssemblyDebuggerControl::debugState(QVariantMap _state)
u256 balance = fromQString(_state.value("balance").toString());
QVariantList transactions = _state.value("transactions").toList();
resetState();
deployContract();
std::vector<TransactionSettings> transactionSequence;
for (auto const& t : transactions)
{
@ -114,93 +113,106 @@ void AssemblyDebuggerControl::debugState(QVariantMap _state)
for (auto p = params.cbegin(); p != params.cend(); ++p)
transactionSettings.parameterValues.insert(std::make_pair(p.key(), fromQString(p.value().toString())));
runTransaction(transactionSettings);
transactionSequence.push_back(transactionSettings);
}
executeSequence(transactionSequence, balance);
}
void AssemblyDebuggerControl::resetState()
{
m_modelDebugger->resetState();
m_ctx->displayMessageDialog(QApplication::tr("State status"), QApplication::tr("State reseted ... need to redeploy contract"));
}
void AssemblyDebuggerControl::callContract(TransactionSettings _tr, dev::Address _contract)
void AssemblyDebuggerControl::executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance)
{
if (m_running)
throw (std::logic_error("debugging already running"));
auto compilerRes = m_ctx->codeModel()->code();
if (!compilerRes->successfull())
m_ctx->displayMessageDialog("debugger","compilation failed");
else
std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract();
m_running = true;
emit runStarted();
emit stateChanged();
//run sequence
QtConcurrent::run([=]()
{
try
{
bytes contractCode = compilerRes->bytes();
std::vector<dev::bytes> transactonData;
QFunctionDefinition* f;
ContractCallDataEncoder c;
QContractDefinition const* contractDef = compilerRes->contract();
QFunctionDefinition* f = nullptr;
//encode data for all transactions
for (auto const& t : _sequence)
{
f = nullptr;
for (int k = 0; k < contractDef->functionsList().size(); k++)
{
if (contractDef->functionsList().at(k)->name() == _tr.functionId)
if (contractDef->functionsList().at(k)->name() == t.functionId)
{
f = contractDef->functionsList().at(k);
break;
}
}
if (!f)
m_ctx->displayMessageDialog(QApplication::tr("debugger"), QApplication::tr("function not found. Please redeploy this contract."));
else
{
throw std::runtime_error("function " + t.functionId.toStdString() + " not found");
c.encode(f->index());
for (int k = 0; k < f->parametersList().size(); k++)
{
QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(k);
c.encode(var, _tr.parameterValues[var->name()]);
}
DebuggingContent debuggingContent = m_modelDebugger->callContract(_contract, c.encodedData(), _tr);
debuggingContent.returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue);
finalizeExecution(debuggingContent);
u256 value = 0;
auto v = t.parameterValues.find(var->name());
if (v != t.parameterValues.cend())
value = v->second;
c.encode(var, value);
}
transactonData.emplace_back(c.encodedData());
}
}
void AssemblyDebuggerControl::deployContract()
{
auto compilerRes = m_ctx->codeModel()->code();
if (!compilerRes->successfull())
emit dataAvailable(false, DebuggingStatusResult::Compilationfailed);
else
{
m_previousDebugResult = m_modelDebugger->deployContract(compilerRes->bytes());
finalizeExecution(m_previousDebugResult);
}
}
//run contract creation first
m_modelDebugger->resetState(_balance);
DebuggingContent debuggingContent = m_modelDebugger->deployContract(contractCode);
Address address = debuggingContent.contractAddress;
for (unsigned i = 0; i < _sequence.size(); ++i)
debuggingContent = m_modelDebugger->callContract(address, transactonData.at(i), _sequence.at(i));
if (f)
debuggingContent.returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue);
void AssemblyDebuggerControl::finalizeExecution(DebuggingContent _debuggingContent)
{
//we need to wrap states in a QObject before sending to QML.
QList<QObject*> wStates;
for(int i = 0; i < _debuggingContent.machineStates.size(); i++)
for(int i = 0; i < debuggingContent.machineStates.size(); i++)
{
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(_debuggingContent.executionCode, _debuggingContent.executionData.toBytes()));
s->setState(_debuggingContent.machineStates.at(i));
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes()));
s->setState(debuggingContent.machineStates.at(i));
wStates.append(s);
}
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(_debuggingContent.executionCode);
emit dataAvailable(true, DebuggingStatusResult::Ok, _debuggingContent.returnParameters, wStates, code);
//collect states for last transaction
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode);
emit dataAvailable(debuggingContent.returnParameters, wStates, code);
emit runComplete();
}
catch(boost::exception const& e)
{
emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information()));
}
catch(std::exception const& e)
{
emit runFailed(e.what());
}
m_running = false;
emit stateChanged();
});
}
void AssemblyDebuggerControl::updateGUI(bool _success, DebuggingStatusResult const& _reason, QList<QVariableDefinition*> const& _returnParam, QList<QObject*> const& _wStates, AssemblyDebuggerData const& _code)
void AssemblyDebuggerControl::showDebugger(QList<QVariableDefinition*> const& _returnParam, QList<QObject*> const& _wStates, AssemblyDebuggerData const& _code)
{
Q_UNUSED(_reason);
if (_success)
{
m_appEngine->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates));
m_appEngine->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code)));
m_appEngine->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code)));
m_appEngine->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam)));
this->addContentOn(this);
}
else
m_ctx->displayMessageDialog(QApplication::tr("debugger"), QApplication::tr("compilation failed"));
}
void AssemblyDebuggerControl::runTransaction(TransactionSettings const& _tr)
void AssemblyDebuggerControl::showDebugError(QString const& _error)
{
callContract(_tr, m_previousDebugResult.contractAddress);
m_ctx->displayMessageDialog(QApplication::tr("Debugger"), _error);
}

33
mix/AssemblyDebuggerControl.h

@ -28,14 +28,8 @@
#include "AssemblyDebuggerModel.h"
using AssemblyDebuggerData = std::tuple<QList<QObject*>, dev::mix::QQMLMap*>;
enum DebuggingStatusResult
{
Ok,
Compilationfailed
};
Q_DECLARE_METATYPE(AssemblyDebuggerData)
Q_DECLARE_METATYPE(DebuggingStatusResult)
Q_DECLARE_METATYPE(dev::mix::DebuggingContent)
class AppContext;
@ -59,26 +53,35 @@ public:
QString title() const override;
QString contentUrl() const override;
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged)
private:
void deployContract();
void callContract(TransactionSettings _tr, Address _contract);
void finalizeExecution(DebuggingContent _content);
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
std::unique_ptr<AssemblyDebuggerModel> m_modelDebugger;
DebuggingContent m_previousDebugResult; //TODO: to be replaced in a more consistent struct. Used for now to keep the contract address in case of future transaction call.
bool m_running;
public slots:
/// Run the contract constructor and show debugger window.
void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction
/// @param _state JS object with state configuration
void debugState(QVariantMap _state);
void resetState();
private slots:
/// Update UI with machine states result. Display a modal dialog.
void updateGUI(bool _success, DebuggingStatusResult const& _reason, QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
/// Run the given transaction.
void runTransaction(TransactionSettings const& _tr);
void showDebugger(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
void showDebugError(QString const& _error);
signals:
void runStarted();
void runComplete();
void runFailed(QString const& _message);
void stateChanged();
/// Emited when machine states are available.
void dataAvailable(bool _success, DebuggingStatusResult const& _reason, QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
void dataAvailable(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
};
}

27
mix/AssemblyDebuggerModel.cpp

@ -41,20 +41,17 @@ namespace mix
{
AssemblyDebuggerModel::AssemblyDebuggerModel():
m_userAccount(KeyPair::create()),
m_baseState(Address(), m_overlayDB, BaseState::Empty)
m_userAccount(KeyPair::create())
{
m_baseState.addBalance(m_userAccount.address(), 10000000 * ether);
m_executiveState = m_baseState;
m_currentExecution = std::unique_ptr<Executive>(new Executive(m_executiveState, 0));
resetState(10000000 * ether);
}
DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const& _rawTransaction)
{
QList<DebuggingState> machineStates;
// Reset the state back to our clean premine.
m_currentExecution = std::unique_ptr<Executive>(new Executive(m_executiveState, 0));
m_currentExecution->setup(_rawTransaction);
eth::Executive execution(m_executiveState, 0);
execution.setup(_rawTransaction);
std::vector<DebuggingState const*> levels;
bytes code;
bytesConstRef data;
@ -80,12 +77,12 @@ DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const&
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels}));
};
m_currentExecution->go(onOp);
m_currentExecution->finalize(onOp);
execution.go(onOp);
execution.finalize(onOp);
m_executiveState.completeMine();
DebuggingContent d;
d.returnValue = m_currentExecution->out().toVector();
d.returnValue = execution.out().toVector();
d.machineStates = machineStates;
d.executionCode = code;
d.executionData = data;
@ -99,7 +96,7 @@ DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code)
u256 gasPrice = 10000000000000;
u256 gas = 1000000;
u256 amount = 100;
Transaction _tr(amount, gasPrice, min(gas, m_baseState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret());
Transaction _tr(amount, gasPrice, min(gas, m_executiveState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret());
bytes b = _tr.rlp();
dev::bytesConstRef bytesRef = &b;
DebuggingContent d = executeTransaction(bytesRef);
@ -110,7 +107,7 @@ DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code)
DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr)
{
Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_baseState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret());
Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_executiveState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret());
bytes b = tr.rlp();
dev::bytesConstRef bytesRef = &b;
DebuggingContent d = executeTransaction(bytesRef);
@ -118,10 +115,10 @@ DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, b
return d;
}
void AssemblyDebuggerModel::resetState()
void AssemblyDebuggerModel::resetState(u256 _balance)
{
// Reset the state back to our clean premine.
m_executiveState = m_baseState;
m_executiveState = eth::State(Address(), m_overlayDB, BaseState::Empty);
m_executiveState.addBalance(m_userAccount.address(), _balance);
}
}

6
mix/AssemblyDebuggerModel.h

@ -66,15 +66,13 @@ public:
DebuggingContent callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
/// Deploy the contract described by _code.
DebuggingContent deployContract(bytes const& _code);
/// Reset state to the base state.
void resetState();
/// Reset state to the empty state with given balance.
void resetState(u256 _balance);
private:
KeyPair m_userAccount;
OverlayDB m_overlayDB;
eth::State m_baseState;
eth::State m_executiveState;
std::unique_ptr<eth::Executive> m_currentExecution;
DebuggingContent executeTransaction(dev::bytesConstRef const& _rawTransaction);
};

4
mix/CodeEditorExtensionManager.cpp

@ -64,11 +64,13 @@ void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor)
void CodeEditorExtensionManager::initExtensions()
{
initExtension(std::make_shared<ConstantCompilationControl>(m_appContext));
std::shared_ptr<ConstantCompilationControl> output = std::make_shared<ConstantCompilationControl>(m_appContext);
std::shared_ptr<AssemblyDebuggerControl> debug = std::make_shared<AssemblyDebuggerControl>(m_appContext);
std::shared_ptr<StateListView> stateList = std::make_shared<StateListView>(m_appContext);
QObject::connect(m_doc, &QTextDocument::contentsChanged, [=]() { m_appContext->codeModel()->registerCodeChange(m_doc->toPlainText()); });
QObject::connect(debug.get(), &AssemblyDebuggerControl::runFailed, output.get(), &ConstantCompilationControl::displayError);
initExtension(output);
initExtension(debug);
initExtension(stateList);
}

12
mix/CodeModel.cpp

@ -63,7 +63,7 @@ CompilationResult::CompilationResult(CompilationResult const& _prev, QString con
{}
CodeModel::CodeModel(QObject* _parent) : QObject(_parent),
m_result(new CompilationResult(nullptr)), m_backgroundWorker(this), m_backgroundJobId(0)
m_compiling(false), m_result(new CompilationResult(nullptr)), m_backgroundWorker(this), m_backgroundJobId(0)
{
m_backgroundWorker.moveToThread(&m_backgroundThread);
connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection);
@ -94,10 +94,11 @@ void CodeModel::registerCodeChange(const QString &_code)
{
// launch the background thread
m_backgroundJobId++;
m_compiling = true;
emit stateChanged();
emit scheduleCompilationJob(m_backgroundJobId, _code);
}
void CodeModel::runCompilationJob(int _jobId, QString const& _code)
{
if (_jobId != m_backgroundJobId)
@ -124,11 +125,18 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code)
void CodeModel::onCompilationComplete(CompilationResult*_newResult)
{
m_compiling = false;
m_result.reset(_newResult);
emit compilationComplete();
emit stateChanged();
if (m_result->successfull())
emit codeChanged();
}
bool CodeModel::hasContract() const
{
return m_result->contract()->functionsList().size() > 0;
}
}
}

22
mix/CodeModel.h

@ -69,8 +69,10 @@ public:
/// Failed compilation result constructor
CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage, QObject* parent);
/// @returns contract definition
/// @returns contract definition for QML property
QContractDefinition* contract() { return m_contract.get(); }
/// @returns contract definition
std::shared_ptr<QContractDefinition> sharedContract() { return m_contract; }
/// Indicates if the compilation was successfull
bool successfull() const { return m_successfull; }
@ -96,12 +98,6 @@ private:
/// Background code compiler
class CodeModel : public QObject
{
enum Status
{
Idle, ///< No compiation in progress
Compiling, ///< Compilation currently in progress
};
Q_OBJECT
public:
@ -114,10 +110,17 @@ public:
CompilationResult const* code() const { return m_result.get(); }
Q_PROPERTY(CompilationResult* code READ code NOTIFY codeChanged)
Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged)
Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged)
/// @returns compilation status
bool isCompiling() const { return m_compiling; }
/// @returns true if contract has at least one function
bool hasContract() const;
signals:
/// Emited on compilation status change
void statusChanged(Status _from, Status _to);
/// Emited on compilation state change
void stateChanged();
/// Emitted on compilation complete
void compilationComplete();
/// Internal signal used to transfer compilation job to background thread
@ -138,6 +141,7 @@ private:
void runCompilationJob(int _jobId, QString const& _content);
void stop();
bool m_compiling;
std::unique_ptr<CompilationResult> m_result;
QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker;

11
mix/ConstantCompilationControl.cpp

@ -37,6 +37,7 @@ using namespace dev::mix;
ConstantCompilationControl::ConstantCompilationControl(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::Tab)
{
connect(_context->codeModel(), &CodeModel::compilationComplete, this, &ConstantCompilationControl::update);
connect(_context->codeModel(), &CodeModel::compilationComplete, this, &ConstantCompilationControl::update);
}
QString ConstantCompilationControl::contentUrl() const
@ -80,3 +81,13 @@ void ConstantCompilationControl::resetOutPut()
status->setProperty("text", "");
content->setProperty("text", "");
}
void ConstantCompilationControl::displayError(QString const& _error)
{
QObject* status = m_view->findChild<QObject*>("status", Qt::FindChildrenRecursively);
QObject* content = m_view->findChild<QObject*>("content", Qt::FindChildrenRecursively);
status->setProperty("text", "failure");
status->setProperty("color", "red");
content->setProperty("text", _error);
}

1
mix/ConstantCompilationControl.h

@ -45,6 +45,7 @@ private:
public slots:
void update();
void displayError(QString const& _error);
};
}

2
mix/MixApplication.cpp

@ -25,6 +25,8 @@
#include "MixApplication.h"
#include "AppContext.h"
#include <QMenuBar>
using namespace dev::mix;
MixApplication::MixApplication(int _argc, char* _argv[]):

13
mix/qml/StateList.qml

@ -35,8 +35,7 @@ Rectangle {
Button {
anchors.bottom: parent.bottom
text: qsTr("Add")
onClicked: stateListModel.addState();
action: addStateAction
}
StateDialog {
@ -61,7 +60,7 @@ Rectangle {
function addState() {
var item = {
title: "",
balance: "1000000000000",
balance: "100000000000000000000000000",
transactions: []
};
stateDialog.open(stateListModel.count, item);
@ -121,5 +120,13 @@ Rectangle {
}
}
}
Action {
id: addStateAction
text: "&Add State"
shortcut: "Ctrl+N"
enabled: codeModel.hasContract && !debugModel.running;
onTriggered: stateListModel.addState();
}
}

7
mix/qml/main.qml

@ -1,6 +1,6 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
@ -51,6 +51,7 @@ ApplicationWindow {
id: debugRunAction
text: "&Run"
shortcut: "F5"
enabled: codeModel.hasContract && !debugModel.running;
onTriggered: debugModel.debugDeployment();
}
@ -60,4 +61,6 @@ ApplicationWindow {
shortcut: "F6"
onTriggered: debugModel.resetState();
}
}

Loading…
Cancel
Save