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,103 @@
1
+ /**
2
+ * Edge Mapper - Maps bytecode PCs to CFG edge IDs
3
+ *
4
+ * Approach using AST-level information:
5
+ * 1. CFG edges have astId (branch condition's AST ID)
6
+ * 2. Source map maps PC → source location → AST ID
7
+ * 3. For each edge's astId, find the first PC that maps to it
8
+ * 4. That PC is the "branch point" for that edge
9
+ *
10
+ * At runtime:
11
+ * - Fuzzer hits PC X
12
+ * - Look up: is X a branch point for any edge?
13
+ * - If yes, mark that edge as covered based on which direction was taken
14
+ */
15
+ /** Edge info parsed from CFG */
16
+ export interface CfgEdge {
17
+ id: string;
18
+ from: string;
19
+ to: string;
20
+ direction: 'T' | 'F' | 'J';
21
+ functionName: string;
22
+ astId?: number;
23
+ }
24
+ /** Branch point mapping for runtime coverage - enhanced with line numbers and body PCs */
25
+ export interface BranchPoint {
26
+ /** PC of the branch (JUMPI or first instruction of branch condition) */
27
+ pc: number;
28
+ /** AST ID of the condition */
29
+ astId: number;
30
+ /** Edge ID for true branch (condition != 0, fall through) */
31
+ trueEdge: string;
32
+ /** Edge ID for false branch (condition == 0, jump taken) */
33
+ falseEdge: string;
34
+ /** Function name */
35
+ function: string;
36
+ /** Block ID where branch occurs */
37
+ block: string;
38
+ /** Line number of the branch condition (1-based) */
39
+ conditionLine?: number;
40
+ /** Line number of the first statement in true branch (1-based) */
41
+ trueBodyLine?: number;
42
+ /** Line number of the first statement in false branch (1-based) */
43
+ falseBodyLine?: number;
44
+ /** First PC of the true branch body (for coverage tracking) */
45
+ trueBodyPc?: number;
46
+ /** First PC of the false branch body (for coverage tracking) */
47
+ falseBodyPc?: number;
48
+ /** Byte offset of condition in source */
49
+ offset?: number;
50
+ /** Length of condition in source */
51
+ length?: number;
52
+ }
53
+ /** Complete edge mapping for a contract */
54
+ export interface EdgeMapping {
55
+ contract: string;
56
+ totalEdges: number;
57
+ /** Branch points - PC → (trueEdge, falseEdge) */
58
+ branches: BranchPoint[];
59
+ /** Quick lookup: PC → branch info */
60
+ pcToBranch: Map<number, BranchPoint>;
61
+ /** Quick lookup: edge ID → PC */
62
+ edgeToPc: Map<string, number>;
63
+ }
64
+ export interface EdgeMappingManifest {
65
+ generated: string;
66
+ totalContracts: number;
67
+ totalEdges: number;
68
+ contracts: {
69
+ name: string;
70
+ edges: number;
71
+ branches: number;
72
+ }[];
73
+ }
74
+ /**
75
+ * Parse CFG s-expression to extract edges
76
+ */
77
+ export declare function parseCfgEdges(cfgContent: string): CfgEdge[];
78
+ export declare class EdgeMapper {
79
+ private mappings;
80
+ /**
81
+ * Load CFG and source map branches, build edge mapping
82
+ */
83
+ loadContract(cfgPath: string, branchesPath: string, contractName: string): Promise<EdgeMapping>;
84
+ /**
85
+ * Get mapping for a contract
86
+ */
87
+ getMapping(contractName: string): EdgeMapping | undefined;
88
+ /**
89
+ * Look up edge from PC and branch direction
90
+ * @param taken - true if condition was non-zero (fall through), false if jump taken
91
+ */
92
+ pcToEdge(contractName: string, pc: number, taken: boolean): string | undefined;
93
+ /**
94
+ * Look up PC from edge ID
95
+ */
96
+ edgeToPc(contractName: string, edgeId: string): number | undefined;
97
+ /**
98
+ * Export mappings to JSON for Rust fuzzer
99
+ * Enhanced with line number information for LLM integration
100
+ */
101
+ exportForFuzzer(outputPath: string): void;
102
+ }
103
+ export declare function generateEdgeMappings(reconDir: string, contractNames: string[]): Promise<EdgeMappingManifest>;
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * Edge Mapper - Maps bytecode PCs to CFG edge IDs
4
+ *
5
+ * Approach using AST-level information:
6
+ * 1. CFG edges have astId (branch condition's AST ID)
7
+ * 2. Source map maps PC → source location → AST ID
8
+ * 3. For each edge's astId, find the first PC that maps to it
9
+ * 4. That PC is the "branch point" for that edge
10
+ *
11
+ * At runtime:
12
+ * - Fuzzer hits PC X
13
+ * - Look up: is X a branch point for any edge?
14
+ * - If yes, mark that edge as covered based on which direction was taken
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.EdgeMapper = void 0;
51
+ exports.parseCfgEdges = parseCfgEdges;
52
+ exports.generateEdgeMappings = generateEdgeMappings;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ // ============================================================================
56
+ // CFG Parser
57
+ // ============================================================================
58
+ /**
59
+ * Parse CFG s-expression to extract edges
60
+ */
61
+ function parseCfgEdges(cfgContent) {
62
+ const edges = [];
63
+ const lines = cfgContent.split('\n');
64
+ for (const line of lines) {
65
+ // Parse edges: (edge "func_B0_T" B0 B1 condition :ast 123)
66
+ // Handle various formats including assert edges like "func_B0_assert0_T"
67
+ // Note: condition can contain nested parentheses, so we can't use [^)]
68
+ // Instead, match :ast at the end or allow optional :ast anywhere
69
+ let edgeMatch = /\(edge\s+"([^"]+)"\s+(\w+)\s+(\w+).*:ast\s+(\d+)/.exec(line);
70
+ if (!edgeMatch) {
71
+ // Try without :ast (edge might not have AST ID)
72
+ edgeMatch = /\(edge\s+"([^"]+)"\s+(\w+)\s+(\w+)/.exec(line);
73
+ }
74
+ if (edgeMatch) {
75
+ const [_, edgeId, fromBlock, toBlock, astIdStr] = edgeMatch;
76
+ // Parse edge ID to extract function, block, direction
77
+ // Patterns: "func_B0_T", "func_B0_F", "func_B0_J", "func_B0_assert0_T"
78
+ const parts = edgeId.match(/^(.+?)_(B\d+).*?_([TFJ])$/);
79
+ if (parts) {
80
+ const [__, funcName, blockId, dir] = parts;
81
+ edges.push({
82
+ id: edgeId,
83
+ from: fromBlock,
84
+ to: toBlock,
85
+ direction: dir,
86
+ functionName: funcName,
87
+ astId: astIdStr ? parseInt(astIdStr, 10) : undefined
88
+ });
89
+ }
90
+ }
91
+ }
92
+ return edges;
93
+ }
94
+ // ============================================================================
95
+ // Edge Mapper Class
96
+ // ============================================================================
97
+ class EdgeMapper {
98
+ constructor() {
99
+ this.mappings = new Map();
100
+ }
101
+ /**
102
+ * Load CFG and source map branches, build edge mapping
103
+ */
104
+ async loadContract(cfgPath, branchesPath, contractName) {
105
+ var _a, _b, _c, _d, _e;
106
+ // Load and parse CFG
107
+ const cfgContent = fs.readFileSync(cfgPath, 'utf-8');
108
+ const edges = parseCfgEdges(cfgContent);
109
+ // Load branches manifest (from source map)
110
+ const branchManifest = JSON.parse(fs.readFileSync(branchesPath, 'utf-8'));
111
+ const contractBranches = (_b = (_a = branchManifest.contracts) === null || _a === void 0 ? void 0 : _a.find) === null || _b === void 0 ? void 0 : _b.call(_a, (c) => c.contract === contractName);
112
+ if (!contractBranches) {
113
+ throw new Error(`Contract ${contractName} not found in branches.json`);
114
+ }
115
+ // Build AST ID → first PC mapping from branches
116
+ // branches.json now contains: { pc, astId, allAstIds, offset, length } for each JUMPI
117
+ // allAstIds contains ALL AST nodes at this source location
118
+ const astIdToPc = new Map();
119
+ for (const branch of contractBranches.branches || []) {
120
+ // Include all AST IDs at this location
121
+ const allIds = branch.allAstIds || (branch.astId !== null ? [branch.astId] : []);
122
+ for (const astId of allIds) {
123
+ const existing = astIdToPc.get(astId);
124
+ // Keep the first (lowest) PC for each AST ID
125
+ if (existing === undefined || branch.pc < existing) {
126
+ astIdToPc.set(astId, branch.pc);
127
+ }
128
+ }
129
+ }
130
+ // Group CFG edges by astId to find T/F pairs
131
+ const edgesByAstId = new Map();
132
+ for (const edge of edges) {
133
+ if (edge.astId === undefined)
134
+ continue;
135
+ if (edge.direction === 'J')
136
+ continue; // Skip unconditional jumps
137
+ if (!edgesByAstId.has(edge.astId)) {
138
+ edgesByAstId.set(edge.astId, {});
139
+ }
140
+ const group = edgesByAstId.get(edge.astId);
141
+ if (edge.direction === 'T')
142
+ group.T = edge;
143
+ if (edge.direction === 'F')
144
+ group.F = edge;
145
+ }
146
+ // Build branch points by correlating AST IDs
147
+ const branches = [];
148
+ const pcToBranch = new Map();
149
+ const edgeToPc = new Map();
150
+ for (const [astId, group] of edgesByAstId) {
151
+ const pc = astIdToPc.get(astId);
152
+ if (pc === undefined)
153
+ continue;
154
+ if (!group.T && !group.F)
155
+ continue;
156
+ // Use the T edge's info for function/block (they should be the same)
157
+ const refEdge = group.T || group.F;
158
+ // Find the branch info from branches.json to get line number and body PCs
159
+ const branchInfo = (_c = contractBranches.branches) === null || _c === void 0 ? void 0 : _c.find((b) => b.pc === pc || (b.allAstIds && b.allAstIds.includes(astId)));
160
+ const branchPoint = {
161
+ pc,
162
+ astId,
163
+ trueEdge: ((_d = group.T) === null || _d === void 0 ? void 0 : _d.id) || `${refEdge.functionName}_${refEdge.from}_T`,
164
+ falseEdge: ((_e = group.F) === null || _e === void 0 ? void 0 : _e.id) || `${refEdge.functionName}_${refEdge.from}_F`,
165
+ function: refEdge.functionName,
166
+ block: refEdge.from,
167
+ // Include line/offset info if available
168
+ conditionLine: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.conditionLine,
169
+ trueBodyLine: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.trueBodyLine,
170
+ falseBodyLine: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.falseBodyLine,
171
+ trueBodyPc: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.trueBodyPc,
172
+ falseBodyPc: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.falseBodyPc,
173
+ offset: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.offset,
174
+ length: branchInfo === null || branchInfo === void 0 ? void 0 : branchInfo.length
175
+ };
176
+ branches.push(branchPoint);
177
+ pcToBranch.set(pc, branchPoint);
178
+ // Map both edges to this PC
179
+ if (group.T)
180
+ edgeToPc.set(group.T.id, pc);
181
+ if (group.F)
182
+ edgeToPc.set(group.F.id, pc);
183
+ }
184
+ const mapping = {
185
+ contract: contractName,
186
+ totalEdges: edges.length,
187
+ branches,
188
+ pcToBranch,
189
+ edgeToPc
190
+ };
191
+ this.mappings.set(contractName, mapping);
192
+ return mapping;
193
+ }
194
+ /**
195
+ * Get mapping for a contract
196
+ */
197
+ getMapping(contractName) {
198
+ return this.mappings.get(contractName);
199
+ }
200
+ /**
201
+ * Look up edge from PC and branch direction
202
+ * @param taken - true if condition was non-zero (fall through), false if jump taken
203
+ */
204
+ pcToEdge(contractName, pc, taken) {
205
+ const mapping = this.mappings.get(contractName);
206
+ if (!mapping)
207
+ return undefined;
208
+ const branch = mapping.pcToBranch.get(pc);
209
+ if (!branch)
210
+ return undefined;
211
+ return taken ? branch.trueEdge : branch.falseEdge;
212
+ }
213
+ /**
214
+ * Look up PC from edge ID
215
+ */
216
+ edgeToPc(contractName, edgeId) {
217
+ const mapping = this.mappings.get(contractName);
218
+ return mapping === null || mapping === void 0 ? void 0 : mapping.edgeToPc.get(edgeId);
219
+ }
220
+ /**
221
+ * Export mappings to JSON for Rust fuzzer
222
+ * Enhanced with line number information for LLM integration
223
+ */
224
+ exportForFuzzer(outputPath) {
225
+ const output = {
226
+ generated: new Date().toISOString(),
227
+ totalContracts: 0,
228
+ totalEdges: 0,
229
+ contracts: {}
230
+ };
231
+ for (const [name, mapping] of this.mappings) {
232
+ output.totalContracts++;
233
+ output.totalEdges += mapping.totalEdges;
234
+ output.contracts[name] = {
235
+ totalEdges: mapping.totalEdges,
236
+ branches: mapping.branches.map(b => ({
237
+ pc: b.pc,
238
+ astId: b.astId,
239
+ trueEdge: b.trueEdge,
240
+ falseEdge: b.falseEdge,
241
+ function: b.function,
242
+ block: b.block,
243
+ // Include line number info for LLM-friendly output
244
+ ...(b.conditionLine !== undefined && { conditionLine: b.conditionLine }),
245
+ ...(b.trueBodyLine !== undefined && { trueBodyLine: b.trueBodyLine }),
246
+ ...(b.falseBodyLine !== undefined && { falseBodyLine: b.falseBodyLine }),
247
+ // Include body PCs for coverage tracking
248
+ ...(b.trueBodyPc !== undefined && { trueBodyPc: b.trueBodyPc }),
249
+ ...(b.falseBodyPc !== undefined && { falseBodyPc: b.falseBodyPc }),
250
+ ...(b.offset !== undefined && { offset: b.offset }),
251
+ ...(b.length !== undefined && { length: b.length })
252
+ }))
253
+ };
254
+ }
255
+ fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
256
+ }
257
+ }
258
+ exports.EdgeMapper = EdgeMapper;
259
+ // ============================================================================
260
+ // CLI Integration
261
+ // ============================================================================
262
+ async function generateEdgeMappings(reconDir, contractNames) {
263
+ const mapper = new EdgeMapper();
264
+ const branchesPath = path.join(reconDir, 'branches.json');
265
+ if (!fs.existsSync(branchesPath)) {
266
+ throw new Error(`branches.json not found in ${reconDir}. Run 'recon-expander sourcemap' first.`);
267
+ }
268
+ const results = [];
269
+ for (const contractName of contractNames) {
270
+ const cfgPath = path.join(reconDir, `${contractName}.cfg.sexp`);
271
+ if (!fs.existsSync(cfgPath)) {
272
+ console.warn(`Warning: ${cfgPath} not found, skipping`);
273
+ continue;
274
+ }
275
+ try {
276
+ const mapping = await mapper.loadContract(cfgPath, branchesPath, contractName);
277
+ results.push({
278
+ name: contractName,
279
+ edges: mapping.totalEdges,
280
+ branches: mapping.branches.length
281
+ });
282
+ console.log(` ✅ ${contractName}: ${mapping.totalEdges} edges, ${mapping.branches.length} branch points mapped`);
283
+ }
284
+ catch (err) {
285
+ console.warn(`Warning: Failed to map ${contractName}: ${err}`);
286
+ }
287
+ }
288
+ // Export combined mappings
289
+ const edgeMappingPath = path.join(reconDir, 'edge-mappings.json');
290
+ mapper.exportForFuzzer(edgeMappingPath);
291
+ return {
292
+ generated: new Date().toISOString(),
293
+ totalContracts: results.length,
294
+ totalEdges: results.reduce((sum, r) => sum + r.edges, 0),
295
+ contracts: results
296
+ };
297
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * S-Expression Emitter - Converts CFG to s-expr format
3
+ *
4
+ * Output is designed for:
5
+ * - Easy parsing in Rust fuzzer
6
+ * - Direct translation to SMT-LIB2 for Bitwuzla
7
+ * - Human readability for debugging
8
+ */
9
+ import { CompletePath, ContractModule, FunctionCFG } from './types';
10
+ export declare function emitContract(module: ContractModule): string;
11
+ export declare function emitPathToSMT(path: CompletePath, func: FunctionCFG): string;