Commit 8c04231e authored by yann300's avatar yann300 Committed by GitHub

Merge pull request #191 from ethereum/breakpoint

Breakpoints
parents 63702cca 7f3f2621
'use strict'
var EventManager = require('../lib/eventManager')
var helper = require('../helpers/traceHelper')
/**
* allow to manage breakpoint
*
* Trigger events: breakpointHit, breakpointAdded, breakpointRemoved
*/
class BreakpointManager {
/**
* constructor
*
* @param {Object} _debugger - type of EthDebugger
* @return {Function} _locationToRowConverter - function implemented by editor which return a column/line position for a char source location
*/
constructor (_debugger, _locationToRowConverter) {
this.event = new EventManager()
this.debugger = _debugger
this.breakpoints = {}
this.locationToRowConverter = _locationToRowConverter
this.previousLine
}
/**
* start looking for the next breakpoint
* @param {Bool} defaultToLimit - if true jump to the end of the trace if no more breakpoint found
*
*/
async jumpNextBreakpoint (defaultToLimit) {
this.jump(1, defaultToLimit)
}
/**
* start looking for the previous breakpoint
* @param {Bool} defaultToLimit - if true jump to the start of the trace if no more breakpoint found
*
*/
async jumpPreviousBreakpoint (defaultToLimit) {
this.jump(-1, defaultToLimit)
}
/**
* start looking for the previous or next breakpoint
* @param {Int} direction - 1 or -1 direction of the search
* @param {Bool} defaultToLimit - if true jump to the limit (end if direction is 1, beginning if direction is -1) of the trace if no more breakpoint found
*
*/
async jump (direction, defaultToLimit) {
if (!this.locationToRowConverter) {
console.log('row converter not provided')
return
}
function depthChange (step, trace) {
return trace[step].depth !== trace[step - 1].depth
}
function hitLine (currentStep, sourceLocation, self) {
// isJumpDestInstruction -> returning from a internal function call
// depthChange -> returning from an external call
if (helper.isJumpDestInstruction(self.debugger.traceManager.trace[currentStep]) ||
depthChange(currentStep, self.debugger.traceManager.trace)) {
return false
} else {
self.debugger.stepManager.jumpTo(currentStep)
self.event.trigger('breakpointHit', [sourceLocation])
return true
}
}
var sourceLocation
var previousSourceLocation
var currentStep = this.debugger.currentStepIndex + direction
var lineHadBreakpoint = false
while (currentStep > 0 && currentStep < this.debugger.traceManager.trace.length) {
try {
previousSourceLocation = sourceLocation
sourceLocation = await this.debugger.callTree.extractSourceLocation(currentStep)
} catch (e) {
console.log('cannot jump to breakpoint ' + e)
return
}
var lineColumn = this.locationToRowConverter(sourceLocation)
if (this.previousLine !== lineColumn.start.line) {
if (direction === -1 && lineHadBreakpoint) { // TODO : improve this when we will build the correct structure before hand
lineHadBreakpoint = false
if (hitLine(currentStep + 1, previousSourceLocation, this)) {
return
}
}
this.previousLine = lineColumn.start.line
if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) {
lineHadBreakpoint = true
if (direction === 1) {
if (hitLine(currentStep, sourceLocation, this)) {
return
}
}
}
}
currentStep += direction
}
if (defaultToLimit) {
if (direction === -1) {
this.debugger.stepManager.jumpTo(0)
} else if (direction === 1) {
this.debugger.stepManager.jumpTo(this.debugger.traceManager.trace.length - 1)
}
}
}
/**
* check the given pair fileIndex/line against registered breakpoints
*
* @param {Int} fileIndex - index of the file content (from the compilation result)
* @param {Int} line - line number where looking for breakpoint
* @return {Bool} return true if the given @arg fileIndex @arg line refers to a breakpoint
*/
hasBreakpointAtLine (fileIndex, line) {
var filename = this.debugger.solidityProxy.fileNameFromIndex(fileIndex)
if (filename && this.breakpoints[filename]) {
var sources = this.breakpoints[filename]
for (var k in sources) {
var source = sources[k]
if (line === source.row) {
return true
}
}
}
return false
}
/**
* return true if current manager has breakpoint
*
* @return {Bool} true if breapoint registered
*/
hasBreakpoint () {
for (var k in this.breakpoints) {
if (this.breakpoints[k].length) {
return true
}
}
return false
}
/**
* add a new breakpoint to the manager
*
* @param {Object} sourceLocation - position of the breakpoint { file: '<file index>', row: '<line number' }
*/
add (sourceLocation) {
if (!this.breakpoints[sourceLocation.fileName]) {
this.breakpoints[sourceLocation.fileName] = []
}
this.breakpoints[sourceLocation.fileName].push(sourceLocation)
this.event.trigger('breakpointAdded', [sourceLocation])
}
/**
* remove a breakpoint from the manager
*
* @param {Object} sourceLocation - position of the breakpoint { file: '<file index>', row: '<line number' }
*/
remove (sourceLocation) {
if (this.breakpoints[sourceLocation.fileName]) {
var sources = this.breakpoints[sourceLocation.fileName]
for (var k in sources) {
var source = sources[k]
if (sourceLocation.row === source.row) {
sources.splice(k, 1)
this.event.trigger('breakpointRemoved', [sourceLocation])
break
}
}
}
}
}
module.exports = BreakpointManager
......@@ -25,6 +25,10 @@ module.exports = {
return step.op === 'RETURN'
},
isJumpDestInstruction: function (step) {
return step.op === 'JUMPDEST'
},
isStopInstruction: function (step) {
return step.op === 'STOP'
},
......
......@@ -6,6 +6,7 @@ var TreeView = require('./ui/TreeView')
var TraceManager = require('./trace/traceManager')
var CodeManager = require('./code/codeManager')
var disassembler = require('./code/disassembler')
var BreakpointManager = require('./code/breakpointManager')
var SourceMappingDecoder = require('./util/sourceMappingDecoder')
var AstWalker = require('./util/astWalker')
var decodeInfo = require('./solidity/decodeInfo')
......@@ -24,7 +25,8 @@ function modules () {
return {
code: {
codeManager: CodeManager,
disassembler: disassembler
disassembler: disassembler,
BreakpointManager: BreakpointManager
},
trace: {
traceManager: TraceManager
......
......@@ -120,6 +120,16 @@ class SolidityProxy {
return null
}
}
/**
* get the filename refering to the index from the compilation result
*
* @param {Int} index - index of the filename
* @return {String} - filename
*/
fileNameFromIndex (index) {
return this.sourceList[index]
}
}
function contractNameFromCode (contracts, code, address) {
......
......@@ -11,6 +11,8 @@ function ButtonNavigator (_parent, _traceManager) {
this.intoForwardDisabled = true
this.overForwardDisabled = true
this.jumpOutDisabled = true
this.jumpNextBreakpointDisabled = true
this.jumpPreviousBreakpointDisabled = true
this.traceManager = _traceManager
this.currentCall = null
......@@ -68,6 +70,10 @@ ButtonNavigator.prototype.render = function () {
</button>
<button id='jumpout' title='jump out' class='fa fa-share' style=${ui.formatCss(style.button)} onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} >
</button>
<button id='jumppreviousbreakpoint' title='jump to the previous breakpoint' class='fa fa-step-backward' style=${ui.formatCss(style.button)} onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} >
</button>
<button id='jumpnextbreakpoint' title='jump to the next breakpoint' class='fa fa-step-forward' style=${ui.formatCss(style.button)} onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} >
</button>
<div id='reverted' style="display:none">
<button id='jumptoexception' title='jump to exception' class='fa fa-exclamation-triangle' style=${ui.formatCss(style.button)} onclick=${function () { self.event.trigger('jumpToException', [self.revertionPoint]) }} disabled=${this.jumpOutDisabled} >
</button>
......@@ -88,6 +94,8 @@ ButtonNavigator.prototype.reset = function () {
this.intoForwardDisabled = true
this.overForwardDisabled = true
this.jumpOutDisabled = true
this.jumpNextBreakpointDisabled = true
this.jumpPreviousBreakpointDisabled = true
resetWarning(this)
}
......@@ -104,6 +112,8 @@ ButtonNavigator.prototype.stepChanged = function (step) {
self.reset()
console.log(error)
} else {
self.jumpNextBreakpointDisabled = step >= length - 1
self.jumpPreviousBreakpointDisabled = step <= 0
self.intoForwardDisabled = step >= length - 1
self.overForwardDisabled = step >= length - 1
var stepOut = self.traceManager.findStepOut(step)
......@@ -122,6 +132,8 @@ ButtonNavigator.prototype.updateAll = function () {
this.updateDisabled('intoforward', this.intoForwardDisabled)
this.updateDisabled('jumpout', this.jumpOutDisabled)
this.updateDisabled('jumptoexception', this.jumpOutDisabled)
this.updateDisabled('jumpnextbreakpoint', this.jumpNextBreakpointDisabled)
this.updateDisabled('jumppreviousbreakpoint', this.jumpPreviousBreakpointDisabled)
}
ButtonNavigator.prototype.updateDisabled = function (id, disabled) {
......
......@@ -62,6 +62,10 @@ function Ethdebugger () {
})
}
Ethdebugger.prototype.setBreakpointManager = function (breakpointManager) {
this.breakpointManager = breakpointManager
}
Ethdebugger.prototype.web3 = function () {
return util.web3
}
......@@ -141,6 +145,9 @@ Ethdebugger.prototype.startDebugging = function (blockNumber, txIndex, tx) {
self.statusMessage = ''
yo.update(self.view, self.render())
self.event.trigger('newTraceLoaded', [self.traceManager.trace])
if (self.breakpointManager && self.breakpointManager.hasBreakpoint()) {
self.breakpointManager.jumpNextBreakpoint(false)
}
} else {
self.statusMessage = error ? error.message : 'Trace not loaded'
yo.update(self.view, self.render())
......
......@@ -60,6 +60,12 @@ function StepManager (_parent, _traceManager) {
this.buttonNavigator.event.register('jumpToException', this, function (exceptionIndex) {
self.jumpTo(exceptionIndex)
})
this.buttonNavigator.event.register('jumpNextBreakpoint', (exceptionIndex) => {
self.parent.breakpointManager.jumpNextBreakpoint(true)
})
this.buttonNavigator.event.register('jumpPreviousBreakpoint', (exceptionIndex) => {
self.parent.breakpointManager.jumpPreviousBreakpoint(true)
})
}
StepManager.prototype.resolveToReducedTrace = function (value, incr) {
......
......@@ -4,7 +4,6 @@ var AstWalker = require('./astWalker')
var EventManager = require('../lib/eventManager')
var decodeInfo = require('../solidity/decodeInfo')
var util = require('../helpers/util')
var traceHelper = require('../helpers/traceHelper')
/**
* Tree representing internal jump into function.
......@@ -83,6 +82,25 @@ class InternalCallTree {
}
return scope
}
extractSourceLocation (step) {
var self = this
return new Promise(function (resolve, reject) {
self.traceManager.getCurrentCalledAddressAt(step, (error, address) => {
if (!error) {
self.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, self.solidityProxy.contracts, (error, sourceLocation) => {
if (!error) {
return resolve(sourceLocation)
} else {
return reject('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
}
})
} else {
return reject('InternalCallTree - Cannot retrieve address for step ' + step + ' ' + error)
}
})
})
}
}
async function buildTree (tree, step, scopeId) {
......@@ -93,7 +111,7 @@ async function buildTree (tree, step, scopeId) {
while (step < tree.traceManager.trace.length) {
var sourceLocation
try {
sourceLocation = await extractSourceLocation(tree, step)
sourceLocation = await tree.extractSourceLocation(step)
if (sourceLocation.start !== currentSourceLocation.start ||
sourceLocation.length !== currentSourceLocation.length ||
sourceLocation.file !== currentSourceLocation.file) {
......@@ -101,7 +119,7 @@ async function buildTree (tree, step, scopeId) {
currentSourceLocation = sourceLocation
}
} catch (e) {
return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e.message }
return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e }
}
if (!sourceLocation) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + step }
......@@ -156,24 +174,6 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId) {
}
}
function extractSourceLocation (tree, step) {
return new Promise(function (resolve, reject) {
tree.traceManager.getCurrentCalledAddressAt(step, (error, address) => {
if (!error) {
tree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, tree.solidityProxy.contracts, (error, sourceLocation) => {
if (!error) {
return resolve(sourceLocation)
} else {
return reject('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
}
})
} else {
return reject('InternalCallTree - Cannot retrieve address for step ' + step + ' ' + error)
}
})
})
}
function resolveVariableDeclaration (tree, step, sourceLocation) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) {
var ast = tree.solidityProxy.ast(sourceLocation)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment