recon-generate 0.0.17 → 0.0.19

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.
@@ -0,0 +1,270 @@
1
+ export type SExpr = {
2
+ atom: string;
3
+ } | {
4
+ list: SExpr[];
5
+ } | {
6
+ comment: string;
7
+ };
8
+ export declare const atom: (s: string) => SExpr;
9
+ export declare const list: (...args: SExpr[]) => SExpr;
10
+ export declare const comment: (s: string) => SExpr;
11
+ export type SymExpr = {
12
+ kind: 'var';
13
+ name: string;
14
+ type: string;
15
+ } | {
16
+ kind: 'lit';
17
+ value: string;
18
+ type: string;
19
+ } | {
20
+ kind: 'bool';
21
+ value: boolean;
22
+ } | {
23
+ kind: 'binop';
24
+ op: BinOp;
25
+ left: SymExpr;
26
+ right: SymExpr;
27
+ checked: boolean;
28
+ signed: boolean;
29
+ } | {
30
+ kind: 'unop';
31
+ op: UnOp;
32
+ arg: SymExpr;
33
+ } | {
34
+ kind: 'select';
35
+ array: SymExpr;
36
+ index: SymExpr;
37
+ } | {
38
+ kind: 'ite';
39
+ cond: SymExpr;
40
+ then: SymExpr;
41
+ else: SymExpr;
42
+ } | {
43
+ kind: 'call';
44
+ target: string;
45
+ method: string;
46
+ args: SymExpr[];
47
+ callType: CallType;
48
+ contract?: string;
49
+ } | {
50
+ kind: 'field';
51
+ base: SymExpr;
52
+ field: string;
53
+ } | {
54
+ kind: 'cast';
55
+ toType: string;
56
+ arg: SymExpr;
57
+ } | {
58
+ kind: 'keccak';
59
+ arg: SymExpr;
60
+ } | {
61
+ kind: 'external_return';
62
+ target: string;
63
+ method: string;
64
+ args: SymExpr[];
65
+ ssaId: string;
66
+ } | {
67
+ kind: 'loop';
68
+ var: string;
69
+ start: SymExpr;
70
+ bound: SymExpr;
71
+ body: string;
72
+ };
73
+ export type BinOp = 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'exp' | 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge' | 'and' | 'or' | 'xor' | 'shl' | 'shr' | 'land' | 'lor';
74
+ export type UnOp = 'not' | 'neg' | 'bnot';
75
+ export type CallType = 'internal' | 'library' | 'external' | 'constructor';
76
+ export interface BasicBlock {
77
+ id: string;
78
+ statements: CFGStatement[];
79
+ terminator: Terminator;
80
+ astIds: number[];
81
+ }
82
+ export type CFGStatement = {
83
+ kind: 'let';
84
+ name: string;
85
+ type: string;
86
+ expr: SymExpr;
87
+ } | {
88
+ kind: 'store';
89
+ target: SymExpr;
90
+ index: SymExpr;
91
+ value: SymExpr;
92
+ } | {
93
+ kind: 'assign';
94
+ target: string;
95
+ value: SymExpr;
96
+ } | {
97
+ kind: 'call';
98
+ expr: SymExpr;
99
+ } | {
100
+ kind: 'assume';
101
+ cond: SymExpr;
102
+ reason?: string;
103
+ revertEdge?: string;
104
+ };
105
+ export type Terminator = {
106
+ kind: 'branch';
107
+ cond: SymExpr;
108
+ trueBlock: string;
109
+ falseBlock: string;
110
+ } | {
111
+ kind: 'jump';
112
+ target: string;
113
+ } | {
114
+ kind: 'return';
115
+ values: SymExpr[];
116
+ } | {
117
+ kind: 'revert';
118
+ reason?: string;
119
+ } | {
120
+ kind: 'placeholder';
121
+ };
122
+ /**
123
+ * A CoverageEdge represents one branch outcome.
124
+ * Covering an edge means executing that branch direction.
125
+ */
126
+ export interface CoverageEdge {
127
+ id: string;
128
+ from: string;
129
+ to: string;
130
+ condition: SymExpr;
131
+ functionName: string;
132
+ astId?: number;
133
+ }
134
+ /**
135
+ * A CompletePath is a sequence of edges from entry to exit.
136
+ * This is what we enumerate for "n paths total".
137
+ */
138
+ export interface CompletePath {
139
+ id: string;
140
+ edges: string[];
141
+ entryBlock: string;
142
+ exitBlock: string;
143
+ pathCondition: SymExpr;
144
+ outcome: 'success' | 'revert';
145
+ solvable: boolean;
146
+ }
147
+ /**
148
+ * Snapshot of state at a block entry - for mid-point solving.
149
+ */
150
+ export interface StateSnapshot {
151
+ atBlock: string;
152
+ variables: Map<string, SymExpr>;
153
+ pathCondition: SymExpr;
154
+ coveredBy: string[];
155
+ }
156
+ /**
157
+ * Observed return values from external calls (from tracing).
158
+ */
159
+ export interface ExternalCallOracle {
160
+ target: string;
161
+ method: string;
162
+ argPatterns: string[];
163
+ observedReturns: string[];
164
+ }
165
+ /**
166
+ * Loop summary for symbolic reasoning
167
+ */
168
+ export interface LoopInfo {
169
+ id: string;
170
+ headerBlock: string;
171
+ exitBlock: string;
172
+ condition: SymExpr;
173
+ inductionVar?: string;
174
+ bound?: SymExpr;
175
+ bodyBlocks: string[];
176
+ }
177
+ export interface FunctionCFG {
178
+ contract: string;
179
+ name: string;
180
+ selector: string;
181
+ visibility: 'public' | 'external' | 'internal' | 'private';
182
+ params: {
183
+ name: string;
184
+ type: string;
185
+ }[];
186
+ returns: {
187
+ name?: string;
188
+ type: string;
189
+ }[];
190
+ blocks: Map<string, BasicBlock>;
191
+ entryBlock: string;
192
+ edges: CoverageEdge[];
193
+ paths: CompletePath[];
194
+ externalCalls: {
195
+ target: string;
196
+ method: string;
197
+ ssaVar: string;
198
+ }[];
199
+ hasKeccak: boolean;
200
+ internalCalls: {
201
+ target: string;
202
+ contract: string;
203
+ callType: 'internal' | 'super' | 'library' | 'modifier' | 'constructor';
204
+ args: SymExpr[];
205
+ }[];
206
+ modifiers: string[];
207
+ ternaryEdges: CoverageEdge[];
208
+ loops: LoopInfo[];
209
+ }
210
+ /**
211
+ * Variable role classification for solver
212
+ */
213
+ export type VarRole = 'input' | 'local' | 'state' | 'global' | 'external';
214
+ /**
215
+ * Variable metadata for solver
216
+ */
217
+ export interface VarInfo {
218
+ name: string;
219
+ baseName: string;
220
+ role: VarRole;
221
+ type: string;
222
+ slot?: number;
223
+ isMapping?: boolean;
224
+ keyTypes?: string[];
225
+ }
226
+ export interface ContractModule {
227
+ name: string;
228
+ solidityVersion: string;
229
+ stateVars: {
230
+ name: string;
231
+ type: string;
232
+ slot?: number;
233
+ contract?: string;
234
+ }[];
235
+ mappings: {
236
+ name: string;
237
+ keyType: string;
238
+ valueType: string;
239
+ slot?: number;
240
+ contract?: string;
241
+ }[];
242
+ constants?: {
243
+ name: string;
244
+ type: string;
245
+ value?: string;
246
+ contract?: string;
247
+ }[];
248
+ immutables?: {
249
+ name: string;
250
+ type: string;
251
+ contract?: string;
252
+ }[];
253
+ functions: FunctionCFG[];
254
+ totalPaths: number;
255
+ totalEdges: number;
256
+ oracle: ExternalCallOracle[];
257
+ varRoles?: Map<string, VarInfo>;
258
+ }
259
+ export interface SSAContext {
260
+ counter: Map<string, number>;
261
+ types: Map<string, string>;
262
+ blockCounter: number;
263
+ pathCounter: number;
264
+ edgeCounter: number;
265
+ }
266
+ export declare function freshSSA(ctx: SSAContext, base: string, type: string): string;
267
+ export declare function currentSSA(ctx: SSAContext, base: string): string;
268
+ export declare function newBlock(ctx: SSAContext): string;
269
+ export declare function newPath(ctx: SSAContext): string;
270
+ export declare function newEdge(ctx: SSAContext, funcName: string, from: string, dir: 'T' | 'F' | 'J'): string;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // S-Expression AST (for output)
4
+ // ============================================================================
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.comment = exports.list = exports.atom = void 0;
7
+ exports.freshSSA = freshSSA;
8
+ exports.currentSSA = currentSSA;
9
+ exports.newBlock = newBlock;
10
+ exports.newPath = newPath;
11
+ exports.newEdge = newEdge;
12
+ // Helper constructors
13
+ const atom = (s) => ({ atom: s });
14
+ exports.atom = atom;
15
+ const list = (...args) => ({ list: args });
16
+ exports.list = list;
17
+ const comment = (s) => ({ comment: s });
18
+ exports.comment = comment;
19
+ function freshSSA(ctx, base, type) {
20
+ const count = (ctx.counter.get(base) || 0) + 1;
21
+ ctx.counter.set(base, count);
22
+ const name = `${base}_${count}`;
23
+ ctx.types.set(name, type);
24
+ return name;
25
+ }
26
+ function currentSSA(ctx, base) {
27
+ var _a;
28
+ // If variable hasn't been assigned yet, return _0 (initial/undefined state)
29
+ // This ensures the first freshSSA returns _1, which will be different
30
+ const count = (_a = ctx.counter.get(base)) !== null && _a !== void 0 ? _a : 0;
31
+ return `${base}_${count}`;
32
+ }
33
+ function newBlock(ctx) {
34
+ return `B${ctx.blockCounter++}`;
35
+ }
36
+ function newPath(ctx) {
37
+ return `P${ctx.pathCounter++}`;
38
+ }
39
+ function newEdge(ctx, funcName, from, dir) {
40
+ return `${funcName}_${from}_${dir}`;
41
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Unified Coverage Map Generator
3
+ *
4
+ * Creates a single coverage-map.json that combines:
5
+ * - branches.json (PC → AST mapping, line numbers)
6
+ * - edge-mappings.json (PC → Edge ID mapping from CFG)
7
+ *
8
+ * This eliminates the need for two separate JSON files and ensures
9
+ * the Rust fuzzer has all coverage information in one place.
10
+ */
11
+ /** Unified branch info combining branches.json and edge-mappings.json data */
12
+ export interface UnifiedBranch {
13
+ /** PC of the JUMPI instruction */
14
+ pc: number;
15
+ /** AST node ID */
16
+ astId: number | null;
17
+ /** All AST IDs at this source location */
18
+ allAstIds: number[];
19
+ /** Source byte offset */
20
+ offset: number;
21
+ /** Source byte length */
22
+ length: number;
23
+ /** Source file index */
24
+ fileIndex: number;
25
+ /** Line number of the branch condition (1-based) */
26
+ conditionLine?: number;
27
+ /** Edge ID when branch is taken (true) */
28
+ trueEdge?: string;
29
+ /** Edge ID when branch is not taken (false) */
30
+ falseEdge?: string;
31
+ /** Function name containing this branch */
32
+ function?: string;
33
+ /** Block ID in CFG */
34
+ block?: string;
35
+ /** Line number of the first statement in true branch */
36
+ trueBodyLine?: number;
37
+ /** Line number of the first statement in false branch */
38
+ falseBodyLine?: number;
39
+ /** First PC of the true branch body */
40
+ trueBodyPc?: number;
41
+ /** First PC of the false branch body */
42
+ falseBodyPc?: number;
43
+ /** Whether this is an assertion (require/assert) - FALSE branch is the bug-finding path */
44
+ isAssertion?: boolean;
45
+ }
46
+ /** Per-contract unified coverage data */
47
+ export interface UnifiedContractCoverage {
48
+ /** Contract name */
49
+ name: string;
50
+ /** Source file path */
51
+ sourceFile: string;
52
+ /** Total edges (2 per branch: true + false) */
53
+ totalEdges: number;
54
+ /** All branches with unified info */
55
+ branches: UnifiedBranch[];
56
+ }
57
+ /** Unified coverage manifest */
58
+ export interface UnifiedCoverageManifest {
59
+ /** Generation timestamp */
60
+ generated: string;
61
+ /** Schema version for forward compatibility */
62
+ version: string;
63
+ /** Total contracts */
64
+ totalContracts: number;
65
+ /** Total branch points */
66
+ totalBranches: number;
67
+ /** Total edges (branch directions) */
68
+ totalEdges: number;
69
+ /** Per-contract coverage data */
70
+ contracts: {
71
+ [name: string]: UnifiedContractCoverage;
72
+ };
73
+ }
74
+ /**
75
+ * Generate unified coverage map by merging branches.json and edge-mappings.json
76
+ * Can be called after both files exist, or during the "all" command
77
+ */
78
+ export declare function generateUnifiedCoverageMap(reconDir: string, branchesData?: any, edgeMappingsData?: any): Promise<UnifiedCoverageManifest>;
79
+ /**
80
+ * Write unified coverage map to file
81
+ */
82
+ export declare function writeUnifiedCoverageMap(reconDir: string, manifest: UnifiedCoverageManifest): void;
83
+ /**
84
+ * Generate unified coverage map directly from branch and edge data
85
+ * Called during the "all" command to avoid re-reading JSON files
86
+ */
87
+ export declare function createUnifiedFromMemory(branchManifest: any, edgeMappings: any | null): UnifiedCoverageManifest;
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ /**
3
+ * Unified Coverage Map Generator
4
+ *
5
+ * Creates a single coverage-map.json that combines:
6
+ * - branches.json (PC → AST mapping, line numbers)
7
+ * - edge-mappings.json (PC → Edge ID mapping from CFG)
8
+ *
9
+ * This eliminates the need for two separate JSON files and ensures
10
+ * the Rust fuzzer has all coverage information in one place.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.generateUnifiedCoverageMap = generateUnifiedCoverageMap;
47
+ exports.writeUnifiedCoverageMap = writeUnifiedCoverageMap;
48
+ exports.createUnifiedFromMemory = createUnifiedFromMemory;
49
+ const fs = __importStar(require("fs"));
50
+ const path = __importStar(require("path"));
51
+ // ============================================================================
52
+ // Generation
53
+ // ============================================================================
54
+ /**
55
+ * Generate unified coverage map by merging branches.json and edge-mappings.json
56
+ * Can be called after both files exist, or during the "all" command
57
+ */
58
+ async function generateUnifiedCoverageMap(reconDir, branchesData, edgeMappingsData) {
59
+ var _a, _b, _c, _d, _e, _f, _g;
60
+ // Load branches.json if not provided
61
+ if (!branchesData) {
62
+ const branchesPath = path.join(reconDir, 'branches.json');
63
+ if (!fs.existsSync(branchesPath)) {
64
+ throw new Error(`branches.json not found in ${reconDir}`);
65
+ }
66
+ branchesData = JSON.parse(fs.readFileSync(branchesPath, 'utf-8'));
67
+ }
68
+ // Load edge-mappings.json if not provided (optional - might not exist)
69
+ if (!edgeMappingsData) {
70
+ const edgeMappingsPath = path.join(reconDir, 'edge-mappings.json');
71
+ if (fs.existsSync(edgeMappingsPath)) {
72
+ edgeMappingsData = JSON.parse(fs.readFileSync(edgeMappingsPath, 'utf-8'));
73
+ }
74
+ }
75
+ // Build lookup maps from edge-mappings
76
+ // Primary: PC → edge info
77
+ // Fallback: AST ID → edge info (handles PC mismatches between branches.json and edge-mappings.json)
78
+ const pcToEdgeInfo = new Map(); // key: "contractName:pc"
79
+ const astIdToEdgeInfo = new Map(); // key: "contractName:astId"
80
+ if (edgeMappingsData === null || edgeMappingsData === void 0 ? void 0 : edgeMappingsData.contracts) {
81
+ for (const [contractName, contractData] of Object.entries(edgeMappingsData.contracts)) {
82
+ const branches = contractData.branches || [];
83
+ for (const branch of branches) {
84
+ const pcKey = `${contractName}:${branch.pc}`;
85
+ pcToEdgeInfo.set(pcKey, branch);
86
+ // Also index by AST ID for fallback lookup
87
+ if (branch.astId !== undefined && branch.astId !== null) {
88
+ const astKey = `${contractName}:${branch.astId}`;
89
+ astIdToEdgeInfo.set(astKey, branch);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // Merge data
95
+ const unifiedContracts = {};
96
+ let totalBranches = 0;
97
+ let totalEdges = 0;
98
+ for (const contract of branchesData.contracts || []) {
99
+ const contractName = contract.contract;
100
+ const unifiedBranches = [];
101
+ for (const branch of contract.branches || []) {
102
+ // Look up edge info - try PC first, then fallback to AST ID
103
+ const pcKey = `${contractName}:${branch.pc}`;
104
+ let edgeInfo = pcToEdgeInfo.get(pcKey);
105
+ // If PC lookup fails, try AST ID lookup (handles PC mismatches)
106
+ if (!edgeInfo && branch.astId !== undefined && branch.astId !== null) {
107
+ const astKey = `${contractName}:${branch.astId}`;
108
+ edgeInfo = astIdToEdgeInfo.get(astKey);
109
+ }
110
+ // Also try allAstIds if primary astId didn't match
111
+ if (!edgeInfo && ((_a = branch.allAstIds) === null || _a === void 0 ? void 0 : _a.length) > 0) {
112
+ for (const astId of branch.allAstIds) {
113
+ const astKey = `${contractName}:${astId}`;
114
+ edgeInfo = astIdToEdgeInfo.get(astKey);
115
+ if (edgeInfo)
116
+ break;
117
+ }
118
+ }
119
+ // Determine if this is an assertion branch
120
+ // Assertions have edge IDs containing "assert" - the FALSE branch is the bug path
121
+ const isAssertion = ((_b = edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueEdge) === null || _b === void 0 ? void 0 : _b.includes('assert')) ||
122
+ ((_c = edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseEdge) === null || _c === void 0 ? void 0 : _c.includes('assert')) ||
123
+ false;
124
+ const unified = {
125
+ pc: branch.pc,
126
+ astId: branch.astId,
127
+ allAstIds: branch.allAstIds || [],
128
+ offset: branch.offset,
129
+ length: branch.length,
130
+ fileIndex: branch.fileIndex,
131
+ conditionLine: branch.conditionLine,
132
+ // Edge info (from edge-mappings.json)
133
+ trueEdge: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueEdge,
134
+ falseEdge: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseEdge,
135
+ function: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.function,
136
+ block: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.block,
137
+ // Body info (can come from either source)
138
+ trueBodyLine: (_d = branch.trueBodyLine) !== null && _d !== void 0 ? _d : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueBodyLine,
139
+ falseBodyLine: (_e = branch.falseBodyLine) !== null && _e !== void 0 ? _e : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseBodyLine,
140
+ trueBodyPc: (_f = branch.trueBodyPc) !== null && _f !== void 0 ? _f : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueBodyPc,
141
+ falseBodyPc: (_g = branch.falseBodyPc) !== null && _g !== void 0 ? _g : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseBodyPc,
142
+ // Metadata
143
+ isAssertion,
144
+ };
145
+ unifiedBranches.push(unified);
146
+ }
147
+ // Count edges: each branch with edge info has 2 edges, others have 2 potential directions
148
+ const edgesForContract = unifiedBranches.length * 2;
149
+ unifiedContracts[contractName] = {
150
+ name: contractName,
151
+ sourceFile: contract.sourceFile,
152
+ totalEdges: edgesForContract,
153
+ branches: unifiedBranches,
154
+ };
155
+ totalBranches += unifiedBranches.length;
156
+ totalEdges += edgesForContract;
157
+ }
158
+ const manifest = {
159
+ generated: new Date().toISOString(),
160
+ version: '1.0',
161
+ totalContracts: Object.keys(unifiedContracts).length,
162
+ totalBranches,
163
+ totalEdges,
164
+ contracts: unifiedContracts,
165
+ };
166
+ return manifest;
167
+ }
168
+ /**
169
+ * Write unified coverage map to file
170
+ */
171
+ function writeUnifiedCoverageMap(reconDir, manifest) {
172
+ const outputPath = path.join(reconDir, 'coverage-map.json');
173
+ fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
174
+ console.log(` ✅ Unified coverage map written to ${outputPath}`);
175
+ }
176
+ /**
177
+ * Generate unified coverage map directly from branch and edge data
178
+ * Called during the "all" command to avoid re-reading JSON files
179
+ */
180
+ function createUnifiedFromMemory(branchManifest, edgeMappings) {
181
+ return generateUnifiedCoverageMapSync(branchManifest, edgeMappings);
182
+ }
183
+ /** Synchronous version for in-memory data */
184
+ function generateUnifiedCoverageMapSync(branchesData, edgeMappingsData) {
185
+ var _a, _b, _c, _d, _e, _f;
186
+ // Build PC → edge info lookup
187
+ const pcToEdgeInfo = new Map();
188
+ if (edgeMappingsData === null || edgeMappingsData === void 0 ? void 0 : edgeMappingsData.contracts) {
189
+ for (const [contractName, contractData] of Object.entries(edgeMappingsData.contracts)) {
190
+ const branches = contractData.branches || [];
191
+ for (const branch of branches) {
192
+ const key = `${contractName}:${branch.pc}`;
193
+ pcToEdgeInfo.set(key, branch);
194
+ }
195
+ }
196
+ }
197
+ // Merge data
198
+ const unifiedContracts = {};
199
+ let totalBranches = 0;
200
+ let totalEdges = 0;
201
+ for (const contract of branchesData.contracts || []) {
202
+ const contractName = contract.contract;
203
+ const unifiedBranches = [];
204
+ for (const branch of contract.branches || []) {
205
+ const edgeKey = `${contractName}:${branch.pc}`;
206
+ const edgeInfo = pcToEdgeInfo.get(edgeKey);
207
+ const isAssertion = ((_a = edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueEdge) === null || _a === void 0 ? void 0 : _a.includes('assert')) ||
208
+ ((_b = edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseEdge) === null || _b === void 0 ? void 0 : _b.includes('assert')) ||
209
+ false;
210
+ const unified = {
211
+ pc: branch.pc,
212
+ astId: branch.astId,
213
+ allAstIds: branch.allAstIds || [],
214
+ offset: branch.offset,
215
+ length: branch.length,
216
+ fileIndex: branch.fileIndex,
217
+ conditionLine: branch.conditionLine,
218
+ trueEdge: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueEdge,
219
+ falseEdge: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseEdge,
220
+ function: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.function,
221
+ block: edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.block,
222
+ trueBodyLine: (_c = branch.trueBodyLine) !== null && _c !== void 0 ? _c : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueBodyLine,
223
+ falseBodyLine: (_d = branch.falseBodyLine) !== null && _d !== void 0 ? _d : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseBodyLine,
224
+ trueBodyPc: (_e = branch.trueBodyPc) !== null && _e !== void 0 ? _e : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.trueBodyPc,
225
+ falseBodyPc: (_f = branch.falseBodyPc) !== null && _f !== void 0 ? _f : edgeInfo === null || edgeInfo === void 0 ? void 0 : edgeInfo.falseBodyPc,
226
+ isAssertion,
227
+ };
228
+ unifiedBranches.push(unified);
229
+ }
230
+ const edgesForContract = unifiedBranches.length * 2;
231
+ unifiedContracts[contractName] = {
232
+ name: contractName,
233
+ sourceFile: contract.sourceFile,
234
+ totalEdges: edgesForContract,
235
+ branches: unifiedBranches,
236
+ };
237
+ totalBranches += unifiedBranches.length;
238
+ totalEdges += edgesForContract;
239
+ }
240
+ return {
241
+ generated: new Date().toISOString(),
242
+ version: '1.0',
243
+ totalContracts: Object.keys(unifiedContracts).length,
244
+ totalBranches,
245
+ totalEdges,
246
+ contracts: unifiedContracts,
247
+ };
248
+ }
package/dist/generator.js CHANGED
@@ -298,7 +298,7 @@ class ReconGenerator {
298
298
  this.logDebug('Skipping artifact from non-source directory', { contractName, sourcePath });
299
299
  continue;
300
300
  }
301
- if (isAbstract && !isExplicitlyIncluded && !isExplicitlyMocked) {
301
+ if (isAbstract) {
302
302
  this.logDebug('Skipping abstract contract', { contractName, sourcePath });
303
303
  continue;
304
304
  }
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ const pathsGenerator_1 = require("./pathsGenerator");
44
44
  const info_1 = require("./info");
45
45
  const utils_1 = require("./utils");
46
46
  const link_1 = require("./link");
47
+ const sourcemap_1 = require("./sourcemap");
47
48
  function parseFilter(input) {
48
49
  if (!input)
49
50
  return undefined;
@@ -193,6 +194,24 @@ async function main() {
193
194
  : path.join(foundryRoot, medusaConfigOpt);
194
195
  await (0, link_1.runLink)(foundryRoot, echidnaConfigPath, medusaConfigPath, !!opts.verbose);
195
196
  });
197
+ program
198
+ .command('sourcemap')
199
+ .description('Generate CFGs and coverage-map.json for coverage-directed fuzzing')
200
+ .option('-o, --output <path>', 'Custom output directory for coverage artifacts (defaults to .recon)')
201
+ .option('--verbose', 'Enable verbose output for debugging')
202
+ .option('--foundry-config <path>', 'Path to foundry.toml (defaults to ./foundry.toml)')
203
+ .action(async (opts, cmd) => {
204
+ const workspaceRoot = process.cwd();
205
+ const foundryConfig = (0, utils_1.getFoundryConfigPath)(workspaceRoot, opts.foundryConfig);
206
+ const foundryRoot = path.dirname(foundryConfig);
207
+ const outputDir = opts.output
208
+ ? (path.isAbsolute(opts.output) ? opts.output : path.join(foundryRoot, opts.output))
209
+ : undefined;
210
+ await (0, sourcemap_1.runSourcemap)(foundryRoot, {
211
+ outputDir,
212
+ verbose: !!opts.verbose,
213
+ });
214
+ });
196
215
  program
197
216
  .command('info <contractName>')
198
217
  .description('Generate contract info JSON (payable, constants, function relations, etc.)')