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

SamboyCoding / Cpp2IL / 30749432829

02 Aug 2026 01:12PM UTC coverage: 36.382% (+0.2%) from 36.169%
30749432829

push

github

SamboyCoding
Decompiler: Stop having operands be raw `object`-typed

2857 of 8963 branches covered (31.88%)

Branch coverage included in aggregate %.

78 of 217 new or added lines in 31 files covered. (35.94%)

7 existing lines in 3 files now uncovered.

5293 of 13438 relevant lines covered (39.39%)

164513.36 hits per line

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

0.0
/Cpp2IL.Core/Analysis/MetadataInitGuardRemover.cs
1
using System.Collections.Generic;
2
using System.Linq;
3
using Cpp2IL.Core.Graphs;
4
using Cpp2IL.Core.ISIL;
5
using Cpp2IL.Core.Model.Contexts;
6

7
namespace Cpp2IL.Core.Analysis;
8

9
/// <summary>
10
/// Removes the IL2CPP runtime-metadata initialization guards the compiler emits near the top of
11
/// (almost) every method, plus il2cpp_runtime_class_init blocks.
12
/// </summary>
13
public static class MetadataInitGuardRemover
14
{
15
    private const string InitializeRuntimeMetadata = "il2cpp_codegen_initialize_runtime_metadata";
16
    private const string InitializeMethod = "il2cpp_codegen_initialize_method";
17
    private const string ClassInitExport = "il2cpp_runtime_class_init_export";
18
    private const string ClassInitActual = "il2cpp_runtime_class_init_actual";
19

20
    // Byte holding Il2CppClass's bitfield, of which bit 0 is initialized_and_no_error.
21
    // TODO this is almost certainly not correct on every version... but which?
22
    private const long InitialisedFlagOffset64 = 0x135;
23
    private const long InitialisedFlagOffset32 = 0xBD;
24

25
    public static void Run(MethodAnalysisContext method)
26
        => Run(method.ControlFlowGraph!, method.AppContext.Binary.is32Bit ? InitialisedFlagOffset32 : InitialisedFlagOffset64);
×
27

28
    public static void Run(ISILControlFlowGraph cfg, long initialisedFlagOffset)
29
    {
30
        var removedAny = false;
×
31

32
        foreach (var guard in cfg.Blocks.ToList())
×
33
            removedAny |= TryRemoveGuard(cfg, guard, initialisedFlagOffset);
×
34

35
        if (removedAny)
×
36
            DeadCodeEliminator.Run(cfg);
×
37
    }
×
38

39
    private static bool TryRemoveGuard(ISILControlFlowGraph cfg, Block guard, long initialisedFlagOffset)
40
    {
41
        if (guard.BlockType != BlockType.TwoWay || guard.Successors.Count != 2
×
42
            || guard.Instructions.Count == 0 || guard.Instructions[^1].OpCode != OpCode.ConditionalJump)
×
43
            return false;
×
44

45
        // see if we're checking Il2CppClass::initialized_and_no_error
46
        // that means this is runtime_init boilerplate and we can drop the block
47
        var initialisedFlagTest = guard.Instructions.Any(i => i.OpCode == OpCode.And
×
48
            && i.Operands is [_, MemoryOperand { Index: null, Scale: 0, Base: LocalVariable } flag, { } mask]
×
49
            && flag.Addend == initialisedFlagOffset && IsOne(mask));
×
50

51
        // Either successor could be the init entry; the other is then the merge.
52
        var first = guard.Successors[0];
×
53
        var second = guard.Successors[1];
×
54

55
        return TryExcise(cfg, guard, first, second, initialisedFlagTest)
×
56
            || TryExcise(cfg, guard, second, first, initialisedFlagTest);
×
57
    }
58

NEW
59
    private static bool IsOne(IOperand operand) => operand is Immediate { Value: 1 };
×
60

61
    private static bool TryExcise(ISILControlFlowGraph cfg, Block guard, Block initEntry, Block merge, bool initialisedFlagTest)
62
    {
63
        if (merge == cfg.EntryBlock || merge == cfg.ExitBlock)
×
64
            return false;
×
65

66
        if (!TryCollectRegion(cfg, guard, initEntry, merge, initialisedFlagTest, out var region))
×
67
            return false;
×
68

69
        Excise(cfg, guard, initEntry, merge, region);
×
70
        return true;
×
71
    }
72

73
    private static bool TryCollectRegion(ISILControlFlowGraph cfg, Block guard, Block initEntry, Block merge,
74
        bool initialisedFlagTest, out HashSet<Block> region)
75
    {
76
        region = [];
×
77

78
        if (initEntry == merge || initEntry == guard)
×
79
            return false;
×
80

81
        var sawMetadataInit = false;
×
82
        var sawClassInit = false;
×
83
        var sawFlagStore = false;
×
84
        var reconverges = false;
×
85

86
        var queue = new Queue<Block>();
×
87
        queue.Enqueue(initEntry);
×
88

89
        while (queue.Count > 0)
×
90
        {
91
            var block = queue.Dequeue();
×
92

93
            if (block == merge)
×
94
            {
95
                reconverges = true;
×
96
                continue;
×
97
            }
98

99
            // The region must not run into the method boundary or loop back through the guard.
100
            if (block == cfg.EntryBlock || block == cfg.ExitBlock || block == guard)
×
101
                return false;
×
102

103
            if (!region.Add(block))
×
104
                continue;
105

106
            if (!ClassifyBlock(block, initialisedFlagTest, ref sawMetadataInit, ref sawClassInit, ref sawFlagStore))
×
107
                return false;
×
108

109
            foreach (var successor in block.Successors)
×
110
                queue.Enqueue(successor);
×
111
        }
112

113
        if (!reconverges || !(sawClassInit || (sawMetadataInit && sawFlagStore)))
×
114
            return false;
×
115

116
        var collected = region;
×
117
        foreach (var block in collected)
×
118
        {
119
            if (block.Predecessors.Any(predecessor => predecessor != guard && !collected.Contains(predecessor)))
×
120
                return false;
×
121
            if (block.Successors.Any(successor => successor != merge && !collected.Contains(successor)))
×
122
                return false;
×
123
        }
124

125
        return true;
×
126
    }
×
127

128
    // A region block is acceptable only if every instruction is intra-region control flow, an init
129
    // call, the flag store, or otherwise side-effect-free (writes a local, not memory). A managed call
130
    // or any other store would have an effect we cannot silently drop, so it disqualifies the region.
131
    private static bool ClassifyBlock(Block block, bool initialisedFlagTest, ref bool sawMetadataInit, ref bool sawClassInit, ref bool sawFlagStore)
132
    {
133
        foreach (var instruction in block.Instructions)
×
134
        {
135
            switch (instruction.OpCode)
×
136
            {
137
                case OpCode.Jump:
138
                    break;
139

140
                // Behind an initialized_and_no_error test the callee is the class initializer, even if we didn't resolve it.
141
                // If we didn't, that's fine, just skip.
142
                case OpCode.Call or OpCode.CallVoid when initialisedFlagTest:
×
143
                    sawClassInit = true;
×
144
                    break;
×
145

146
                case OpCode.Call or OpCode.CallVoid:
NEW
147
                    if (instruction.Operands is not [StringLiteral { Value: var name }, ..])
×
148
                        return false;
×
149

150
                    if (name is InitializeRuntimeMetadata or InitializeMethod)
×
151
                        sawMetadataInit = true;
×
152
                    else if (name is ClassInitExport or ClassInitActual)
×
153
                        sawClassInit = true;
×
154
                    else
155
                        return false;
×
156

157
                    break;
158

159
                case OpCode.Move when instruction.Operands is [MemoryOperand { IsConstant: true }, _]:
×
160
                    sawFlagStore = true;
×
161
                    break;
×
162

163
                default:
164
                    if (!IsSideEffectFree(instruction))
×
165
                        return false;
×
166
                    break;
167
            }
168
        }
169

170
        return true;
×
171
    }
×
172

173
    // True for instructions that only compute a value into a local (or do nothing). A store - any
174
    // instruction whose destination operand is a memory or field reference rather than a local - is
175
    // excluded, as is anything that transfers control or merges values (phi/return/indirect).
176
    private static bool IsSideEffectFree(Instruction instruction) =>
177
        instruction.OpCode switch
×
178
        {
×
179
            OpCode.Nop => true,
×
180
            OpCode.Move or OpCode.Add or OpCode.Subtract or OpCode.Multiply or OpCode.Divide
×
181
                or OpCode.ShiftLeft or OpCode.ShiftRight or OpCode.And or OpCode.Or or OpCode.Xor
×
182
                or OpCode.Not or OpCode.Negate
×
183
                or (>= OpCode.CheckEqual and <= OpCode.CheckLessOrEqual)
×
184
                => instruction.Operands is [LocalVariable, ..],
×
185
            _ => false,
×
186
        };
×
187

188
    private static void Excise(ISILControlFlowGraph cfg, Block guard, Block initEntry, Block merge, HashSet<Block> region)
189
    {
190
        // 1. Repair the merge's phis: drop the inputs from the region's back-edges.
191
        for (var i = merge.Predecessors.Count - 1; i >= 0; i--)
×
192
        {
193
            if (!region.Contains(merge.Predecessors[i]))
×
194
                continue;
195

196
            foreach (var phi in merge.Instructions)
×
197
                if (phi.OpCode == OpCode.Phi && 1 + i < phi.Operands.Count)
×
198
                    phi.RemoveOperandAt(1 + i);
×
199

200
            merge.Predecessors.RemoveAt(i);
×
201
        }
202

203
        // 2. Fold the guard so it goes straight to the merge.
204
        guard.Successors.Remove(initEntry);
×
205
        initEntry.Predecessors.Remove(guard);
×
206

207
        var terminator = guard.Instructions[^1];
×
208
        terminator.OpCode = OpCode.Jump;
×
NEW
209
        terminator.SetOperands(merge);
×
210
        guard.CalculateBlockType();
×
211

212
        // 3. Delete the region. 
213
        foreach (var block in region)
×
214
        {
215
            foreach (var successor in block.Successors)
×
216
                successor.Predecessors.Remove(block);
×
217
            foreach (var predecessor in block.Predecessors)
×
218
                predecessor.Successors.Remove(block);
×
219

220
            block.Successors.Clear();
×
221
            block.Predecessors.Clear();
×
222
            cfg.Blocks.Remove(block);
×
223
        }
224
    }
×
225
}
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