recon-generate 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/info.js ADDED
@@ -0,0 +1,1017 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runInfo = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs/promises"));
39
+ const path = __importStar(require("path"));
40
+ const solc_typed_ast_1 = require("solc-typed-ast");
41
+ const utils_1 = require("./utils");
42
+ const z3Solver_1 = require("./z3Solver");
43
+ const call_tree_builder_1 = require("./analyzer/call-tree-builder");
44
+ const runCmd = (cmd, cwd) => {
45
+ return new Promise((resolve, reject) => {
46
+ (0, child_process_1.exec)(cmd, { cwd, env: { ...process.env, PATH: (0, utils_1.getEnvPath)() } }, (err, _stdout, stderr) => {
47
+ if (err) {
48
+ reject(new Error(stderr || err.message));
49
+ }
50
+ else {
51
+ resolve();
52
+ }
53
+ });
54
+ });
55
+ };
56
+ const loadLatestBuildInfo = async (foundryRoot) => {
57
+ var _a;
58
+ const outDir = path.join(foundryRoot, 'out');
59
+ const buildInfoDir = path.join(outDir, 'build-info');
60
+ let files = [];
61
+ try {
62
+ const entries = await fs.readdir(buildInfoDir);
63
+ const jsonFiles = entries.filter((f) => f.endsWith('.json'));
64
+ files = await Promise.all(jsonFiles.map(async (f) => ({
65
+ name: f,
66
+ path: path.join(buildInfoDir, f),
67
+ mtime: (await fs.stat(path.join(buildInfoDir, f))).mtime,
68
+ })));
69
+ }
70
+ catch (e) {
71
+ throw new Error(`No build-info directory found at ${buildInfoDir}: ${e}`);
72
+ }
73
+ if (files.length === 0) {
74
+ throw new Error(`No build-info JSON files found in ${buildInfoDir}.`);
75
+ }
76
+ const latestFile = files.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())[0].path;
77
+ const fileContent = await fs.readFile(latestFile, 'utf-8');
78
+ const buildInfo = JSON.parse(fileContent);
79
+ const buildOutput = (_a = buildInfo.output) !== null && _a !== void 0 ? _a : buildInfo;
80
+ if (!buildOutput) {
81
+ throw new Error(`Build-info file ${latestFile} is missing output data.`);
82
+ }
83
+ // Extract ABIs from contracts
84
+ const contractAbis = new Map();
85
+ if (buildOutput.contracts) {
86
+ for (const [_filePath, contracts] of Object.entries(buildOutput.contracts)) {
87
+ for (const [contractName, contractData] of Object.entries(contracts)) {
88
+ if (contractData.abi) {
89
+ contractAbis.set(contractName, contractData.abi);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ const filteredAstData = { ...buildOutput };
95
+ if (filteredAstData.sources) {
96
+ const validSources = {};
97
+ for (const [key, content] of Object.entries(filteredAstData.sources)) {
98
+ const ast = content.ast || content.legacyAST || content.AST;
99
+ if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
100
+ validSources[key] = content;
101
+ }
102
+ }
103
+ filteredAstData.sources = validSources;
104
+ }
105
+ const reader = new solc_typed_ast_1.ASTReader();
106
+ const sourceUnits = reader.read(filteredAstData);
107
+ return { sourceUnits, contractAbis };
108
+ };
109
+ const stripDataLocation = (raw) => {
110
+ if (!raw)
111
+ return '';
112
+ const noLocation = raw
113
+ .replace(/\s+(storage|memory|calldata)(\s+pointer)?/g, '')
114
+ .replace(/\s+pointer/g, '');
115
+ const noPrefix = noLocation
116
+ .replace(/^struct\s+/, '')
117
+ .replace(/^enum\s+/, '')
118
+ .replace(/^contract\s+/, '')
119
+ .trim();
120
+ return noPrefix;
121
+ };
122
+ const signatureFromFnDef = (fnDef) => {
123
+ const params = fnDef.vParameters.vParameters
124
+ .map((p) => { var _a; return stripDataLocation((_a = p.typeString) !== null && _a !== void 0 ? _a : ''); })
125
+ .join(',');
126
+ const name = fnDef.name
127
+ || (fnDef.kind === solc_typed_ast_1.FunctionKind.Constructor
128
+ ? 'constructor'
129
+ : fnDef.kind === solc_typed_ast_1.FunctionKind.Fallback
130
+ ? 'fallback'
131
+ : fnDef.kind === solc_typed_ast_1.FunctionKind.Receive
132
+ ? 'receive'
133
+ : '');
134
+ return `${name}(${params})`;
135
+ };
136
+ /**
137
+ * Convert typeString to actual Solidity type
138
+ * e.g. "int_const 42" -> "uint256", "bool" -> "bool", etc.
139
+ */
140
+ const typeStringToSolidityType = (typeString, value) => {
141
+ if (!typeString)
142
+ return 'unknown';
143
+ // Handle int_const and rational_const
144
+ if (typeString.startsWith('int_const')) {
145
+ // Check if the value is negative
146
+ if (value && value.startsWith('-')) {
147
+ return 'int256';
148
+ }
149
+ return 'uint256';
150
+ }
151
+ if (typeString.startsWith('rational_const')) {
152
+ return 'uint256';
153
+ }
154
+ // Handle literal_string
155
+ if (typeString.startsWith('literal_string')) {
156
+ return 'string';
157
+ }
158
+ // Handle basic types
159
+ const basicTypes = [
160
+ 'bool', 'address', 'bytes', 'string',
161
+ 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
162
+ 'int8', 'int16', 'int32', 'int64', 'int128', 'int256',
163
+ 'bytes1', 'bytes2', 'bytes4', 'bytes8', 'bytes16', 'bytes32',
164
+ ];
165
+ for (const t of basicTypes) {
166
+ if (typeString === t || typeString.startsWith(t + ' ')) {
167
+ return t;
168
+ }
169
+ }
170
+ // Default fallback
171
+ return typeString;
172
+ };
173
+ /**
174
+ * Convert literal value for display
175
+ */
176
+ const formatLiteralValue = (lit) => {
177
+ var _a;
178
+ if (lit.kind === 'bool') {
179
+ return lit.value === 'true' ? 'True' : 'False';
180
+ }
181
+ return (_a = lit.value) !== null && _a !== void 0 ? _a : '';
182
+ };
183
+ const getAllContracts = (sourceUnits) => {
184
+ const contracts = new Map();
185
+ for (const unit of sourceUnits) {
186
+ for (const contract of unit.getChildrenByType(solc_typed_ast_1.ContractDefinition)) {
187
+ contracts.set(contract.name, contract);
188
+ }
189
+ }
190
+ return contracts;
191
+ };
192
+ /**
193
+ * Recursively collect all contracts related to the target contract:
194
+ * - Base contracts (inheritance chain)
195
+ * - Contracts referenced in state variables
196
+ * - Contracts called in functions
197
+ */
198
+ const collectRelatedContracts = (targetContract, allContracts) => {
199
+ const visited = new Set();
200
+ const queue = [targetContract];
201
+ while (queue.length > 0) {
202
+ const contract = queue.shift();
203
+ if (visited.has(contract))
204
+ continue;
205
+ visited.add(contract);
206
+ // 1. Collect from linearized base contracts (inheritance)
207
+ for (const baseContract of contract.vLinearizedBaseContracts) {
208
+ if (!visited.has(baseContract)) {
209
+ queue.push(baseContract);
210
+ }
211
+ }
212
+ // 2. Collect from state variable types (contract references)
213
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
214
+ for (const stateVar of stateVars) {
215
+ // Check if the type references a contract
216
+ const typeNode = stateVar.vType;
217
+ if (typeNode instanceof solc_typed_ast_1.UserDefinedTypeName) {
218
+ const ref = typeNode.vReferencedDeclaration;
219
+ if (ref instanceof solc_typed_ast_1.ContractDefinition && !visited.has(ref)) {
220
+ queue.push(ref);
221
+ }
222
+ }
223
+ // Also check typeString for contract types
224
+ const typeString = stateVar.typeString;
225
+ if (typeString === null || typeString === void 0 ? void 0 : typeString.startsWith('contract ')) {
226
+ const contractName = typeString.replace('contract ', '');
227
+ const refContract = allContracts.get(contractName);
228
+ if (refContract && !visited.has(refContract)) {
229
+ queue.push(refContract);
230
+ }
231
+ }
232
+ }
233
+ // 3. Collect from function calls to other contracts
234
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
235
+ for (const fnDef of allFunctions) {
236
+ for (const call of fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
237
+ // Check if call target is on another contract
238
+ const expr = call.vExpression;
239
+ if (expr instanceof solc_typed_ast_1.MemberAccess) {
240
+ const baseExpr = expr.vExpression;
241
+ // Check typeString for contract type
242
+ const typeString = baseExpr.typeString;
243
+ if (typeString === null || typeString === void 0 ? void 0 : typeString.startsWith('contract ')) {
244
+ const contractName = typeString.replace('contract ', '');
245
+ const refContract = allContracts.get(contractName);
246
+ if (refContract && !visited.has(refContract)) {
247
+ queue.push(refContract);
248
+ }
249
+ }
250
+ // Also check the referenced declaration
251
+ const ref = expr.vReferencedDeclaration;
252
+ if (ref instanceof solc_typed_ast_1.FunctionDefinition) {
253
+ const parentContract = ref.vScope;
254
+ if (parentContract instanceof solc_typed_ast_1.ContractDefinition && !visited.has(parentContract)) {
255
+ queue.push(parentContract);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ return visited;
263
+ };
264
+ /**
265
+ * Recursively collect all inherited contracts for a given contract
266
+ * Uses a visited set to avoid infinite loops
267
+ */
268
+ const collectAllInheritances = (contract, visited = new Set()) => {
269
+ const inheritedNames = [];
270
+ // Get direct base contracts
271
+ const directBases = contract.vLinearizedBaseContracts.filter(base => base.id !== contract.id);
272
+ for (const base of directBases) {
273
+ // Skip if already visited (avoid infinite loops)
274
+ if (visited.has(base.id))
275
+ continue;
276
+ visited.add(base.id);
277
+ // Add this base contract
278
+ if (!inheritedNames.includes(base.name)) {
279
+ inheritedNames.push(base.name);
280
+ }
281
+ // Recursively collect inheritances from this base
282
+ const baseInheritances = collectAllInheritances(base, visited);
283
+ for (const name of baseInheritances) {
284
+ if (!inheritedNames.includes(name)) {
285
+ inheritedNames.push(name);
286
+ }
287
+ }
288
+ }
289
+ return inheritedNames;
290
+ };
291
+ const collectPayableFunctions = (contract) => {
292
+ const payable = [];
293
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
294
+ for (const fnDef of allFunctions) {
295
+ if (fnDef.stateMutability === solc_typed_ast_1.FunctionStateMutability.Payable) {
296
+ if (fnDef.kind === solc_typed_ast_1.FunctionKind.Receive) {
297
+ payable.push('()');
298
+ }
299
+ else if (fnDef.kind === solc_typed_ast_1.FunctionKind.Fallback) {
300
+ payable.push('fallback()');
301
+ }
302
+ else {
303
+ payable.push(signatureFromFnDef(fnDef));
304
+ }
305
+ }
306
+ }
307
+ return payable;
308
+ };
309
+ /**
310
+ * Collect constant functions from ABI (view/pure functions)
311
+ * This is more reliable than AST for complex types
312
+ */
313
+ const collectConstantFunctionsFromAbi = (contractName, contractAbis) => {
314
+ const abi = contractAbis.get(contractName);
315
+ if (!abi)
316
+ return [];
317
+ const constants = [];
318
+ for (const entry of abi) {
319
+ if (entry.type === 'function' &&
320
+ (entry.stateMutability === 'view' || entry.stateMutability === 'pure')) {
321
+ const params = (entry.inputs || []).map(inp => inp.type).join(',');
322
+ const sig = `${entry.name}(${params})`;
323
+ constants.push(sig);
324
+ }
325
+ }
326
+ return constants;
327
+ };
328
+ /**
329
+ * Collect constant functions including:
330
+ * - view/pure functions
331
+ * - public state variable getters
332
+ */
333
+ const collectConstantFunctions = (contract) => {
334
+ const constants = [];
335
+ const seen = new Set();
336
+ // 1. Collect view/pure functions
337
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
338
+ for (const fnDef of allFunctions) {
339
+ if (fnDef.visibility === solc_typed_ast_1.FunctionVisibility.External || fnDef.visibility === solc_typed_ast_1.FunctionVisibility.Public) {
340
+ const sig = signatureFromFnDef(fnDef);
341
+ if (!seen.has(sig)) {
342
+ seen.add(sig);
343
+ constants.push(sig);
344
+ }
345
+ }
346
+ }
347
+ // 2. Collect public state variable auto-generated getters
348
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
349
+ for (const stateVar of stateVars) {
350
+ if (stateVar.visibility === 'public') {
351
+ // Generate getter signature based on type
352
+ const sig = generateGetterSignature(stateVar);
353
+ if (sig && !seen.has(sig)) {
354
+ seen.add(sig);
355
+ constants.push(sig);
356
+ }
357
+ }
358
+ }
359
+ return constants;
360
+ };
361
+ /**
362
+ * Generate getter function signature for public state variables
363
+ */
364
+ const generateGetterSignature = (stateVar) => {
365
+ const name = stateVar.name;
366
+ const typeString = stateVar.typeString;
367
+ if (!typeString)
368
+ return null;
369
+ // Handle mappings - generate signature with key type
370
+ if (typeString.startsWith('mapping(')) {
371
+ const keyType = extractMappingKeyType(typeString);
372
+ if (keyType) {
373
+ return `${name}(${keyType})`;
374
+ }
375
+ return null;
376
+ }
377
+ // Handle arrays - generate signature with uint256 index
378
+ if (typeString.endsWith('[]') || typeString.match(/\[\d+\]$/)) {
379
+ return `${name}(uint256)`;
380
+ }
381
+ // Simple variables - no parameters
382
+ return `${name}()`;
383
+ };
384
+ /**
385
+ * Extract the key type from a mapping type string
386
+ */
387
+ const extractMappingKeyType = (typeString) => {
388
+ // mapping(address => uint256) -> address
389
+ // mapping(uint256 => mapping(address => bool)) -> uint256
390
+ const match = typeString.match(/^mapping\((\w+)\s*=>/);
391
+ if (match) {
392
+ return match[1];
393
+ }
394
+ return null;
395
+ };
396
+ /**
397
+ * Collect assert statements with source location info
398
+ */
399
+ const collectAssertStatements = (contract, foundryRoot, sourceContents) => {
400
+ const asserts = {};
401
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
402
+ for (const fnDef of allFunctions) {
403
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
404
+ continue;
405
+ }
406
+ if (fnDef.kind !== solc_typed_ast_1.FunctionKind.Function && fnDef.kind !== solc_typed_ast_1.FunctionKind.Constructor) {
407
+ continue;
408
+ }
409
+ const sig = signatureFromFnDef(fnDef);
410
+ const assertInfos = [];
411
+ for (const call of fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
412
+ const expr = call.vExpression;
413
+ if (expr instanceof solc_typed_ast_1.Identifier && expr.name === 'assert') {
414
+ // Get source location
415
+ const src = call.src;
416
+ if (src) {
417
+ const [startStr, lengthStr, fileIdStr] = src.split(':');
418
+ const start = parseInt(startStr, 10);
419
+ const length = parseInt(lengthStr, 10);
420
+ // Get the source file path
421
+ const sourceUnit = call.getClosestParentByType(solc_typed_ast_1.SourceUnit);
422
+ let filePath = (sourceUnit === null || sourceUnit === void 0 ? void 0 : sourceUnit.absolutePath) || '';
423
+ // Make path relative to foundry root
424
+ let relPath = filePath;
425
+ if (filePath.startsWith(foundryRoot)) {
426
+ relPath = filePath.slice(foundryRoot.length + 1);
427
+ }
428
+ // Get line info from source content
429
+ const content = sourceContents.get(filePath) || '';
430
+ let lines = [];
431
+ let startingColumn = 1;
432
+ let endingColumn = 1;
433
+ if (content) {
434
+ const lineInfo = (0, utils_1.getLines)(content, src);
435
+ lines = [];
436
+ for (let i = lineInfo.start; i <= lineInfo.end; i++) {
437
+ lines.push(i);
438
+ }
439
+ // Calculate columns
440
+ const beforeStart = content.substring(0, start);
441
+ const lastNewline = beforeStart.lastIndexOf('\n');
442
+ startingColumn = start - lastNewline;
443
+ endingColumn = startingColumn + length;
444
+ }
445
+ assertInfos.push({
446
+ start,
447
+ length,
448
+ filename_relative: relPath,
449
+ filename_absolute: filePath,
450
+ filename_short: relPath,
451
+ is_dependency: filePath.includes('lib/') || filePath.includes('node_modules/'),
452
+ lines,
453
+ starting_column: startingColumn,
454
+ ending_column: endingColumn,
455
+ });
456
+ }
457
+ }
458
+ }
459
+ // Only include functions that have asserts
460
+ if (assertInfos.length > 0) {
461
+ asserts[sig] = assertInfos;
462
+ }
463
+ }
464
+ return asserts;
465
+ };
466
+ /**
467
+ * Collect constants used in functions with proper type formatting
468
+ * Also includes Z3-solved values from constraint expressions
469
+ */
470
+ const collectConstantsUsed = async (contract) => {
471
+ var _a;
472
+ const constantsUsed = {};
473
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
474
+ for (const fnDef of allFunctions) {
475
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
476
+ continue;
477
+ }
478
+ const sig = signatureFromFnDef(fnDef);
479
+ const literals = [];
480
+ // Collect literal constants
481
+ for (const lit of fnDef.getChildrenByType(solc_typed_ast_1.Literal)) {
482
+ const value = formatLiteralValue(lit);
483
+ const type = typeStringToSolidityType((_a = lit.typeString) !== null && _a !== void 0 ? _a : '', value);
484
+ // Each constant is wrapped in an array (matching slither format)
485
+ literals.push([{ value, type }]);
486
+ }
487
+ // Collect Z3-solved constants from constraint expressions
488
+ try {
489
+ const z3Solutions = await (0, z3Solver_1.findZ3Solutions)(fnDef);
490
+ for (const solution of z3Solutions) {
491
+ // Add Z3 solution as a special constant with metadata
492
+ literals.push([{
493
+ value: solution.value,
494
+ type: solution.type,
495
+ }]);
496
+ }
497
+ }
498
+ catch (e) {
499
+ // Z3 errors are non-fatal, continue without Z3 solutions
500
+ }
501
+ // Only include functions that have constants
502
+ if (literals.length > 0) {
503
+ constantsUsed[sig] = literals;
504
+ }
505
+ }
506
+ return constantsUsed;
507
+ };
508
+ const collectFunctionsRelations = (contract) => {
509
+ const relations = {};
510
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
511
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
512
+ // Build a map of state variable IDs for faster lookup
513
+ const stateVarIds = new Set(stateVars.map(v => v.id));
514
+ const stateVarNames = new Set(stateVars.map(v => v.name));
515
+ const fnWritesVar = new Map();
516
+ const fnReadsVar = new Map();
517
+ // Helper to check if a node is part of assignment left-hand side
518
+ const isInAssignmentLHS = (node) => {
519
+ const parentAssignment = node.getClosestParentByType(solc_typed_ast_1.Assignment);
520
+ if (!parentAssignment)
521
+ return false;
522
+ // Walk up from the node to see if it's under the LHS
523
+ let current = node;
524
+ while (current && current !== parentAssignment) {
525
+ if (current === parentAssignment.vLeftHandSide) {
526
+ return true;
527
+ }
528
+ current = current.parent;
529
+ }
530
+ return false;
531
+ };
532
+ for (const fnDef of allFunctions) {
533
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
534
+ continue;
535
+ }
536
+ const sig = signatureFromFnDef(fnDef);
537
+ const writes = new Set();
538
+ const reads = new Set();
539
+ // Collect writes (assignments to state variables)
540
+ for (const assignment of fnDef.getChildrenByType(solc_typed_ast_1.Assignment)) {
541
+ if ((0, utils_1.isStateVarAssignment)(assignment)) {
542
+ const varDecl = (0, utils_1.getStateVarAssignment)(assignment);
543
+ if (varDecl && stateVarNames.has(varDecl.name)) {
544
+ writes.add(varDecl.name);
545
+ // Compound assignments (+=, -=, etc.) also read
546
+ if (assignment.operator !== '=') {
547
+ reads.add(varDecl.name);
548
+ }
549
+ }
550
+ }
551
+ }
552
+ // Collect reads (identifiers referencing state variables)
553
+ for (const id of fnDef.getChildrenByType(solc_typed_ast_1.Identifier)) {
554
+ const ref = id.vReferencedDeclaration;
555
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
556
+ // Check if this identifier is in an assignment LHS
557
+ if (!isInAssignmentLHS(id)) {
558
+ reads.add(ref.name);
559
+ }
560
+ else {
561
+ // Even in LHS, compound assignments read
562
+ const parentAssignment = id.getClosestParentByType(solc_typed_ast_1.Assignment);
563
+ if (parentAssignment && parentAssignment.operator !== '=') {
564
+ reads.add(ref.name);
565
+ }
566
+ }
567
+ }
568
+ }
569
+ // Collect reads from member accesses (for struct fields, mapping accesses)
570
+ for (const ma of fnDef.getChildrenByType(solc_typed_ast_1.MemberAccess)) {
571
+ const ref = ma.vReferencedDeclaration;
572
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
573
+ if (!isInAssignmentLHS(ma)) {
574
+ reads.add(ref.name);
575
+ }
576
+ else {
577
+ const parentAssignment = ma.getClosestParentByType(solc_typed_ast_1.Assignment);
578
+ if (parentAssignment && parentAssignment.operator !== '=') {
579
+ reads.add(ref.name);
580
+ }
581
+ }
582
+ }
583
+ }
584
+ fnWritesVar.set(sig, writes);
585
+ fnReadsVar.set(sig, reads);
586
+ }
587
+ // Build relations
588
+ for (const fnDef of allFunctions) {
589
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
590
+ continue;
591
+ }
592
+ const sig = signatureFromFnDef(fnDef);
593
+ const writes = fnWritesVar.get(sig) || new Set();
594
+ const reads = fnReadsVar.get(sig) || new Set();
595
+ const impacts = [];
596
+ const isImpactedBy = [];
597
+ // This function impacts others (including itself) that read variables this function writes
598
+ for (const [otherSig, otherReads] of fnReadsVar) {
599
+ for (const w of writes) {
600
+ if (otherReads.has(w)) {
601
+ if (!impacts.includes(otherSig)) {
602
+ impacts.push(otherSig);
603
+ }
604
+ break;
605
+ }
606
+ }
607
+ }
608
+ // This function is impacted by others (including itself) that write variables this function reads
609
+ for (const [otherSig, otherWrites] of fnWritesVar) {
610
+ for (const r of reads) {
611
+ if (otherWrites.has(r)) {
612
+ if (!isImpactedBy.includes(otherSig)) {
613
+ isImpactedBy.push(otherSig);
614
+ }
615
+ break;
616
+ }
617
+ }
618
+ }
619
+ // Check for single external call (harness wrapper pattern)
620
+ let externalCall;
621
+ const functionCalls = fnDef.getChildrenByType(solc_typed_ast_1.FunctionCall).filter(call => {
622
+ // Only consider regular function calls (not type conversions, struct constructors, etc.)
623
+ if (call.kind !== solc_typed_ast_1.FunctionCallKind.FunctionCall)
624
+ return false;
625
+ // Must be a member access (e.g., foo.bar())
626
+ if (!(call.vExpression instanceof solc_typed_ast_1.MemberAccess))
627
+ return false;
628
+ const memberAccess = call.vExpression;
629
+ const referencedDecl = memberAccess.vReferencedDeclaration;
630
+ // Must reference a function
631
+ if (!(referencedDecl instanceof solc_typed_ast_1.FunctionDefinition))
632
+ return false;
633
+ // The function must be external or public
634
+ if (referencedDecl.visibility !== solc_typed_ast_1.FunctionVisibility.External &&
635
+ referencedDecl.visibility !== solc_typed_ast_1.FunctionVisibility.Public)
636
+ return false;
637
+ // Must be on a contract (not an interface)
638
+ const parentContract = referencedDecl.vScope;
639
+ if (!(parentContract instanceof solc_typed_ast_1.ContractDefinition))
640
+ return false;
641
+ if (parentContract.kind !== solc_typed_ast_1.ContractKind.Contract)
642
+ return false;
643
+ return true;
644
+ });
645
+ // If exactly one external call to a contract, record it
646
+ if (functionCalls.length === 1) {
647
+ const call = functionCalls[0];
648
+ const memberAccess = call.vExpression;
649
+ const referencedFn = memberAccess.vReferencedDeclaration;
650
+ const parentContract = referencedFn.vScope;
651
+ const fnSig = signatureFromFnDef(referencedFn);
652
+ externalCall = `${parentContract.name}::${fnSig}`;
653
+ }
654
+ const relation = {
655
+ impacts,
656
+ is_impacted_by: isImpactedBy
657
+ };
658
+ if (externalCall) {
659
+ relation.external = externalCall;
660
+ }
661
+ relations[sig] = relation;
662
+ }
663
+ return relations;
664
+ };
665
+ /**
666
+ * Get line number from an AST node's src attribute
667
+ */
668
+ const getLineFromSrc = (src, sourceContents, absolutePath) => {
669
+ const content = sourceContents.get(absolutePath);
670
+ if (!content || !src)
671
+ return null;
672
+ const lineInfo = (0, utils_1.getLines)(content, src);
673
+ return lineInfo.start;
674
+ };
675
+ /**
676
+ * Check if a function call is an external/high-level call (not internal)
677
+ */
678
+ const isExternalCall = (call) => {
679
+ // High-level calls to external contracts
680
+ if ((0, utils_1.highLevelCall)(call))
681
+ return true;
682
+ // Member access calls like contract.function()
683
+ if (call.vExpression instanceof solc_typed_ast_1.MemberAccess) {
684
+ const memberAccess = call.vExpression;
685
+ const baseType = memberAccess.vExpression.typeString;
686
+ // If base is a contract type, it's external
687
+ if (baseType === null || baseType === void 0 ? void 0 : baseType.startsWith('contract '))
688
+ return true;
689
+ }
690
+ return false;
691
+ };
692
+ /**
693
+ * Collect external call lines from a list of statements
694
+ */
695
+ const collectExternalCallLines = (statements, sourceContents, absolutePath) => {
696
+ const lines = [];
697
+ for (const stmt of statements) {
698
+ const calls = stmt.getChildrenByType(solc_typed_ast_1.FunctionCall);
699
+ for (const call of calls) {
700
+ if (call.kind === solc_typed_ast_1.FunctionCallKind.FunctionCall && isExternalCall(call)) {
701
+ const line = getLineFromSrc(call.src, sourceContents, absolutePath);
702
+ if (line !== null) {
703
+ const lineStr = line.toString();
704
+ if (!lines.includes(lineStr)) {
705
+ lines.push(lineStr);
706
+ }
707
+ }
708
+ }
709
+ }
710
+ }
711
+ return lines;
712
+ };
713
+ /**
714
+ * Build coverage block from a function/modifier body
715
+ */
716
+ const buildCoverageBlockFromBody = (definition, sourceContents, contract, callTree, visited = new Set()) => {
717
+ const sourceUnit = definition.getClosestParentByType(solc_typed_ast_1.SourceUnit);
718
+ const absolutePath = (sourceUnit === null || sourceUnit === void 0 ? void 0 : sourceUnit.absolutePath) || '';
719
+ const content = sourceContents.get(absolutePath) || '';
720
+ const lines = [];
721
+ const children = [];
722
+ if (!definition.vBody || definition.vBody.vStatements.length === 0) {
723
+ return { lines, absolutePath, children };
724
+ }
725
+ const statements = definition.vBody.vStatements;
726
+ // Add first statement line
727
+ const firstStmt = statements[0];
728
+ if (firstStmt.src && content) {
729
+ const lineInfo = (0, utils_1.getLines)(content, firstStmt.src);
730
+ lines.push(lineInfo.start.toString());
731
+ }
732
+ // Add last statement line (if different from first)
733
+ const lastStmt = statements[statements.length - 1];
734
+ if (lastStmt.src && content && statements.length > 1) {
735
+ const lineInfo = (0, utils_1.getLines)(content, lastStmt.src);
736
+ const lastLine = lineInfo.end.toString();
737
+ if (!lines.includes(lastLine)) {
738
+ lines.push(lastLine);
739
+ }
740
+ }
741
+ // Add external call lines
742
+ const externalLines = collectExternalCallLines(statements, sourceContents, absolutePath);
743
+ for (const line of externalLines) {
744
+ if (!lines.includes(line)) {
745
+ lines.push(line);
746
+ }
747
+ }
748
+ // Process nested blocks (if statements, try/catch, etc.)
749
+ for (const stmt of statements) {
750
+ // Handle if statements
751
+ if (stmt instanceof solc_typed_ast_1.IfStatement) {
752
+ const ifChildren = buildCoverageBlockFromIfStatement(stmt, sourceContents, absolutePath);
753
+ children.push(...ifChildren);
754
+ }
755
+ // Handle try statements
756
+ if (stmt instanceof solc_typed_ast_1.TryStatement) {
757
+ const tryChildren = buildCoverageBlockFromTryStatement(stmt, sourceContents, absolutePath);
758
+ children.push(...tryChildren);
759
+ }
760
+ }
761
+ // Process children from call tree (internal/modifier calls)
762
+ for (const child of callTree.children) {
763
+ // Skip if already visited (recursive call)
764
+ if (child.isRecursive || visited.has(child.nodeId)) {
765
+ continue;
766
+ }
767
+ visited.add(child.nodeId);
768
+ const childBlock = buildCoverageBlockFromBody(child.definition, sourceContents, contract, child, visited);
769
+ if (childBlock.lines.length > 0 || childBlock.children.length > 0) {
770
+ children.push(childBlock);
771
+ }
772
+ }
773
+ return { lines, absolutePath, children };
774
+ };
775
+ /**
776
+ * Build coverage blocks from an if statement
777
+ */
778
+ const buildCoverageBlockFromIfStatement = (ifStmt, sourceContents, absolutePath) => {
779
+ const blocks = [];
780
+ const content = sourceContents.get(absolutePath) || '';
781
+ // True branch
782
+ if (ifStmt.vTrueBody) {
783
+ const trueBranch = buildCoverageBlockFromStatementOrBlock(ifStmt.vTrueBody, sourceContents, absolutePath);
784
+ if (trueBranch.lines.length > 0) {
785
+ blocks.push(trueBranch);
786
+ }
787
+ }
788
+ // False branch (else)
789
+ if (ifStmt.vFalseBody) {
790
+ const falseBranch = buildCoverageBlockFromStatementOrBlock(ifStmt.vFalseBody, sourceContents, absolutePath);
791
+ if (falseBranch.lines.length > 0) {
792
+ blocks.push(falseBranch);
793
+ }
794
+ }
795
+ return blocks;
796
+ };
797
+ /**
798
+ * Build coverage blocks from a try statement
799
+ */
800
+ const buildCoverageBlockFromTryStatement = (tryStmt, sourceContents, absolutePath) => {
801
+ const blocks = [];
802
+ // Process each catch clause
803
+ for (const clause of tryStmt.vClauses) {
804
+ if (clause.vBlock) {
805
+ const clauseBlock = buildCoverageBlockFromStatementOrBlock(clause.vBlock, sourceContents, absolutePath);
806
+ if (clauseBlock.lines.length > 0) {
807
+ blocks.push(clauseBlock);
808
+ }
809
+ }
810
+ }
811
+ return blocks;
812
+ };
813
+ /**
814
+ * Build coverage block from a statement or block
815
+ */
816
+ const buildCoverageBlockFromStatementOrBlock = (node, sourceContents, absolutePath) => {
817
+ const content = sourceContents.get(absolutePath) || '';
818
+ const lines = [];
819
+ const children = [];
820
+ let statements = [];
821
+ if (node instanceof solc_typed_ast_1.Block) {
822
+ statements = node.vStatements;
823
+ }
824
+ else {
825
+ statements = [node];
826
+ }
827
+ if (statements.length === 0) {
828
+ return { lines, absolutePath, children };
829
+ }
830
+ // Add first statement line
831
+ const firstStmt = statements[0];
832
+ if (firstStmt.src && content) {
833
+ const lineInfo = (0, utils_1.getLines)(content, firstStmt.src);
834
+ lines.push(lineInfo.start.toString());
835
+ }
836
+ // Add last statement line
837
+ const lastStmt = statements[statements.length - 1];
838
+ if (lastStmt.src && content && statements.length > 1) {
839
+ const lineInfo = (0, utils_1.getLines)(content, lastStmt.src);
840
+ const lastLine = lineInfo.end.toString();
841
+ if (!lines.includes(lastLine)) {
842
+ lines.push(lastLine);
843
+ }
844
+ }
845
+ // Add external call lines
846
+ const externalLines = collectExternalCallLines(statements, sourceContents, absolutePath);
847
+ for (const line of externalLines) {
848
+ if (!lines.includes(line)) {
849
+ lines.push(line);
850
+ }
851
+ }
852
+ // Process nested blocks
853
+ for (const stmt of statements) {
854
+ if (stmt instanceof solc_typed_ast_1.IfStatement) {
855
+ const ifChildren = buildCoverageBlockFromIfStatement(stmt, sourceContents, absolutePath);
856
+ children.push(...ifChildren);
857
+ }
858
+ if (stmt instanceof solc_typed_ast_1.TryStatement) {
859
+ const tryChildren = buildCoverageBlockFromTryStatement(stmt, sourceContents, absolutePath);
860
+ children.push(...tryChildren);
861
+ }
862
+ }
863
+ return { lines, absolutePath, children };
864
+ };
865
+ /**
866
+ * Collect coverage map for a contract
867
+ */
868
+ const collectCoverageMap = (contract, sourceContents) => {
869
+ const coverageMap = {};
870
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true).reverse();
871
+ for (const fnDef of allFunctions) {
872
+ // Only include external/public functions
873
+ if (fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.External && fnDef.visibility !== solc_typed_ast_1.FunctionVisibility.Public) {
874
+ continue;
875
+ }
876
+ const sig = signatureFromFnDef(fnDef);
877
+ // Build call tree context
878
+ const context = {
879
+ nodeCounter: { value: 0 },
880
+ contract: contract,
881
+ callStack: [],
882
+ activeNodes: new Map()
883
+ };
884
+ // Build call tree for this function
885
+ const callTree = (0, call_tree_builder_1.buildCallTree)(fnDef, [], undefined, undefined, context);
886
+ // Build coverage block from call tree
887
+ const coverageBlock = buildCoverageBlockFromBody(fnDef, sourceContents, contract, callTree);
888
+ coverageMap[sig] = coverageBlock;
889
+ }
890
+ return coverageMap;
891
+ };
892
+ const hasFallback = (contract) => {
893
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
894
+ return allFunctions.some(fn => fn.kind === solc_typed_ast_1.FunctionKind.Fallback);
895
+ };
896
+ const hasReceive = (contract) => {
897
+ const allFunctions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
898
+ return allFunctions.some(fn => fn.kind === solc_typed_ast_1.FunctionKind.Receive);
899
+ };
900
+ /**
901
+ * Load source file contents for source location info
902
+ */
903
+ const loadSourceContents = async (sourceUnits) => {
904
+ const contents = new Map();
905
+ for (const unit of sourceUnits) {
906
+ const filePath = unit.absolutePath;
907
+ if (filePath) {
908
+ try {
909
+ const content = await fs.readFile(filePath, 'utf-8');
910
+ contents.set(filePath, content);
911
+ }
912
+ catch {
913
+ // File might not exist or not accessible
914
+ }
915
+ }
916
+ }
917
+ return contents;
918
+ };
919
+ const runInfo = async (foundryRoot, contractName, options = {}) => {
920
+ const log = options.json ? () => { } : console.log.bind(console);
921
+ // Set Z3 to silent mode if --json is used
922
+ if (options.json) {
923
+ (0, z3Solver_1.setZ3Silent)(true);
924
+ }
925
+ const { sourceUnits, contractAbis } = await loadLatestBuildInfo(foundryRoot);
926
+ if (!sourceUnits || sourceUnits.length === 0) {
927
+ throw new Error('No source units were produced from the build; cannot generate info.');
928
+ }
929
+ // Load source file contents for assert info
930
+ const sourceContents = await loadSourceContents(sourceUnits);
931
+ const allContracts = getAllContracts(sourceUnits);
932
+ const targetContract = allContracts.get(contractName);
933
+ if (!targetContract) {
934
+ throw new Error(`Contract ${contractName} not found in build output.`);
935
+ }
936
+ // Recursively collect all related contracts
937
+ const relatedContracts = collectRelatedContracts(targetContract, allContracts);
938
+ log(`[recon-generate] Found ${relatedContracts.size} related contracts`);
939
+ const output = {
940
+ payable: {},
941
+ assert: {},
942
+ constant_functions: {},
943
+ constants_used: {},
944
+ inheritances: {},
945
+ functions_relations: {},
946
+ with_fallback: [],
947
+ with_receive: [],
948
+ coverage_map: {},
949
+ };
950
+ // Collect info for all related contracts
951
+ for (const contract of relatedContracts) {
952
+ // Skip abstract contracts (their functions are inherited by concrete contracts)
953
+ // But include libraries (ContractKind.Library) since they contain important logic
954
+ if (contract.abstract) {
955
+ continue;
956
+ }
957
+ if (contract.kind !== solc_typed_ast_1.ContractKind.Contract && contract.kind !== solc_typed_ast_1.ContractKind.Library) {
958
+ continue;
959
+ }
960
+ // Payable functions
961
+ const payable = collectPayableFunctions(contract);
962
+ if (payable.length > 0) {
963
+ output.payable[contract.name] = payable;
964
+ }
965
+ // Constant functions from ABI (more reliable for complex types)
966
+ const constantFns = collectConstantFunctionsFromAbi(contract.name, contractAbis);
967
+ if (constantFns.length > 0) {
968
+ output.constant_functions[contract.name] = constantFns;
969
+ }
970
+ // Assert statements (only include contracts with asserts)
971
+ const asserts = collectAssertStatements(contract, foundryRoot, sourceContents);
972
+ if (Object.keys(asserts).length > 0) {
973
+ output.assert[contract.name] = asserts;
974
+ }
975
+ // Constants used (only include contracts/functions with constants)
976
+ // Also includes Z3-solved values from constraint expressions
977
+ const constants = await collectConstantsUsed(contract);
978
+ if (Object.keys(constants).length > 0) {
979
+ output.constants_used[contract.name] = constants;
980
+ }
981
+ // Functions relations
982
+ const relations = collectFunctionsRelations(contract);
983
+ output.functions_relations[contract.name] = relations;
984
+ // Coverage map
985
+ const coverage = collectCoverageMap(contract, sourceContents);
986
+ if (Object.keys(coverage).length > 0) {
987
+ output.coverage_map[contract.name] = coverage;
988
+ }
989
+ // Fallback and receive
990
+ if (hasFallback(contract)) {
991
+ output.with_fallback.push(contract.name);
992
+ }
993
+ if (hasReceive(contract)) {
994
+ output.with_receive.push(contract.name);
995
+ }
996
+ // Inheritances (all base contracts recursively, excluding self)
997
+ const inheritedContracts = collectAllInheritances(contract);
998
+ if (inheritedContracts.length > 0) {
999
+ output.inheritances[contract.name] = inheritedContracts;
1000
+ }
1001
+ }
1002
+ const jsonOutput = JSON.stringify(output, null, 2);
1003
+ // Cleanup Z3 context
1004
+ await (0, z3Solver_1.cleanupZ3)();
1005
+ // Output to terminal if --json flag is set
1006
+ if (options.json) {
1007
+ console.log(jsonOutput);
1008
+ }
1009
+ // Write to file if outputPath is provided or --json is not set
1010
+ if (options.outputPath || !options.json) {
1011
+ const infoPath = options.outputPath || path.join(foundryRoot, `recon-info-${contractName}.json`);
1012
+ await fs.writeFile(infoPath, jsonOutput);
1013
+ log(`[recon-generate] Wrote info file to ${infoPath}`);
1014
+ }
1015
+ return output;
1016
+ };
1017
+ exports.runInfo = runInfo;