From a0a571630cfecfb647b4abe03daee38ece0b521a Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 20 Apr 2020 09:48:48 +0200 Subject: [PATCH 1/6] function stack trace --- package.json | 2 +- remix-debug/src/debugger/VmDebugger.js | 2 + .../src/solidity-decoder/internalCallTree.js | 48 +++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 17ebcc6126..d5d0101243 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "diff": "lerna diff", "updated": "lerna updated", - "bootstrap": "npm run build-ts-packages; lerna bootstrap", + "bootstrap": "lerna bootstrap; npm run build-ts-packages", "build-ts-packages": "lerna run --scope 'remix-analyzer' --scope 'remix-astwalker' --scope 'remix-solidity' --scope 'remix-tests' --scope 'remix-url-resolver' build", "publish": "npm run build-ts-packages; lerna publish", "release": "lerna bootstrap; lerna publish;", diff --git a/remix-debug/src/debugger/VmDebugger.js b/remix-debug/src/debugger/VmDebugger.js index c51f8ee16e..8291e600ae 100644 --- a/remix-debug/src/debugger/VmDebugger.js +++ b/remix-debug/src/debugger/VmDebugger.js @@ -58,6 +58,8 @@ class VmDebuggerLogic { this.event.trigger('indexUpdate', [index]) + this.event.trigger('functionsStackUpdate', [this._callTree.retrieveFunctionsStack(index)]) + this._traceManager.getCallDataAt(index, (error, calldata) => { if (error) { // console.log(error) diff --git a/remix-debug/src/solidity-decoder/internalCallTree.js b/remix-debug/src/solidity-decoder/internalCallTree.js index 7da943a0be..8348f4fb19 100644 --- a/remix-debug/src/solidity-decoder/internalCallTree.js +++ b/remix-debug/src/solidity-decoder/internalCallTree.js @@ -66,6 +66,7 @@ class InternalCallTree { */ this.sourceLocationTracker.clearCache() this.functionCallStack = [] + this.functionDefinitionsByScope = {} this.scopeStarts = {} this.variableDeclarationByFile = {} this.functionDefinitionByFile = {} @@ -79,21 +80,43 @@ class InternalCallTree { * @param {Int} vmtraceIndex - index on the vm trace */ findScope (vmtraceIndex) { - const scopes = Object.keys(this.scopeStarts) - if (!scopes.length) { - return null - } - let scopeId = util.findLowerBoundValue(vmtraceIndex, scopes) - scopeId = this.scopeStarts[scopeId] + let scopeId = this.findScopeId(vmtraceIndex) + if (!scopeId) return null let scope = this.scopes[scopeId] while (scope.lastStep && scope.lastStep < vmtraceIndex && scope.firstStep > 0) { - const matched = scopeId.match(/(.\d|\d)$/) - scopeId = scopeId.replace(matched[1], '') + scopeId = this.parentScope(scopeId) scope = this.scopes[scopeId] } return scope } + parentScope (scopeId) { + const matched = scopeId.match(/(.\d|\d)$/) + return scopeId.replace(matched[1], '') + } + + findScopeId (vmtraceIndex) { + const scopes = Object.keys(this.scopeStarts) + if (!scopes.length) return null + const scopeStart = util.findLowerBoundValue(vmtraceIndex, scopes) + return this.scopeStarts[scopeStart] + } + + retrieveFunctionsStack (vmtraceIndex) { + let scope = this.findScope(vmtraceIndex) + if (!scope) return [] + let scopeId = this.scopeStarts[scope.firstStep] + let functions = [] + if (!scopeId) return functions + while (true) { + functions.push(this.functionDefinitionsByScope[scopeId]) + let parent = this.parentScope(scopeId) + if (!parent) break + else scopeId = parent + } + return functions + } + extractSourceLocation (step) { return new Promise((resolve, reject) => { this.traceManager.getCurrentCalledAddressAt(step, (error, address) => { @@ -220,6 +243,7 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc const functionDefinition = resolveFunctionDefinition(tree, step, previousSourceLocation) if (functionDefinition && (newLocation && traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1]) || functionDefinition.attributes.isConstructor)) { tree.functionCallStack.push(step) + 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, (error, contractName) => { // cached @@ -240,7 +264,9 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc } } // input params - if (inputs) addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.children.length, -1) + if (inputs) { + functionDefinitionAndInputs.inputs.push(addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.children.length, -1)) + } // output params if (outputs) addParams(outputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, 0, 1) } @@ -248,6 +274,7 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc }) } }) + tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs } } @@ -304,6 +331,7 @@ function extractFunctionDefinitions (ast, astWalker) { } function addParams (parameterList, tree, scopeId, states, contractName, sourceLocation, stackLength, stackPosition, dir) { + let params = [] for (let inputParam in parameterList.children) { const param = parameterList.children[inputParam] const stackDepth = stackLength + (dir * stackPosition) @@ -317,9 +345,11 @@ function addParams (parameterList, tree, scopeId, states, contractName, sourceLo stackDepth: stackDepth, sourceLocation: sourceLocation } + params.push(attributesName) } stackPosition += dir } + return params } module.exports = InternalCallTree From 18afeb293fc33806bcf67d0d28a251b94fdf0910 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Thu, 7 May 2020 11:48:39 -0400 Subject: [PATCH 2/6] take into account when scopeId is empty string --- remix-debug/src/solidity-decoder/internalCallTree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remix-debug/src/solidity-decoder/internalCallTree.js b/remix-debug/src/solidity-decoder/internalCallTree.js index 8348f4fb19..057552fbfb 100644 --- a/remix-debug/src/solidity-decoder/internalCallTree.js +++ b/remix-debug/src/solidity-decoder/internalCallTree.js @@ -81,7 +81,7 @@ class InternalCallTree { */ findScope (vmtraceIndex) { let scopeId = this.findScopeId(vmtraceIndex) - if (!scopeId) return null + if (scopeId !== '' && !scopeId) return null let scope = this.scopes[scopeId] while (scope.lastStep && scope.lastStep < vmtraceIndex && scope.firstStep > 0) { scopeId = this.parentScope(scopeId) From 9ca1b3bf26c8707a6d2c69daefeda4458e2bf53b Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Sun, 24 May 2020 07:40:49 -0400 Subject: [PATCH 3/6] fix issue where a undefined definition would be added to the callstack --- remix-debug/src/solidity-decoder/internalCallTree.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/remix-debug/src/solidity-decoder/internalCallTree.js b/remix-debug/src/solidity-decoder/internalCallTree.js index 057552fbfb..0447e04e63 100644 --- a/remix-debug/src/solidity-decoder/internalCallTree.js +++ b/remix-debug/src/solidity-decoder/internalCallTree.js @@ -109,7 +109,10 @@ class InternalCallTree { let functions = [] if (!scopeId) return functions while (true) { - functions.push(this.functionDefinitionsByScope[scopeId]) + let functionDefinition = this.functionDefinitionsByScope[scopeId] + if (functionDefinition !== undefined) { + functions.push(functionDefinition) + } let parent = this.parentScope(scopeId) if (!parent) break else scopeId = parent From 6fd4555b1184ddcc2e074135c59bcc8ac5c3987f Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Sun, 24 May 2020 07:41:25 -0400 Subject: [PATCH 4/6] add tests for the calltree --- remix-debug/test/decoder/localsTests/int.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/remix-debug/test/decoder/localsTests/int.js b/remix-debug/test/decoder/localsTests/int.js index 766d46414d..fbc85ed048 100644 --- a/remix-debug/test/decoder/localsTests/int.js +++ b/remix-debug/test/decoder/localsTests/int.js @@ -37,7 +37,25 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu }) callTree.event.register('callTreeReady', (scopes, scopeStarts) => { try { - console.log(scopeStarts) + let functions1 = callTree.retrieveFunctionsStack(101) + let functions2 = callTree.retrieveFunctionsStack(113) + let functions3 = callTree.retrieveFunctionsStack(13) + + st.equals(functions1.length, 1) + st.equals(functions2.length, 2) + st.equals(functions3.length, 0) + + st.equals(Object.keys(functions1[0])[0], 'functionDefinition') + st.equals(Object.keys(functions1[0])[1], 'inputs') + st.equals(Object.keys(functions2[0])[0], 'functionDefinition') + st.equals(Object.keys(functions2[0])[1], 'inputs') + st.equals(Object.keys(functions2[1])[0], 'functionDefinition') + st.equals(Object.keys(functions2[1])[1], 'inputs') + + st.equals(functions1[0].functionDefinition.attributes.name, 'level11') + st.equals(functions2[0].functionDefinition.attributes.name, 'level12') + st.equals(functions2[1].functionDefinition.attributes.name, 'level11') + st.equals(scopeStarts[0], '') st.equals(scopeStarts[13], '1') st.equals(scopeStarts[101], '2') From dec525b6d610e0a239513f394d5b4e462e419853 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Tue, 23 Jun 2020 09:31:43 -0400 Subject: [PATCH 5/6] address code review: guard against infinite recursion; fix array inside array --- remix-debug/src/solidity-decoder/internalCallTree.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/remix-debug/src/solidity-decoder/internalCallTree.js b/remix-debug/src/solidity-decoder/internalCallTree.js index 0447e04e63..f01ae418e1 100644 --- a/remix-debug/src/solidity-decoder/internalCallTree.js +++ b/remix-debug/src/solidity-decoder/internalCallTree.js @@ -108,7 +108,10 @@ class InternalCallTree { let scopeId = this.scopeStarts[scope.firstStep] let functions = [] if (!scopeId) return functions + let i = 0 while (true) { + i += 1 + if (i > 1000) throw new Error('retrieFunctionStack: recursion too deep') let functionDefinition = this.functionDefinitionsByScope[scopeId] if (functionDefinition !== undefined) { functions.push(functionDefinition) @@ -268,7 +271,7 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc } // input params if (inputs) { - functionDefinitionAndInputs.inputs.push(addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.children.length, -1)) + functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.children.length, -1) } // output params if (outputs) addParams(outputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, 0, 1) From d2a8380f2284bf59a6df88cb674dd973b08f4d8b Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Tue, 23 Jun 2020 11:26:00 -0400 Subject: [PATCH 6/6] add tests for inputs --- .../test/decoder/contracts/intLocal.js | 16 +++++++------- remix-debug/test/decoder/localsTests/int.js | 21 +++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/remix-debug/test/decoder/contracts/intLocal.js b/remix-debug/test/decoder/contracts/intLocal.js index 737bac6e49..0b4a112808 100644 --- a/remix-debug/test/decoder/contracts/intLocal.js +++ b/remix-debug/test/decoder/contracts/intLocal.js @@ -26,17 +26,17 @@ contract intLocal { int256 i256 = 3434343; int i = -32432423423; int32 ishrink = 2; - level11(); - level12(); - level11(); + level11(123); + level12(12); + level11(123); } - function level11() public { - uint8 ui8 = 123; - level12(); + function level11(uint8 foo) public { + uint8 ui8 = foo; + level12(12); } - function level12() public { - uint8 ui81 = 12; + function level12(uint8 asd) public { + uint8 ui81 = asd; } } `} diff --git a/remix-debug/test/decoder/localsTests/int.js b/remix-debug/test/decoder/localsTests/int.js index fbc85ed048..b051c13d7a 100644 --- a/remix-debug/test/decoder/localsTests/int.js +++ b/remix-debug/test/decoder/localsTests/int.js @@ -37,8 +37,8 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu }) callTree.event.register('callTreeReady', (scopes, scopeStarts) => { try { - let functions1 = callTree.retrieveFunctionsStack(101) - let functions2 = callTree.retrieveFunctionsStack(113) + let functions1 = callTree.retrieveFunctionsStack(102) + let functions2 = callTree.retrieveFunctionsStack(115) let functions3 = callTree.retrieveFunctionsStack(13) st.equals(functions1.length, 1) @@ -47,10 +47,13 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[1], 'inputs') + st.equals(functions1[0].inputs[0], 'foo') st.equals(Object.keys(functions2[0])[0], 'functionDefinition') st.equals(Object.keys(functions2[0])[1], 'inputs') st.equals(Object.keys(functions2[1])[0], 'functionDefinition') st.equals(Object.keys(functions2[1])[1], 'inputs') + st.equals(functions2[0].inputs[0], 'asd') + st.equals(functions2[1].inputs[0], 'foo') st.equals(functions1[0].functionDefinition.attributes.name, 'level11') st.equals(functions2[0].functionDefinition.attributes.name, 'level12') @@ -58,11 +61,11 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu st.equals(scopeStarts[0], '') st.equals(scopeStarts[13], '1') - st.equals(scopeStarts[101], '2') - st.equals(scopeStarts[113], '2.1') - st.equals(scopeStarts[131], '3') - st.equals(scopeStarts[146], '4') - st.equals(scopeStarts[158], '4.1') + st.equals(scopeStarts[102], '2') + st.equals(scopeStarts[115], '2.1') + st.equals(scopeStarts[136], '3') + st.equals(scopeStarts[153], '4') + st.equals(scopeStarts[166], '4.1') st.equals(scopes[''].locals['ui8'].type.typeName, 'uint8') st.equals(scopes[''].locals['ui16'].type.typeName, 'uint16') st.equals(scopes[''].locals['ui32'].type.typeName, 'uint32') @@ -106,10 +109,10 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu st.equals(locals['ishrink'].value, '2') }) - helper.decodeLocals(st, 105, traceManager, callTree, function (locals) { + helper.decodeLocals(st, 106, traceManager, callTree, function (locals) { try { st.equals(locals['ui8'].value, '123') - st.equals(Object.keys(locals).length, 1) + st.equals(Object.keys(locals).length, 2) } catch (e) { st.fail(e.message) }