• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 27098183143

07 Jun 2026 04:25PM UTC coverage: 33.682% (-1.4%) from 35.035%
27098183143

push

github

SamboyCoding
Decompiler: add SSA dead-code elimination

2122 of 7666 branches covered (27.68%)

Branch coverage included in aggregate %.

36 of 45 new or added lines in 2 files covered. (80.0%)

884 existing lines in 14 files now uncovered.

4571 of 12205 relevant lines covered (37.45%)

246086.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

49.64
/Cpp2IL.Core/Graphs/ISILControlFlowGraph.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using Cpp2IL.Core.ISIL;
5

6
namespace Cpp2IL.Core.Graphs;
7

8
public class ISILControlFlowGraph
9
{
10
    public Block EntryBlock;
11
    public Block ExitBlock;
12
    public int Count => Blocks.Count;
×
13
    public List<Block> Blocks;
14

15
    public List<Instruction> Instructions
16
    {
17
        get
18
        {
19
            // BFS search
20
            var visited = new HashSet<Block>();
2✔
21
            var queue = new Queue<Block>();
2✔
22
            var result = new List<Instruction>();
2✔
23

24
            queue.Enqueue(EntryBlock);
2✔
25

26
            while (queue.Count > 0)
16✔
27
            {
28
                var current = queue.Dequeue();
14✔
29

30
                if (!visited.Add(current))
14✔
31
                    continue;
32

33
                result.AddRange(current.Instructions);
13✔
34

35
                foreach (var successor in current.Successors)
52✔
36
                {
37
                    if (!visited.Contains(successor))
13✔
38
                        queue.Enqueue(successor);
12✔
39
                }
40
            }
41

42
            return result; // Should this be cached?
2✔
43
        }
44
    }
45

46
    private int idCounter;
47

48
    public ISILControlFlowGraph(List<Instruction> instructions)
10✔
49
    {
50
        EntryBlock = new Block
10✔
51
        {
10✔
52
            ID = idCounter++,
10✔
53
            BlockType = BlockType.Entry
10✔
54
        };
10✔
55

56
        ExitBlock = new Block
10✔
57
        {
10✔
58
            ID = idCounter++,
10✔
59
            BlockType = BlockType.Exit
10✔
60
        };
10✔
61

62
        Blocks =
10✔
63
        [
10✔
64
            EntryBlock,
10✔
65
            ExitBlock
10✔
66
        ];
10✔
67

68
        Build(instructions);
10✔
69
    }
10✔
70

71
    private bool TryGetTargetJumpInstructionIndex(Instruction instruction, out int jumpInstructionIndex)
72
    {
73
        jumpInstructionIndex = 0;
3✔
74
        try
75
        {
76
            jumpInstructionIndex = ((Instruction)instruction.Operands[0]).Index;
3✔
77
            return true;
3✔
78
        }
79
        catch
×
80
        {
81
            // ignore
82
        }
×
83

UNCOV
84
        return false;
×
85
    }
3✔
86

87
    public void RemoveUnreachableBlocks()
88
    {
UNCOV
89
        if (Blocks.Count == 0)
×
UNCOV
90
            return;
×
91

92
        // Get blocks reachable from entry
UNCOV
93
        var reachable = new List<Block>();
×
UNCOV
94
        var visited = new List<Block> { EntryBlock };
×
UNCOV
95
        reachable.Add(EntryBlock);
×
96

UNCOV
97
        var total = 0;
×
UNCOV
98
        while (total < reachable.Count)
×
99
        {
UNCOV
100
            var block = reachable[total];
×
UNCOV
101
            total++;
×
102

UNCOV
103
            foreach (var successor in block.Successors)
×
104
            {
UNCOV
105
                if (visited.Contains(successor))
×
106
                    continue;
UNCOV
107
                visited.Add(successor);
×
UNCOV
108
                reachable.Add(successor);
×
109
            }
110
        }
111

112
        // Get unreachable blocks
UNCOV
113
        var unreachable = Blocks.Where(block => !visited.Remove(block)).ToList();
×
114

115
        // Remove those
UNCOV
116
        foreach (var block in unreachable)
×
117
        {
118
            // Don't remove entry or exit
UNCOV
119
            if (block == EntryBlock || block == ExitBlock)
×
120
                continue;
121

122
            // Fully detach the block so no remaining block keeps a dangling reference to it.
123
            // (A reachable block can have an unreachable predecessor; leaving that reference
124
            // behind makes later passes such as dominator computation throw.)
UNCOV
125
            foreach (var successor in block.Successors)
×
UNCOV
126
                successor.Predecessors.Remove(block);
×
UNCOV
127
            foreach (var predecessor in block.Predecessors)
×
UNCOV
128
                predecessor.Successors.Remove(block);
×
129

130
            block.Successors.Clear();
×
131
            block.Predecessors.Clear();
×
UNCOV
132
            Blocks.Remove(block);
×
133
        }
134
    }
×
135

136
    public void RemoveNops()
137
    {
UNCOV
138
        var usedAsTarget = new HashSet<Instruction>();
×
139

140
        // Get all instructions used as branch targets
UNCOV
141
        foreach (var block in Blocks)
×
142
        {
UNCOV
143
            foreach (var instr in block.Instructions)
×
144
            {
UNCOV
145
                foreach (var operand in instr.Operands)
×
146
                {
UNCOV
147
                    if (operand is Instruction target)
×
148
                        usedAsTarget.Add(target);
×
149
                }
150
            }
151
        }
152

153
        // Build replacement map for NOPs that are safe to replace
UNCOV
154
        var instructionReplacement = new Dictionary<Instruction, Instruction>();
×
UNCOV
155
        foreach (var block in Blocks)
×
156
        {
UNCOV
157
            Instruction? replacement = null;
×
UNCOV
158
            for (var i = block.Instructions.Count - 1; i >= 0; i--)
×
159
            {
UNCOV
160
                var instr = block.Instructions[i];
×
UNCOV
161
                if (instr.OpCode == OpCode.Nop)
×
162
                {
UNCOV
163
                    if (replacement != null && !usedAsTarget.Contains(instr))
×
164
                        instructionReplacement[instr] = replacement;
×
165
                }
166
                else
167
                {
UNCOV
168
                    replacement = instr;
×
169
                }
170
            }
171
        }
172

173
        // Update operands
UNCOV
174
        foreach (var block in Blocks)
×
175
        {
UNCOV
176
            foreach (var instr in block.Instructions)
×
177
            {
UNCOV
178
                for (var i = 0; i < instr.Operands.Count; i++)
×
179
                {
UNCOV
180
                    if (instr.Operands[i] is Instruction target && instructionReplacement.TryGetValue(target, out var newTarget))
×
181
                        instr.Operands[i] = newTarget;
×
182
                }
183
            }
184
        }
185

186
        // Remove NOPs
UNCOV
187
        foreach (var block in Blocks)
×
188
        {
UNCOV
189
            block.Instructions.RemoveAll(i => i.OpCode == OpCode.Nop && !usedAsTarget.Contains(i));
×
190
        }
UNCOV
191
    }
×
192

193
    public void RemoveEmptyBlocks()
194
    {
UNCOV
195
        var toRemove = new List<Block>();
×
196

UNCOV
197
        foreach (var block in Blocks)
×
198
        {
199
            if (block == EntryBlock || block == ExitBlock)
×
200
                continue;
201

UNCOV
202
            if (block.Instructions.Count == 0)
×
203
            {
204
                // Redirect predecessors to successors
UNCOV
205
                foreach (var pred in block.Predecessors)
×
206
                {
UNCOV
207
                    pred.Successors.Remove(block);
×
UNCOV
208
                    foreach (var succ in block.Successors)
×
209
                    {
UNCOV
210
                        if (!pred.Successors.Contains(succ))
×
UNCOV
211
                            pred.Successors.Add(succ);
×
212
                    }
213
                }
214

215
                // Redirect successors to predecessors
UNCOV
216
                foreach (var succ in block.Successors)
×
217
                {
218
                    succ.Predecessors.Remove(block);
×
UNCOV
219
                    foreach (var pred in block.Predecessors)
×
220
                    {
UNCOV
221
                        if (!succ.Predecessors.Contains(pred))
×
UNCOV
222
                            succ.Predecessors.Add(pred);
×
223
                    }
224
                }
225

UNCOV
226
                toRemove.Add(block);
×
227
            }
228
        }
229

UNCOV
230
        foreach (var block in toRemove)
×
UNCOV
231
            Blocks.Remove(block);
×
UNCOV
232
    }
×
233

234
    public void BuildUseDefLists()
235
    {
236
        foreach (var block in Blocks)
30✔
237
        {
238
            var use = new List<object>();
13✔
239
            var def = new List<object>();
13✔
240

241
            foreach (var instruction in block.Instructions)
56✔
242
            {
243
                foreach (var operand in instruction.Sources.Where(operand => !use.Contains(operand)))
49✔
244
                    use.Add(operand);
6✔
245

246
                if (instruction.Destination != null && !def.Contains(instruction.Destination))
15✔
247
                    def.Add(instruction.Destination);
7✔
248
            }
249

250
            block.Use = use;
13✔
251
            block.Def = def;
13✔
252
        }
253
    }
2✔
254

255
    public void MergeCallBlocks()
256
    {
UNCOV
257
        var toRemove = new List<Block>();
×
258

UNCOV
259
        for (var i = 0; i < Blocks.Count; i++)
×
260
        {
UNCOV
261
            var block = Blocks[i];
×
UNCOV
262
            if (block.BlockType != BlockType.Call) continue;
×
263

UNCOV
264
            if (block.Successors.Count != 1)
×
265
                continue;
266

UNCOV
267
            var nextBlock = block.Successors[0];
×
268

269
            // make sure that the next block only has one predecessor (this)
UNCOV
270
            if (nextBlock.Predecessors.Count != 1 || nextBlock.Predecessors[0] != block)
×
271
                continue;
272

273
            // merge instructions
UNCOV
274
            block.Instructions.AddRange(nextBlock.Instructions);
×
UNCOV
275
            block.Successors = nextBlock.Successors;
×
276

277
            // fix up successors predecessors
UNCOV
278
            foreach (var successor in nextBlock.Successors)
×
279
            {
UNCOV
280
                for (var j = 0; j < successor.Predecessors.Count; j++)
×
281
                {
UNCOV
282
                    if (successor.Predecessors[j] == nextBlock)
×
UNCOV
283
                        successor.Predecessors[j] = block;
×
284
                }
285
            }
286

UNCOV
287
            toRemove.Add(nextBlock);
×
288
        }
289

290
        // Remove all merged blocks
UNCOV
291
        foreach (var removed in toRemove)
×
UNCOV
292
            Blocks.Remove(removed);
×
293

UNCOV
294
        foreach (var block in Blocks)
×
UNCOV
295
            block.CalculateBlockType();
×
UNCOV
296
    }
×
297

298
    private void Build(List<Instruction> instructions)
299
    {
300
        if (instructions == null)
10!
UNCOV
301
            throw new ArgumentNullException(nameof(instructions));
×
302

303
        var currentBlock = new Block() { ID = idCounter++ };
10✔
304
        AddBlock(currentBlock);
10✔
305
        AddDirectedEdge(EntryBlock, currentBlock);
10✔
306

307
        for (var i = 0; i < instructions.Count; i++)
374✔
308
        {
309
            var isLast = i == instructions.Count - 1;
177✔
310
            Block newBlock;
311

312
            switch (instructions[i].OpCode)
177✔
313
            {
314
                case OpCode.Jump:
315
                case OpCode.ConditionalJump:
316
                    currentBlock.AddInstruction(instructions[i]);
22✔
317

318
                    if (!isLast)
22!
319
                    {
320
                        newBlock = new Block() { ID = idCounter++ };
22✔
321
                        AddBlock(newBlock);
22✔
322

323
                        if (instructions[i].OpCode == OpCode.Jump)
22✔
324
                        {
325
                            if (TryGetTargetJumpInstructionIndex(instructions[i], out int jumpTargetIndex))
3!
326
                                currentBlock.Dirty = true;
3✔
327
                            else
UNCOV
328
                                AddDirectedEdge(currentBlock, ExitBlock);
×
329
                        }
330
                        else
331
                        {
332
                            AddDirectedEdge(currentBlock, newBlock);
19✔
333
                            currentBlock.Dirty = true;
19✔
334
                        }
335

336
                        currentBlock.CalculateBlockType();
22✔
337
                        currentBlock = newBlock;
22✔
338
                    }
339
                    else
340
                    {
UNCOV
341
                        AddDirectedEdge(currentBlock, ExitBlock);
×
342

UNCOV
343
                        if (instructions[i].OpCode == OpCode.Jump)
×
UNCOV
344
                            currentBlock.Dirty = true;
×
345
                    }
346

UNCOV
347
                    break;
×
348

349
                case OpCode.Call:
350
                case OpCode.CallVoid:
351
                case OpCode.Return:
352
                    var isReturn = instructions[i].OpCode == OpCode.Return;
32✔
353

354
                    currentBlock.AddInstruction(instructions[i]);
32✔
355

356
                    if (!isLast)
32✔
357
                    {
358
                        newBlock = new Block() { ID = idCounter++ };
22✔
359
                        AddBlock(newBlock);
22✔
360
                        AddDirectedEdge(currentBlock, isReturn ? ExitBlock : newBlock);
22✔
361
                        currentBlock.CalculateBlockType();
22✔
362
                        currentBlock = newBlock;
22✔
363
                    }
364
                    else
365
                    {
366
                        AddDirectedEdge(currentBlock, ExitBlock);
10✔
367
                        currentBlock.CalculateBlockType();
10✔
368
                    }
369

370
                    break;
10✔
371

372
                default:
373
                    currentBlock.AddInstruction(instructions[i]);
123✔
374
                    if (isLast)
123!
375
                    {
UNCOV
376
                        AddDirectedEdge(currentBlock, ExitBlock);
×
UNCOV
377
                        currentBlock.CalculateBlockType();
×
378
                    }
379
                    break;
380
            }
381
        }
382

383
        for (var index = 0; index < Blocks.Count; index++)
190✔
384
        {
385
            var node = Blocks[index];
85✔
386
            if (node.Dirty)
85✔
387
                FixBlock(node);
22✔
388
        }
389

390
        // Connect blocks without successors to exit
391
        foreach (var block in Blocks)
190✔
392
        {
393
            if (block.Successors.Count == 0 && block != EntryBlock && block != ExitBlock)
85!
UNCOV
394
                AddDirectedEdge(block, ExitBlock);
×
395
        }
396

397
        // Change branch targets to blocks
398
        foreach (var instruction in Blocks.SelectMany(block => block.Instructions))
459✔
399
        {
400
            if (instruction.Operands.Count > 0 && instruction.Operands[0] is Instruction target)
177✔
401
                instruction.Operands[0] = FindBlockByInstruction(target)!;
22✔
402
        }
403
    }
10✔
404

405
    private void FixBlock(Block block, bool removeJmp = false)
406
    {
407
        if (block.BlockType is BlockType.Fall)
22!
UNCOV
408
            return;
×
409

410
        var jump = block.Instructions.Last();
22✔
411

412
        var targetInstruction = jump.Operands[0] as Instruction;
22✔
413

414
        var destination = FindBlockByInstruction(targetInstruction);
22✔
415

416
        if (destination == null)
22!
417
        {
418
            //We assume that we're tail calling another method somewhere. Need to verify if this breaks anywhere but it shouldn't in general
UNCOV
419
            block.BlockType = BlockType.TailCall;
×
UNCOV
420
            return;
×
421
        }
422

423

424
        int index = destination.Instructions.FindIndex(instruction => instruction == targetInstruction);
59✔
425

426
        var targetNode = SplitAndCreate(destination, index);
22✔
427

428
        AddDirectedEdge(block, targetNode);
22✔
429
        block.Dirty = false;
22✔
430

431
        if (removeJmp)
22!
UNCOV
432
            block.Instructions.Remove(jump);
×
433
    }
22✔
434

435
    public Block? FindBlockByInstruction(Instruction? instruction)
436
    {
437
        if (instruction == null)
44!
UNCOV
438
            return null;
×
439

440
        for (var i = 0; i < Blocks.Count; i++)
884!
441
        {
442
            var block = Blocks[i];
442✔
443
            for (var j = 0; j < block.Instructions.Count; j++)
2,878✔
444
            {
445
                var instr = block.Instructions[j];
1,041✔
446
                if (instr == instruction)
1,041✔
447
                {
448
                    return block;
44✔
449
                }
450
            }
451
        }
452

UNCOV
453
        return null;
×
454
    }
455

456
    private Block SplitAndCreate(Block target, int index)
457
    {
458
        if (index < 0 || index >= target.Instructions.Count)
22!
UNCOV
459
            throw new ArgumentOutOfRangeException(nameof(index));
×
460

461
        // Don't need to split...
462
        if (index == 0)
22✔
463
            return target;
11✔
464

465
        var newBlock = new Block() { ID = idCounter++ };
11✔
466

467
        // target split in two
468
        // targetFirstPart -> targetSecondPart aka newNode
469

470
        // Take the instructions for the secondPart
471
        var instructions = target.Instructions.GetRange(index, target.Instructions.Count - index);
11✔
472
        target.Instructions.RemoveRange(index, target.Instructions.Count - index);
11✔
473

474
        // Add those to the newNode
475
        newBlock.Instructions.AddRange(instructions);
11✔
476
        // Transfer control flow
477
        newBlock.BlockType = target.BlockType;
11✔
478
        target.BlockType = BlockType.Fall;
11✔
479

480
        // Transfer successors
481
        newBlock.Successors = target.Successors;
11✔
482
        if (target.Dirty)
11✔
483
            newBlock.Dirty = true;
4✔
484
        target.Dirty = false;
11✔
485
        target.Successors = [];
11✔
486

487
        // Correct the predecessors for all the successors
488
        foreach (var successor in newBlock.Successors)
46✔
489
        {
490
            for (int i = 0; i < successor.Predecessors.Count; i++)
48✔
491
            {
492
                if (successor.Predecessors[i].ID == target.ID)
12✔
493
                    successor.Predecessors[i] = newBlock;
12✔
494
            }
495
        }
496

497
        // Add newNode and connect it
498
        AddBlock(newBlock);
11✔
499
        AddDirectedEdge(target, newBlock);
11✔
500

501
        return newBlock;
11✔
502
    }
503

504
    private void AddDirectedEdge(Block from, Block to)
505
    {
506
        from.Successors.Add(to);
94✔
507
        to.Predecessors.Add(from);
94✔
508
    }
94✔
509

510
    protected void AddBlock(Block block) => Blocks.Add(block);
65✔
511
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc