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