manage generated sources

pull/374/head
yann300 4 years ago
parent 8f4f9118b4
commit 948332300a
  1. 35
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  2. 63
      apps/remix-ide/src/app/tabs/debugger/debuggerUI.js
  3. 2
      apps/remix-ide/src/remixAppManager.js
  4. 2
      libs/remix-astwalker/src/astWalker.ts
  5. 15
      libs/remix-debug/src/Ethdebugger.js
  6. 1
      libs/remix-debug/src/cmdline/index.js
  7. 16
      libs/remix-debug/src/debugger/debugger.js
  8. 130
      libs/remix-debug/src/solidity-decoder/internalCallTree.js
  9. 30
      libs/remix-debug/src/solidity-decoder/solidityProxy.js
  10. 57
      libs/remix-debug/src/source/astWalker.js
  11. 25
      libs/remix-debug/src/source/sourceLocationTracker.js
  12. 4
      libs/remix-debug/test/debugger.js

@ -147,7 +147,27 @@ module.exports = {
.waitForElementPresent('*[data-id="treeViewLoadMore"]')
.click('*[data-id="treeViewLoadMore"]')
.assert.containsText('*[data-id="solidityLocals"]', '149: 0 uint256')
.notContainsText('*[data-id="solidityLocals"]', '150: 0 uint256')
.notContainsText('*[data-id="solidityLocals"]', '150: 0 uint256')
},
'Should debug using generated sources': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.7.2+commit.51b20bc0.js')
.clickLaunchIcon('udapp')
.testContracts('withGeneratedSources.sol', sources[4]['browser/withGeneratedSources.sol'], ['A'])
.createContract('')
.clickInstance(4)
.clickFunction('f - transact (not payable)', {types: 'uint256[] ', values: '[]'})
.debugTransaction(8)
.pause(2000)
.click('*[data-id="debuggerTransactionStartButton"]') // stop debugging
.click('*[data-id="debugGeneratedSourcesLabel"]') // select debug with generated sources
.click('*[data-id="debuggerTransactionStartButton"]') // start debugging
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32) { revert(0, 0) }') != -1, 'current displayed content is not a generated source')
})
.end()
},
@ -227,6 +247,17 @@ const sources = [
}
`
}
},
{
'browser/withGeneratedSources.sol': {
content: `
// SPDX-License-Identifier: GPL-3.0
pragma experimental ABIEncoderV2;
contract A {
function f(uint[] memory) public returns (uint256) { }
}
`
}
}
]
@ -293,4 +324,4 @@ const localVariable_step717_ABIEncoder = {
"error": "<decoding failed - no decoder for calldata>",
"type": "bytes"
}
}
}

@ -22,6 +22,26 @@ var css = csjs`
.statusMessage {
margin-left: 15px;
}
.debuggerConfig {
display: flex;
align-items: center;
}
.debuggerConfig label {
margin: 0;
}
.debuggerSection {
padding: 12px 24px 16px;
}
.debuggerLabel {
margin-bottom: 2px;
font-size: 11px;
line-height: 12px;
text-transform: uppercase;
}
`
class DebuggerUI {
@ -32,7 +52,9 @@ class DebuggerUI {
this.event = new EventManager()
this.isActive = false
this.opt = {
debugWithGeneratedSources: false
}
this.sourceHighlighter = new SourceHighlighter()
this.startTxBrowser()
@ -72,12 +94,29 @@ class DebuggerUI {
this.isActive = isActive
})
this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation) => {
this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources) => {
if (!lineColumnPos) return
const contracts = await this.fetchContractAndCompile(
this.currentReceipt.contractAddress || this.currentReceipt.to,
this.currentReceipt)
if (contracts) {
const path = contracts.getSourceName(rawLocation.file)
let path = contracts.getSourceName(rawLocation.file)
if (!path) {
// check in generated sources
for (const source of generatedSources) {
if (source.id === rawLocation.file) {
path = `.debugger/generated-sources/${source.name}`
let content
try {
content = await this.debuggerModule.call('fileManager', 'getFile', path, source.contents)
} catch (e) {}
if (content !== source.contents) {
await this.debuggerModule.call('fileManager', 'setFile', path, source.contents)
}
break
}
}
}
if (path) {
await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
@ -137,7 +176,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: this.opt.debugWithGeneratedSources
})
this.listenToEvents()
@ -167,7 +207,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: false
})
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error)
@ -186,12 +227,18 @@ class DebuggerUI {
this.stepManagerView = yo`<div class="px-2"></div>`
var view = yo`
<div>
<div>
<div class="px-2">
<div class="mt-3">
<p class="mt-2 ${css.debuggerLabel}">Debugger Configuration</p>
<div class="mt-2 ${css.debuggerConfig} custom-control custom-checkbox">
<input class="custom-control-input" id="debugGeneratedSourcesInput" onchange=${(event) => { this.opt.debugWithGeneratedSources = event.target.checked }} type="checkbox" title="Debug with generated sources">
<label data-id="debugGeneratedSourcesLabel" class="form-check-label custom-control-label" for="debugGeneratedSourcesInput">Debug Generated sources if available (>= Solidity 0.7.2)</label>
</div>
</div>
${this.txBrowser.render()}
${this.stepManagerView}
${this.debuggerHeadPanelsView}
</div>
${this.debuggerHeadPanelsView}
<div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView}
</div>

@ -10,7 +10,7 @@ const requiredModules = [ // services + layout views + system views
'terminal', 'settings', 'pluginManager']
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops']
const nativePlugins = ['vyper', 'workshops', 'debugger']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

@ -12,7 +12,7 @@ const isObject = function(obj: any): boolean {
export function isAstNode(node: Record<string, unknown>): boolean {
return (
isObject(node) &&
'id' in node &&
// 'id' in node &&
'nodeType' in node &&
'src' in node
)

@ -26,6 +26,7 @@ const {SolidityProxy, stateDecoder, localDecoder, InternalCallTree} = require('.
function Ethdebugger (opts) {
this.compilationResult = opts.compilationResult || function (contractAddress) { return null }
this.web3 = opts.web3
this.opts = opts
this.event = new EventManager()
@ -36,7 +37,12 @@ function Ethdebugger (opts) {
this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null
this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
const includeLocalVariables = true
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...opts, includeLocalVariables})
}
Ethdebugger.prototype.setManagers = function () {
@ -44,8 +50,13 @@ Ethdebugger.prototype.setManagers = function () {
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null
const includeLocalVariables = true
this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...this.opts, includeLocalVariables})
this.event.trigger('managersChanged')
}

@ -79,6 +79,7 @@ class CmdLine {
this.txHash = txNumber
this.debugger.debug(null, txNumber, null, () => {
this.debugger.event.register('newSourceLocation', (lineColumnPos, rawLocation) => {
if (!lineColumnPos) return
this.lineColumnPos = lineColumnPos
this.rawLocation = rawLocation
this.events.emit('source', [lineColumnPos, rawLocation])

@ -18,7 +18,8 @@ function Debugger (options) {
this.debugger = new Ethdebugger({
web3: options.web3,
compilationResult: this.compilationResult
debugWithGeneratedSources: options.debugWithGeneratedSources,
compilationResult: this.compilationResult,
})
const {traceManager, callTree, solidityProxy} = this.debugger
@ -59,8 +60,17 @@ Debugger.prototype.registerAndHighlightCodeItem = async function (index) {
this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then((rawLocation) => {
if (compilationResultForAddress && compilationResultForAddress.data) {
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, compilationResultForAddress.source.sources, compilationResultForAddress.data.sources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation])
const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, compilationResultForAddress.data.sources)
const sources = Object.assign({}, compilationResultForAddress.source.sources)
if (generatedSources) {
for (const genSource of generatedSources) {
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast }
sources[genSource.name] = { content: genSource.contents }
}
}
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources])
} else {
this.event.trigger('newSourceLocation', [null])
}

@ -22,21 +22,24 @@ class InternalCallTree {
* @param {Object} traceManager - trace manager
* @param {Object} solidityProxy - solidity proxy
* @param {Object} codeManager - code manager
* @param {Object} opts - { includeLocalVariables }
* @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources }
*/
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) {
this.includeLocalVariables = opts.includeLocalVariables
this.debugWithGeneratedSources = opts.debugWithGeneratedSources
this.event = new EventManager()
this.solidityProxy = solidityProxy
this.traceManager = traceManager
this.sourceLocationTracker = new SourceLocationTracker(codeManager)
this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources })
debuggerEvent.register('newTraceLoaded', (trace) => {
this.reset()
if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
} else {
// each recursive call to buildTree represent a new context (either call, delegatecall, internal function)
buildTree(this, 0, '', true).then((result) => {
const calledAddress = traceManager.getCurrentCalledAddressAt(0)
const isCreation = traceHelper.isContractCreation(calledAddress)
buildTree(this, 0, '', true, isCreation).then((result) => {
if (result.error) {
this.event.trigger('callTreeBuildFailed', [result.error])
} else {
@ -146,10 +149,10 @@ class InternalCallTree {
}
}
async function buildTree (tree, step, scopeId, isExternalCall) {
async function buildTree (tree, step, scopeId, isExternalCall, isCreation) {
let subScope = 1
tree.scopeStarts[step] = scopeId
tree.scopes[scopeId] = { firstStep: step, locals: {} }
tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation }
function callDepthChange (step, trace) {
if (step + 1 < trace.length) {
@ -167,7 +170,7 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
included.file === source.file)
}
let currentSourceLocation = {start: -1, length: -1, file: -1}
let currentSourceLocation = { start: -1, length: -1, file: -1 }
let previousSourceLocation = currentSourceLocation
while (step < tree.traceManager.trace.length) {
let sourceLocation
@ -186,10 +189,11 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + step }
}
const isCallInstruction = traceHelper.isCallInstruction(tree.traceManager.trace[step])
const isCreateInstruction = traceHelper.isCreateInstruction(tree.traceManager.trace[step])
// we are checking if we are jumping in a new CALL or in an internal function
if (isCallInstruction || sourceLocation.jump === 'i') {
try {
const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstruction)
const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstruction, isCreateInstruction)
if (externalCallResult.error) {
return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error }
} else {
@ -221,8 +225,19 @@ function createReducedTrace (tree, index) {
tree.reducedTrace.push(index)
}
function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) {
const variableDeclaration = resolveVariableDeclaration(tree, step, sourceLocation)
function getGeneratedSources (tree, scopeId, contractObj) {
if (tree.debugWithGeneratedSources && contractObj && tree.scopes[scopeId]) {
return tree.scopes[scopeId].isCreation ? contractObj.contract.evm.bytecode.generatedSources : contractObj.contract.evm.deployedBytecode.generatedSources
}
return null
}
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) {
const contractObj = await tree.solidityProxy.contractObjectAt(step)
let states = null
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
const variableDeclaration = resolveVariableDeclaration(tree, sourceLocation, generatedSources)
// using the vm trace step, the current source location and the ast,
// we check if the current vm trace step target a new ast node of type VariableDeclaration
// that way we know that there is a new local variable from here.
@ -231,60 +246,61 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
const stack = tree.traceManager.getStackAt(step)
// the stack length at this point is where the value of the new local variable will be stored.
// so, either this is the direct value, or the offset in memory. That depends on the type.
tree.solidityProxy.contractNameAt(step).then((contractName) => {
if (variableDeclaration.name !== '') {
var states = tree.solidityProxy.extractStatesDefinitions()
var location = typesUtil.extractLocationFromAstVariable(variableDeclaration)
location = location === 'default' ? 'storage' : location
// we push the new local variable in our tree
tree.scopes[scopeId].locals[variableDeclaration.name] = {
name: variableDeclaration.name,
type: decodeInfo.parseType(variableDeclaration.typeDescriptions.typeString, states, contractName, location),
stackDepth: stack.length,
sourceLocation: sourceLocation
}
if (variableDeclaration.name !== '') {
states = tree.solidityProxy.extractStatesDefinitions()
var location = typesUtil.extractLocationFromAstVariable(variableDeclaration)
location = location === 'default' ? 'storage' : location
// we push the new local variable in our tree
tree.scopes[scopeId].locals[variableDeclaration.name] = {
name: variableDeclaration.name,
type: decodeInfo.parseType(variableDeclaration.typeDescriptions.typeString, states, contractObj.name, location),
stackDepth: stack.length,
sourceLocation: sourceLocation
}
})
}
} catch (error) {
console.log(error)
}
}
// we check here if we are at the beginning inside a new function.
// if that is the case, we have to add to locals tree the inputs and output params
const functionDefinition = resolveFunctionDefinition(tree, step, previousSourceLocation)
if (functionDefinition && (newLocation && traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1]) || functionDefinition.kind === 'constructor')) {
const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources)
if (!functionDefinition) return
const previousIsJumpDest2 = traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 2])
const previousIsJumpDest1 = traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1])
const isConstructor = functionDefinition.kind === 'constructor'
if (newLocation && (previousIsJumpDest1 || previousIsJumpDest2 || isConstructor)) {
tree.functionCallStack.push(step)
const functionDefinitionAndInputs = {functionDefinition, inputs: []}
const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup
tree.solidityProxy.contractNameAt(step).then((contractName) => { // cached
try {
const stack = tree.traceManager.getStackAt(step)
var states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) {
let inputs = functionDefinition.parameters
let outputs = functionDefinition.returnParameters
// for (const element of functionDefinition.parameters) {
// if (element.nodeType === 'ParameterList') {
// if (!inputs) inputs = element
// else {
// outputs = element
// break
// }
// }
// }
// input params
if (inputs) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.parameters.length, -1)
}
// output params
if (outputs) addParams(outputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, 0, 1)
try {
const stack = tree.traceManager.getStackAt(step)
states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) {
let inputs = functionDefinition.parameters
let outputs = functionDefinition.returnParameters
// for (const element of functionDefinition.parameters) {
// if (element.nodeType === 'ParameterList') {
// if (!inputs) inputs = element
// else {
// outputs = element
// break
// }
// }
// }
// input params
if (inputs) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, inputs.parameters.length, -1)
}
} catch (error) {
console.log(error)
// output params
if (outputs) addParams(outputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, 0, 1)
}
})
} catch (error) {
console.log(error)
}
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
}
@ -292,13 +308,12 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
// this extract all the variable declaration for a given ast and file
// and keep this in a cache
function resolveVariableDeclaration (tree, step, sourceLocation) {
function resolveVariableDeclaration (tree, sourceLocation, generatedSources) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation)
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) {
tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(ast, tree.astWalker)
} else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null
}
}
@ -307,13 +322,12 @@ function resolveVariableDeclaration (tree, step, sourceLocation) {
// this extract all the function definition for a given ast and file
// and keep this in a cache
function resolveFunctionDefinition (tree, step, sourceLocation) {
function resolveFunctionDefinition (tree, sourceLocation, generatedSources) {
if (!tree.functionDefinitionByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation)
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) {
tree.functionDefinitionByFile[sourceLocation.file] = extractFunctionDefinitions(ast, tree.astWalker)
} else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null
}
}
@ -323,7 +337,7 @@ function resolveFunctionDefinition (tree, step, sourceLocation) {
function extractVariableDeclarations (ast, astWalker) {
const ret = {}
astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'VariableDeclaration') {
if (node.nodeType === 'VariableDeclaration' || node.nodeType === 'YulVariableDeclaration') {
ret[node.src] = node
}
})
@ -333,7 +347,7 @@ function extractVariableDeclarations (ast, astWalker) {
function extractFunctionDefinitions (ast, astWalker) {
const ret = {}
astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'FunctionDefinition') {
if (node.nodeType === 'FunctionDefinition' || node.nodeType === 'YulFunctionDefinition') {
ret[node.src] = node
}
})

@ -39,15 +39,15 @@ class SolidityProxy {
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
*/
async contractNameAt (vmTraceIndex) {
async contractObjectAt (vmTraceIndex) {
const address = this.getCurrentCalledAddressAt(vmTraceIndex)
if (this.cache.contractNameByAddress[address]) {
return this.cache.contractNameByAddress[address]
if (this.cache.contractObjectByAddress[address]) {
return this.cache.contractObjectByAddress[address]
}
const code = await this.getCode(address)
const contractName = contractNameFromCode(this.contracts, code.bytecode, address)
this.cache.contractNameByAddress[address] = contractName
return contractName
const contract = contractObjectFromCode(this.contracts, code.bytecode, address)
this.cache.contractObjectByAddress[address] = contract
return contract
}
/**
@ -86,8 +86,8 @@ class SolidityProxy {
* @return {Object} - returns state variables of @args vmTraceIndex
*/
async extractStateVariablesAt (vmtraceIndex) {
const contractName = await this.contractNameAt(vmtraceIndex)
return this.extractStateVariables(contractName)
const contract = await this.contractObjectAt(vmtraceIndex)
return this.extractStateVariables(contract.name)
}
/**
@ -96,9 +96,13 @@ class SolidityProxy {
* @param {Object} sourceLocation - source location containing the 'file' to retrieve the AST from
* @return {Object} - AST of the current file
*/
ast (sourceLocation) {
ast (sourceLocation, generatedSources) {
const file = this.fileNameFromIndex(sourceLocation.file)
if (this.sources[file]) {
if (!file && generatedSources && generatedSources.length) {
for (const source of generatedSources) {
if (source.id === sourceLocation.file) return source.ast
}
} else if (this.sources[file]) {
return this.sources[file].ast
}
return null
@ -115,13 +119,13 @@ class SolidityProxy {
}
}
function contractNameFromCode (contracts, code, address) {
function contractObjectFromCode (contracts, code, address) {
const isCreation = traceHelper.isContractCreation(address)
for (let file in contracts) {
for (let contract in contracts[file]) {
const bytecode = isCreation ? contracts[file][contract].evm.bytecode.object : contracts[file][contract].evm.deployedBytecode.object
if (util.compareByteCode(code, '0x' + bytecode)) {
return contract
return { name: contract, contract: contracts[file][contract] }
}
}
}
@ -133,7 +137,7 @@ class Cache {
this.reset()
}
reset () {
this.contractNameByAddress = {}
this.contractObjectByAddress = {}
this.stateVariablesByContractName = {}
this.contractDeclarations = null
this.statesDefinitions = null

@ -0,0 +1,57 @@
'use strict'
/**
* Crawl the given AST through the function walk(ast, callback)
*/
function AstWalker () {} // eslint-disable-line
/**
* visit all the AST nodes
*
* @param {Object} ast - AST node
* @param {Object or Function} callback - if (Function) the function will be called for every node.
* - if (Object) callback[<Node Type>] will be called for
* every node of type <Node Type>. callback["*"] will be called for all other nodes.
* in each case, if the callback returns false it does not descend into children.
* If no callback for the current type, children are visited.
*/
AstWalker.prototype.walk = function (ast, callback) {
if (callback instanceof Function) {
callback = { '*': callback }
}
if (!('*' in callback)) {
callback['*'] = function () { return true }
}
const nodes = ast.nodes || (ast.body && ast.body.statements) || ast.declarations || ast.statements
if (nodes && ast.initializationExpression) { // 'for' loop handling
nodes.push(ast.initializationExpression)
}
if (manageCallBack(ast, callback) && nodes && nodes.length > 0) {
for (let k in nodes) {
const child = nodes[k]
this.walk(child, callback)
}
}
}
/**
* walk the given @astList
*
* @param {Object} sourcesList - sources list (containing root AST node)
* @param {Function} - callback used by AstWalker to compute response
*/
AstWalker.prototype.walkAstList = function (sourcesList, callback) {
const walker = new AstWalker()
for (let k in sourcesList) {
walker.walk(sourcesList[k].ast, callback)
}
}
function manageCallBack (node, callback) {
if (node.nodeType in callback) {
return callback[node.nodeType](node)
} else {
return callback['*'](node)
}
}
module.exports = AstWalker

@ -9,7 +9,10 @@ const util = remixLib.util
/**
* Process the source code location for the current executing bytecode
*/
function SourceLocationTracker (_codeManager) {
function SourceLocationTracker (_codeManager, { debugWithGeneratedSources }) {
this.opts = {
debugWithGeneratedSources: debugWithGeneratedSources || false
}
this.codeManager = _codeManager
this.event = new EventManager()
this.sourceMappingDecoder = new SourceMappingDecoder()
@ -25,7 +28,7 @@ function SourceLocationTracker (_codeManager) {
*/
SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = async function (address, index, contracts) {
const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts)
return this.sourceMappingDecoder.atIndex(index, sourceMap)
return this.sourceMappingDecoder.atIndex(index, sourceMap.map)
}
/**
@ -38,7 +41,19 @@ SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = async fu
SourceLocationTracker.prototype.getSourceLocationFromVMTraceIndex = async function (address, vmtraceStepIndex, contracts) {
const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts)
const index = this.codeManager.getInstructionIndex(address, vmtraceStepIndex)
return this.sourceMappingDecoder.atIndex(index, sourceMap)
return this.sourceMappingDecoder.atIndex(index, sourceMap.map)
}
/**
* Returns the generated sources from a specific @arg address
*
* @param {String} address - contract address from which has generated sources
* @param {Object} generatedSources - Object containing the sourceid, ast and the source code.
*/
SourceLocationTracker.prototype.getGeneratedSourcesFromAddress = function (address) {
if (!this.opts.debugWithGeneratedSources) return null
if (this.sourceMapByAddress[address]) return this.sourceMapByAddress[address].generatedSources
return null
}
/**
@ -73,7 +88,9 @@ function getSourceMap (address, code, contracts) {
bytes = isCreation ? bytecode.object : deployedBytecode.object
if (util.compareByteCode(code, '0x' + bytes)) {
return isCreation ? bytecode.sourceMap : deployedBytecode.sourceMap
const generatedSources = isCreation ? bytecode.generatedSources : deployedBytecode.generatedSources
const map = isCreation ? bytecode.sourceMap : deployedBytecode.sourceMap
return { generatedSources, map }
}
}
}

@ -1,4 +1,5 @@
var tape = require('tape')
var deepequal = require('deep-equal')
var remixLib = require('@remix-project/remix-lib')
var compilerInput = require('./helpers/compilerHelper').compilerInput
var SourceMappingDecoder = require('../src/source/sourceMappingDecoder')
@ -263,7 +264,8 @@ function testDebugging (debugManager) {
const location = await debugManager.sourceLocationFromVMTraceIndex(address, 330)
debugManager.decodeLocalsAt(330, location, (error, decodedlocals) => {
if (error) return t.end(error)
t.equal(JSON.stringify(decodedlocals), JSON.stringify(tested))
t.ok(deepequal(decodedlocals, tested), `locals does not match. expected: ${JSON.stringify(tested)} - current: ${decodedlocals}`)
})
} catch (error) {
return t.end(error)

Loading…
Cancel
Save