Browse Source

Merge pull request #910 from arkpar/mix_dbg

mix: Debugger fixes
cl-refactor
Gav Wood 10 years ago
parent
commit
a2fb636fc9
  1. 3
      mix/AppContext.cpp
  2. 7
      mix/AssemblyDebuggerControl.cpp
  3. 4
      mix/AssemblyDebuggerControl.h
  4. 40
      mix/ClientModel.cpp
  5. 10
      mix/ClientModel.h
  6. 2
      mix/CodeModel.cpp
  7. 172
      mix/DebuggingStateWrapper.cpp
  8. 89
      mix/DebuggingStateWrapper.h
  9. 2
      mix/MixApplication.cpp
  10. 48
      mix/MixClient.cpp
  11. 9
      mix/MixClient.h
  12. 44
      mix/qml/Debugger.qml
  13. 15
      mix/qml/ItemDelegateDataDump.qml
  14. 2
      mix/qml/StepActionImage.qml
  15. 32
      mix/qml/html/cm/dialog.css
  16. 155
      mix/qml/html/cm/dialog.js
  17. 164
      mix/qml/html/cm/search.js
  18. 189
      mix/qml/html/cm/searchcursor.js
  19. 4
      mix/qml/html/codeeditor.html
  20. 3
      mix/qml/html/codeeditor.js
  21. 196
      mix/qml/js/Debugger.js
  22. 1
      mix/res.qrc
  23. BIN
      mix/res/mix_256x256x32.png
  24. 4
      mix/web.qrc

3
mix/AppContext.cpp

@ -27,6 +27,7 @@
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include "CodeModel.h"
#include "FileIo.h"
#include "ClientModel.h"
@ -75,6 +76,8 @@ void AppContext::load()
qmlRegisterType<CodeEditorExtensionManager>("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager");
qmlRegisterType<HttpServer>("HttpServer", 1, 0, "HttpServer");
m_applicationEngine->load(QUrl("qrc:/qml/main.qml"));
QQuickWindow *window = qobject_cast<QQuickWindow *>(m_applicationEngine->rootObjects().at(0));
window->setIcon(QIcon(":/res/mix_256x256x32.png"));
appLoaded();
}

7
mix/AssemblyDebuggerControl.cpp

@ -29,7 +29,6 @@ using namespace dev::mix;
AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context):
Extension(_context, ExtensionDisplayBehavior::RightView)
{
connect(_context->clientModel(), &ClientModel::showDebuggerWindow, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection);
}
QString AssemblyDebuggerControl::contentUrl() const
@ -45,9 +44,3 @@ QString AssemblyDebuggerControl::title() const
void AssemblyDebuggerControl::start() const
{
}
void AssemblyDebuggerControl::showDebugger()
{
QObject* debugPanel = m_view->findChild<QObject*>("debugPanel", Qt::FindChildrenRecursively);
QMetaObject::invokeMethod(debugPanel, "update", Q_ARG(QVariant, true));
}

4
mix/AssemblyDebuggerControl.h

@ -42,10 +42,6 @@ public:
void start() const override;
QString title() const override;
QString contentUrl() const override;
private slots:
/// Update UI with machine states result. Displayed in the right side tab.
void showDebugger();
};
}

40
mix/ClientModel.cpp

@ -72,7 +72,10 @@ ClientModel::ClientModel(AppContext* _context):
qRegisterMetaType<QList<QVariableDefinition*>>("QList<QVariableDefinition*>");
qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>");
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
qRegisterMetaType<AssemblyDebuggerData>("AssemblyDebuggerData");
qRegisterMetaType<QMachineState*>("QMachineState");
qRegisterMetaType<QInstruction*>("QInstruction");
qRegisterMetaType<QCode*>("QCode");
qRegisterMetaType<QCallData*>("QCallData");
qRegisterMetaType<TransactionLogEntry*>("TransactionLogEntry");
connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection);
@ -251,24 +254,27 @@ void ClientModel::showDebugger()
void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
{
//we need to wrap states in a QObject before sending to QML.
QList<QObject*> wStates;
for (unsigned i = 0; i < _t.machineStates.size(); i++)
{
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(_t.executionCode, _t.executionData.toBytes()));
s->setState(_t.machineStates[i]);
wStates.append(s);
}
QDebugData* debugData = new QDebugData();
QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership);
QList<QCode*> codes;
for (bytes const& code: _t.executionCode)
codes.push_back(QMachineState::getHumanReadableCode(debugData, code));
QList<QCallData*> data;
for (bytes const& d: _t.transactionData)
data.push_back(QMachineState::getDebugCallData(debugData, d));
QVariantList states;
for (MachineState const& s: _t.machineStates)
states.append(QVariant::fromValue(new QMachineState(debugData, s, codes[s.codeIndex], data[s.dataIndex])));
debugData->setStates(std::move(states));
QList<QVariableDefinition*> returnParameters;
//QList<QVariableDefinition*> returnParameters;
//returnParameters = encoder.decode(f->returnParameters(), debuggingContent.returnValue);
//collect states for last transaction
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(_t.executionCode);
m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(wStates));
m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(code)));
m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(code)));
m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(returnParameters)));
showDebuggerWindow();
debugDataReady(debugData);
}
@ -339,9 +345,9 @@ void ClientModel::onNewTransaction()
else
{
//call
if (tr.transactionData.size() >= 4)
if (tr.transactionData.size() > 0 && tr.transactionData.front().size() >= 4)
{
functionHash = FixedHash<4>(tr.transactionData.data(), FixedHash<4>::ConstructFromPointer);
functionHash = FixedHash<4>(tr.transactionData.front().data(), FixedHash<4>::ConstructFromPointer);
function = QString::fromStdString(toJS(functionHash));
call = true;
}

10
mix/ClientModel.h

@ -25,14 +25,9 @@
#include <atomic>
#include <map>
#include "DebuggingStateWrapper.h"
#include <QString>
#include "MixClient.h"
using AssemblyDebuggerData = std::tuple<QList<QObject*>, dev::mix::QQMLMap*>;
Q_DECLARE_METATYPE(AssemblyDebuggerData)
Q_DECLARE_METATYPE(dev::mix::ExecutionResult)
namespace dev
{
namespace mix
@ -42,6 +37,7 @@ class AppContext;
class Web3Server;
class RpcConnector;
class QEther;
class QDebugData;
/// Backend transaction config class
struct TransactionSettings
@ -156,7 +152,7 @@ signals:
/// Execution state changed
void runStateChanged();
/// Show debugger window request
void showDebuggerWindow();
void debugDataReady(QObject* _debugData);
/// ethereum.js RPC response ready
/// @param _message RPC response in Json format
void apiResponse(QString const& _message);

2
mix/CodeModel.cpp

@ -133,7 +133,7 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code)
if (_jobId != m_backgroundJobId)
return; //obsolete job
solidity::CompilerStack cs;
solidity::CompilerStack cs(true);
std::unique_ptr<CompilationResult> result;
std::string source = _code.toStdString();

172
mix/DebuggingStateWrapper.cpp

@ -20,10 +20,11 @@
* Used to translate c++ type (u256, bytes, ...) into friendly value (to be used by QML).
*/
#include <QApplication>
#include <tuple>
#include <QDebug>
#include <QPointer>
#include <QQmlEngine>
#include <QVariantList>
#include <libevmcore/Instruction.h>
#include <libdevcore/CommonJS.h>
#include <libdevcrypto/Common.h>
@ -35,10 +36,42 @@ using namespace dev;
using namespace dev::eth;
using namespace dev::mix;
std::tuple<QList<QObject*>, QQMLMap*> DebuggingStateWrapper::getHumanReadableCode(const bytes& _code)
namespace
{
QList<QObject*> codeStr;
QMap<int, int> codeMapping;
static QVariantList memDumpToList(bytes const& _bytes, unsigned _width)
{
QVariantList dumpList;
for (unsigned i = 0; i < _bytes.size(); i += _width)
{
std::stringstream ret;
for (unsigned j = i; j < i + _width; ++j)
if (j < _bytes.size())
if (_bytes[j] >= 32 && _bytes[j] < 127)
ret << (char)_bytes[j];
else
ret << '?';
else
ret << ' ';
QString strPart = QString::fromStdString(ret.str());
ret.clear();
ret.str(std::string());
for (unsigned j = i; j < i + _width && j < _bytes.size(); ++j)
ret << std::setfill('0') << std::setw(2) << std::hex << (unsigned)_bytes[j] << " ";
QString hexPart = QString::fromStdString(ret.str());
QStringList line = { strPart, hexPart };
dumpList.push_back(line);
}
return dumpList;
}
}
QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code)
{
QVariantList codeStr;
for (unsigned i = 0; i <= _code.size(); ++i)
{
byte b = i < _code.size() ? _code[i] : 0;
@ -47,7 +80,6 @@ std::tuple<QList<QObject*>, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod
QString s = QString::fromStdString(instructionInfo((Instruction)b).name);
std::ostringstream out;
out << std::hex << std::setw(4) << std::setfill('0') << i;
codeMapping[i] = codeStr.size();
int line = i;
if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32)
{
@ -55,8 +87,7 @@ std::tuple<QList<QObject*>, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod
s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&_code[i + 1], bc)));
i += bc;
}
QPointer<HumanReadableCode> humanCode(new HumanReadableCode(QString::fromStdString(out.str()) + " " + s, line));
codeStr.append(humanCode);
codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s, line)));
}
catch (...)
{
@ -65,137 +96,61 @@ std::tuple<QList<QObject*>, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod
break; // probably hit data segment
}
}
return std::make_tuple(codeStr, QPointer<QQMLMap>(new QQMLMap(codeMapping)));
return new QCode(_owner, std::move(codeStr));
}
QBigInt* DebuggingStateWrapper::gasCost()
QBigInt* QMachineState::gasCost()
{
return new QBigInt(m_state.gasCost);
}
QBigInt* DebuggingStateWrapper::gas()
QBigInt* QMachineState::gas()
{
return new QBigInt(m_state.gas);
}
QBigInt* DebuggingStateWrapper::newMemSize()
QBigInt* QMachineState::newMemSize()
{
return new QBigInt(m_state.newMemSize);
}
QStringList DebuggingStateWrapper::debugStack()
QStringList QMachineState::debugStack()
{
QStringList stack;
for (std::vector<u256>::reverse_iterator i = m_state.stack.rbegin(); i != m_state.stack.rend(); ++i)
stack.append(QString::fromStdString(prettyU256(*i)));
return fillList(stack, "");
return stack;
}
QStringList DebuggingStateWrapper::debugStorage()
QStringList QMachineState::debugStorage()
{
QStringList storage;
for (auto const& i: m_state.storage)
{
std::stringstream s;
s << "@" << prettyU256(i.first) << " " << prettyU256(i.second);
s << "@" << prettyU256(i.first) << "\t" << prettyU256(i.second);
storage.append(QString::fromStdString(s.str()));
}
return fillList(storage, "@ -");
}
QVariantList DebuggingStateWrapper::debugMemory()
{
std::vector<std::vector<std::string>> dump = memDumpToList(m_state.memory, 16);
QStringList filled;
filled.append(" ");
filled.append(" ");
filled.append(" ");
return fillList(qVariantDump(dump), QVariant(filled));
}
QVariantList DebuggingStateWrapper::debugCallData()
{
std::vector<std::vector<std::string>> dump = memDumpToList(m_data, 16);
QStringList filled;
filled.append(" ");
filled.append(" ");
filled.append(" ");
return fillList(qVariantDump(dump), QVariant(filled));
}
std::vector<std::vector<std::string>> DebuggingStateWrapper::memDumpToList(bytes const& _bytes, unsigned _width)
{
std::vector<std::vector<std::string>> dump;
for (unsigned i = 0; i < _bytes.size(); i += _width)
{
std::stringstream ret;
std::vector<std::string> dumpLine;
ret << std::hex << std::setw(4) << std::setfill('0') << i << " ";
dumpLine.push_back(ret.str());
ret.str(std::string());
ret.clear();
for (unsigned j = i; j < i + _width; ++j)
if (j < _bytes.size())
if (_bytes[j] >= 32 && _bytes[j] < 127)
ret << (char)_bytes[j];
else
ret << '?';
else
ret << ' ';
dumpLine.push_back(ret.str());
ret.str(std::string());
ret.clear();
for (unsigned j = i; j < i + _width && j < _bytes.size(); ++j)
ret << std::setfill('0') << std::setw(2) << std::hex << (unsigned)_bytes[j] << " ";
dumpLine.push_back(ret.str());
dump.push_back(dumpLine);
}
return dump;
}
QVariantList DebuggingStateWrapper::qVariantDump(std::vector<std::vector<std::string>> const& _dump)
{
QVariantList ret;
for (std::vector<std::string> const& line: _dump)
{
QStringList qLine;
for (std::string const& cell: line)
qLine.push_back(QString::fromStdString(cell));
ret.append(QVariant(qLine));
}
return ret;
return storage;
}
QStringList DebuggingStateWrapper::fillList(QStringList& _list, QString const& _emptyValue)
QVariantList QMachineState::debugMemory()
{
if (_list.size() < 20)
{
for (int k = _list.size(); k < 20 - _list.size(); k++)
_list.append(_emptyValue);
}
return _list;
return memDumpToList(m_state.memory, 16);
}
QVariantList DebuggingStateWrapper::fillList(QVariantList _list, QVariant const& _emptyValue)
QCallData* QMachineState::getDebugCallData(QObject* _owner, bytes const& _data)
{
if (_list.size() < 20)
{
for (int k = _list.size(); k < 20 - _list.size(); k++)
_list.append(_emptyValue);
}
return _list;
return new QCallData(_owner, memDumpToList(_data, 16));
}
QStringList DebuggingStateWrapper::levels()
QStringList QMachineState::levels()
{
QStringList levelsStr;
for (unsigned i = 0; i <= m_state.levels.size(); ++i)
{
std::ostringstream out;
out << m_state.cur.abridged();
out << m_state.address.abridged();
if (i)
out << " " << instructionInfo(m_state.inst).name << " @0x" << std::hex << m_state.curPC;
levelsStr.append(QString::fromStdString(out.str()));
@ -203,22 +158,15 @@ QStringList DebuggingStateWrapper::levels()
return levelsStr;
}
QString DebuggingStateWrapper::headerInfo()
{
std::ostringstream ss;
ss << std::dec << " " << QApplication::tr("STEP").toStdString() << " : " << m_state.steps << " | PC: 0x" << std::hex << m_state.curPC << " : " << dev::eth::instructionInfo(m_state.inst).name << " | ADDMEM: " << std::dec << m_state.newMemSize << " " << QApplication::tr("words").toStdString() << " | " << QApplication::tr("COST").toStdString() << " : " << std::dec << m_state.gasCost << " | " << QApplication::tr("GAS").toStdString() << " : " << std::dec << m_state.gas;
return QString::fromStdString(ss.str());
}
QString DebuggingStateWrapper::instruction()
QString QMachineState::instruction()
{
return QString::fromStdString(dev::eth::instructionInfo(m_state.inst).name);
}
QString DebuggingStateWrapper::endOfDebug()
QString QMachineState::endOfDebug()
{
if (m_state.gasCost > m_state.gas)
return QApplication::tr("OUT-OF-GAS");
return QObject::tr("OUT-OF-GAS");
else if (m_state.inst == Instruction::RETURN && m_state.stack.size() >= 2)
{
unsigned from = (unsigned)m_state.stack.back();
@ -227,12 +175,12 @@ QString DebuggingStateWrapper::endOfDebug()
bytes out(size, 0);
for (; o < size && from + o < m_state.memory.size(); ++o)
out[o] = m_state.memory[from + o];
return QApplication::tr("RETURN") + " " + QString::fromStdString(dev::memDump(out, 16, false));
return QObject::tr("RETURN") + " " + QString::fromStdString(dev::memDump(out, 16, false));
}
else if (m_state.inst == Instruction::STOP)
return QApplication::tr("STOP");
return QObject::tr("STOP");
else if (m_state.inst == Instruction::SUICIDE && m_state.stack.size() >= 1)
return QApplication::tr("SUICIDE") + " 0x" + QString::fromStdString(toString(right160(m_state.stack.back())));
return QObject::tr("SUICIDE") + " 0x" + QString::fromStdString(toString(right160(m_state.stack.back())));
else
return QApplication::tr("EXCEPTION");
return QObject::tr("EXCEPTION");
}

89
mix/DebuggingStateWrapper.h

@ -39,45 +39,70 @@ namespace mix
/**
* @brief Contains the line nb of the assembly code and the corresponding index in the code bytes array.
*/
class HumanReadableCode: public QObject
class QInstruction: public QObject
{
Q_OBJECT
Q_PROPERTY(QString line READ line CONSTANT)
Q_PROPERTY(int processIndex READ processIndex CONSTANT)
Q_PROPERTY(QString line MEMBER m_line CONSTANT)
Q_PROPERTY(int processIndex MEMBER m_processIndex CONSTANT)
public:
HumanReadableCode(QString _line, int _processIndex): QObject(), m_line(_line), m_processIndex(_processIndex) {}
/// Get the assembly code line.
QString line() { return m_line; }
/// Get corresponding index.
int processIndex() { return m_processIndex; }
QInstruction(QObject* _owner, QString _line, int _processIndex): QObject(_owner), m_line(_line), m_processIndex(_processIndex) {}
private:
QString m_line;
int m_processIndex;
};
/**
* @brief Shared container for lines
*/
class QCode: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT)
public:
QCode(QObject* _owner, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions) {}
private:
QVariantList m_instructions;
};
/**
* @brief Shared container for call data
*/
class QCallData: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList items MEMBER m_items CONSTANT)
public:
QCallData(QObject* _owner, QVariantList&& _items): QObject(_owner), m_items(_items) {}
private:
QVariantList m_items;
};
/**
* @brief Publish QMap type to QML.
* @brief Shared container for machine states
*/
class QQMLMap: public QObject
class QDebugData: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList states MEMBER m_states CONSTANT)
public:
QQMLMap(QMap<int, int> _map): QObject(), m_map(_map) { }
/// Get the value associated with _key store in n_map.
Q_INVOKABLE int getValue(int _key) { return m_map.value(_key); }
QDebugData() { }
void setStates(QVariantList&& _states) { m_states = _states; }
private:
QMap<int, int> m_map;
QVariantList m_states;
};
/**
* @brief Wrap DebuggingState in QObject
* @brief Wrap MachineState in QObject
*/
class DebuggingStateWrapper: public QObject
class QMachineState: public QObject
{
Q_OBJECT
Q_PROPERTY(int step READ step CONSTANT)
@ -88,18 +113,25 @@ class DebuggingStateWrapper: public QObject
Q_PROPERTY(QStringList debugStack READ debugStack CONSTANT)
Q_PROPERTY(QStringList debugStorage READ debugStorage CONSTANT)
Q_PROPERTY(QVariantList debugMemory READ debugMemory CONSTANT)
Q_PROPERTY(QVariantList debugCallData READ debugCallData CONSTANT)
Q_PROPERTY(QString headerInfo READ headerInfo CONSTANT)
Q_PROPERTY(QObject* code MEMBER m_code CONSTANT)
Q_PROPERTY(QObject* callData MEMBER m_callData CONSTANT)
Q_PROPERTY(QString endOfDebug READ endOfDebug CONSTANT)
Q_PROPERTY(QBigInt* newMemSize READ newMemSize CONSTANT)
Q_PROPERTY(QStringList levels READ levels CONSTANT)
Q_PROPERTY(unsigned codeIndex READ codeIndex CONSTANT)
Q_PROPERTY(unsigned dataIndex READ dataIndex CONSTANT)
public:
DebuggingStateWrapper(bytes _code, bytes _data): QObject(), m_code(_code), m_data(_data) {}
QMachineState(QObject* _owner, MachineState const& _state, QCode* _code, QCallData* _callData):
QObject(_owner), m_state(_state), m_code(_code), m_callData(_callData) {}
/// Get the step of this machine states.
int step() { return (int)m_state.steps; }
/// Get the proccessed code index.
int curPC() { return (int)m_state.curPC; }
/// Get the code id
unsigned codeIndex() { return m_state.codeIndex; }
/// Get the call data id
unsigned dataIndex() { return m_state.dataIndex; }
/// Get gas cost.
QBigInt* gasCost();
/// Get gas used.
@ -111,8 +143,6 @@ public:
/// Get memory.
QVariantList debugMemory();
/// Get call data.
QVariantList debugCallData();
/// Get info to be displayed in the header.
QString headerInfo();
/// get end of debug information.
QString endOfDebug();
@ -126,20 +156,15 @@ public:
MachineState state() { return m_state; }
/// Set the current processed machine state.
void setState(MachineState _state) { m_state = _state; }
/// Convert all machine state in human readable code.
static std::tuple<QList<QObject*>, QQMLMap*> getHumanReadableCode(bytes const& _code);
/// Convert all machine states in human readable code.
static QCode* getHumanReadableCode(QObject* _owner, bytes const& _code);
/// Convert call data into human readable form
static QCallData* getDebugCallData(QObject* _owner, bytes const& _data);
private:
MachineState m_state;
bytes m_code;
bytes m_data;
QStringList fillList(QStringList& _list, QString const& _emptyValue);
QVariantList fillList(QVariantList _list, QVariant const& _emptyValue);
QVariantList qVariantDump(std::vector<std::vector<std::string>> const& _dump);
/// Nicely renders the given bytes to a string, store the content in an array.
/// @a _bytes: bytes array to be rendered as string. @a _width of a bytes line.
std::vector<std::vector<std::string>> memDumpToList(bytes const& _bytes, unsigned _width);
QCode* m_code;
QCallData* m_callData;
};
}

2
mix/MixApplication.cpp

@ -36,7 +36,7 @@ using namespace dev::mix;
MixApplication::MixApplication(int _argc, char* _argv[]):
QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get()))
{
setOrganizationName(tr("Ethreum"));
setOrganizationName(tr("Ethereum"));
setOrganizationDomain(tr("ethereum.org"));
setApplicationName(tr("Mix"));
setApplicationVersion("0.1");

48
mix/MixClient.cpp

@ -63,21 +63,46 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state)
bytes rlp = _t.rlp();
Executive execution(_state, LastHashes(), 0);
execution.setup(&rlp);
bytes code;
bytesConstRef data;
bool firstIteration = true;
std::vector<MachineState> machineStates;
std::vector<MachineState const*> levels;
std::vector<bytes> codes;
std::map<bytes const*, unsigned> codeIndexes;
std::vector<bytes> data;
std::map<bytesConstRef const*, unsigned> dataIndexes;
bytes const* lastCode = nullptr;
bytesConstRef const* lastData = nullptr;
unsigned codeIndex = 0;
unsigned dataIndex = 0;
auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt)
{
VM& vm = *(VM*)voidVM;
ExtVM const& ext = *(ExtVM const*)voidExt;
if (lastCode == nullptr || lastCode != &ext.code)
{
auto const& iter = codeIndexes.find(&ext.code);
if (iter != codeIndexes.end())
codeIndex = iter->second;
else
{
codeIndex = codes.size();
codes.push_back(ext.code);
codeIndexes[&ext.code] = codeIndex;
}
lastCode = &ext.code;
}
if (firstIteration)
if (lastData == nullptr || lastData != &ext.data)
{
code = ext.code;
data = ext.data;
firstIteration = false;
auto const& iter = dataIndexes.find(&ext.data);
if (iter != dataIndexes.end())
dataIndex = iter->second;
else
{
dataIndex = data.size();
data.push_back(ext.data.toBytes());
dataIndexes[&ext.data] = dataIndex;
}
lastData = &ext.data;
}
if (levels.size() < ext.depth)
@ -85,8 +110,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state)
else
levels.resize(ext.depth);
machineStates.push_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(),
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels}));
machineStates.emplace_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(),
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels, codeIndex, dataIndex}));
};
execution.go(onOp);
@ -95,9 +120,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state)
ExecutionResult d;
d.returnValue = execution.out().toVector();
d.machineStates = machineStates;
d.executionCode = code;
d.executionData = data;
d.transactionData = _t.data();
d.executionCode = std::move(codes);
d.transactionData = std::move(data);
d.address = _t.receiveAddress();
d.sender = _t.sender();
d.value = _t.value();

9
mix/MixClient.h

@ -38,7 +38,7 @@ namespace mix
struct MachineState
{
uint64_t steps;
dev::Address cur;
dev::Address address;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
@ -48,6 +48,8 @@ struct MachineState
dev::bigint gasCost;
std::map<dev::u256, dev::u256> storage;
std::vector<MachineState const*> levels;
unsigned codeIndex;
unsigned dataIndex;
};
/**
@ -58,9 +60,8 @@ struct ExecutionResult
ExecutionResult(): receipt(dev::h256(), dev::h256(), dev::eth::LogEntries()) {}
std::vector<MachineState> machineStates;
bytes transactionData;
bytes executionCode;
bytesConstRef executionData;
std::vector<bytes> transactionData;
std::vector<bytes> executionCode;
bytes returnValue;
dev::Address address;
dev::Address sender;

44
mix/qml/Debugger.qml

@ -13,13 +13,6 @@ Rectangle {
anchors.fill: parent;
color: "#ededed"
clip: true
Keys.onPressed:
{
if (event.key === Qt.Key_F10)
Debugger.moveSelection(1);
else if (event.key === Qt.Key_F9)
Debugger.moveSelection(-1);
}
onVisibleChanged:
{
@ -27,11 +20,11 @@ Rectangle {
forceActiveFocus();
}
function update(giveFocus)
function update(data, giveFocus)
{
if (statusPane.result.successful)
{
Debugger.init();
Debugger.init(data);
debugScrollArea.visible = true;
compilationErrorArea.visible = false;
machineStates.visible = true;
@ -50,9 +43,16 @@ Rectangle {
forceActiveFocus();
}
Connections {
target: clientModel
onDebugDataReady: {
update(_debugData, true);
}
}
Connections {
target: codeModel
onCompilationComplete: update(false)
onCompilationComplete: update(null, false);
}
Rectangle
@ -159,6 +159,7 @@ Rectangle {
onClicked: Debugger.stepOutBack()
width: 28
height: 30
buttonShortcut: "Ctrl+Shift+F11"
buttonTooltip: qsTr("Step Out Back")
}
@ -170,6 +171,7 @@ Rectangle {
onClicked: Debugger.stepIntoBack()
width: 28
height: 30
buttonShortcut: "Ctrl+F11"
buttonTooltip: qsTr("Step Into Back")
}
@ -181,6 +183,7 @@ Rectangle {
onClicked: Debugger.stepOverBack()
width: 28
height: 30
buttonShortcut: "Ctrl+F10"
buttonTooltip: qsTr("Step Over Back")
}
@ -192,6 +195,7 @@ Rectangle {
onClicked: Debugger.stepOverForward()
width: 28
height: 30
buttonShortcut: "F10"
buttonTooltip: qsTr("Step Over Forward")
}
@ -203,6 +207,7 @@ Rectangle {
onClicked: Debugger.stepIntoForward()
width: 28
height: 30
buttonShortcut: "F11"
buttonTooltip: qsTr("Step Into Forward")
}
@ -214,6 +219,7 @@ Rectangle {
onClicked: Debugger.stepOutForward()
width: 28
height: 30
buttonShortcut: "Shift+F11"
buttonTooltip: qsTr("Step Out Forward")
}
}
@ -276,7 +282,8 @@ Rectangle {
id: statesList
delegate: renderDelegate
highlight: highlightBar
highlightFollowsCurrentItem: false
//highlightFollowsCurrentItem: false
model: ListModel {}
}
Component {
@ -287,9 +294,9 @@ Rectangle {
width: statesList.currentItem.width;
y: statesList.currentItem.y
color: "#4A90E2"
Behavior on y {
PropertyAnimation { properties: "y"; easing.type: Easing.InOutQuad; duration: 50}
}
//Behavior on y {
// PropertyAnimation { properties: "y"; easing.type: Easing.InOutQuad; duration: 50}
//}
}
}
@ -306,6 +313,7 @@ Rectangle {
width: 15
color: "#b2b3ae"
text: line.split(' ')[0]
font.family: "monospace"
font.pointSize: 9
id: id
wrapMode: Text.NoWrap
@ -313,6 +321,7 @@ Rectangle {
Text {
wrapMode: Text.NoWrap
color: parent.ListView.isCurrentItem ? "white" : "black"
font.family: "monospace"
text: line.replace(line.split(' ')[0], '')
anchors.left: id.right
font.pointSize: 9
@ -473,8 +482,10 @@ Rectangle {
font.family: "monospace"
anchors.leftMargin: 5
color: "#4a4a4a"
text: modelData.split(' ')[0].substring(0, 10);
text: modelData.split('\t')[0];
font.pointSize: 9
width: parent.width - 5
elide: Text.ElideRight
}
}
Rectangle
@ -494,7 +505,8 @@ Rectangle {
font.family: "monospace"
anchors.verticalCenter: parent.verticalCenter
color: "#4a4a4a"
text: modelData.split(' ')[1].substring(0, 10);
text: modelData.split('\t')[1];
elide: Text.ElideRight
font.pointSize: 9
}
}

15
mix/qml/ItemDelegateDataDump.qml

@ -49,21 +49,6 @@ Rectangle {
font.pointSize: 8
}
}
Rectangle
{
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.minimumHeight: parent.height
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.family: "monospace"
color: "#4a4a4a"
text: modelData[2]
font.pointSize: 8
}
}
}
Rectangle {

2
mix/qml/StepActionImage.qml

@ -9,6 +9,7 @@ Rectangle {
property string disableStateImg
property string enabledStateImg
property string buttonTooltip
property string buttonShortcut
signal clicked
function enabled(state)
@ -33,6 +34,7 @@ Rectangle {
Action {
tooltip: buttonTooltip
id: buttonAction
shortcut: buttonShortcut
onTriggered: {
buttonActionContainer.clicked();
}

32
mix/qml/html/cm/dialog.css

@ -0,0 +1,32 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
background: white;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
color: #333;
}
.CodeMirror-dialog-top {
border-bottom: 1px solid #eee;
top: 0;
}
.CodeMirror-dialog-bottom {
border-top: 1px solid #eee;
bottom: 0;
}
.CodeMirror-dialog input {
border: none;
outline: none;
background: transparent;
width: 20em;
color: inherit;
font-family: monospace;
}
.CodeMirror-dialog button {
font-size: 70%;
}

155
mix/qml/html/cm/dialog.js

@ -0,0 +1,155 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function dialogDiv(cm, template, bottom) {
var wrap = cm.getWrapperElement();
var dialog;
dialog = wrap.appendChild(document.createElement("div"));
if (bottom)
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
else
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
if (typeof template == "string") {
dialog.innerHTML = template;
} else { // Assuming it's a detached DOM element.
dialog.appendChild(template);
}
return dialog;
}
function closeNotification(cm, newVal) {
if (cm.state.currentNotificationClose)
cm.state.currentNotificationClose();
cm.state.currentNotificationClose = newVal;
}
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
if (!options) options = {};
closeNotification(this, null);
var dialog = dialogDiv(this, template, options.bottom);
var closed = false, me = this;
function close(newVal) {
if (typeof newVal == 'string') {
inp.value = newVal;
} else {
if (closed) return;
closed = true;
dialog.parentNode.removeChild(dialog);
me.focus();
if (options.onClose) options.onClose(dialog);
}
}
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
if (options.value) {
inp.value = options.value;
inp.select();
}
if (options.onInput)
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
if (options.onKeyUp)
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
inp.blur();
CodeMirror.e_stop(e);
close();
}
if (e.keyCode == 13) callback(inp.value, e);
});
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
inp.focus();
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
me.focus();
});
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
button.focus();
}
return close;
});
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
closeNotification(this, null);
var dialog = dialogDiv(this, template, options && options.bottom);
var buttons = dialog.getElementsByTagName("button");
var closed = false, me = this, blurring = 1;
function close() {
if (closed) return;
closed = true;
dialog.parentNode.removeChild(dialog);
me.focus();
}
buttons[0].focus();
for (var i = 0; i < buttons.length; ++i) {
var b = buttons[i];
(function(callback) {
CodeMirror.on(b, "click", function(e) {
CodeMirror.e_preventDefault(e);
close();
if (callback) callback(me);
});
})(callbacks[i]);
CodeMirror.on(b, "blur", function() {
--blurring;
setTimeout(function() { if (blurring <= 0) close(); }, 200);
});
CodeMirror.on(b, "focus", function() { ++blurring; });
}
});
/*
* openNotification
* Opens a notification, that can be closed with an optional timer
* (default 5000ms timer) and always closes on click.
*
* If a notification is opened while another is opened, it will close the
* currently opened one and open the new one immediately.
*/
CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom);
var closed = false, doneTimer;
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() {
if (closed) return;
closed = true;
clearTimeout(doneTimer);
dialog.parentNode.removeChild(dialog);
}
CodeMirror.on(dialog, 'click', function(e) {
CodeMirror.e_preventDefault(e);
close();
});
if (duration)
doneTimer = setTimeout(close, duration);
return close;
});
});

164
mix/qml/html/cm/search.js

@ -0,0 +1,164 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
// Replace works a little oddly -- it will do the replace on the next
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function searchOverlay(query, caseInsensitive) {
if (typeof query == "string")
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
else if (!query.global)
query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
return {token: function(stream) {
query.lastIndex = stream.pos;
var match = query.exec(stream.string);
if (match && match.index == stream.pos) {
stream.pos += match[0].length;
return "searching";
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
}};
}
function SearchState() {
this.posFrom = this.posTo = this.query = null;
this.overlay = null;
}
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
}
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
else f(prompt(shortText, deflt));
}
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
else if (confirm(shortText)) fs[0]();
}
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
catch(e) {} // Not a regular expression after all, do a string search
}
if (typeof query == "string" ? query == "" : query.test(""))
query = /x^/;
return query;
}
var queryDialog =
'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
function doSearch(cm, rev) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
cm.operation(function() {
if (!query || state.query) return;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
cm.addOverlay(state.overlay);
if (cm.showMatchesOnScrollbar) {
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
}
state.posFrom = state.posTo = cm.getCursor();
findNext(cm, rev);
});
});
}
function findNext(cm, rev) {cm.operation(function() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
if (!cursor.find(rev)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
state.posFrom = cursor.from(); state.posTo = cursor.to();
});}
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
if (!state.query) return;
state.query = null;
cm.removeOverlay(state.overlay);
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
});}
var replaceQueryDialog =
'Replace: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
if (all) {
cm.operation(function() {
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
if (typeof query != "string") {
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
} else cursor.replace(text);
}
});
} else {
clearSearch(cm);
var cursor = getSearchCursor(cm, query, cm.getCursor());
var advance = function() {
var start = cursor.from(), match;
if (!(match = cursor.findNext())) {
cursor = getSearchCursor(cm, query);
if (!(match = cursor.findNext()) ||
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
confirmDialog(cm, doReplaceConfirm, "Replace?",
[function() {doReplace(match);}, advance]);
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
advance();
};
advance();
}
});
});
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
CodeMirror.commands.replace = replace;
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
});

189
mix/qml/html/cm/searchcursor.js

@ -0,0 +1,189 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
function SearchCursor(doc, query, pos, caseFold) {
this.atOccurrence = false; this.doc = doc;
if (caseFold == null && typeof query == "string") caseFold = false;
pos = pos ? doc.clipPos(pos) : Pos(0, 0);
this.pos = {from: pos, to: pos};
// The matches method is filled in based on the type of query.
// It takes a position and a direction, and returns an object
// describing the next occurrence of the query, or null if no
// more matches were found.
if (typeof query != "string") { // Regexp match
if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
this.matches = function(reverse, pos) {
if (reverse) {
query.lastIndex = 0;
var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
for (;;) {
query.lastIndex = cutOff;
var newMatch = query.exec(line);
if (!newMatch) break;
match = newMatch;
start = match.index;
cutOff = match.index + (match[0].length || 1);
if (cutOff == line.length) break;
}
var matchLen = (match && match[0].length) || 0;
if (!matchLen) {
if (start == 0 && line.length == 0) {match = undefined;}
else if (start != doc.getLine(pos.line).length) {
matchLen++;
}
}
} else {
query.lastIndex = pos.ch;
var line = doc.getLine(pos.line), match = query.exec(line);
var matchLen = (match && match[0].length) || 0;
var start = match && match.index;
if (start + matchLen != line.length && !matchLen) matchLen = 1;
}
if (match && matchLen)
return {from: Pos(pos.line, start),
to: Pos(pos.line, start + matchLen),
match: match};
};
} else { // String query
var origQuery = query;
if (caseFold) query = query.toLowerCase();
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
var target = query.split("\n");
// Different methods for single-line and multi-line queries
if (target.length == 1) {
if (!query.length) {
// Empty string would match anything and never progress, so
// we define it to match nothing instead.
this.matches = function() {};
} else {
this.matches = function(reverse, pos) {
if (reverse) {
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
var match = line.lastIndexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match);
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
} else {
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
var match = line.indexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match) + pos.ch;
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
}
};
}
} else {
var origTarget = origQuery.split("\n");
this.matches = function(reverse, pos) {
var last = target.length - 1;
if (reverse) {
if (pos.line - (target.length - 1) < doc.firstLine()) return;
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
var to = Pos(pos.line, origTarget[last].length);
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
if (target[i] != fold(doc.getLine(ln))) return;
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
return {from: Pos(ln, cut), to: to};
} else {
if (pos.line + (target.length - 1) > doc.lastLine()) return;
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
}
}
}
SearchCursor.prototype = {
findNext: function() {return this.find(false);},
findPrevious: function() {return this.find(true);},
find: function(reverse) {
var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
function savePosAndFail(line) {
var pos = Pos(line, 0);
self.pos = {from: pos, to: pos};
self.atOccurrence = false;
return false;
}
for (;;) {
if (this.pos = this.matches(reverse, pos)) {
this.atOccurrence = true;
return this.pos.match || true;
}
if (reverse) {
if (!pos.line) return savePosAndFail(0);
pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
}
else {
var maxLine = this.doc.lineCount();
if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
pos = Pos(pos.line + 1, 0);
}
}
},
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},
replace: function(newText) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
this.doc.replaceRange(lines, this.pos.from, this.pos.to);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
}
};
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos) {
if (orig.length == folded.length) return pos;
for (var pos1 = Math.min(pos, orig.length);;) {
var len1 = orig.slice(0, pos1).toLowerCase().length;
if (len1 < pos) ++pos1;
else if (len1 > pos) --pos1;
else return pos1;
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold);
});
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold);
});
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = [], next;
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
while (next = cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({anchor: cur.from(), head: cur.to()});
}
if (ranges.length)
this.setSelections(ranges, 0);
});
});

4
mix/qml/html/codeeditor.html

@ -3,6 +3,7 @@
<link rel="stylesheet" href="cm/codemirror.css">
<link rel="stylesheet" href="cm/solarized.css">
<link rel="stylesheet" href="cm/fullscreen.css">
<link rel="stylesheet" href="cm/dialog.css">
<script src="cm/codemirror.js"></script>
<script src="cm/javascript.js"></script>
<script src="cm/css.js"></script>
@ -12,5 +13,8 @@
<script src="cm/fullscreen.js"></script>
<script src="cm/active-line.js"></script>
<script src="cm/matchbrackets.js"></script>
<script src="cm/search.js"></script>
<script src="cm/searchcursor.js"></script>
<script src="cm/dialog.js"></script>
<body oncontextmenu="return false;"></body>
<script src="codeeditor.js"></script>

3
mix/qml/html/codeeditor.js

@ -4,10 +4,11 @@ var editor = CodeMirror(document.body, {
//styleActiveLine: true,
matchBrackets: true,
autofocus: true,
indentWithTabs: true,
});
editor.setOption("theme", "solarized dark");
editor.setOption("indentUnit", 4);
editor.setOption("indentWithTabs", true);
editor.setOption("fullScreen", true);
editor.changeRegistered = false;

196
mix/qml/js/Debugger.js

@ -1,110 +1,115 @@
//humanReadableExecutionCode => contain human readable code.
//debugStates => contain all debug states.
//bytesCodeMapping => mapping between humanReadableExecutionCode and bytesCode.
//debugData => contain all debug states.
//statesList => ListView
var currentSelectedState = null;
var jumpStartingPoint = null;
function init()
var debugData = null;
var codeMap = null;
function init(data)
{
if (typeof(debugStates) === "undefined")
jumpOutBackAction.enabled(false);
jumpIntoBackAction.enabled(false);
jumpIntoForwardAction.enabled(false);
jumpOutForwardAction.enabled(false);
jumpOverBackAction.enabled(false);
jumpOverForwardAction.enabled(false);
if (data === null) {
statesList.model.clear();
statesSlider.maximumValue = 0;
statesSlider.value = 0;
currentSelectedState = null;
debugData = null;
return;
}
statesSlider.maximumValue = debugStates.length - 1;
debugData = data;
statesSlider.maximumValue = data.states.length - 1;
statesSlider.value = 0;
statesList.model = humanReadableExecutionCode;
currentSelectedState = 0;
setupInstructions(currentSelectedState);
select(currentSelectedState);
}
jumpOutBackAction.enabled(false);
jumpIntoBackAction.enabled(false);
jumpIntoForwardAction.enabled(false);
jumpOutForwardAction.enabled(false);
function setupInstructions(stateIndex) {
var instructions = debugData.states[stateIndex].code.instructions;
codeMap = {};
statesList.model.clear();
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;
}
function moveSelection(incr)
{
if (typeof(debugStates) === "undefined")
return;
var prevState = currentSelectedState;
if (currentSelectedState + incr >= 0)
{
if (currentSelectedState + incr < debugStates.length)
if (currentSelectedState + incr < debugData.states.length)
select(currentSelectedState + incr);
statesSlider.value = currentSelectedState;
}
}
function select(stateIndex)
{
if (typeof(debugStates) === "undefined")
return;
if (stateIndex < 0)
stateIndex = 0;
if (stateIndex >= debugData.states.length)
stateIndex = debugData.state.length - 1;
if (debugData.states[stateIndex].codeIndex !== debugData.states[currentSelectedState].codeIndex)
setupInstructions(stateIndex);
currentSelectedState = stateIndex;
var codeLine = codeStr(stateIndex);
var state = debugStates[stateIndex];
var state = debugData.states[stateIndex];
highlightSelection(codeLine);
currentSelectedState = stateIndex;
completeCtxInformation(state);
if (state.instruction === "JUMP")
jumpIntoForwardAction.enabled(true);
else
jumpIntoForwardAction.enabled(false);
if (state.instruction === "JUMPDEST")
jumpIntoBackAction.enabled(true);
else
jumpIntoBackAction.enabled(false);
statesSlider.value = currentSelectedState;
jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1)
jumpIntoBackAction.enabled(stateIndex > 0);
jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1);
jumpOverBackAction.enabled(stateIndex > 0);
jumpOutBackAction.enabled(state.levels.length > 1);
jumpOutForwardAction.enabled(state.levels.length > 1);
}
function codeStr(stateIndex)
{
if (typeof(debugStates) === "undefined")
return;
var state = debugStates[stateIndex];
return bytesCodeMapping.getValue(state.curPC);
var state = debugData.states[stateIndex];
return codeMap[state.curPC];
}
function highlightSelection(index)
{
statesList.currentIndex = index;
statesList.positionViewAtIndex(index, ListView.Center);
}
function completeCtxInformation(state)
{
if (typeof(debugStates) === "undefined")
return;
currentStep.update(state.step);
mem.update(state.newMemSize.value() + " " + qsTr("words"));
stepCost.update(state.gasCost.value());
gasSpent.update(debugStates[0].gas.subtract(state.gas).value());
gasSpent.update(debugData.states[0].gas.subtract(state.gas).value());
stack.listModel = state.debugStack;
storage.listModel = state.debugStorage;
memoryDump.listModel = state.debugMemory;
callDataDump.listModel = state.debugCallData;
}
function displayReturnValue()
function isCallInstruction(index)
{
headerReturnList.model = contractCallReturnParameters;
headerReturnList.update();
var state = debugData.states[index];
return state.instruction === "CALL" || state.instruction === "CREATE";
}
function stepOutBack()
function isReturnInstruction(index)
{
if (typeof(debugStates) === "undefined")
return;
if (jumpStartingPoint != null)
{
select(jumpStartingPoint);
jumpStartingPoint = null;
jumpOutBackAction.enabled(false);
jumpOutForwardAction.enabled(false);
}
var state = debugData.states[index];
return state.instruction === "RETURN"
}
function stepIntoBack()
@ -114,76 +119,55 @@ function stepIntoBack()
function stepOverBack()
{
if (typeof(debugStates) === "undefined")
return;
var state = debugStates[currentSelectedState];
if (state.instruction === "JUMPDEST")
{
for (var k = currentSelectedState; k > 0; k--)
{
var line = bytesCodeMapping.getValue(debugStates[k].curPC);
if (line === statesList.currentIndex - 2)
{
select(k);
break;
}
}
}
if (currentSelectedState > 0 && isReturnInstruction(currentSelectedState - 1))
stepOutBack();
else
moveSelection(-1);
}
function stepOverForward()
{
if (typeof(debugStates) === "undefined")
return;
var state = debugStates[currentSelectedState];
if (state.instruction === "JUMP")
{
for (var k = currentSelectedState; k < debugStates.length; k++)
{
var line = bytesCodeMapping.getValue(debugStates[k].curPC);
if (line === statesList.currentIndex + 2)
{
select(k);
break;
}
}
}
if (isCallInstruction(currentSelectedState))
stepOutForward();
else
moveSelection(1);
}
function stepIntoForward()
{
if (typeof(debugStates) === "undefined")
return;
moveSelection(1);
}
var state = debugStates[currentSelectedState];
if (state.instruction === "JUMP")
{
jumpStartingPoint = currentSelectedState;
moveSelection(1);
jumpOutBackAction.enabled(true);
jumpOutForwardAction.enabled(true);
}
function stepOutBack()
{
var i = currentSelectedState - 1;
var depth = 0;
while (--i >= 0)
if (isCallInstruction(i))
if (depth == 0)
break;
else depth--;
else if (isReturnInstruction(i))
depth++;
select(i);
}
function stepOutForward()
{
if (jumpStartingPoint != null)
{
stepOutBack();
stepOverForward();
jumpOutBackAction.enabled(false);
jumpOutForwardAction.enabled(false);
}
var i = currentSelectedState;
var depth = 0;
while (++i < debugData.states.length)
if (isReturnInstruction(i))
if (depth == 0)
break;
else
depth--;
else if (isCallInstruction(i))
depth++;
select(i + 1);
}
function jumpTo(value)
{
currentSelectedState = value;
select(currentSelectedState);
select(value);
}

1
mix/res.qrc

@ -50,5 +50,6 @@
<file>stdc/namereg.sol</file>
<file>stdc/std.sol</file>
<file>qml/TransactionLog.qml</file>
<file>res/mix_256x256x32.png</file>
</qresource>
</RCC>

BIN
mix/res/mix_256x256x32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

4
mix/web.qrc

@ -17,5 +17,9 @@
<file>qml/html/cm/htmlmixed.js</file>
<file>qml/html/cm/css.js</file>
<file>qml/html/cm/solidity.js</file>
<file>qml/html/cm/dialog.css</file>
<file>qml/html/cm/dialog.js</file>
<file>qml/html/cm/search.js</file>
<file>qml/html/cm/searchcursor.js</file>
</qresource>
</RCC>

Loading…
Cancel
Save