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.
- package/dist/cfg/builder.d.ts +89 -0
- package/dist/cfg/builder.js +1295 -0
- package/dist/cfg/edge-mapper.d.ts +103 -0
- package/dist/cfg/edge-mapper.js +297 -0
- package/dist/cfg/emitter.d.ts +11 -0
- package/dist/cfg/emitter.js +492 -0
- package/dist/cfg/index.d.ts +35 -0
- package/dist/cfg/index.js +77 -0
- package/dist/cfg/sourcemap.d.ts +169 -0
- package/dist/cfg/sourcemap.js +605 -0
- package/dist/cfg/types.d.ts +270 -0
- package/dist/cfg/types.js +41 -0
- package/dist/cfg/unified-coverage.d.ts +87 -0
- package/dist/cfg/unified-coverage.js +248 -0
- package/dist/generator.js +1 -1
- package/dist/index.js +19 -0
- package/dist/info.js +0 -13
- package/dist/sourcemap.d.ts +14 -0
- package/dist/sourcemap.js +317 -0
- package/dist/types.d.ts +5 -0
- package/dist/wakeGenerator.js +2 -1
- package/package.json +2 -1
|
@@ -0,0 +1,1295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CFG Builder - Transforms Solidity AST into Control Flow Graph
|
|
4
|
+
*
|
|
5
|
+
* Key features:
|
|
6
|
+
* - Enumerates ALL paths (for "n total, m covered" tracking)
|
|
7
|
+
* - Each branch creates a CoverageEdge
|
|
8
|
+
* - External calls marked for oracle integration
|
|
9
|
+
* - Supports forward/backward/mid-point solving
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CFGBuilder = void 0;
|
|
13
|
+
const solc_typed_ast_1 = require("solc-typed-ast");
|
|
14
|
+
const types_1 = require("./types");
|
|
15
|
+
const utils_1 = require("../utils");
|
|
16
|
+
const ethers_1 = require("ethers");
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Main Builder Class
|
|
19
|
+
// ============================================================================
|
|
20
|
+
class CFGBuilder {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.ctx = {
|
|
23
|
+
counter: new Map(),
|
|
24
|
+
types: new Map(),
|
|
25
|
+
blockCounter: 0,
|
|
26
|
+
pathCounter: 0,
|
|
27
|
+
edgeCounter: 0
|
|
28
|
+
};
|
|
29
|
+
this.blocks = new Map();
|
|
30
|
+
this.edges = [];
|
|
31
|
+
this.paths = [];
|
|
32
|
+
this.externalCalls = [];
|
|
33
|
+
this.ternaryEdges = [];
|
|
34
|
+
this.loops = [];
|
|
35
|
+
this.hasKeccak = false;
|
|
36
|
+
this.inUnchecked = false;
|
|
37
|
+
this.currentFunction = '';
|
|
38
|
+
this.contract = null;
|
|
39
|
+
this.ternaryCounter = 0;
|
|
40
|
+
this.loopCounter = 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build CFG for a contract from CallTreeData
|
|
44
|
+
*/
|
|
45
|
+
buildContract(contract, callTrees) {
|
|
46
|
+
this.contract = contract;
|
|
47
|
+
const functions = [];
|
|
48
|
+
for (const ctData of callTrees) {
|
|
49
|
+
if (ctData.contract !== contract.name)
|
|
50
|
+
continue;
|
|
51
|
+
const funcCFG = this.buildFunction(ctData.function, ctData.callTree);
|
|
52
|
+
functions.push(funcCFG);
|
|
53
|
+
}
|
|
54
|
+
// Extract state variables with storage slot computation
|
|
55
|
+
// IMPORTANT: Solidity storage layout follows C3 linearization order
|
|
56
|
+
// Parent contract state vars come FIRST, then child contract vars
|
|
57
|
+
// We use forwardLinearization which gives us [child, parent1, parent2, ...]
|
|
58
|
+
// So we process in REVERSE order to get correct slot assignments
|
|
59
|
+
//
|
|
60
|
+
// MUTABILITY:
|
|
61
|
+
// - constant: Compile-time constant, embedded in bytecode, no slot
|
|
62
|
+
// - immutable: Set in constructor, stored in bytecode, no slot
|
|
63
|
+
// - mutable: Regular storage variable, has slot
|
|
64
|
+
const stateVars = [];
|
|
65
|
+
const mappings = [];
|
|
66
|
+
const constants = [];
|
|
67
|
+
const immutables = [];
|
|
68
|
+
let currentSlot = 0;
|
|
69
|
+
// Get all contracts in linearization order and reverse to process parents first
|
|
70
|
+
const linearized = (0, utils_1.forwardLinearization)(contract);
|
|
71
|
+
const contractsInStorageOrder = [...linearized].reverse();
|
|
72
|
+
// Track seen variable names to avoid duplicates from overrides
|
|
73
|
+
const seenVars = new Set();
|
|
74
|
+
for (const c of contractsInStorageOrder) {
|
|
75
|
+
for (const v of c.vStateVariables) {
|
|
76
|
+
// Skip if we've already seen this var name (override)
|
|
77
|
+
if (seenVars.has(v.name))
|
|
78
|
+
continue;
|
|
79
|
+
seenVars.add(v.name);
|
|
80
|
+
const typeStr = v.typeString || 'unknown';
|
|
81
|
+
const contractName = c.name;
|
|
82
|
+
// Handle constant variables - no storage slot, value known at compile time
|
|
83
|
+
if (v.mutability === solc_typed_ast_1.Mutability.Constant) {
|
|
84
|
+
let constValue;
|
|
85
|
+
if (v.vValue) {
|
|
86
|
+
// Try to extract the constant value
|
|
87
|
+
constValue = this.tryExtractConstantValue(v.vValue);
|
|
88
|
+
}
|
|
89
|
+
constants.push({
|
|
90
|
+
name: v.name,
|
|
91
|
+
type: typeStr,
|
|
92
|
+
value: constValue,
|
|
93
|
+
contract: contractName
|
|
94
|
+
});
|
|
95
|
+
continue; // No storage slot for constants
|
|
96
|
+
}
|
|
97
|
+
// Handle immutable variables - no storage slot, set in constructor
|
|
98
|
+
if (v.mutability === solc_typed_ast_1.Mutability.Immutable) {
|
|
99
|
+
immutables.push({
|
|
100
|
+
name: v.name,
|
|
101
|
+
type: typeStr,
|
|
102
|
+
contract: contractName
|
|
103
|
+
});
|
|
104
|
+
continue; // No storage slot for immutables
|
|
105
|
+
}
|
|
106
|
+
// Regular mutable state variables - have storage slots
|
|
107
|
+
if (typeStr.startsWith('mapping')) {
|
|
108
|
+
// Parse mapping(K => V)
|
|
109
|
+
const match = typeStr.match(/mapping\((\w+)\s*=>\s*(.+)\)/);
|
|
110
|
+
if (match) {
|
|
111
|
+
mappings.push({
|
|
112
|
+
name: v.name,
|
|
113
|
+
keyType: match[1],
|
|
114
|
+
valueType: match[2].trim(),
|
|
115
|
+
slot: currentSlot,
|
|
116
|
+
contract: contractName
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
currentSlot++; // Mappings use one slot for base
|
|
120
|
+
}
|
|
121
|
+
else if (typeStr.includes('[]')) {
|
|
122
|
+
// Dynamic array - uses one slot for length
|
|
123
|
+
stateVars.push({ name: v.name, type: typeStr, slot: currentSlot, contract: contractName });
|
|
124
|
+
currentSlot++;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Simple type - compute slot based on type size
|
|
128
|
+
const typeSize = this.getTypeSlots(typeStr);
|
|
129
|
+
stateVars.push({ name: v.name, type: typeStr, slot: currentSlot, contract: contractName });
|
|
130
|
+
currentSlot += typeSize;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Build variable roles map
|
|
135
|
+
const varRoles = this.buildVarRoles(contract, functions, stateVars, mappings);
|
|
136
|
+
const totalPaths = functions.reduce((sum, f) => sum + f.paths.length, 0);
|
|
137
|
+
const totalEdges = functions.reduce((sum, f) => sum + f.edges.length, 0);
|
|
138
|
+
return {
|
|
139
|
+
name: contract.name,
|
|
140
|
+
solidityVersion: '0.8.x', // TODO: extract from pragma
|
|
141
|
+
stateVars,
|
|
142
|
+
mappings,
|
|
143
|
+
functions,
|
|
144
|
+
totalPaths,
|
|
145
|
+
totalEdges,
|
|
146
|
+
oracle: [],
|
|
147
|
+
varRoles,
|
|
148
|
+
constants,
|
|
149
|
+
immutables
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Try to extract a constant value from an expression
|
|
154
|
+
*/
|
|
155
|
+
tryExtractConstantValue(expr) {
|
|
156
|
+
if (expr instanceof solc_typed_ast_1.Literal) {
|
|
157
|
+
return expr.value;
|
|
158
|
+
}
|
|
159
|
+
// For more complex constant expressions, we could evaluate them
|
|
160
|
+
// For now, just return the source representation
|
|
161
|
+
try {
|
|
162
|
+
return (0, utils_1.toSource)(expr);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get number of storage slots a type uses
|
|
170
|
+
*/
|
|
171
|
+
getTypeSlots(typeStr) {
|
|
172
|
+
// Most types fit in one slot (32 bytes)
|
|
173
|
+
// Structs and fixed arrays may use more
|
|
174
|
+
if (typeStr.includes('struct')) {
|
|
175
|
+
// Simplified: assume 1 slot per struct (would need ABI for accurate count)
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
if (typeStr.match(/\[\d+\]/)) {
|
|
179
|
+
// Fixed array - extract size
|
|
180
|
+
const match = typeStr.match(/\[(\d+)\]/);
|
|
181
|
+
if (match) {
|
|
182
|
+
return parseInt(match[1]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Build variable roles map from functions and state variables
|
|
189
|
+
*/
|
|
190
|
+
buildVarRoles(contract, functions, stateVars, mappings) {
|
|
191
|
+
const roles = new Map();
|
|
192
|
+
// State variables
|
|
193
|
+
for (const sv of stateVars) {
|
|
194
|
+
roles.set(sv.name, {
|
|
195
|
+
name: sv.name,
|
|
196
|
+
baseName: sv.name,
|
|
197
|
+
role: 'state',
|
|
198
|
+
type: sv.type,
|
|
199
|
+
slot: sv.slot
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// Mappings
|
|
203
|
+
for (const m of mappings) {
|
|
204
|
+
roles.set(m.name, {
|
|
205
|
+
name: m.name,
|
|
206
|
+
baseName: m.name,
|
|
207
|
+
role: 'state',
|
|
208
|
+
type: `mapping(${m.keyType} => ${m.valueType})`,
|
|
209
|
+
slot: m.slot,
|
|
210
|
+
isMapping: true,
|
|
211
|
+
keyTypes: [m.keyType]
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Global variables
|
|
215
|
+
const globals = [
|
|
216
|
+
{ name: 'msg_sender', type: 'address' },
|
|
217
|
+
{ name: 'msg_value', type: 'uint256' },
|
|
218
|
+
{ name: 'msg_data', type: 'bytes' },
|
|
219
|
+
{ name: 'block_timestamp', type: 'uint256' },
|
|
220
|
+
{ name: 'block_number', type: 'uint256' },
|
|
221
|
+
{ name: 'block_coinbase', type: 'address' },
|
|
222
|
+
{ name: 'block_difficulty', type: 'uint256' },
|
|
223
|
+
{ name: 'block_gaslimit', type: 'uint256' },
|
|
224
|
+
{ name: 'block_basefee', type: 'uint256' },
|
|
225
|
+
{ name: 'tx_origin', type: 'address' },
|
|
226
|
+
{ name: 'tx_gasprice', type: 'uint256' },
|
|
227
|
+
];
|
|
228
|
+
for (const g of globals) {
|
|
229
|
+
roles.set(g.name, {
|
|
230
|
+
name: g.name,
|
|
231
|
+
baseName: g.name,
|
|
232
|
+
role: 'global',
|
|
233
|
+
type: g.type
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// Function parameters and locals (from functions)
|
|
237
|
+
for (const func of functions) {
|
|
238
|
+
// Parameters are inputs
|
|
239
|
+
for (const p of func.params) {
|
|
240
|
+
// SSA version will be param_1, param_2, etc.
|
|
241
|
+
for (let v = 1; v <= 10; v++) {
|
|
242
|
+
const ssaName = `${p.name}_${v}`;
|
|
243
|
+
roles.set(ssaName, {
|
|
244
|
+
name: ssaName,
|
|
245
|
+
baseName: p.name,
|
|
246
|
+
role: 'input',
|
|
247
|
+
type: p.type
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Local variables defined in let statements
|
|
252
|
+
for (const block of func.blocks.values()) {
|
|
253
|
+
for (const stmt of block.statements) {
|
|
254
|
+
if (stmt.kind === 'let') {
|
|
255
|
+
const baseName = stmt.name.replace(/_\d+$/, '');
|
|
256
|
+
// Check if it's a state var read or a local
|
|
257
|
+
const isStateVar = stateVars.some(sv => sv.name === baseName) ||
|
|
258
|
+
mappings.some(m => m.name === baseName);
|
|
259
|
+
if (!isStateVar) {
|
|
260
|
+
roles.set(stmt.name, {
|
|
261
|
+
name: stmt.name,
|
|
262
|
+
baseName,
|
|
263
|
+
role: 'local',
|
|
264
|
+
type: stmt.type
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return roles;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Build CFG for a single function
|
|
275
|
+
*/
|
|
276
|
+
buildFunction(func, callTree) {
|
|
277
|
+
// Reset state
|
|
278
|
+
this.ctx = {
|
|
279
|
+
counter: new Map(),
|
|
280
|
+
types: new Map(),
|
|
281
|
+
blockCounter: 0,
|
|
282
|
+
pathCounter: 0,
|
|
283
|
+
edgeCounter: 0
|
|
284
|
+
};
|
|
285
|
+
this.blocks = new Map();
|
|
286
|
+
this.edges = [];
|
|
287
|
+
this.paths = [];
|
|
288
|
+
this.externalCalls = [];
|
|
289
|
+
this.ternaryEdges = [];
|
|
290
|
+
this.loops = [];
|
|
291
|
+
this.ternaryCounter = 0;
|
|
292
|
+
this.loopCounter = 0;
|
|
293
|
+
this.hasKeccak = false;
|
|
294
|
+
this.currentFunction = func.name;
|
|
295
|
+
// Initialize parameters as SSA variables
|
|
296
|
+
const params = [];
|
|
297
|
+
for (const p of func.vParameters.vParameters) {
|
|
298
|
+
const type = p.typeString || 'uint256';
|
|
299
|
+
(0, types_1.freshSSA)(this.ctx, p.name, type);
|
|
300
|
+
params.push({ name: p.name, type });
|
|
301
|
+
}
|
|
302
|
+
// Returns
|
|
303
|
+
const returns = [];
|
|
304
|
+
for (const r of func.vReturnParameters.vParameters) {
|
|
305
|
+
returns.push({ name: r.name || undefined, type: r.typeString || 'uint256' });
|
|
306
|
+
}
|
|
307
|
+
// Build CFG from function body
|
|
308
|
+
const entryBlock = (0, types_1.newBlock)(this.ctx);
|
|
309
|
+
if (func.vBody) {
|
|
310
|
+
const exitBlock = this.processBlock(func.vBody, entryBlock, null);
|
|
311
|
+
// Ensure the exit block has a proper terminator (implicit return if missing)
|
|
312
|
+
const exitBlockData = this.blocks.get(exitBlock);
|
|
313
|
+
if (exitBlockData && exitBlockData.terminator.kind === 'jump' && exitBlockData.terminator.target === '') {
|
|
314
|
+
// Implicit return at end of function
|
|
315
|
+
exitBlockData.terminator = { kind: 'return', values: [] };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// Abstract function - single empty block
|
|
320
|
+
this.blocks.set(entryBlock, {
|
|
321
|
+
id: entryBlock,
|
|
322
|
+
statements: [],
|
|
323
|
+
terminator: { kind: 'return', values: [] },
|
|
324
|
+
astIds: [func.id]
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// Enumerate all complete paths
|
|
328
|
+
this.enumeratePaths(entryBlock);
|
|
329
|
+
// Extract internal calls from CallTree
|
|
330
|
+
const internalCalls = this.extractInternalCalls(callTree);
|
|
331
|
+
// Get selector
|
|
332
|
+
const selector = this.computeSelector(func);
|
|
333
|
+
return {
|
|
334
|
+
contract: this.contract.name,
|
|
335
|
+
name: func.name,
|
|
336
|
+
selector,
|
|
337
|
+
visibility: func.visibility,
|
|
338
|
+
params,
|
|
339
|
+
returns,
|
|
340
|
+
blocks: this.blocks,
|
|
341
|
+
entryBlock,
|
|
342
|
+
edges: [...this.edges, ...this.ternaryEdges], // Include ternary edges in main edges
|
|
343
|
+
paths: this.paths,
|
|
344
|
+
externalCalls: this.externalCalls,
|
|
345
|
+
hasKeccak: this.hasKeccak,
|
|
346
|
+
internalCalls,
|
|
347
|
+
modifiers: func.vModifiers.map(m => m.vModifierName.name),
|
|
348
|
+
ternaryEdges: this.ternaryEdges,
|
|
349
|
+
loops: this.loops
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
// ==========================================================================
|
|
353
|
+
// Statement Processing
|
|
354
|
+
// ==========================================================================
|
|
355
|
+
/**
|
|
356
|
+
* Process a block of statements, returns exit block ID
|
|
357
|
+
*/
|
|
358
|
+
processBlock(block, currentBlockId, exitTarget) {
|
|
359
|
+
const wasUnchecked = this.inUnchecked;
|
|
360
|
+
if (block instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
361
|
+
this.inUnchecked = true;
|
|
362
|
+
}
|
|
363
|
+
let blockId = currentBlockId;
|
|
364
|
+
// Ensure block exists
|
|
365
|
+
if (!this.blocks.has(blockId)) {
|
|
366
|
+
this.blocks.set(blockId, {
|
|
367
|
+
id: blockId,
|
|
368
|
+
statements: [],
|
|
369
|
+
terminator: { kind: 'jump', target: '' },
|
|
370
|
+
astIds: []
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
for (const stmt of block.vStatements) {
|
|
374
|
+
blockId = this.processStatement(stmt, blockId);
|
|
375
|
+
}
|
|
376
|
+
// If we have an exit target and the current block hasn't terminated
|
|
377
|
+
const currentBlock = this.blocks.get(blockId);
|
|
378
|
+
if (exitTarget && currentBlock.terminator.kind === 'jump' && currentBlock.terminator.target === '') {
|
|
379
|
+
currentBlock.terminator = { kind: 'jump', target: exitTarget };
|
|
380
|
+
this.addEdge(blockId, exitTarget, { kind: 'bool', value: true }, 'J');
|
|
381
|
+
}
|
|
382
|
+
this.inUnchecked = wasUnchecked;
|
|
383
|
+
return blockId;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Process a single statement, returns the block ID to continue from
|
|
387
|
+
*/
|
|
388
|
+
processStatement(stmt, blockId) {
|
|
389
|
+
const block = this.blocks.get(blockId);
|
|
390
|
+
block.astIds.push(stmt.id);
|
|
391
|
+
if (stmt instanceof solc_typed_ast_1.IfStatement) {
|
|
392
|
+
return this.processIf(stmt, blockId);
|
|
393
|
+
}
|
|
394
|
+
if (stmt instanceof solc_typed_ast_1.WhileStatement) {
|
|
395
|
+
return this.processWhile(stmt, blockId);
|
|
396
|
+
}
|
|
397
|
+
if (stmt instanceof solc_typed_ast_1.ForStatement) {
|
|
398
|
+
return this.processFor(stmt, blockId);
|
|
399
|
+
}
|
|
400
|
+
if (stmt instanceof solc_typed_ast_1.Return) {
|
|
401
|
+
return this.processReturn(stmt, blockId);
|
|
402
|
+
}
|
|
403
|
+
if (stmt instanceof solc_typed_ast_1.RevertStatement) {
|
|
404
|
+
block.terminator = { kind: 'revert', reason: this.extractRevertReason(stmt) };
|
|
405
|
+
return blockId;
|
|
406
|
+
}
|
|
407
|
+
if (stmt instanceof solc_typed_ast_1.VariableDeclarationStatement) {
|
|
408
|
+
this.processVarDecl(stmt, block);
|
|
409
|
+
return blockId;
|
|
410
|
+
}
|
|
411
|
+
if (stmt instanceof solc_typed_ast_1.ExpressionStatement) {
|
|
412
|
+
this.processExprStmt(stmt, block);
|
|
413
|
+
return blockId;
|
|
414
|
+
}
|
|
415
|
+
if (stmt instanceof solc_typed_ast_1.Block) {
|
|
416
|
+
return this.processBlock(stmt, blockId, null);
|
|
417
|
+
}
|
|
418
|
+
if (stmt instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
419
|
+
return this.processBlock(stmt, blockId, null);
|
|
420
|
+
}
|
|
421
|
+
if (stmt instanceof solc_typed_ast_1.PlaceholderStatement) {
|
|
422
|
+
block.terminator = { kind: 'placeholder' };
|
|
423
|
+
// Create continuation block
|
|
424
|
+
const nextBlock = (0, types_1.newBlock)(this.ctx);
|
|
425
|
+
this.blocks.set(nextBlock, {
|
|
426
|
+
id: nextBlock,
|
|
427
|
+
statements: [],
|
|
428
|
+
terminator: { kind: 'jump', target: '' },
|
|
429
|
+
astIds: []
|
|
430
|
+
});
|
|
431
|
+
return nextBlock;
|
|
432
|
+
}
|
|
433
|
+
// Unknown statement type - add as comment/skip
|
|
434
|
+
return blockId;
|
|
435
|
+
}
|
|
436
|
+
// ==========================================================================
|
|
437
|
+
// Control Flow
|
|
438
|
+
// ==========================================================================
|
|
439
|
+
processIf(stmt, blockId) {
|
|
440
|
+
const block = this.blocks.get(blockId);
|
|
441
|
+
// Convert condition to symbolic expression
|
|
442
|
+
const cond = this.exprToSym(stmt.vCondition);
|
|
443
|
+
// Create blocks for true/false branches
|
|
444
|
+
const trueBlockId = (0, types_1.newBlock)(this.ctx);
|
|
445
|
+
const falseBlockId = (0, types_1.newBlock)(this.ctx);
|
|
446
|
+
const mergeBlockId = (0, types_1.newBlock)(this.ctx);
|
|
447
|
+
// Initialize blocks
|
|
448
|
+
this.blocks.set(trueBlockId, {
|
|
449
|
+
id: trueBlockId,
|
|
450
|
+
statements: [],
|
|
451
|
+
terminator: { kind: 'jump', target: mergeBlockId },
|
|
452
|
+
astIds: []
|
|
453
|
+
});
|
|
454
|
+
this.blocks.set(falseBlockId, {
|
|
455
|
+
id: falseBlockId,
|
|
456
|
+
statements: [],
|
|
457
|
+
terminator: { kind: 'jump', target: mergeBlockId },
|
|
458
|
+
astIds: []
|
|
459
|
+
});
|
|
460
|
+
this.blocks.set(mergeBlockId, {
|
|
461
|
+
id: mergeBlockId,
|
|
462
|
+
statements: [],
|
|
463
|
+
terminator: { kind: 'jump', target: '' },
|
|
464
|
+
astIds: []
|
|
465
|
+
});
|
|
466
|
+
// Set branch terminator
|
|
467
|
+
block.terminator = { kind: 'branch', cond, trueBlock: trueBlockId, falseBlock: falseBlockId };
|
|
468
|
+
// Add coverage edges
|
|
469
|
+
this.addEdge(blockId, trueBlockId, cond, 'T');
|
|
470
|
+
this.addEdge(blockId, falseBlockId, { kind: 'unop', op: 'not', arg: cond }, 'F');
|
|
471
|
+
// Process true branch - handle both Block and single Statement
|
|
472
|
+
if (stmt.vTrueBody instanceof solc_typed_ast_1.Block || stmt.vTrueBody instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
473
|
+
this.processBlock(stmt.vTrueBody, trueBlockId, mergeBlockId);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
// Single statement - process directly
|
|
477
|
+
this.processStatement(stmt.vTrueBody, trueBlockId);
|
|
478
|
+
const tb = this.blocks.get(trueBlockId);
|
|
479
|
+
if (tb.terminator.kind === 'jump' && tb.terminator.target === '') {
|
|
480
|
+
tb.terminator = { kind: 'jump', target: mergeBlockId };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Process false branch if exists
|
|
484
|
+
if (stmt.vFalseBody) {
|
|
485
|
+
if (stmt.vFalseBody instanceof solc_typed_ast_1.Block || stmt.vFalseBody instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
486
|
+
this.processBlock(stmt.vFalseBody, falseBlockId, mergeBlockId);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
// Single statement
|
|
490
|
+
this.processStatement(stmt.vFalseBody, falseBlockId);
|
|
491
|
+
const fb = this.blocks.get(falseBlockId);
|
|
492
|
+
if (fb.terminator.kind === 'jump' && fb.terminator.target === '') {
|
|
493
|
+
fb.terminator = { kind: 'jump', target: mergeBlockId };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
// No else - false block jumps directly to merge
|
|
499
|
+
const fb = this.blocks.get(falseBlockId);
|
|
500
|
+
fb.terminator = { kind: 'jump', target: mergeBlockId };
|
|
501
|
+
}
|
|
502
|
+
return mergeBlockId;
|
|
503
|
+
}
|
|
504
|
+
processWhile(stmt, blockId) {
|
|
505
|
+
const block = this.blocks.get(blockId);
|
|
506
|
+
// Create blocks: header (condition check), body, exit
|
|
507
|
+
const headerBlockId = (0, types_1.newBlock)(this.ctx);
|
|
508
|
+
const bodyBlockId = (0, types_1.newBlock)(this.ctx);
|
|
509
|
+
const exitBlockId = (0, types_1.newBlock)(this.ctx);
|
|
510
|
+
// Jump to header
|
|
511
|
+
block.terminator = { kind: 'jump', target: headerBlockId };
|
|
512
|
+
this.addEdge(blockId, headerBlockId, { kind: 'bool', value: true }, 'J');
|
|
513
|
+
// Header block - condition check
|
|
514
|
+
const cond = this.exprToSym(stmt.vCondition);
|
|
515
|
+
this.blocks.set(headerBlockId, {
|
|
516
|
+
id: headerBlockId,
|
|
517
|
+
statements: [],
|
|
518
|
+
terminator: { kind: 'branch', cond, trueBlock: bodyBlockId, falseBlock: exitBlockId },
|
|
519
|
+
astIds: [stmt.vCondition.id]
|
|
520
|
+
});
|
|
521
|
+
this.addEdge(headerBlockId, bodyBlockId, cond, 'T');
|
|
522
|
+
this.addEdge(headerBlockId, exitBlockId, { kind: 'unop', op: 'not', arg: cond }, 'F');
|
|
523
|
+
// Body block
|
|
524
|
+
this.blocks.set(bodyBlockId, {
|
|
525
|
+
id: bodyBlockId,
|
|
526
|
+
statements: [],
|
|
527
|
+
terminator: { kind: 'jump', target: headerBlockId }, // Loop back
|
|
528
|
+
astIds: []
|
|
529
|
+
});
|
|
530
|
+
// Process body - handle both Block and single Statement
|
|
531
|
+
if (stmt.vBody instanceof solc_typed_ast_1.Block || stmt.vBody instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
532
|
+
this.processBlock(stmt.vBody, bodyBlockId, headerBlockId);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
this.processStatement(stmt.vBody, bodyBlockId);
|
|
536
|
+
const bb = this.blocks.get(bodyBlockId);
|
|
537
|
+
if (bb.terminator.kind === 'jump' && bb.terminator.target === '') {
|
|
538
|
+
bb.terminator = { kind: 'jump', target: headerBlockId };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Exit block
|
|
542
|
+
this.blocks.set(exitBlockId, {
|
|
543
|
+
id: exitBlockId,
|
|
544
|
+
statements: [],
|
|
545
|
+
terminator: { kind: 'jump', target: '' },
|
|
546
|
+
astIds: []
|
|
547
|
+
});
|
|
548
|
+
// Record loop info for symbolic reasoning
|
|
549
|
+
const loopId = `L${this.loopCounter++}`;
|
|
550
|
+
this.loops.push({
|
|
551
|
+
id: loopId,
|
|
552
|
+
headerBlock: headerBlockId,
|
|
553
|
+
exitBlock: exitBlockId,
|
|
554
|
+
condition: cond,
|
|
555
|
+
bodyBlocks: [bodyBlockId]
|
|
556
|
+
});
|
|
557
|
+
return exitBlockId;
|
|
558
|
+
}
|
|
559
|
+
processFor(stmt, blockId) {
|
|
560
|
+
const block = this.blocks.get(blockId);
|
|
561
|
+
// For loop: init; cond; post { body }
|
|
562
|
+
// Try to extract induction variable from init
|
|
563
|
+
let inductionVar;
|
|
564
|
+
let startValue;
|
|
565
|
+
// Process init in current block
|
|
566
|
+
if (stmt.vInitializationExpression) {
|
|
567
|
+
if (stmt.vInitializationExpression instanceof solc_typed_ast_1.VariableDeclarationStatement) {
|
|
568
|
+
const decls = stmt.vInitializationExpression.vDeclarations;
|
|
569
|
+
if (decls.length === 1) {
|
|
570
|
+
inductionVar = (0, types_1.currentSSA)(this.ctx, decls[0].name);
|
|
571
|
+
if (stmt.vInitializationExpression.vInitialValue) {
|
|
572
|
+
startValue = this.exprToSym(stmt.vInitializationExpression.vInitialValue);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
this.processVarDecl(stmt.vInitializationExpression, block);
|
|
576
|
+
// Update inductionVar to the SSA version after processing
|
|
577
|
+
if (decls.length === 1) {
|
|
578
|
+
inductionVar = (0, types_1.currentSSA)(this.ctx, decls[0].name);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (stmt.vInitializationExpression instanceof solc_typed_ast_1.ExpressionStatement) {
|
|
582
|
+
this.processExprStmt(stmt.vInitializationExpression, block);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// Create blocks
|
|
586
|
+
const headerBlockId = (0, types_1.newBlock)(this.ctx);
|
|
587
|
+
const bodyBlockId = (0, types_1.newBlock)(this.ctx);
|
|
588
|
+
const postBlockId = (0, types_1.newBlock)(this.ctx);
|
|
589
|
+
const exitBlockId = (0, types_1.newBlock)(this.ctx);
|
|
590
|
+
// Jump to header
|
|
591
|
+
block.terminator = { kind: 'jump', target: headerBlockId };
|
|
592
|
+
this.addEdge(blockId, headerBlockId, { kind: 'bool', value: true }, 'J');
|
|
593
|
+
// Header - condition check
|
|
594
|
+
const cond = stmt.vCondition
|
|
595
|
+
? this.exprToSym(stmt.vCondition)
|
|
596
|
+
: { kind: 'bool', value: true };
|
|
597
|
+
// Try to extract bound from condition (e.g., i < n)
|
|
598
|
+
let bound;
|
|
599
|
+
if (cond.kind === 'binop' && (cond.op === 'lt' || cond.op === 'le')) {
|
|
600
|
+
bound = cond.right;
|
|
601
|
+
}
|
|
602
|
+
this.blocks.set(headerBlockId, {
|
|
603
|
+
id: headerBlockId,
|
|
604
|
+
statements: [],
|
|
605
|
+
terminator: { kind: 'branch', cond, trueBlock: bodyBlockId, falseBlock: exitBlockId },
|
|
606
|
+
astIds: stmt.vCondition ? [stmt.vCondition.id] : []
|
|
607
|
+
});
|
|
608
|
+
this.addEdge(headerBlockId, bodyBlockId, cond, 'T');
|
|
609
|
+
this.addEdge(headerBlockId, exitBlockId, { kind: 'unop', op: 'not', arg: cond }, 'F');
|
|
610
|
+
// Body
|
|
611
|
+
this.blocks.set(bodyBlockId, {
|
|
612
|
+
id: bodyBlockId,
|
|
613
|
+
statements: [],
|
|
614
|
+
terminator: { kind: 'jump', target: postBlockId },
|
|
615
|
+
astIds: []
|
|
616
|
+
});
|
|
617
|
+
// Handle both Block and single statement bodies
|
|
618
|
+
if (stmt.vBody instanceof solc_typed_ast_1.Block || stmt.vBody instanceof solc_typed_ast_1.UncheckedBlock) {
|
|
619
|
+
this.processBlock(stmt.vBody, bodyBlockId, postBlockId);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
// Single statement body (not a block)
|
|
623
|
+
this.processStatement(stmt.vBody, bodyBlockId);
|
|
624
|
+
// Fix terminator if still empty
|
|
625
|
+
const bb = this.blocks.get(bodyBlockId);
|
|
626
|
+
if (bb.terminator.kind === 'jump' && bb.terminator.target === postBlockId) {
|
|
627
|
+
// Already correct
|
|
628
|
+
}
|
|
629
|
+
else if (bb.terminator.kind === 'jump' && bb.terminator.target === '') {
|
|
630
|
+
bb.terminator = { kind: 'jump', target: postBlockId };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Post (loop increment)
|
|
634
|
+
this.blocks.set(postBlockId, {
|
|
635
|
+
id: postBlockId,
|
|
636
|
+
statements: [],
|
|
637
|
+
terminator: { kind: 'jump', target: headerBlockId },
|
|
638
|
+
astIds: []
|
|
639
|
+
});
|
|
640
|
+
if (stmt.vLoopExpression) {
|
|
641
|
+
this.processExprStmt(stmt.vLoopExpression, this.blocks.get(postBlockId));
|
|
642
|
+
}
|
|
643
|
+
// Exit
|
|
644
|
+
this.blocks.set(exitBlockId, {
|
|
645
|
+
id: exitBlockId,
|
|
646
|
+
statements: [],
|
|
647
|
+
terminator: { kind: 'jump', target: '' },
|
|
648
|
+
astIds: []
|
|
649
|
+
});
|
|
650
|
+
// Record loop info for symbolic reasoning
|
|
651
|
+
const loopId = `L${this.loopCounter++}`;
|
|
652
|
+
this.loops.push({
|
|
653
|
+
id: loopId,
|
|
654
|
+
headerBlock: headerBlockId,
|
|
655
|
+
exitBlock: exitBlockId,
|
|
656
|
+
condition: cond,
|
|
657
|
+
inductionVar,
|
|
658
|
+
bound,
|
|
659
|
+
bodyBlocks: [bodyBlockId, postBlockId]
|
|
660
|
+
});
|
|
661
|
+
return exitBlockId;
|
|
662
|
+
}
|
|
663
|
+
processReturn(stmt, blockId) {
|
|
664
|
+
const block = this.blocks.get(blockId);
|
|
665
|
+
const values = [];
|
|
666
|
+
if (stmt.vExpression) {
|
|
667
|
+
if (stmt.vExpression instanceof solc_typed_ast_1.TupleExpression) {
|
|
668
|
+
for (const comp of stmt.vExpression.vComponents) {
|
|
669
|
+
if (comp)
|
|
670
|
+
values.push(this.exprToSym(comp));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
values.push(this.exprToSym(stmt.vExpression));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
block.terminator = { kind: 'return', values };
|
|
678
|
+
return blockId;
|
|
679
|
+
}
|
|
680
|
+
// ==========================================================================
|
|
681
|
+
// Variable/Expression Processing
|
|
682
|
+
// ==========================================================================
|
|
683
|
+
processVarDecl(stmt, block) {
|
|
684
|
+
const decls = stmt.vDeclarations;
|
|
685
|
+
const init = stmt.vInitialValue;
|
|
686
|
+
if (decls.length === 1 && init) {
|
|
687
|
+
const decl = decls[0];
|
|
688
|
+
const type = decl.typeString || 'uint256';
|
|
689
|
+
const name = (0, types_1.freshSSA)(this.ctx, decl.name, type);
|
|
690
|
+
const expr = this.exprToSym(init);
|
|
691
|
+
block.statements.push({ kind: 'let', name, type, expr });
|
|
692
|
+
}
|
|
693
|
+
else if (decls.length > 1 && init) {
|
|
694
|
+
// Tuple assignment
|
|
695
|
+
for (let i = 0; i < decls.length; i++) {
|
|
696
|
+
const decl = decls[i];
|
|
697
|
+
if (decl) {
|
|
698
|
+
const type = decl.typeString || 'uint256';
|
|
699
|
+
const name = (0, types_1.freshSSA)(this.ctx, decl.name, type);
|
|
700
|
+
// Extract i-th component
|
|
701
|
+
const expr = {
|
|
702
|
+
kind: 'field',
|
|
703
|
+
base: this.exprToSym(init),
|
|
704
|
+
field: `_${i}`
|
|
705
|
+
};
|
|
706
|
+
block.statements.push({ kind: 'let', name, type, expr });
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
processExprStmt(stmt, block) {
|
|
712
|
+
const expr = stmt.vExpression;
|
|
713
|
+
if (expr instanceof solc_typed_ast_1.Assignment) {
|
|
714
|
+
this.processAssignment(expr, block);
|
|
715
|
+
}
|
|
716
|
+
else if (expr instanceof solc_typed_ast_1.FunctionCall) {
|
|
717
|
+
this.processFunctionCall(expr, block);
|
|
718
|
+
}
|
|
719
|
+
else if (expr instanceof solc_typed_ast_1.UnaryOperation) {
|
|
720
|
+
// Could be ++/-- which modify state
|
|
721
|
+
this.processUnaryOp(expr, block);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
processAssignment(expr, block) {
|
|
725
|
+
const lhs = expr.vLeftHandSide;
|
|
726
|
+
const rhs = this.exprToSym(expr.vRightHandSide);
|
|
727
|
+
if (lhs instanceof solc_typed_ast_1.Identifier) {
|
|
728
|
+
// Simple variable assignment
|
|
729
|
+
const type = lhs.typeString || 'uint256';
|
|
730
|
+
// For compound assignments (+=, -=, etc.), get the OLD SSA name BEFORE incrementing
|
|
731
|
+
const oldName = (0, types_1.currentSSA)(this.ctx, lhs.name);
|
|
732
|
+
// Now create the new SSA version
|
|
733
|
+
const name = (0, types_1.freshSSA)(this.ctx, lhs.name, type);
|
|
734
|
+
// Handle compound assignments (+=, -=, etc.)
|
|
735
|
+
let value = rhs;
|
|
736
|
+
if (expr.operator !== '=') {
|
|
737
|
+
const op = this.compoundOpToBinOp(expr.operator);
|
|
738
|
+
if (op) {
|
|
739
|
+
value = {
|
|
740
|
+
kind: 'binop',
|
|
741
|
+
op,
|
|
742
|
+
left: { kind: 'var', name: oldName, type }, // Use the OLD version
|
|
743
|
+
right: rhs,
|
|
744
|
+
checked: !this.inUnchecked,
|
|
745
|
+
signed: this.isSigned(type)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
block.statements.push({ kind: 'let', name, type, expr: value });
|
|
750
|
+
}
|
|
751
|
+
else if (lhs instanceof solc_typed_ast_1.IndexAccess) {
|
|
752
|
+
// Array/mapping assignment: arr[idx] = value or arr[idx] += value
|
|
753
|
+
const array = this.exprToSym(lhs.vBaseExpression);
|
|
754
|
+
const index = this.exprToSym(lhs.vIndexExpression);
|
|
755
|
+
// Handle compound assignments (+=, -=, etc.)
|
|
756
|
+
let value = rhs;
|
|
757
|
+
if (expr.operator !== '=') {
|
|
758
|
+
const op = this.compoundOpToBinOp(expr.operator);
|
|
759
|
+
if (op) {
|
|
760
|
+
// For compound assignment, need to read the old value first
|
|
761
|
+
const oldValue = { kind: 'select', array, index };
|
|
762
|
+
value = {
|
|
763
|
+
kind: 'binop',
|
|
764
|
+
op,
|
|
765
|
+
left: oldValue,
|
|
766
|
+
right: rhs,
|
|
767
|
+
checked: !this.inUnchecked,
|
|
768
|
+
signed: this.isSigned(lhs.typeString || 'uint256')
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
block.statements.push({ kind: 'store', target: array, index, value });
|
|
773
|
+
}
|
|
774
|
+
else if (lhs instanceof solc_typed_ast_1.MemberAccess) {
|
|
775
|
+
// Struct field or state var via this
|
|
776
|
+
const base = this.exprToSym(lhs.vExpression);
|
|
777
|
+
const index = { kind: 'lit', value: lhs.memberName, type: 'string' };
|
|
778
|
+
// Handle compound assignment (+=, -=, etc.) for member access
|
|
779
|
+
let value = rhs;
|
|
780
|
+
if (expr.operator !== '=') {
|
|
781
|
+
const op = this.compoundOpToBinOp(expr.operator);
|
|
782
|
+
if (op) {
|
|
783
|
+
const oldValue = { kind: 'select', array: base, index };
|
|
784
|
+
value = {
|
|
785
|
+
kind: 'binop',
|
|
786
|
+
op,
|
|
787
|
+
left: oldValue,
|
|
788
|
+
right: rhs,
|
|
789
|
+
checked: !this.inUnchecked,
|
|
790
|
+
signed: this.isSigned(lhs.typeString || 'uint256')
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// Model as store with field name as "index"
|
|
795
|
+
block.statements.push({
|
|
796
|
+
kind: 'store',
|
|
797
|
+
target: base,
|
|
798
|
+
index,
|
|
799
|
+
value
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
processFunctionCall(call, block) {
|
|
804
|
+
// Check if it's require/assert
|
|
805
|
+
if (call.vExpression instanceof solc_typed_ast_1.Identifier) {
|
|
806
|
+
const name = call.vExpression.name;
|
|
807
|
+
if (name === 'require' || name === 'assert') {
|
|
808
|
+
const cond = this.exprToSym(call.vArguments[0]);
|
|
809
|
+
const reason = call.vArguments.length > 1
|
|
810
|
+
? this.extractStringLiteral(call.vArguments[1])
|
|
811
|
+
: undefined;
|
|
812
|
+
// Generate a revert edge ID for tracking
|
|
813
|
+
const revertEdgeId = `${this.currentFunction}_${name}${this.ctx.edgeCounter++}_revert`;
|
|
814
|
+
block.statements.push({ kind: 'assume', cond, reason, revertEdge: revertEdgeId });
|
|
815
|
+
// Add coverage edges for require/assert conditions
|
|
816
|
+
// These allow the solver to target specific require failures
|
|
817
|
+
// T edge: condition is true (continue execution)
|
|
818
|
+
// F edge: condition is false (revert) - this is the revert edge
|
|
819
|
+
this.edges.push({
|
|
820
|
+
id: `${this.currentFunction}_B${block.id.slice(1)}_${name}${this.ctx.edgeCounter - 1}_T`,
|
|
821
|
+
from: block.id,
|
|
822
|
+
to: block.id, // Same block - continue execution
|
|
823
|
+
condition: cond,
|
|
824
|
+
functionName: this.currentFunction,
|
|
825
|
+
astId: call.id
|
|
826
|
+
});
|
|
827
|
+
this.edges.push({
|
|
828
|
+
id: revertEdgeId.replace('_revert', '_F'), // Use consistent naming
|
|
829
|
+
from: block.id,
|
|
830
|
+
to: 'REVERT', // Special marker for revert
|
|
831
|
+
condition: { kind: 'unop', op: 'not', arg: cond },
|
|
832
|
+
functionName: this.currentFunction,
|
|
833
|
+
astId: call.id
|
|
834
|
+
});
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Regular function call
|
|
839
|
+
const sym = this.exprToSym(call);
|
|
840
|
+
if (sym.kind === 'call' || sym.kind === 'external_return') {
|
|
841
|
+
block.statements.push({ kind: 'call', expr: sym });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
processUnaryOp(expr, block) {
|
|
845
|
+
// Handle ++/--
|
|
846
|
+
if (expr.operator === '++' || expr.operator === '--') {
|
|
847
|
+
const operand = expr.vSubExpression;
|
|
848
|
+
if (operand instanceof solc_typed_ast_1.Identifier) {
|
|
849
|
+
const type = operand.typeString || 'uint256';
|
|
850
|
+
const oldName = (0, types_1.currentSSA)(this.ctx, operand.name);
|
|
851
|
+
const newName = (0, types_1.freshSSA)(this.ctx, operand.name, type);
|
|
852
|
+
const op = expr.operator === '++' ? 'add' : 'sub';
|
|
853
|
+
block.statements.push({
|
|
854
|
+
kind: 'let',
|
|
855
|
+
name: newName,
|
|
856
|
+
type,
|
|
857
|
+
expr: {
|
|
858
|
+
kind: 'binop',
|
|
859
|
+
op,
|
|
860
|
+
left: { kind: 'var', name: oldName, type },
|
|
861
|
+
right: { kind: 'lit', value: '1', type },
|
|
862
|
+
checked: !this.inUnchecked,
|
|
863
|
+
signed: this.isSigned(type)
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// ==========================================================================
|
|
870
|
+
// Expression to Symbolic
|
|
871
|
+
// ==========================================================================
|
|
872
|
+
exprToSym(expr) {
|
|
873
|
+
if (expr instanceof solc_typed_ast_1.Identifier) {
|
|
874
|
+
const type = expr.typeString || 'uint256';
|
|
875
|
+
return { kind: 'var', name: (0, types_1.currentSSA)(this.ctx, expr.name), type };
|
|
876
|
+
}
|
|
877
|
+
if (expr instanceof solc_typed_ast_1.Literal) {
|
|
878
|
+
const type = expr.typeString || 'uint256';
|
|
879
|
+
const value = expr.value || '0';
|
|
880
|
+
if (type === 'bool') {
|
|
881
|
+
return { kind: 'bool', value: value === 'true' };
|
|
882
|
+
}
|
|
883
|
+
// Convert to hex if numeric
|
|
884
|
+
if (/^\d+$/.test(value)) {
|
|
885
|
+
return { kind: 'lit', value: '0x' + BigInt(value).toString(16), type };
|
|
886
|
+
}
|
|
887
|
+
return { kind: 'lit', value, type };
|
|
888
|
+
}
|
|
889
|
+
if (expr instanceof solc_typed_ast_1.BinaryOperation) {
|
|
890
|
+
const op = this.solOpToBinOp(expr.operator);
|
|
891
|
+
if (!op) {
|
|
892
|
+
return { kind: 'lit', value: '0', type: 'uint256' }; // Unsupported
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
kind: 'binop',
|
|
896
|
+
op,
|
|
897
|
+
left: this.exprToSym(expr.vLeftExpression),
|
|
898
|
+
right: this.exprToSym(expr.vRightExpression),
|
|
899
|
+
checked: !this.inUnchecked,
|
|
900
|
+
signed: this.isSigned(expr.typeString || '')
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (expr instanceof solc_typed_ast_1.UnaryOperation) {
|
|
904
|
+
const op = this.solOpToUnOp(expr.operator);
|
|
905
|
+
if (op) {
|
|
906
|
+
return { kind: 'unop', op, arg: this.exprToSym(expr.vSubExpression) };
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (expr instanceof solc_typed_ast_1.IndexAccess) {
|
|
910
|
+
return {
|
|
911
|
+
kind: 'select',
|
|
912
|
+
array: this.exprToSym(expr.vBaseExpression),
|
|
913
|
+
index: this.exprToSym(expr.vIndexExpression)
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (expr instanceof solc_typed_ast_1.MemberAccess) {
|
|
917
|
+
// Check for msg.sender, block.timestamp, etc.
|
|
918
|
+
if (expr.vExpression instanceof solc_typed_ast_1.Identifier) {
|
|
919
|
+
const base = expr.vExpression.name;
|
|
920
|
+
if (base === 'msg' || base === 'block' || base === 'tx') {
|
|
921
|
+
return { kind: 'var', name: `${base}_${expr.memberName}`, type: this.getEnvType(base, expr.memberName) };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
kind: 'field',
|
|
926
|
+
base: this.exprToSym(expr.vExpression),
|
|
927
|
+
field: expr.memberName
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
if (expr instanceof solc_typed_ast_1.Conditional) {
|
|
931
|
+
// Generate coverage edges for ternary expressions
|
|
932
|
+
const cond = this.exprToSym(expr.vCondition);
|
|
933
|
+
const ternaryId = this.ternaryCounter++;
|
|
934
|
+
const trueEdgeId = `${this.currentFunction}_ternary${ternaryId}_T`;
|
|
935
|
+
const falseEdgeId = `${this.currentFunction}_ternary${ternaryId}_F`;
|
|
936
|
+
// Add ternary edges for coverage tracking
|
|
937
|
+
this.ternaryEdges.push({
|
|
938
|
+
id: trueEdgeId,
|
|
939
|
+
from: `ternary${ternaryId}`,
|
|
940
|
+
to: 'then',
|
|
941
|
+
condition: cond,
|
|
942
|
+
functionName: this.currentFunction,
|
|
943
|
+
astId: expr.id
|
|
944
|
+
});
|
|
945
|
+
this.ternaryEdges.push({
|
|
946
|
+
id: falseEdgeId,
|
|
947
|
+
from: `ternary${ternaryId}`,
|
|
948
|
+
to: 'else',
|
|
949
|
+
condition: { kind: 'unop', op: 'not', arg: cond },
|
|
950
|
+
functionName: this.currentFunction,
|
|
951
|
+
astId: expr.id
|
|
952
|
+
});
|
|
953
|
+
return {
|
|
954
|
+
kind: 'ite',
|
|
955
|
+
cond,
|
|
956
|
+
then: this.exprToSym(expr.vTrueExpression),
|
|
957
|
+
else: this.exprToSym(expr.vFalseExpression)
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
if (expr instanceof solc_typed_ast_1.FunctionCall) {
|
|
961
|
+
return this.functionCallToSym(expr);
|
|
962
|
+
}
|
|
963
|
+
if (expr instanceof solc_typed_ast_1.TupleExpression) {
|
|
964
|
+
// Single element tuple - unwrap
|
|
965
|
+
const comps = expr.vComponents.filter(c => c !== null);
|
|
966
|
+
if (comps.length === 1) {
|
|
967
|
+
return this.exprToSym(comps[0]);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
// Fallback
|
|
971
|
+
return { kind: 'lit', value: '0', type: 'uint256' };
|
|
972
|
+
}
|
|
973
|
+
functionCallToSym(call) {
|
|
974
|
+
// Check for keccak256
|
|
975
|
+
if (call.vExpression instanceof solc_typed_ast_1.Identifier && call.vExpression.name === 'keccak256') {
|
|
976
|
+
this.hasKeccak = true;
|
|
977
|
+
return { kind: 'keccak', arg: this.exprToSym(call.vArguments[0]) };
|
|
978
|
+
}
|
|
979
|
+
// Check for type conversion
|
|
980
|
+
if (call.kind === solc_typed_ast_1.FunctionCallKind.TypeConversion) {
|
|
981
|
+
const toType = call.typeString || 'uint256';
|
|
982
|
+
return { kind: 'cast', toType, arg: this.exprToSym(call.vArguments[0]) };
|
|
983
|
+
}
|
|
984
|
+
// Check if external call
|
|
985
|
+
if (this.isExternalCall(call)) {
|
|
986
|
+
const { target, method } = this.extractCallTarget(call);
|
|
987
|
+
const args = call.vArguments.map(a => this.exprToSym(a));
|
|
988
|
+
const ssaId = (0, types_1.freshSSA)(this.ctx, `ext_${target}_${method.split('(')[0]}`, call.typeString || 'uint256');
|
|
989
|
+
this.externalCalls.push({ target, method, ssaVar: ssaId });
|
|
990
|
+
return {
|
|
991
|
+
kind: 'external_return',
|
|
992
|
+
target,
|
|
993
|
+
method,
|
|
994
|
+
args,
|
|
995
|
+
ssaId
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
// Internal/library call
|
|
999
|
+
const { target, method } = this.extractCallTarget(call);
|
|
1000
|
+
const args = call.vArguments.map(a => this.exprToSym(a));
|
|
1001
|
+
return {
|
|
1002
|
+
kind: 'call',
|
|
1003
|
+
target,
|
|
1004
|
+
method,
|
|
1005
|
+
args,
|
|
1006
|
+
callType: this.getCallType(call)
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
// ==========================================================================
|
|
1010
|
+
// Path Enumeration
|
|
1011
|
+
// ==========================================================================
|
|
1012
|
+
/**
|
|
1013
|
+
* Enumerate all paths from entry to any terminal block (return/revert)
|
|
1014
|
+
*/
|
|
1015
|
+
enumeratePaths(entryBlock) {
|
|
1016
|
+
const worklist = [{
|
|
1017
|
+
currentBlock: entryBlock,
|
|
1018
|
+
edges: [],
|
|
1019
|
+
condition: { kind: 'bool', value: true }
|
|
1020
|
+
}];
|
|
1021
|
+
const visited = new Set();
|
|
1022
|
+
const maxPaths = 100; // Keep paths small - edges are the key metric
|
|
1023
|
+
const maxDepth = 50; // Limit path depth to avoid loop explosion
|
|
1024
|
+
while (worklist.length > 0 && this.paths.length < maxPaths) {
|
|
1025
|
+
const state = worklist.pop();
|
|
1026
|
+
const block = this.blocks.get(state.currentBlock);
|
|
1027
|
+
if (!block)
|
|
1028
|
+
continue;
|
|
1029
|
+
// Skip if path is too deep (loop protection)
|
|
1030
|
+
if (state.edges.length > maxDepth)
|
|
1031
|
+
continue;
|
|
1032
|
+
// Check for loops - simple cycle detection
|
|
1033
|
+
const pathKey = state.edges.join(',') + ':' + state.currentBlock;
|
|
1034
|
+
if (visited.has(pathKey))
|
|
1035
|
+
continue;
|
|
1036
|
+
visited.add(pathKey);
|
|
1037
|
+
const term = block.terminator;
|
|
1038
|
+
if (term.kind === 'return') {
|
|
1039
|
+
// Complete path - success
|
|
1040
|
+
this.paths.push({
|
|
1041
|
+
id: (0, types_1.newPath)(this.ctx),
|
|
1042
|
+
edges: state.edges,
|
|
1043
|
+
entryBlock,
|
|
1044
|
+
exitBlock: state.currentBlock,
|
|
1045
|
+
pathCondition: state.condition,
|
|
1046
|
+
outcome: 'success',
|
|
1047
|
+
solvable: !this.hasKeccak
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
else if (term.kind === 'revert') {
|
|
1051
|
+
// Complete path - revert
|
|
1052
|
+
this.paths.push({
|
|
1053
|
+
id: (0, types_1.newPath)(this.ctx),
|
|
1054
|
+
edges: state.edges,
|
|
1055
|
+
entryBlock,
|
|
1056
|
+
exitBlock: state.currentBlock,
|
|
1057
|
+
pathCondition: state.condition,
|
|
1058
|
+
outcome: 'revert',
|
|
1059
|
+
solvable: !this.hasKeccak
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
else if (term.kind === 'branch') {
|
|
1063
|
+
// Fork into two paths
|
|
1064
|
+
const trueEdgeId = `${this.currentFunction}_${state.currentBlock}_T`;
|
|
1065
|
+
const falseEdgeId = `${this.currentFunction}_${state.currentBlock}_F`;
|
|
1066
|
+
// True branch
|
|
1067
|
+
worklist.push({
|
|
1068
|
+
currentBlock: term.trueBlock,
|
|
1069
|
+
edges: [...state.edges, trueEdgeId],
|
|
1070
|
+
condition: this.andConditions(state.condition, term.cond)
|
|
1071
|
+
});
|
|
1072
|
+
// False branch
|
|
1073
|
+
worklist.push({
|
|
1074
|
+
currentBlock: term.falseBlock,
|
|
1075
|
+
edges: [...state.edges, falseEdgeId],
|
|
1076
|
+
condition: this.andConditions(state.condition, { kind: 'unop', op: 'not', arg: term.cond })
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
else if (term.kind === 'jump') {
|
|
1080
|
+
// Follow jump
|
|
1081
|
+
if (term.target) {
|
|
1082
|
+
worklist.push({
|
|
1083
|
+
currentBlock: term.target,
|
|
1084
|
+
edges: state.edges,
|
|
1085
|
+
condition: state.condition
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
andConditions(a, b) {
|
|
1092
|
+
if (a.kind === 'bool' && a.value === true)
|
|
1093
|
+
return b;
|
|
1094
|
+
if (b.kind === 'bool' && b.value === true)
|
|
1095
|
+
return a;
|
|
1096
|
+
return { kind: 'binop', op: 'land', left: a, right: b, checked: false, signed: false };
|
|
1097
|
+
}
|
|
1098
|
+
// ==========================================================================
|
|
1099
|
+
// Helper Methods
|
|
1100
|
+
// ==========================================================================
|
|
1101
|
+
addEdge(from, to, condition, dir) {
|
|
1102
|
+
var _a;
|
|
1103
|
+
const id = (0, types_1.newEdge)(this.ctx, this.currentFunction, from, dir);
|
|
1104
|
+
this.edges.push({
|
|
1105
|
+
id,
|
|
1106
|
+
from,
|
|
1107
|
+
to,
|
|
1108
|
+
condition,
|
|
1109
|
+
functionName: this.currentFunction,
|
|
1110
|
+
astId: (_a = this.blocks.get(from)) === null || _a === void 0 ? void 0 : _a.astIds[0]
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
isExternalCall(call) {
|
|
1114
|
+
// Check for .call, .delegatecall, or cross-contract calls
|
|
1115
|
+
if (call.vExpression instanceof solc_typed_ast_1.MemberAccess) {
|
|
1116
|
+
const memberName = call.vExpression.memberName;
|
|
1117
|
+
if (memberName === 'call' || memberName === 'delegatecall' || memberName === 'staticcall') {
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
// Check if calling a different contract
|
|
1121
|
+
const baseType = call.vExpression.vExpression.typeString || '';
|
|
1122
|
+
if (baseType.startsWith('contract ') || baseType.startsWith('interface ')) {
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
extractCallTarget(call) {
|
|
1129
|
+
if (call.vExpression instanceof solc_typed_ast_1.MemberAccess) {
|
|
1130
|
+
const base = call.vExpression.vExpression;
|
|
1131
|
+
const method = call.vExpression.memberName;
|
|
1132
|
+
let target = 'unknown';
|
|
1133
|
+
if (base instanceof solc_typed_ast_1.Identifier) {
|
|
1134
|
+
target = base.name;
|
|
1135
|
+
// Resolve 'super' to actual contract in linearization
|
|
1136
|
+
if (target === 'super' && this.contract) {
|
|
1137
|
+
target = this.resolveSuperTarget(method);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
try {
|
|
1142
|
+
target = (0, utils_1.toSource)(base).slice(0, 32);
|
|
1143
|
+
}
|
|
1144
|
+
catch { }
|
|
1145
|
+
}
|
|
1146
|
+
return { target, method };
|
|
1147
|
+
}
|
|
1148
|
+
if (call.vExpression instanceof solc_typed_ast_1.Identifier) {
|
|
1149
|
+
return { target: 'this', method: call.vExpression.name };
|
|
1150
|
+
}
|
|
1151
|
+
return { target: 'unknown', method: 'unknown' };
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Resolve super.method() to actual contract name using C3 linearization
|
|
1155
|
+
*/
|
|
1156
|
+
resolveSuperTarget(methodName) {
|
|
1157
|
+
if (!this.contract)
|
|
1158
|
+
return 'super';
|
|
1159
|
+
// Get linearized base contracts (skip self which is first)
|
|
1160
|
+
const linearized = this.contract.vLinearizedBaseContracts;
|
|
1161
|
+
for (let i = 1; i < linearized.length; i++) {
|
|
1162
|
+
const baseContract = linearized[i];
|
|
1163
|
+
// Check if this base contract has the method
|
|
1164
|
+
for (const fn of baseContract.vFunctions) {
|
|
1165
|
+
if (fn.name === methodName) {
|
|
1166
|
+
return baseContract.name;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return 'super'; // Fallback if not found
|
|
1171
|
+
}
|
|
1172
|
+
getCallType(call) {
|
|
1173
|
+
if (this.isExternalCall(call))
|
|
1174
|
+
return 'external';
|
|
1175
|
+
// Could add more sophisticated detection
|
|
1176
|
+
return 'internal';
|
|
1177
|
+
}
|
|
1178
|
+
extractInternalCalls(callTree) {
|
|
1179
|
+
const calls = [];
|
|
1180
|
+
const visit = (node, parentContract) => {
|
|
1181
|
+
var _a;
|
|
1182
|
+
let callType = 'internal';
|
|
1183
|
+
let contractName = this.contract.name;
|
|
1184
|
+
if (node.definition instanceof solc_typed_ast_1.FunctionDefinition) {
|
|
1185
|
+
const funcDef = node.definition;
|
|
1186
|
+
const target = (0, utils_1.getSignature)(funcDef);
|
|
1187
|
+
// Determine the contract this function belongs to
|
|
1188
|
+
if (funcDef.vScope && funcDef.vScope instanceof solc_typed_ast_1.ContractDefinition) {
|
|
1189
|
+
contractName = funcDef.vScope.name;
|
|
1190
|
+
}
|
|
1191
|
+
// Detect call type
|
|
1192
|
+
if (funcDef.isConstructor) {
|
|
1193
|
+
callType = 'constructor';
|
|
1194
|
+
}
|
|
1195
|
+
else if (funcDef.vScope && funcDef.vScope instanceof solc_typed_ast_1.ContractDefinition) {
|
|
1196
|
+
const funcContract = funcDef.vScope;
|
|
1197
|
+
// Check if it's a library call
|
|
1198
|
+
if (funcContract.kind === 'library') {
|
|
1199
|
+
callType = 'library';
|
|
1200
|
+
}
|
|
1201
|
+
// Check if it's a super call (function from parent contract)
|
|
1202
|
+
else if (funcContract.name !== this.contract.name &&
|
|
1203
|
+
((_a = this.contract.vLinearizedBaseContracts) === null || _a === void 0 ? void 0 : _a.some(c => c && c.id === funcContract.id))) {
|
|
1204
|
+
callType = 'super';
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
// Check if this is via ModifierInvocation (super constructor or base call)
|
|
1208
|
+
if (node.fnCall && 'vModifierName' in node.fnCall) {
|
|
1209
|
+
if (funcDef.isConstructor) {
|
|
1210
|
+
callType = 'constructor';
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
callType = 'super';
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
const args = node.callArgs.map(a => this.exprToSym(a));
|
|
1217
|
+
calls.push({ target, contract: contractName, callType, args });
|
|
1218
|
+
}
|
|
1219
|
+
else if (node.definition instanceof solc_typed_ast_1.ModifierDefinition) {
|
|
1220
|
+
const modDef = node.definition;
|
|
1221
|
+
const target = modDef.name;
|
|
1222
|
+
// Get contract for modifier
|
|
1223
|
+
if (modDef.vScope && modDef.vScope instanceof solc_typed_ast_1.ContractDefinition) {
|
|
1224
|
+
contractName = modDef.vScope.name;
|
|
1225
|
+
}
|
|
1226
|
+
const args = node.callArgs.map(a => this.exprToSym(a));
|
|
1227
|
+
calls.push({ target, contract: contractName, callType: 'modifier', args });
|
|
1228
|
+
}
|
|
1229
|
+
for (const child of node.children) {
|
|
1230
|
+
visit(child, contractName);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
for (const child of callTree.children) {
|
|
1234
|
+
visit(child);
|
|
1235
|
+
}
|
|
1236
|
+
return calls;
|
|
1237
|
+
}
|
|
1238
|
+
computeSelector(func) {
|
|
1239
|
+
// Compute proper Solidity function selector using keccak256
|
|
1240
|
+
const sig = (0, utils_1.getSignature)(func);
|
|
1241
|
+
const hash = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(sig));
|
|
1242
|
+
return hash.slice(0, 10); // First 4 bytes = 8 hex chars + '0x'
|
|
1243
|
+
}
|
|
1244
|
+
solOpToBinOp(op) {
|
|
1245
|
+
const map = {
|
|
1246
|
+
'+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod', '**': 'exp',
|
|
1247
|
+
'==': 'eq', '!=': 'ne', '<': 'lt', '<=': 'le', '>': 'gt', '>=': 'ge',
|
|
1248
|
+
'&': 'and', '|': 'or', '^': 'xor', '<<': 'shl', '>>': 'shr',
|
|
1249
|
+
'&&': 'land', '||': 'lor'
|
|
1250
|
+
};
|
|
1251
|
+
return map[op] || null;
|
|
1252
|
+
}
|
|
1253
|
+
compoundOpToBinOp(op) {
|
|
1254
|
+
const map = {
|
|
1255
|
+
'+=': 'add', '-=': 'sub', '*=': 'mul', '/=': 'div', '%=': 'mod',
|
|
1256
|
+
'&=': 'and', '|=': 'or', '^=': 'xor', '<<=': 'shl', '>>=': 'shr'
|
|
1257
|
+
};
|
|
1258
|
+
return map[op] || null;
|
|
1259
|
+
}
|
|
1260
|
+
solOpToUnOp(op) {
|
|
1261
|
+
const map = {
|
|
1262
|
+
'!': 'not', '-': 'neg', '~': 'bnot'
|
|
1263
|
+
};
|
|
1264
|
+
return map[op] || null;
|
|
1265
|
+
}
|
|
1266
|
+
isSigned(type) {
|
|
1267
|
+
return /^int\d*$/.test(type.trim());
|
|
1268
|
+
}
|
|
1269
|
+
getEnvType(base, member) {
|
|
1270
|
+
var _a;
|
|
1271
|
+
const types = {
|
|
1272
|
+
msg: { sender: 'address', value: 'uint256', data: 'bytes', sig: 'bytes4' },
|
|
1273
|
+
block: { timestamp: 'uint256', number: 'uint256', basefee: 'uint256', chainid: 'uint256', coinbase: 'address' },
|
|
1274
|
+
tx: { origin: 'address', gasprice: 'uint256' }
|
|
1275
|
+
};
|
|
1276
|
+
return ((_a = types[base]) === null || _a === void 0 ? void 0 : _a[member]) || 'uint256';
|
|
1277
|
+
}
|
|
1278
|
+
extractRevertReason(stmt) {
|
|
1279
|
+
var _a;
|
|
1280
|
+
try {
|
|
1281
|
+
if (((_a = stmt.errorCall) === null || _a === void 0 ? void 0 : _a.vArguments.length) > 0) {
|
|
1282
|
+
return this.extractStringLiteral(stmt.errorCall.vArguments[0]);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
catch { }
|
|
1286
|
+
return undefined;
|
|
1287
|
+
}
|
|
1288
|
+
extractStringLiteral(expr) {
|
|
1289
|
+
if (expr instanceof solc_typed_ast_1.Literal && expr.kind === 'string') {
|
|
1290
|
+
return expr.value;
|
|
1291
|
+
}
|
|
1292
|
+
return undefined;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
exports.CFGBuilder = CFGBuilder;
|