Commit a3dc9679 authored by chriseth's avatar chriseth Committed by GitHub

Merge pull request #157 from ethereum/locals

Locals Decoder
parents ce1ab50a aa9ce739
{ {
"plugins": ["fast-async", "plugins": [["fast-async", {
"runtimePatten": null,
"compiler": {
"promises": true,
"es7": true,
"noRuntime": true,
"wrapAwait": true
}
}],
"check-es2015-constants", "check-es2015-constants",
"transform-es2015-arrow-functions", "transform-es2015-arrow-functions",
"transform-es2015-block-scoped-functions", "transform-es2015-block-scoped-functions",
......
...@@ -50,7 +50,10 @@ ...@@ -50,7 +50,10 @@
"tape": "^4.6.0", "tape": "^4.6.0",
"web3": "^0.15.3", "web3": "^0.15.3",
"yo-yo": "^1.2.1", "yo-yo": "^1.2.1",
"yo-yoify": "^3.1.0" "yo-yoify": "^3.1.0",
"ethereumjs-block": "^1.2.2",
"ethereumjs-tx": "^1.1.1",
"ethereumjs-vm": "^2.0.1"
}, },
"scripts": { "scripts": {
"start_node": "./runNode.sh", "start_node": "./runNode.sh",
......
...@@ -20,16 +20,36 @@ function SourceLocationTracker (_codeManager) { ...@@ -20,16 +20,36 @@ function SourceLocationTracker (_codeManager) {
* @param {Object} contractDetails - AST of compiled contracts * @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function * @param {Function} cb - callback function
*/ */
SourceLocationTracker.prototype.getSourceLocation = function (address, index, contractsDetails, cb) { SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = function (address, index, contracts, cb) {
var self = this var self = this
this.codeManager.getCode(address, function (error, result) { extractSourceMap(this.codeManager, address, contracts, function (error, sourceMap) {
if (error) {
cb(error)
} else {
cb(null, self.sourceMappingDecoder.atIndex(index, sourceMap))
}
})
}
/**
* Return the source location associated with the given @arg pc
*
* @param {String} address - contract address from which the source location is retrieved
* @param {Int} vmtraceStepIndex - index of the current code in the vmtrace
* @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function
*/
SourceLocationTracker.prototype.getSourceLocationFromVMTraceIndex = function (address, vmtraceStepIndex, contracts, cb) {
var self = this
extractSourceMap(this.codeManager, address, contracts, function (error, sourceMap) {
if (!error) { if (!error) {
var sourceMap = getSourceMap(address, result.bytecode, contractsDetails) self.codeManager.getInstructionIndex(address, vmtraceStepIndex, function (error, index) {
if (sourceMap) { if (error) {
cb(null, self.sourceMappingDecoder.atIndex(index, sourceMap)) cb(error)
} else { } else {
cb('no srcmap associated with the code ' + address) cb(null, self.sourceMappingDecoder.atIndex(index, sourceMap))
} }
})
} else { } else {
cb(error) cb(error)
} }
...@@ -43,15 +63,30 @@ function srcmapRuntime (contract) { ...@@ -43,15 +63,30 @@ function srcmapRuntime (contract) {
return contract.srcmapRuntime ? contract.srcmapRuntime : contract['srcmap-runtime'] return contract.srcmapRuntime ? contract.srcmapRuntime : contract['srcmap-runtime']
} }
function getSourceMap (address, code, contractsDetails) { function getSourceMap (address, code, contracts) {
var isCreation = helper.isContractCreation(address) var isCreation = helper.isContractCreation(address)
var byteProp = isCreation ? 'bytecode' : 'runtimeBytecode' var byteProp = isCreation ? 'bytecode' : 'runtimeBytecode'
for (var k in contractsDetails) { for (var k in contracts) {
if ('0x' + contractsDetails[k][byteProp] === code) { if ('0x' + contracts[k][byteProp] === code) {
return isCreation ? contractsDetails[k].srcmap : srcmapRuntime(contractsDetails[k]) return isCreation ? contracts[k].srcmap : srcmapRuntime(contracts[k])
} }
} }
return null return null
} }
function extractSourceMap (codeManager, address, contracts, cb) {
codeManager.getCode(address, function (error, result) {
if (!error) {
var sourceMap = getSourceMap(address, result.bytecode, contracts)
if (sourceMap) {
cb(null, sourceMap)
} else {
cb('no srcmap associated with the code ' + address)
}
} else {
cb(error)
}
})
}
module.exports = SourceLocationTracker module.exports = SourceLocationTracker
...@@ -73,10 +73,13 @@ function extractStateDefinitions (contractName, sourcesList, contracts) { ...@@ -73,10 +73,13 @@ function extractStateDefinitions (contractName, sourcesList, contracts) {
* return state var and type definition of all the contracts from the given @args sourcesList * return state var and type definition of all the contracts from the given @args sourcesList
* *
* @param {Object} sourcesList - sources list (containing root AST node) * @param {Object} sourcesList - sources list (containing root AST node)
* @param {Object} [contracts] - map of contract definitions (contains contractsById, contractsByName)
* @return {Object} - returns a mapping between contract name and contract state * @return {Object} - returns a mapping between contract name and contract state
*/ */
function extractStatesDefinitions (sourcesList) { function extractStatesDefinitions (sourcesList, contracts) {
var contracts = extractContractDefinitions(sourcesList) if (!contracts) {
contracts = extractContractDefinitions(sourcesList)
}
var ret = {} var ret = {}
for (var contract in contracts.contractsById) { for (var contract in contracts.contractsById) {
var name = contracts.contractsById[contract].attributes.name var name = contracts.contractsById[contract].attributes.name
......
'use strict'
function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory) {
var scope = internalTreeCall.findScope(vmtraceIndex)
if (!scope) {
return { 'error': 'Can\'t display locals. reason: compilation result might not have been provided' }
}
var locals = {}
for (var local in scope.locals) {
let variable = scope.locals[local]
if (variable.type.decodeLocals) {
locals[variable.name] = variable.type.decodeLocals(variable.stackHeight, stack, memory)
} else {
locals[variable.name] = ''
}
}
return locals
}
module.exports = {
solidityLocals: solidityLocals
}
'use strict'
var traceHelper = require('../helpers/traceHelper')
var stateDecoder = require('./stateDecoder')
var astHelper = require('./astHelper')
class SolidityProxy {
constructor (traceManager, codeManager) {
this.cache = new Cache()
this.reset({})
this.traceManager = traceManager
this.codeManager = codeManager
}
/**
* reset the cache and apply a new @arg compilationResult
*
* @param {Object} compilationResult - result os a compilatiion (diectly returned by the compiler)
*/
reset (compilationResult) {
this.sources = compilationResult.sources
this.sourceList = compilationResult.sourceList
this.contracts = compilationResult.contracts
this.cache.reset()
}
/**
* check if the object has been properly loaded
*
* @return {Bool} - returns true if a compilation result has been applied
*/
loaded () {
return this.contracts !== undefined
}
/**
* retrieve the compiled contract name at the @arg vmTraceIndex (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
*/
contractNameAt (vmTraceIndex, cb) {
this.traceManager.getCurrentCalledAddressAt(vmTraceIndex, (error, address) => {
if (error) {
cb(error)
} else {
if (this.cache.contractNameByAddress[address]) {
cb(null, this.cache.contractNameByAddress[address])
} else {
this.codeManager.getCode(address, (error, code) => {
if (error) {
cb(error)
} else {
var contractName = contractNameFromCode(this.contracts, code.bytecode, address)
this.cache.contractNameByAddress[address] = contractName
cb(null, contractName)
}
})
}
}
})
}
/**
* extract the state variables of the given compiled @arg contractName (cached)
*
* @param {String} contractName - name of the contract to retrieve state variables from
* @return {Object} - returns state variables of @args contractName
*/
extractStatesDefinitions () {
if (!this.cache.contractDeclarations) {
this.cache.contractDeclarations = astHelper.extractContractDefinitions(this.sources)
}
if (!this.cache.statesDefinitions) {
this.cache.statesDefinitions = astHelper.extractStatesDefinitions(this.sources, this.cache.contractDeclarations)
}
return this.cache.statesDefinitions
}
/**
* extract the state variables of the given compiled @arg contractName (cached)
*
* @param {String} contractName - name of the contract to retrieve state variables from
* @return {Object} - returns state variables of @args contractName
*/
extractStateVariables (contractName) {
if (!this.cache.stateVariablesByContractName[contractName]) {
this.cache.stateVariablesByContractName[contractName] = stateDecoder.extractStateVariables(contractName, this.sources)
}
return this.cache.stateVariablesByContractName[contractName]
}
/**
* extract the state variables of the given compiled @arg vmtraceIndex (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the state variables
* @return {Object} - returns state variables of @args vmTraceIndex
*/
extractStateVariablesAt (vmtraceIndex, cb) {
this.contractNameAt(vmtraceIndex, (error, contractName) => {
if (error) {
cb(error)
} else {
cb(null, this.extractStateVariables(contractName))
}
})
}
/**
* get the AST of the file declare in the @arg sourceLocation
*
* @param {Object} sourceLocation - source location containing the 'file' to retrieve the AST from
* @return {Object} - AST of the current file
*/
ast (sourceLocation) {
var file = this.sourceList[sourceLocation.file]
return this.sources[file].AST
}
}
function contractNameFromCode (contracts, code, address) {
var isCreation = traceHelper.isContractCreation(address)
var byteProp = isCreation ? 'bytecode' : 'runtimeBytecode'
for (var k in contracts) {
if ('0x' + contracts[k][byteProp] === code) {
return k
}
}
return null
}
class Cache {
constructor () {
this.reset()
}
reset () {
this.contractNameByAddress = {}
this.stateVariablesByContractName = {}
this.contractDeclarations = null
this.statesDefinitions = null
}
}
module.exports = SolidityProxy
...@@ -281,4 +281,16 @@ TraceManager.prototype.checkRequestedStep = function (stepIndex) { ...@@ -281,4 +281,16 @@ TraceManager.prototype.checkRequestedStep = function (stepIndex) {
return undefined return undefined
} }
TraceManager.prototype.waterfall = function (calls, stepindex, cb) {
var ret = []
var retError = null
for (var call in calls) {
calls[call].apply(this, [stepindex, function (error, result) {
retError = error
ret.push({ error: error, value: result })
}])
}
cb(retError, ret)
}
module.exports = TraceManager module.exports = TraceManager
...@@ -11,15 +11,15 @@ var ui = require('../helpers/ui') ...@@ -11,15 +11,15 @@ var ui = require('../helpers/ui')
var Web3Providers = require('../web3Provider/web3Providers') var Web3Providers = require('../web3Provider/web3Providers')
var DummyProvider = require('../web3Provider/dummyProvider') var DummyProvider = require('../web3Provider/dummyProvider')
var CodeManager = require('../code/codeManager') var CodeManager = require('../code/codeManager')
var SourceLocationTracker = require('../code/sourceLocationTracker') var SolidityProxy = require('../solidity/solidityProxy')
var InternalCallTree = require('../util/internalCallTree')
function Ethdebugger () { function Ethdebugger () {
var self = this
this.event = new EventManager() this.event = new EventManager()
this.currentStepIndex = -1 this.currentStepIndex = -1
this.tx this.tx
this.sources
this.contractsDetail
this.statusMessage = '' this.statusMessage = ''
this.view this.view
...@@ -28,9 +28,10 @@ function Ethdebugger () { ...@@ -28,9 +28,10 @@ function Ethdebugger () {
this.switchProvider('DUMMYWEB3') this.switchProvider('DUMMYWEB3')
this.traceManager = new TraceManager() this.traceManager = new TraceManager()
this.codeManager = new CodeManager(this.traceManager) this.codeManager = new CodeManager(this.traceManager)
this.sourceLocationTracker = new SourceLocationTracker(this.codeManager) this.solidityProxy = new SolidityProxy(this.traceManager, this.codeManager)
var self = this var callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
this.callTree = callTree
this.event.register('indexChanged', this, function (index) { this.event.register('indexChanged', this, function (index) {
self.codeManager.resolveStep(index, self.tx) self.codeManager.resolveStep(index, self.tx)
}) })
...@@ -49,7 +50,7 @@ function Ethdebugger () { ...@@ -49,7 +50,7 @@ function Ethdebugger () {
this.stepManager.event.register('stepChanged', this, function (stepIndex) { this.stepManager.event.register('stepChanged', this, function (stepIndex) {
self.stepChanged(stepIndex) self.stepChanged(stepIndex)
}) })
this.vmDebugger = new VmDebugger(this, this.traceManager, this.codeManager) this.vmDebugger = new VmDebugger(this, this.traceManager, this.codeManager, this.solidityProxy, callTree)
} }
Ethdebugger.prototype.web3 = function () { Ethdebugger.prototype.web3 = function () {
...@@ -75,11 +76,9 @@ Ethdebugger.prototype.switchProvider = function (type) { ...@@ -75,11 +76,9 @@ Ethdebugger.prototype.switchProvider = function (type) {
Ethdebugger.prototype.setCompilationResult = function (compilationResult) { Ethdebugger.prototype.setCompilationResult = function (compilationResult) {
if (compilationResult && compilationResult.sources && compilationResult.contracts) { if (compilationResult && compilationResult.sources && compilationResult.contracts) {
this.sources = compilationResult.sources this.solidityProxy.reset(compilationResult)
this.contractsDetail = compilationResult.contracts
} else { } else {
this.sources = null this.solidityProxy.reset({})
this.contractsDetail = null
} }
} }
...@@ -132,7 +131,7 @@ Ethdebugger.prototype.startDebugging = function (blockNumber, txIndex, tx) { ...@@ -132,7 +131,7 @@ Ethdebugger.prototype.startDebugging = function (blockNumber, txIndex, tx) {
if (result) { if (result) {
self.statusMessage = '' self.statusMessage = ''
yo.update(self.view, self.render()) yo.update(self.view, self.render())
self.event.trigger('newTraceLoaded') self.event.trigger('newTraceLoaded', [self.traceManager.trace])
} else { } else {
self.statusMessage = error self.statusMessage = error
yo.update(self.view, self.render()) yo.update(self.view, self.render())
......
'use strict'
var DropdownPanel = require('./DropdownPanel')
var localDecoder = require('../solidity/localDecoder')
var yo = require('yo-yo')
class SolidityLocals {
constructor (_parent, _traceManager, internalTreeCall) {
this.parent = _parent
this.internalTreeCall = internalTreeCall
this.traceManager = _traceManager
this.basicPanel = new DropdownPanel('Solidity Locals')
this.init()
}
render () {
return yo`<div id='soliditylocals' >${this.basicPanel.render()}</div>`
}
init () {
this.parent.event.register('indexChanged', this, (index) => {
if (index < 0) {
this.basicPanel.update({info: 'invalid step index'})
return
}
if (this.parent.currentStepIndex !== index) return
this.traceManager.waterfall([
this.traceManager.getStackAt,
this.traceManager.getMemoryAt],
index,
(error, result) => {
if (!error) {
var stack = result[0].value
var memory = result[1].value
var locals = localDecoder.solidityLocals(index, this.internalTreeCall, stack, memory)
this.basicPanel.update(locals)
}
})
})
}
}
module.exports = SolidityLocals
'use strict' 'use strict'
var DropdownPanel = require('./DropdownPanel') var DropdownPanel = require('./DropdownPanel')
var traceHelper = require('../helpers/traceHelper')
var stateDecoder = require('../solidity/stateDecoder') var stateDecoder = require('../solidity/stateDecoder')
var yo = require('yo-yo') var yo = require('yo-yo')
function SolidityState (_parent, _traceManager, _codeManager) { function SolidityState (_parent, _traceManager, _codeManager, _solidityProxy) {
this.parent = _parent this.parent = _parent
this.traceManager = _traceManager this.traceManager = _traceManager
this.codeManager = _codeManager this.codeManager = _codeManager
this.solidityProxy = _solidityProxy
this.basicPanel = new DropdownPanel('Solidity State') this.basicPanel = new DropdownPanel('Solidity State')
this.init() this.init()
} }
...@@ -24,7 +24,7 @@ SolidityState.prototype.init = function () { ...@@ -24,7 +24,7 @@ SolidityState.prototype.init = function () {
return return
} }
if (self.parent.currentStepIndex !== index) return if (self.parent.currentStepIndex !== index) return
if (!this.parent.contractsDetail || !this.parent.sources) { if (!this.solidityProxy.loaded()) {
self.basicPanel.update({info: 'no source has been specified'}) self.basicPanel.update({info: 'no source has been specified'})
return return
} }
...@@ -33,23 +33,11 @@ SolidityState.prototype.init = function () { ...@@ -33,23 +33,11 @@ SolidityState.prototype.init = function () {
if (error) { if (error) {
self.basicPanel.update({ info: error }) self.basicPanel.update({ info: error })
} else { } else {
self.traceManager.getCurrentCalledAddressAt(index, function (error, address) { self.solidityProxy.extractStateVariablesAt(index, function (error, stateVars) {
if (error) { if (error) {
self.basicPanel.update({ info: error }) self.basicPanel.update({ info: error })
} else { } else {
self.codeManager.getCode(address, function (error, code) { self.basicPanel.update(stateDecoder.decodeState(stateVars, storage))
if (error) {
self.basicPanel.update({ info: error })
} else {
var contractName = contractNameFromCode(self.parent.contractsDetail, code.bytecode, address)
if (contractName === null) {
self.basicPanel.update({ info: 'could not find compiled contract with address ' + address })
} else {
var state = stateDecoder.solidityState(storage, self.parent.sources, contractName)
self.basicPanel.update(state)
}
}
})
} }
}) })
} }
...@@ -57,15 +45,4 @@ SolidityState.prototype.init = function () { ...@@ -57,15 +45,4 @@ SolidityState.prototype.init = function () {
}) })
} }
function contractNameFromCode (contracts, code, address) {
var isCreation = traceHelper.isContractCreation(address)
var byteProp = isCreation ? 'bytecode' : 'runtimeBytecode'
for (var k in contracts) {
if ('0x' + contracts[k][byteProp] === code) {
return k
}
}
return null
}
module.exports = SolidityState module.exports = SolidityState
...@@ -9,9 +9,10 @@ var FullStoragesChangesPanel = require('./FullStoragesChanges') ...@@ -9,9 +9,10 @@ var FullStoragesChangesPanel = require('./FullStoragesChanges')
var StepDetail = require('./StepDetail') var StepDetail = require('./StepDetail')
var DropdownPanel = require('./DropdownPanel') var DropdownPanel = require('./DropdownPanel')
var SolidityState = require('./SolidityState') var SolidityState = require('./SolidityState')
var SolidityLocals = require('./SolidityLocals')
var yo = require('yo-yo') var yo = require('yo-yo')
function VmDebugger (_parent, _traceManager, _codeManager) { function VmDebugger (_parent, _traceManager, _codeManager, _solidityProxy, _callTree) {
this.asmCode = new CodeListView(_parent, _codeManager) this.asmCode = new CodeListView(_parent, _codeManager)
this.stackPanel = new StackPanel(_parent, _traceManager) this.stackPanel = new StackPanel(_parent, _traceManager)
this.storagePanel = new StoragePanel(_parent, _traceManager) this.storagePanel = new StoragePanel(_parent, _traceManager)
...@@ -19,7 +20,8 @@ function VmDebugger (_parent, _traceManager, _codeManager) { ...@@ -19,7 +20,8 @@ function VmDebugger (_parent, _traceManager, _codeManager) {
this.calldataPanel = new CalldataPanel(_parent, _traceManager) this.calldataPanel = new CalldataPanel(_parent, _traceManager)
this.callstackPanel = new CallstackPanel(_parent, _traceManager) this.callstackPanel = new CallstackPanel(_parent, _traceManager)
this.stepDetail = new StepDetail(_parent, _traceManager) this.stepDetail = new StepDetail(_parent, _traceManager)
this.solidityState = new SolidityState(_parent, _traceManager, _codeManager) this.solidityState = new SolidityState(_parent, _traceManager, _codeManager, _solidityProxy)
this.solidityLocals = new SolidityLocals(_parent, _traceManager, _callTree)
/* Return values - */ /* Return values - */
this.returnValuesPanel = new DropdownPanel('Return Value') this.returnValuesPanel = new DropdownPanel('Return Value')
...@@ -53,6 +55,7 @@ VmDebugger.prototype.render = function () { ...@@ -53,6 +55,7 @@ VmDebugger.prototype.render = function () {
var view = yo`<div id='vmdebugger' style='display:none'> var view = yo`<div id='vmdebugger' style='display:none'>
<div> <div>
${this.asmCode.render()} ${this.asmCode.render()}
${this.solidityLocals.render()}
${this.solidityState.render()} ${this.solidityState.render()}
${this.stepDetail.render()} ${this.stepDetail.render()}
${this.stackPanel.render()} ${this.stackPanel.render()}
......
'use strict'
var SourceLocationTracker = require('../code/sourceLocationTracker')
var AstWalker = require('./astWalker')
var EventManager = require('../lib/eventManager')
var decodeInfo = require('../solidity/decodeInfo')
var util = require('../helpers/util')
/**
* Tree representing internal jump into function.
* Triggers `callTreeReady` event when tree is ready
* Triggers `callTreeBuildFailed` event when tree fails to build
*/
class InternalCallTree {
/**
* constructor
*
* @param {Object} debuggerEvent - event declared by the debugger (EthDebugger)
* @param {Object} traceManager - trace manager
* @param {Object} solidityProxy - solidity proxy
* @param {Object} codeManager - code manager
* @param {Object} opts - { includeLocalVariables }
*/
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) {
this.includeLocalVariables = opts.includeLocalVariables
this.event = new EventManager()
this.solidityProxy = solidityProxy
this.traceManager = traceManager
this.sourceLocationTracker = new SourceLocationTracker(codeManager)
debuggerEvent.register('newTraceLoaded', (trace) => {
this.reset()
if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
} else {
buildTree(this, 0, '').then((result) => {
if (result.error) {
this.event.trigger('callTreeBuildFailed', [result.error])
} else {
console.log('ready')
this.event.trigger('callTreeReady', [this.scopes, this.scopeStarts])
}
})
}
})
}
/**
* reset tree
*
*/
reset () {
/*
scopes: map of scopes defined by range in the vmtrace {firstStep, lastStep, locals}. Keys represent the level of deepness (scopeId)
*/
this.scopes = {}
/*
scopeStart: represent start of a new scope. Keys are index in the vmtrace, values are scopeId
*/
this.scopeStarts = {}
this.variableDeclarationByFile = {}
this.astWalker = new AstWalker()
}
/**
* find the scope given @arg vmTraceIndex
*
* @param {Int} vmtraceIndex - index on the vm trace
*/
findScope (vmtraceIndex) {
var scopes = Object.keys(this.scopeStarts)
if (!scopes.length) {
return null
}
var scopeId = util.findLowerBoundValue(vmtraceIndex, scopes)
scopeId = this.scopeStarts[scopeId]
var scope = this.scopes[scopeId]
while (scope.lastStep && scope.lastStep < vmtraceIndex) {
var matched = scopeId.match(/(.\d|\d)$/)
scopeId = scopeId.replace(matched[1], '')
scope = this.scopes[scopeId]
}
return scope
}
}
async function buildTree (tree, step, scopeId) {
let subScope = 1
tree.scopeStarts[step] = scopeId
tree.scopes[scopeId] = { firstStep: step, locals: {} }
while (step < tree.traceManager.trace.length) {
var sourceLocation
try {
sourceLocation = await extractSourceLocation(tree, step)
} catch (e) {
return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e.messager }
}
if (!sourceLocation) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + step }
}
if (sourceLocation.jump === 'i') {
try {
var result = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope)
if (result.error) {
return { outStep: step, error: 'InternalCallTree - ' + result.error }
} else {
step = result.outStep
subScope++
}
} catch (e) {
return { outStep: step, error: 'InternalCallTree - ' + e.message }
}
} else if (sourceLocation.jump === 'o') {
tree.scopes[scopeId].lastStep = step
return { outStep: step + 1 }
} else {
if (tree.includeLocalVariables) {
includeVariableDeclaration(tree, step, sourceLocation, scopeId)
}
step++
}
}
return { outStep: step }
}
function includeVariableDeclaration (tree, step, sourceLocation, scopeId) {
var variableDeclaration = resolveVariableDeclaration(tree, step, sourceLocation)
if (variableDeclaration && !tree.scopes[scopeId].locals[variableDeclaration.attributes.name]) {
tree.traceManager.getStackAt(step, (error, stack) => {
if (!error) {
tree.solidityProxy.contractNameAt(step, (error, contractName) => { // cached
if (!error) {
var states = tree.solidityProxy.extractStatesDefinitions()
tree.scopes[scopeId].locals[variableDeclaration.attributes.name] = {
name: variableDeclaration.attributes.name,
type: decodeInfo.parseType(variableDeclaration.attributes.type, states, contractName),
stackHeight: stack.length
}
}
})
}
})
}
}
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)
}
})
} else {
return reject('InternalCallTree - Cannot retrieve address for step ' + step)
}
})
})
}
function resolveVariableDeclaration (tree, step, sourceLocation) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) {
tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(tree.solidityProxy.ast(sourceLocation), tree.astWalker)
}
var variableDeclarations = tree.variableDeclarationByFile[sourceLocation.file]
return variableDeclarations[sourceLocation.start + ':' + sourceLocation.length + ':' + sourceLocation.file]
}
function extractVariableDeclarations (ast, astWalker) {
var ret = {}
astWalker.walk(ast, (node) => {
if (node.name === 'VariableDeclaration') {
ret[node.src] = node
}
return true
})
return ret
}
module.exports = InternalCallTree
'use strict'
module.exports = {
contract: `
contract proxy {
struct testStruct {
int one;
uint two;
}
}
contract intLocal {
function intLocal () {
proxy.testStruct memory p;
uint8 ui8 = 130;
uint16 ui16 = 456;
uint32 ui32 = 4356;
uint64 ui64 = 3543543543;
uint128 ui128 = 234567;
uint256 ui256 = 115792089237316195423570985008687907853269984665640564039457584007880697216513;
uint ui = 123545666;
int8 i8 = -45;
int16 i16 = -1234;
int32 i32 = 3455;
int64 i64 = -35566;
int128 i128 = -444444;
int256 i256 = 3434343;
int i = -32432423423;
int32 ishrink = 2;
level11();
level12();
level11();
}
function level11() {
uint8 ui8 = 123;
level12();
}
function level12() {
uint8 ui81 = 12;
}
}
`}
'use strict'
var tape = require('tape')
var compiler = require('solc')
var intLocal = require('./contracts/intLocal')
var TraceManager = require('../../babelify-src/trace/traceManager')
var CodeManager = require('../../babelify-src/code/codeManager')
var VM = require('ethereumjs-vm')
var Tx = require('ethereumjs-tx')
var Block = require('ethereumjs-block')
var BN = require('ethereumjs-util').BN
var utileth = require('ethereumjs-util')
var Web3Providers = require('../../babelify-src/web3Provider/web3Providers')
var traceHelper = require('../../babelify-src/helpers/traceHelper')
var util = require('../../babelify-src/helpers/global')
var SolidityProxy = require('../../babelify-src/solidity/solidityProxy')
var InternalCallTree = require('../../babelify-src/util/internalCallTree')
var EventManager = require('../../babelify-src/lib/eventManager')
var localDecoder = require('../../babelify-src/solidity/localDecoder')
tape('solidity', function (t) {
t.test('local decoder', function (st) {
var privateKey = new Buffer('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', 'hex')
var address = utileth.privateToAddress(privateKey)
var vm = initVM(st, address)
var output = compiler.compile(intLocal.contract, 0)
sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, output.contracts['intLocal'].bytecode, function (error, txHash) {
if (error) {
st.fail(error)
} else {
util.web3.getTransaction(txHash, function (error, tx) {
if (error) {
st.fail(error)
} else {
tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager()
var codeManager = new CodeManager(traceManager)
var solidityProxy = new SolidityProxy(traceManager, codeManager)
solidityProxy.reset(output)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(JSON.stringify(error))
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
st.equals(scopeStarts[0], '')
st.equals(scopeStarts[97], '1')
st.equals(scopeStarts[112], '1.1')
st.equals(scopeStarts[135], '2')
st.equals(scopeStarts[154], '3')
st.equals(scopeStarts[169], '3.1')
st.equals(scopes[''].locals['ui8'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui16'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui32'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui64'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui128'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui256'].type.typeName, 'uint')
st.equals(scopes[''].locals['ui'].type.typeName, 'uint')
st.equals(scopes[''].locals['i8'].type.typeName, 'int')
st.equals(scopes[''].locals['i16'].type.typeName, 'int')
st.equals(scopes[''].locals['i32'].type.typeName, 'int')
st.equals(scopes[''].locals['i64'].type.typeName, 'int')
st.equals(scopes[''].locals['i128'].type.typeName, 'int')
st.equals(scopes[''].locals['i256'].type.typeName, 'int')
st.equals(scopes[''].locals['i'].type.typeName, 'int')
st.equals(scopes[''].locals['ishrink'].type.typeName, 'int')
st.equals(scopes['1'].locals['ui8'].type.typeName, 'uint')
st.equals(scopes['1.1'].locals['ui81'].type.typeName, 'uint')
st.equals(scopes['2'].locals['ui81'].type.typeName, 'uint')
st.equals(scopes['3'].locals['ui8'].type.typeName, 'uint')
st.equals(scopes['3.1'].locals['ui81'].type.typeName, 'uint')
decodeLocal(st, 125, traceManager, callTree, function (locals) {
st.equals(Object.keys(locals).length, 16)
})
decodeLocal(st, 177, traceManager, callTree, function (locals) {
try {
st.equals(locals['ui8'], '')
st.equals(Object.keys(locals).length, 1)
} catch (e) {
st.fail(e.message)
}
})
})
traceManager.resolveTrace(tx, (error, result) => {
if (error) {
st.fail(error)
} else {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}
})
}
})
}
})
})
})
/*
Decode local variable
*/
function decodeLocal (st, index, traceManager, callTree, verifier) {
traceManager.waterfall([
traceManager.getStackAt,
traceManager.getMemoryAt],
index,
function (error, result) {
if (!error) {
var locals = localDecoder.solidityLocals(index, callTree, result[0].value, result[1].value)
verifier(locals)
} else {
st.fail(error)
}
})
}
/*
Init VM / Send Transaction
*/
function initVM (st, address) {
var vm = new VM({
enableHomestead: true,
activatePrecompiles: true
})
vm.stateManager.putAccountBalance(address, 'f00000000000000001', function cb () {})
var web3Providers = new Web3Providers()
web3Providers.addVM('VM', vm)
web3Providers.get('VM', function (error, obj) {
if (error) {
var mes = 'provider TEST not defined'
console.log(mes)
st.fail(mes)
} else {
util.web3 = obj
st.end()
}
})
return vm
}
function sendTx (vm, from, to, value, data, cb) {
var tx = new Tx({
nonce: new BN(from.nonce++),
gasPrice: new BN(1),
gasLimit: new BN(3000000, 10),
to: to,
value: new BN(value, 10),
data: new Buffer(data, 'hex')
})
tx.sign(from.privateKey)
var block = new Block({
header: {
timestamp: new Date().getTime() / 1000 | 0,
number: 0
},
transactions: [],
uncleHeaders: []
})
vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (error, result) {
cb(error, utileth.bufferToHex(tx.hash()))
})
}
...@@ -10,4 +10,5 @@ require('./sourceMappingDecoder.js') ...@@ -10,4 +10,5 @@ require('./sourceMappingDecoder.js')
require('./solidity/decodeInfo.js') require('./solidity/decodeInfo.js')
require('./solidity/storageLocation.js') require('./solidity/storageLocation.js')
require('./solidity/storageDecoder.js') require('./solidity/storageDecoder.js')
require('./solidity/localDecoder.js')
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