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

SamboyCoding / Cpp2IL / 30769998286

02 Aug 2026 10:23PM UTC coverage: 34.169% (-1.8%) from 35.984%
30769998286

push

github

SamboyCoding
Decompiler: Support szarray, virtual calls, address of local, plus clean up ILSpy output by adjusting the IL

2899 of 9930 branches covered (29.19%)

Branch coverage included in aggregate %.

27 of 614 new or added lines in 19 files covered. (4.4%)

9 existing lines in 4 files now uncovered.

5316 of 14112 relevant lines covered (37.67%)

156656.13 hits per line

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

58.9
/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>();
3✔
21
            var queue = new Queue<Block>();
3✔
22
            var result = new List<Instruction>();
3✔
23

24
            queue.Enqueue(EntryBlock);
3✔
25

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

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

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

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

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

46
    private int idCounter;
47

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

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

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

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

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

84
        return false;
×
85
    }
5✔
86

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

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

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

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

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

115
        // Remove those
116
        foreach (var block in unreachable)
×
117
        {
118
            // Don't remove entry or exit
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.)
125
            foreach (var successor in block.Successors)
×
126
                successor.Predecessors.Remove(block);
×
127
            foreach (var predecessor in block.Predecessors)
×
128
                predecessor.Successors.Remove(block);
×
129

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

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

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

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

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

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

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

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

202
            if (block.Instructions.Count == 0)
10!
203
            {
204
                // jumps into the removed block must be retargeted, which needs an unambiguous successor
NEW
205
                if (block.Successors.Count != 1 && HasJumpOperandTo(block))
×
206
                    continue;
207

NEW
208
                var jumpTarget = block.Successors.Count == 1 ? block.Successors[0] : null;
×
209

210
                // Redirect predecessors to successors
211
                foreach (var pred in block.Predecessors)
×
212
                {
NEW
213
                    if (pred.Instructions.Count > 0
×
NEW
214
                        && pred.Instructions[^1] is { OpCode: OpCode.Jump or OpCode.ConditionalJump } jump
×
NEW
215
                        && ReferenceEquals(jump.Operands[0], block))
×
NEW
216
                        jump.SetOperand(0, jumpTarget!);
×
217

218
                    pred.Successors.Remove(block);
×
219
                    foreach (var succ in block.Successors)
×
220
                    {
221
                        if (!pred.Successors.Contains(succ))
×
222
                            pred.Successors.Add(succ);
×
223
                    }
224
                }
225

226
                // Redirect successors to predecessors
227
                foreach (var succ in block.Successors)
×
228
                {
229
                    succ.Predecessors.Remove(block);
×
230
                    foreach (var pred in block.Predecessors)
×
231
                    {
232
                        if (!succ.Predecessors.Contains(pred))
×
233
                            succ.Predecessors.Add(pred);
×
234
                    }
235
                }
236

237
                toRemove.Add(block);
×
238
            }
239
        }
240

241
        foreach (var block in toRemove)
6!
242
            Blocks.Remove(block);
×
243
    }
3✔
244

245
    private bool HasJumpOperandTo(Block block) =>
NEW
246
        block.Predecessors.Any(pred => pred.Instructions.Count > 0
×
NEW
247
            && pred.Instructions[^1] is { OpCode: OpCode.Jump or OpCode.ConditionalJump } jump
×
NEW
248
            && ReferenceEquals(jump.Operands[0], block));
×
249

250
    public void BuildUseDefLists(HashSet<Instruction>? clobberingAddressTakes = null)
251
    {
252
        foreach (var block in Blocks)
30✔
253
        {
254
            var use = new List<IOperand>();
13✔
255
            var def = new List<IOperand>();
13✔
256

257
            foreach (var instruction in block.Instructions)
56✔
258
            {
259
                foreach (var operand in instruction.Sources.Where(operand => !use.Contains(operand)))
49✔
260
                    use.Add(operand);
6✔
261

262
                if (instruction.Destination != null && !def.Contains(instruction.Destination))
15✔
263
                    def.Add(instruction.Destination);
7✔
264

265
                if (clobberingAddressTakes?.Contains(instruction) == true)
15!
266
                {
NEW
267
                    foreach (var operand in instruction.Operands)
×
NEW
268
                        if (operand is AddressOf { Target: { } addressed } && !def.Contains(addressed))
×
NEW
269
                            def.Add(addressed);
×
270
                }
271
            }
272

273
            block.Use = use;
13✔
274
            block.Def = def;
13✔
275
        }
276
    }
2✔
277

278
    public void MergeCallBlocks()
279
    {
280
        var toRemove = new List<Block>();
×
281

282
        for (var i = 0; i < Blocks.Count; i++)
×
283
        {
284
            var block = Blocks[i];
×
285
            if (block.BlockType != BlockType.Call) continue;
×
286

287
            if (block.Successors.Count != 1)
×
288
                continue;
289

290
            var nextBlock = block.Successors[0];
×
291

292
            // make sure that the next block only has one predecessor (this)
293
            if (nextBlock.Predecessors.Count != 1 || nextBlock.Predecessors[0] != block)
×
294
                continue;
295

296
            // merge instructions
297
            block.Instructions.AddRange(nextBlock.Instructions);
×
298
            block.Successors = nextBlock.Successors;
×
299

300
            // fix up successors predecessors
301
            foreach (var successor in nextBlock.Successors)
×
302
            {
303
                for (var j = 0; j < successor.Predecessors.Count; j++)
×
304
                {
305
                    if (successor.Predecessors[j] == nextBlock)
×
306
                        successor.Predecessors[j] = block;
×
307
                }
308
            }
309

310
            toRemove.Add(nextBlock);
×
311
        }
312

313
        // Remove all merged blocks
314
        foreach (var removed in toRemove)
×
315
            Blocks.Remove(removed);
×
316

317
        foreach (var block in Blocks)
×
318
            block.CalculateBlockType();
×
319
    }
×
320

321
    private void Build(List<Instruction> instructions)
322
    {
323
        if (instructions == null)
23!
324
            throw new ArgumentNullException(nameof(instructions));
×
325

326
        var currentBlock = new Block() { ID = idCounter++ };
23✔
327
        AddBlock(currentBlock);
23✔
328
        AddDirectedEdge(EntryBlock, currentBlock);
23✔
329

330
        for (var i = 0; i < instructions.Count; i++)
546✔
331
        {
332
            var isLast = i == instructions.Count - 1;
250✔
333
            Block newBlock;
334

335
            switch (instructions[i].OpCode)
250✔
336
            {
337
                case OpCode.Jump:
338
                case OpCode.ConditionalJump:
339
                    currentBlock.AddInstruction(instructions[i]);
30✔
340

341
                    if (!isLast)
30!
342
                    {
343
                        newBlock = new Block() { ID = idCounter++ };
30✔
344
                        AddBlock(newBlock);
30✔
345

346
                        if (instructions[i].OpCode == OpCode.Jump)
30✔
347
                        {
348
                            if (TryGetTargetJumpInstructionIndex(instructions[i], out int jumpTargetIndex))
5!
349
                                currentBlock.Dirty = true;
5✔
350
                            else
351
                                AddDirectedEdge(currentBlock, ExitBlock);
×
352
                        }
353
                        else
354
                        {
355
                            AddDirectedEdge(currentBlock, newBlock);
25✔
356
                            currentBlock.Dirty = true;
25✔
357
                        }
358

359
                        currentBlock.CalculateBlockType();
30✔
360
                        currentBlock = newBlock;
30✔
361
                    }
362
                    else
363
                    {
364
                        AddDirectedEdge(currentBlock, ExitBlock);
×
365

366
                        if (instructions[i].OpCode == OpCode.Jump)
×
367
                            currentBlock.Dirty = true;
×
368
                    }
369

370
                    break;
×
371

372
                case OpCode.Call:
373
                case OpCode.CallVoid:
374
                case OpCode.Return:
375
                    var isReturn = instructions[i].OpCode == OpCode.Return;
59✔
376

377
                    currentBlock.AddInstruction(instructions[i]);
59✔
378

379
                    if (!isLast)
59✔
380
                    {
381
                        newBlock = new Block() { ID = idCounter++ };
36✔
382
                        AddBlock(newBlock);
36✔
383
                        AddDirectedEdge(currentBlock, isReturn ? ExitBlock : newBlock);
36✔
384
                        currentBlock.CalculateBlockType();
36✔
385
                        currentBlock = newBlock;
36✔
386
                    }
387
                    else
388
                    {
389
                        AddDirectedEdge(currentBlock, ExitBlock);
23✔
390
                        currentBlock.CalculateBlockType();
23✔
391
                    }
392

393
                    break;
23✔
394

395
                default:
396
                    currentBlock.AddInstruction(instructions[i]);
161✔
397
                    if (isLast)
161!
398
                    {
399
                        AddDirectedEdge(currentBlock, ExitBlock);
×
400
                        currentBlock.CalculateBlockType();
×
401
                    }
402
                    break;
403
            }
404
        }
405

406
        for (var index = 0; index < Blocks.Count; index++)
338✔
407
        {
408
            var node = Blocks[index];
146✔
409
            if (node.Dirty)
146✔
410
                FixBlock(node);
30✔
411
        }
412

413
        // Connect blocks without successors to exit
414
        foreach (var block in Blocks)
338✔
415
        {
416
            if (block.Successors.Count == 0 && block != EntryBlock && block != ExitBlock)
146!
417
                AddDirectedEdge(block, ExitBlock);
×
418
        }
419

420
        // Change branch targets to blocks
421
        foreach (var instruction in Blocks.SelectMany(block => block.Instructions))
692✔
422
        {
423
            if (instruction.Operands.Count > 0 && instruction.Operands[0] is Instruction target)
250✔
424
                instruction.SetOperand(0, FindBlockByInstruction(target)!);
30✔
425
        }
426
    }
23✔
427

428
    private void FixBlock(Block block, bool removeJmp = false)
429
    {
430
        if (block.BlockType is BlockType.Fall)
30!
431
            return;
×
432

433
        var jump = block.Instructions.Last();
30✔
434

435
        var targetInstruction = jump.Operands[0] as Instruction;
30✔
436

437
        var destination = FindBlockByInstruction(targetInstruction);
30✔
438

439
        if (destination == null)
30!
440
        {
441
            //We assume that we're tail calling another method somewhere. Need to verify if this breaks anywhere but it shouldn't in general
442
            block.BlockType = BlockType.TailCall;
×
443
            return;
×
444
        }
445

446

447
        int index = destination.Instructions.FindIndex(instruction => instruction == targetInstruction);
75✔
448

449
        var targetNode = SplitAndCreate(destination, index);
30✔
450

451
        AddDirectedEdge(block, targetNode);
30✔
452
        block.Dirty = false;
30✔
453

454
        if (removeJmp)
30!
455
            block.Instructions.Remove(jump);
×
456
    }
30✔
457

458
    public Block? FindBlockByInstruction(Instruction? instruction)
459
    {
460
        if (instruction == null)
60!
461
            return null;
×
462

463
        for (var i = 0; i < Blocks.Count; i++)
1,052!
464
        {
465
            var block = Blocks[i];
526✔
466
            for (var j = 0; j < block.Instructions.Count; j++)
3,254✔
467
            {
468
                var instr = block.Instructions[j];
1,161✔
469
                if (instr == instruction)
1,161✔
470
                {
471
                    return block;
60✔
472
                }
473
            }
474
        }
475

476
        return null;
×
477
    }
478

479
    private Block SplitAndCreate(Block target, int index)
480
    {
481
        if (index < 0 || index >= target.Instructions.Count)
30!
482
            throw new ArgumentOutOfRangeException(nameof(index));
×
483

484
        // Don't need to split...
485
        if (index == 0)
30✔
486
            return target;
19✔
487

488
        var newBlock = new Block() { ID = idCounter++ };
11✔
489

490
        // target split in two
491
        // targetFirstPart -> targetSecondPart aka newNode
492

493
        // Take the instructions for the secondPart
494
        var instructions = target.Instructions.GetRange(index, target.Instructions.Count - index);
11✔
495
        target.Instructions.RemoveRange(index, target.Instructions.Count - index);
11✔
496

497
        // Add those to the newNode
498
        newBlock.Instructions.AddRange(instructions);
11✔
499
        // Transfer control flow
500
        newBlock.BlockType = target.BlockType;
11✔
501
        target.BlockType = BlockType.Fall;
11✔
502

503
        // Transfer successors
504
        newBlock.Successors = target.Successors;
11✔
505
        if (target.Dirty)
11✔
506
            newBlock.Dirty = true;
4✔
507
        target.Dirty = false;
11✔
508
        target.Successors = [];
11✔
509

510
        // Correct the predecessors for all the successors
511
        foreach (var successor in newBlock.Successors)
46✔
512
        {
513
            for (int i = 0; i < successor.Predecessors.Count; i++)
48✔
514
            {
515
                if (successor.Predecessors[i].ID == target.ID)
12✔
516
                    successor.Predecessors[i] = newBlock;
12✔
517
            }
518
        }
519

520
        // Add newNode and connect it
521
        AddBlock(newBlock);
11✔
522
        AddDirectedEdge(target, newBlock);
11✔
523

524
        return newBlock;
11✔
525
    }
526

527
    private void AddDirectedEdge(Block from, Block to)
528
    {
529
        from.Successors.Add(to);
148✔
530
        to.Predecessors.Add(from);
148✔
531
    }
148✔
532

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

© 2026 Coveralls, Inc