parent
c33a671148
commit
e7f0512635
@ -1,38 +1,132 @@ |
|||||||
var remix = require('ethereum-remix'); |
var remix = require('ethereum-remix'); |
||||||
|
var utils = require('./utils'); |
||||||
|
var ace = require('brace'); |
||||||
|
var Range = ace.acequire('ace/range').Range; |
||||||
|
|
||||||
function Debugger (id, executionContextEvent) { |
/** |
||||||
|
* Manage remix and source highlighting |
||||||
|
*/ |
||||||
|
function Debugger (id, editor, compiler, executionContextEvent, switchToFile) { |
||||||
this.el = document.querySelector(id); |
this.el = document.querySelector(id); |
||||||
this.debugger = new remix.ui.Debugger(); |
this.debugger = new remix.ui.Debugger(); |
||||||
|
this.sourceMappingDecoder = new remix.util.SourceMappingDecoder() |
||||||
this.el.appendChild(this.debugger.render()); |
this.el.appendChild(this.debugger.render()); |
||||||
|
this.editor = editor; |
||||||
|
this.switchToFile = switchToFile; |
||||||
|
this.compiler = compiler; |
||||||
|
this.cache = new Cache() |
||||||
|
|
||||||
var self = this; |
var self = this; |
||||||
executionContextEvent.register('contextChanged', this, function (context) { |
executionContextEvent.register('contextChanged', this, function (context) { |
||||||
context = context === 'vm' ? 'VM' : context; |
|
||||||
context = context === 'injected' ? 'EXTERNAL' : context; |
|
||||||
context = context === 'web3' ? 'INTERNAL' : context; |
|
||||||
self.switchProvider(context); |
self.switchProvider(context); |
||||||
}); |
}); |
||||||
|
|
||||||
|
this.lastCompilationResult = null; |
||||||
|
this.debugger.register('newTraceLoaded', this, function () { |
||||||
|
self.cache.clear() |
||||||
|
self.lastCompilationResult = self.compiler.lastCompilationResult; |
||||||
|
}); |
||||||
|
|
||||||
|
this.debugger.register('traceUnloaded', this, function () { |
||||||
|
self.lastCompilationResult = null; |
||||||
|
self.removeCurrentMarker() |
||||||
|
self.cache.clear() |
||||||
|
}); |
||||||
|
|
||||||
|
this.editor.onChangeSetup(function () { |
||||||
|
if (arguments.length > 0) { // if arguments.length === 0 this is a session change, we don't want to stop debugging in that case
|
||||||
|
self.debugger.unLoad() |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// register selected code item, highlight the corresponding source location
|
||||||
|
this.debugger.codeManager.register('changed', this, function (code, address, index) { |
||||||
|
this.debugger.sourceLocationTracker.getSourceLocation(address, index, self.lastCompilationResult.data.contracts, function (error, rawLocation) { |
||||||
|
if (!error) { |
||||||
|
if (!self.cache.lineBreakPositionsByContent[address]) { |
||||||
|
self.cache.lineBreakPositionsByContent[address] = self.sourceMappingDecoder.getLinebreakPositions(self.editor.getFile(self.lastCompilationResult.data.sourceList[rawLocation.file]))
|
||||||
|
} |
||||||
|
var lineColumnPos = self.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, self.cache.lineBreakPositionsByContent[address]) |
||||||
|
self.highlight(lineColumnPos, rawLocation); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
} |
} |
||||||
|
|
||||||
Debugger.prototype.debug = function (receipt) { |
/** |
||||||
var self = this |
* Start debugging using Remix |
||||||
this.debugger.web3().eth.getTransaction(receipt.transactionHash, function (error, tx) { |
* |
||||||
|
* @param {String} txHash - hash of the transaction |
||||||
|
*/ |
||||||
|
Debugger.prototype.debug = function (txHash) { |
||||||
|
var self = this; |
||||||
|
this.debugger.web3().eth.getTransaction(txHash, function (error, tx) { |
||||||
if (!error) { |
if (!error) { |
||||||
self.debugger.debug(tx); |
self.debugger.debug(tx); |
||||||
} |
} |
||||||
}); |
}); |
||||||
}; |
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* highlight the given @arg lineColumnPos |
||||||
|
* |
||||||
|
* @param {Object} lineColumnPos - position of the source code to hightlight {start: {line, column}, end: {line, column}} |
||||||
|
* @param {Object} rawLocation - raw position of the source code to hightlight {start, length, file, jump} |
||||||
|
*/ |
||||||
|
Debugger.prototype.highlight = function (lineColumnPos, rawLocation) { |
||||||
|
var name = utils.fileNameFromKey(this.editor.getCacheFile()); // current opened tab
|
||||||
|
var source = this.lastCompilationResult.data.sourceList[rawLocation.file]; // auto switch to that tab
|
||||||
|
this.removeCurrentMarker(); |
||||||
|
if (name !== source) { |
||||||
|
this.switchToFile(source); // command the app to swicth to the next file
|
||||||
|
} |
||||||
|
this.currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column); |
||||||
|
this.currentMarker = this.editor.addMarker(this.currentRange, 'highlightcode'); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* add a new web3 provider to remix |
||||||
|
* |
||||||
|
* @param {String} type - type/name of the provider to add |
||||||
|
* @param {Object} obj - provider |
||||||
|
*/ |
||||||
Debugger.prototype.addProvider = function (type, obj) { |
Debugger.prototype.addProvider = function (type, obj) { |
||||||
this.debugger.addProvider(type, obj); |
this.debugger.addProvider(type, obj); |
||||||
}; |
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* switch the provider |
||||||
|
* |
||||||
|
* @param {String} type - type/name of the provider to use |
||||||
|
*/ |
||||||
Debugger.prototype.switchProvider = function (type) { |
Debugger.prototype.switchProvider = function (type) { |
||||||
this.debugger.switchProvider(type); |
this.debugger.switchProvider(type); |
||||||
}; |
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* get the current provider |
||||||
|
*/ |
||||||
Debugger.prototype.web3 = function (type) { |
Debugger.prototype.web3 = function (type) { |
||||||
return this.debugger.web3(); |
return this.debugger.web3(); |
||||||
}; |
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* unhighlight the current highlighted statement |
||||||
|
*/ |
||||||
|
Debugger.prototype.removeCurrentMarker = function () { |
||||||
|
if (this.currentMarker) { |
||||||
|
this.editor.removeMarker(this.currentMarker); |
||||||
|
this.currentMarker = null; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
function Cache () { |
||||||
|
this.contentLineBreakPosition = {} |
||||||
|
} |
||||||
|
|
||||||
|
Cache.prototype.clear = function () { |
||||||
|
this.lineBreakPositionsByContent = {} |
||||||
|
} |
||||||
|
|
||||||
module.exports = Debugger; |
module.exports = Debugger; |
||||||
|
@ -1,206 +0,0 @@ |
|||||||
var utils = require('./utils') |
|
||||||
var remix = require('ethereum-remix') |
|
||||||
var ace = require('brace') |
|
||||||
var Range = ace.acequire('ace/range').Range |
|
||||||
|
|
||||||
/* |
|
||||||
Provides source highlighting when debugging a transaction |
|
||||||
*/ |
|
||||||
function SourceHighlighter (editor, txdebugger, compilerEvent, appEvent, switchToFile) { |
|
||||||
this.switchToFile = switchToFile |
|
||||||
this.editor = editor |
|
||||||
this.txdebugger = txdebugger |
|
||||||
this.sourceMappingDecoder = new remix.util.SourceMappingDecoder() |
|
||||||
this.compilationData |
|
||||||
|
|
||||||
this.currentSourceMap |
|
||||||
this.currentLineColumnLayout // used to retrieve line/column from char/length
|
|
||||||
this.currentRange |
|
||||||
this.currentMarker |
|
||||||
this.currentExecutingAddress |
|
||||||
this.currentFile |
|
||||||
|
|
||||||
this.isDebugging = false |
|
||||||
|
|
||||||
var self = this |
|
||||||
|
|
||||||
/* hide/show marker when debugger does not have the focus */ |
|
||||||
appEvent.register('tabChanged', function (tab) { |
|
||||||
if (tab !== 'debugView') { |
|
||||||
self.editor.removeMarker(self.currentMarker) |
|
||||||
} else { |
|
||||||
if (self.currentRange) { |
|
||||||
self.currentMarker = self.editor.addMarker(self.currentRange, 'highlightcode') |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
/* update compilation data */ |
|
||||||
compilerEvent.register('compilationFinished', this, function (success, data, source) { |
|
||||||
if (!self.isDebugging) { |
|
||||||
self.reset() |
|
||||||
self.compilationData = success ? data : null |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
/* update marker, switch file if necessary */ |
|
||||||
txdebugger.codeManager.register('changed', this, function (code, address, index) { |
|
||||||
if (!this.currentExecutingAddress !== address) { |
|
||||||
// context changed, need to update srcmap
|
|
||||||
this.currentExecutingAddress = address |
|
||||||
self.loadContext(txdebugger.tx, function (error, ctrName, srcmap) { |
|
||||||
if (!error) { |
|
||||||
self.currentSourceMap = srcmap |
|
||||||
self.currentContractName = ctrName |
|
||||||
self.highlightSource(index) |
|
||||||
} |
|
||||||
}) |
|
||||||
} else { |
|
||||||
self.highlightSource(index) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
txdebugger.register('newTraceLoaded', this, function () { |
|
||||||
self.isDebugging = true |
|
||||||
}) |
|
||||||
|
|
||||||
txdebugger.register('traceUnloaded', this, function () { |
|
||||||
self.reset() |
|
||||||
self.isDebugging = false |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* Load a new debugging context. A context is define by a new transaction. |
|
||||||
* it: |
|
||||||
* - builds the line/column layout from the source code. |
|
||||||
* - retrieves the source map |
|
||||||
* |
|
||||||
* @param {Integer} index - the tx which defined the new debugging context |
|
||||||
* @param {Function} callback - returns the decompressed source mapping for the given index {start, length, file, jump} |
|
||||||
*/ |
|
||||||
SourceHighlighter.prototype.loadContext = function (tx, cb) { |
|
||||||
var self = this |
|
||||||
contractName(self.currentExecutingAddress, self, function (error, ctrName) { |
|
||||||
if (!error) { |
|
||||||
var srcmap = sourceMap(isContractCreation(self.currentExecutingAddress), ctrName, self.compilationData) |
|
||||||
cb(null, ctrName, srcmap) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* remove the editor marker and init attributes |
|
||||||
*/ |
|
||||||
SourceHighlighter.prototype.reset = function () { |
|
||||||
this.currentSourceMap = null |
|
||||||
this.currentLineColumnLayout = null |
|
||||||
this.removeCurrentMarker() |
|
||||||
this.currentRange = null |
|
||||||
this.currentMarker = null |
|
||||||
this.currentExecutingAddress = null |
|
||||||
this.currentFile = null |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* remove the current highlighted statement |
|
||||||
*/ |
|
||||||
SourceHighlighter.prototype.removeCurrentMarker = function () { |
|
||||||
if (this.currentMarker) { |
|
||||||
this.editor.removeMarker(this.currentMarker) |
|
||||||
this.currentMarker = null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* highlight the statement with the given @arg index |
|
||||||
* |
|
||||||
* @param {Integer} index - the index of the assembly item to be highlighted |
|
||||||
*/ |
|
||||||
SourceHighlighter.prototype.highlightSource = function (index) { |
|
||||||
var self = this |
|
||||||
this.sourceMappingDecoder.decompress(index, self.currentSourceMap, function (error, rawPos) { // retrieve the sourcemap location
|
|
||||||
if (!error) { |
|
||||||
if (self.currentFile !== rawPos.file) { // source file changed, need to update the line/column layout
|
|
||||||
var file = self.compilationData.sourceList[rawPos.file] |
|
||||||
self.sourceMappingDecoder.retrieveLineColumnLayout(self.editor.getFile(file), function (error, result) { |
|
||||||
if (!error) { |
|
||||||
self.currentLineColumnLayout = result |
|
||||||
self.currentFile = rawPos.file |
|
||||||
self.sourceMappingDecoder.getLineColumnPosition(rawPos, self.currentLineColumnLayout, function (error, pos) { |
|
||||||
if (!error) { |
|
||||||
self.highlight(pos, rawPos.file) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} else { |
|
||||||
self.sourceMappingDecoder.getLineColumnPosition(rawPos, self.currentLineColumnLayout, function (error, pos) { |
|
||||||
if (!error) { |
|
||||||
self.highlight(pos, rawPos.file) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
* highlight the statement with the given @arg position |
|
||||||
* |
|
||||||
* @param {Object} position - the position to highlight { start: {line, column}, end: {line, column} } |
|
||||||
*/ |
|
||||||
SourceHighlighter.prototype.highlight = function (position, fileIndex) { |
|
||||||
var name = utils.fileNameFromKey(this.editor.getCacheFile()) // current opened tab
|
|
||||||
var source = this.compilationData.sourceList[parseInt(fileIndex)] // auto switch to that tab
|
|
||||||
this.removeCurrentMarker() |
|
||||||
if (name !== source) { |
|
||||||
this.switchToFile(source) // command the app to swicth to the curent file
|
|
||||||
} |
|
||||||
this.currentRange = new Range(position.start.line, position.start.column, position.end.line, position.end.column) |
|
||||||
this.currentMarker = this.editor.addMarker(this.currentRange, 'highlightcode') |
|
||||||
} |
|
||||||
|
|
||||||
function sourceMap (isConstructor, contractName, compilationData) { |
|
||||||
if (isConstructor) { |
|
||||||
return compilationData.contracts[contractName].srcmap |
|
||||||
} else { |
|
||||||
return srcmapRuntime(compilationData.contracts[contractName]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function contractName (executingAddress, self, cb) { |
|
||||||
if (isContractCreation(executingAddress)) { |
|
||||||
self.txdebugger.traceManager.getContractCreationCode(executingAddress, function (error, creationCode) { |
|
||||||
if (!error) { |
|
||||||
retrieveByteCode(creationCode, self.compilationData, 'bytecode', cb) |
|
||||||
} |
|
||||||
}) |
|
||||||
} else { |
|
||||||
self.txdebugger.web3().eth.getCode(executingAddress, function (error, code) { |
|
||||||
if (!error) { |
|
||||||
retrieveByteCode(code, self.compilationData, 'runtimeBytecode', cb) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function retrieveByteCode (code, compilationData, prop, cb) { |
|
||||||
for (var k in compilationData.contracts) { |
|
||||||
if (code === '0x' + compilationData.contracts[k][prop]) { |
|
||||||
cb(null, k) |
|
||||||
return |
|
||||||
} |
|
||||||
cb('unable to retrieve contract name') |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function srcmapRuntime (contract) { |
|
||||||
return contract.srcmapRuntime ? contract.srcmapRuntime : contract['srcmap-runtime'] |
|
||||||
} |
|
||||||
|
|
||||||
function isContractCreation (address) { |
|
||||||
return address.indexOf('Contract Creation') !== -1 |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = SourceHighlighter |
|
Loading…
Reference in new issue