Skip to content

Commit 5ad659e

Browse files
author
ci-bot
committed
fix decoding ctor
1 parent 06a9f99 commit 5ad659e

File tree

3 files changed

+140
-24
lines changed

3 files changed

+140
-24
lines changed

libs/remix-debug/src/solidity-decoder/helpers/callTreeHelper.ts

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,14 @@ export function getGeneratedSources (tree, scopeId, contractObj) {
115115
*
116116
* @param {InternalCallTree} tree - The call tree instance
117117
* @param {Object} functionDefinition - AST function definition node
118+
* @param {Object} contractDefinition - AST function definition node
118119
* @param {number} step - VM trace step index at function entry
119120
* @param {string} scopeId - Scope identifier for this function
120121
* @param {Object} contractObj - Contract object with ABI
121122
* @param {Object} sourceLocation - Source location of the function
122123
* @param {string} address - Contract address
123124
*/
124-
export async function registerFunctionParameters (tree, functionDefinition, step, scopeId, contractObj, sourceLocation, address) {
125+
export async function registerFunctionParameters (tree, functionDefinition, contractDefinition, step, scopeId, contractObj, sourceLocation, address) {
125126
if (!sourceLocation) return
126127
if (sourceLocation.jump !== 'i') return
127128
tree.functionCallStack.push(step)
@@ -147,6 +148,11 @@ export async function registerFunctionParameters (tree, functionDefinition, step
147148
functionDefinitionAndInputs.inputs = addInputParams(step, functionDefinition, inputs, tree, scopeId, states, contractObj, sourceLocation, stack.length)
148149
}
149150

151+
// For constructors, also register inherited constructor parameters
152+
if (functionDefinition.kind === 'constructor') {
153+
await registerInheritedConstructorParams(step, functionDefinition, contractDefinition, tree, scopeId, states, contractObj, sourceLocation, address, stack.length)
154+
}
155+
150156
// return params - register them but they're not yet on the stack
151157
if (outputs && outputs.parameters && outputs.parameters.length > 0) {
152158
addReturnParams(step + 1, functionDefinition, outputs, tree, scopeId, states, contractObj, sourceLocation)
@@ -164,6 +170,120 @@ export async function registerFunctionParameters (tree, functionDefinition, step
164170
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
165171
}
166172

173+
/**
174+
* Registers inherited constructor parameters for a constructor function.
175+
* The layout follows: main constructor params start at stack index 0, then inherited constructors
176+
* in reverse order (last inherited constructor first, then the one at length-2, etc.)
177+
*
178+
* @param {number} step - current step
179+
* @param {Object} functionDefinition - Constructor function definition
180+
* @param {InternalCallTree} tree - The call tree instance
181+
* @param {string} scopeId - Current scope identifier
182+
* @param {Object} states - State variable definitions
183+
* @param {Object} contractObj - Contract object with name and ABI
184+
* @param {Object} sourceLocation - Source location of the constructor
185+
* @param {string} address - Contract address
186+
* @param {number} stackLength - Current stack depth at constructor entry
187+
*/
188+
export async function registerInheritedConstructorParams(step, functionDefinition, contractDefinition, tree: InternalCallTree, scopeId, states, contractObj, sourceLocation, address, stackLength) {
189+
try {
190+
if (!contractDefinition) {
191+
if (tree.debug) console.log(`[registerInheritedConstructorParams] No contract definition or linearized base contracts found`)
192+
return
193+
}
194+
195+
// Get linearized base contracts (excluding the current contract which is first)
196+
const baseContracts = await tree.solidityProxy.getLinearizedBaseContracts(address, contractDefinition.id)
197+
198+
// Find constructors in inherited contracts
199+
const inheritedConstructors = []
200+
for (const baseContract of baseContracts) {
201+
if (baseContract.nodes) {
202+
const constructor = baseContract.nodes.find(node =>
203+
node.nodeType === 'FunctionDefinition' && node.kind === 'constructor'
204+
)
205+
if (constructor && constructor.parameters && constructor.parameters.parameters && constructor.parameters.parameters.length > 0) {
206+
inheritedConstructors.push(constructor)
207+
}
208+
}
209+
}
210+
211+
if (inheritedConstructors.length === 0) {
212+
if (tree.debug) console.log(`[registerInheritedConstructorParams] No inherited constructors with parameters found`)
213+
return
214+
}
215+
216+
if (tree.debug) {
217+
console.log(`[registerInheritedConstructorParams] Found ${inheritedConstructors.length} inherited constructors with parameters`)
218+
}
219+
220+
// Calculate starting stack index for inherited constructor parameters
221+
// Main constructor params start at index 0, inherited constructor params follow
222+
const mainConstructorParamCount = functionDefinition.parameters?.parameters?.length || 0
223+
let currentStackIndex = mainConstructorParamCount
224+
225+
// Process inherited constructors in reverse order (last inherited first)
226+
for (let i = 0; i < inheritedConstructors.length; i++) {
227+
const inheritedConstructor = inheritedConstructors[i]
228+
const parameterList = inheritedConstructor.parameters
229+
const paramCount = parameterList.parameters.length
230+
231+
if (tree.debug) {
232+
console.log(`[registerInheritedConstructorParams] Processing inherited constructor from contract ${contractsById[inheritedConstructor.parent]?.name || 'unknown'}`)
233+
console.log(` - Parameter count: ${paramCount}`)
234+
console.log(` - Starting stack index: ${currentStackIndex}`)
235+
}
236+
237+
// Register parameters for this inherited constructor
238+
for (let j = 0; j < paramCount; j++) {
239+
const param = parameterList.parameters[j]
240+
const stackIndex = currentStackIndex + j
241+
242+
// Ensure stack index is valid
243+
if (stackIndex < 0 || stackIndex >= stackLength) {
244+
if (tree.debug) console.warn(`[registerInheritedConstructorParams] Invalid stack index ${stackIndex} for inherited parameter ${param.name}`)
245+
continue
246+
}
247+
248+
let location = extractLocationFromAstVariable(param)
249+
location = location === 'default' ? 'memory' : location
250+
const attributesName = param.name === '' ? `$inherited_${i}_${j}` : `inherited_${param.name}`
251+
252+
const newParam = {
253+
name: attributesName,
254+
type: parseType(param.typeDescriptions.typeString, states, contractObj.name, location),
255+
stackIndex: stackIndex,
256+
sourceLocation: sourceLocation,
257+
abi: contractObj.contract.abi,
258+
isParameter: true,
259+
isReturnParameter: false,
260+
isInheritedConstructorParam: true,
261+
declarationStep: step,
262+
safeToDecodeAtStep: step,
263+
scope: inheritedConstructor.body?.id,
264+
id: param.id,
265+
inheritedFrom: contractsById[inheritedConstructor.parent]?.name || 'unknown'
266+
}
267+
268+
tree.scopes[scopeId].locals[attributesName] = newParam
269+
if (!tree.variables[param.id]) tree.variables[param.id] = newParam
270+
271+
// Bind parameter to symbolic stack with lifecycle tracking
272+
tree.symbolicStackManager.bindVariableWithLifecycle(step + 1, newParam, stackIndex, 'assigned', scopeId)
273+
274+
if (tree.debug) {
275+
console.log(`[registerInheritedConstructorParams] Bound inherited parameter: ${attributesName} at stack index ${stackIndex} from ${newParam.inheritedFrom}`)
276+
}
277+
}
278+
279+
currentStackIndex += paramCount
280+
}
281+
282+
} catch (error) {
283+
console.error('Error in registerInheritedConstructorParams:', error)
284+
}
285+
}
286+
167287
/**
168288
* Includes variable declarations in the current scope if a new local variable is encountered at this step.
169289
* Checks the AST for variable declarations at the current source location and adds them to scope locals.
@@ -675,6 +795,7 @@ export function debugVariableTracking(tree: InternalCallTree, step: number, scop
675795
export async function resolveNodesAtSourceLocation (tree, sourceLocation, generatedSources, address) {
676796
const ast = await tree.solidityProxy.ast(sourceLocation, generatedSources, address)
677797
let funcDef
798+
let contractDef
678799
const blocksDef = []
679800
if (ast) {
680801
const nodes = nodesAtPosition(null, sourceLocation.start, { ast })
@@ -690,12 +811,15 @@ export async function resolveNodesAtSourceLocation (tree, sourceLocation, genera
690811
funcDef = node
691812
blocksDef.push(node)
692813
}
814+
if (node && node.nodeType === 'ContractDefinition') {
815+
contractDef = node
816+
}
693817
}
694818
}
695819

696-
return { nodes, functionDefinition: funcDef, blocksDefinition: blocksDef }
820+
return { nodes, functionDefinition: funcDef, contractDefinition: contractDef, blocksDefinition: blocksDef }
697821
} else {
698-
return { nodes: [], functionDefinition: null, blocksDefinition: []}
822+
return { nodes: [], functionDefinition: null, contractDefinition: null, blocksDefinition: []}
699823
}
700824
}
701825

libs/remix-debug/src/solidity-decoder/internalCallTree.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,6 @@ export class InternalCallTree {
203203
gasCostPerLine
204204
/** Converter for transforming source offsets to line/column positions */
205205
offsetToLineColumnConverter
206-
/** VM trace index where pending constructor execution is expected to start */
207-
pendingConstructorExecutionAt: number
208-
/** AST node ID of the pending constructor */
209-
pendingConstructorId: number
210-
/** Pending constructor function definition waiting for execution */
211-
pendingConstructor
212-
/** Pending constructor entry stack index */
213-
pendingConstructorEntryStackIndex
214-
/** Map tracking which constructors have started execution and at what source location offset */
215-
constructorsStartExecution
216206
/** Map of variable IDs to their metadata (name, type, stackIndex, sourceLocation, declarationStep, safeToDecodeAtStep) */
217207
variables: {
218208
[Key: number]: any
@@ -241,7 +231,7 @@ export class InternalCallTree {
241231
* @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources }
242232
*/
243233
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts, offsetToLineColumnConverter?) {
244-
this.debug = opts.debug || false
234+
this.debug = opts.debug || true
245235
this.getCache = opts.getCache
246236
this.includeLocalVariables = opts.includeLocalVariables
247237
this.debugWithGeneratedSources = opts.debugWithGeneratedSources
@@ -312,11 +302,6 @@ export class InternalCallTree {
312302
this.astWalker = new AstWalker()
313303
this.reducedTrace = []
314304
this.locationAndOpcodePerVMTraceIndex = {}
315-
this.pendingConstructorExecutionAt = -1
316-
this.pendingConstructorId = -1
317-
this.constructorsStartExecution = {}
318-
this.pendingConstructorEntryStackIndex = -1
319-
this.pendingConstructor = null
320305
this.variables = {}
321306
this.symbolicStackManager.reset()
322307
this.mergedScope = {}
@@ -766,12 +751,12 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
766751
// check if there is a function at destination - but only for AST node resolution
767752
const contractObj = await tree.solidityProxy.contractObjectAtAddress(address)
768753
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
769-
const { nodes, blocksDefinition, functionDefinition } = await resolveNodesAtSourceLocation(tree, sourceLocation, generatedSources, address)
754+
const { nodes, blocksDefinition, functionDefinition, contractDefinition } = await resolveNodesAtSourceLocation(tree, validSourceLocation || sourceLocation, generatedSources, address)
770755

771756
if (!tree.scopes[scopeId].functionDefinition && stepDetail.op === 'JUMPDEST' && functionDefinition && (tree.scopes[scopeId].firstStep === step - 1 || tree.scopes[scopeId].firstStep === step - 2)) {
772757
tree.scopes[scopeId].functionDefinition = functionDefinition
773758
tree.scopes[scopeId].lowLevelScope = false
774-
await registerFunctionParameters(tree, functionDefinition, step - 1, scopeId, contractObj, previousSourceLocation, address)
759+
await registerFunctionParameters(tree, functionDefinition, contractDefinition, step - 1, scopeId, contractObj, previousSourceLocation, address)
775760
}
776761

777762
// if the first step of the execution leads to invalid source (generated code), we consider it a low level scope.
@@ -829,7 +814,7 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
829814
tree.scopes[newScopeId].functionDefinition = functionDefinition
830815
tree.scopes[newScopeId].lowLevelScope = false
831816
// Register function parameters when entering new function scope (internal calls or external calls)
832-
await registerFunctionParameters(tree, functionDefinition, step, newScopeId, contractObj, sourceLocation, address)
817+
await registerFunctionParameters(tree, functionDefinition, contractDefinition, step, newScopeId, contractObj, sourceLocation, address)
833818
}
834819
let externalCallResult
835820
try {
@@ -857,7 +842,7 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
857842
console.error(e)
858843
return { outStep: step, error: 'InternalCallTree - ' + e.message }
859844
}
860-
} else if (callDepthChange(step, tree.traceManager.trace) || isStopInstruction(stepDetail) || isReturnInstruction(stepDetail) || (isJumpOutOfFunction && !originIsCall) || isRevert || isConstructorExit(tree, step, scopeId, tree.pendingConstructorEntryStackIndex, stepDetail, isConstructor)) {
845+
} else if (callDepthChange(step, tree.traceManager.trace) || isStopInstruction(stepDetail) || isReturnInstruction(stepDetail) || (isJumpOutOfFunction && !originIsCall) || isRevert) {
861846
const popCount = countConsecutivePopOpcodes(tree.traceManager.trace, step)
862847
// if not, we might be returning from a CALL or internal function. This is what is checked here.
863848
// For constructors in inheritance chains, we also check if stack depth has returned to entry level

libs/remix-debug/src/solidity-decoder/solidityProxy.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { util } from '@remix-project/remix-lib'
33
import { isContractCreation } from '../trace/traceHelper'
44
import { extractStateVariables } from './stateDecoder'
5-
import { extractContractDefinitions, extractStatesDefinitions } from './helpers/astHelper'
5+
import { extractContractDefinitions, extractStatesDefinitions, getLinearizedBaseContracts } from './helpers/astHelper'
66
import type { CompilerAbstract } from '@remix-project/remix-solidity'
77

88
export class SolidityProxy {
@@ -78,6 +78,13 @@ export class SolidityProxy {
7878
return this.cache.statesDefinitions[address]
7979
}
8080

81+
async getLinearizedBaseContracts(address: string, id: number) {
82+
const compilationResult = await this.compilationResult(address)
83+
if (!compilationResult || !compilationResult.data) return null
84+
this.extractStatesDefinitions(address)
85+
return getLinearizedBaseContracts(id, this.cache.contractDeclarations[address].contractById)
86+
}
87+
8188
/**
8289
* extract the state variables of the given compiled @arg contractName (cached)
8390
*

0 commit comments

Comments
 (0)