// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using Internal.TypeSystem; namespace Internal.IL { public static class ILStackHelper { /// /// Validates that the CIL evaluation stack is properly balanced. /// [Conditional("DEBUG")] public static void CheckStackBalance(this MethodIL methodIL) { methodIL.ComputeMaxStack(); } /// /// Computes the maximum number of items that can be pushed onto the CIL evaluation stack. /// public static int ComputeMaxStack(this MethodIL methodIL) { const int StackHeightNotSet = int.MinValue; byte[] ilbytes = methodIL.GetILBytes(); int currentOffset = 0; int stackHeight = 0; int maxStack = 0; // TODO: Use Span for this and stackalloc the array if reasonably sized int[] stackHeights = new int[ilbytes.Length]; for (int i = 0; i < stackHeights.Length; i++) stackHeights[i] = StackHeightNotSet; // Catch and filter clauses have a known non-zero stack height. foreach (ILExceptionRegion region in methodIL.GetExceptionRegions()) { if (region.Kind == ILExceptionRegionKind.Catch) { stackHeights[region.HandlerOffset] = 1; } else if (region.Kind == ILExceptionRegionKind.Filter) { stackHeights[region.FilterOffset] = 1; stackHeights[region.HandlerOffset] = 1; } } while (currentOffset < ilbytes.Length) { ILOpcode opcode = (ILOpcode)ilbytes[currentOffset]; if (opcode == ILOpcode.prefix1) opcode = 0x100 + (ILOpcode)ilbytes[currentOffset + 1]; // The stack height could be unknown if the previous instruction // was an unconditional control transfer. // In that case we check if we have a known stack height due to // this instruction being a target of a previous branch or an EH block. if (stackHeight == StackHeightNotSet) stackHeight = stackHeights[currentOffset]; // If we still don't know the stack height, ECMA-335 III.1.7.5 // "Backward branch constraint" demands the evaluation stack be empty. if (stackHeight == StackHeightNotSet) stackHeight = 0; // Remeber the stack height at this offset. Debug.Assert(stackHeights[currentOffset] == StackHeightNotSet || stackHeights[currentOffset] == stackHeight); stackHeights[currentOffset] = stackHeight; bool isVariableSize = false; switch (opcode) { case ILOpcode.arglist: case ILOpcode.dup: case ILOpcode.ldc_i4: case ILOpcode.ldc_i4_0: case ILOpcode.ldc_i4_1: case ILOpcode.ldc_i4_2: case ILOpcode.ldc_i4_3: case ILOpcode.ldc_i4_4: case ILOpcode.ldc_i4_5: case ILOpcode.ldc_i4_6: case ILOpcode.ldc_i4_7: case ILOpcode.ldc_i4_8: case ILOpcode.ldc_i4_m1: case ILOpcode.ldc_i4_s: case ILOpcode.ldc_i8: case ILOpcode.ldc_r4: case ILOpcode.ldc_r8: case ILOpcode.ldftn: case ILOpcode.ldnull: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.ldstr: case ILOpcode.ldtoken: case ILOpcode.ldarg: case ILOpcode.ldarg_0: case ILOpcode.ldarg_1: case ILOpcode.ldarg_2: case ILOpcode.ldarg_3: case ILOpcode.ldarg_s: case ILOpcode.ldarga: case ILOpcode.ldarga_s: case ILOpcode.ldloc: case ILOpcode.ldloc_0: case ILOpcode.ldloc_1: case ILOpcode.ldloc_2: case ILOpcode.ldloc_3: case ILOpcode.ldloc_s: case ILOpcode.ldloca: case ILOpcode.ldloca_s: case ILOpcode.sizeof_: stackHeight += 1; break; case ILOpcode.add: case ILOpcode.add_ovf: case ILOpcode.add_ovf_un: case ILOpcode.and: case ILOpcode.ceq: case ILOpcode.cgt: case ILOpcode.cgt_un: case ILOpcode.clt: case ILOpcode.clt_un: case ILOpcode.div: case ILOpcode.div_un: case ILOpcode.initobj: case ILOpcode.ldelem: case ILOpcode.ldelem_i: case ILOpcode.ldelem_i1: case ILOpcode.ldelem_i2: case ILOpcode.ldelem_i4: case ILOpcode.ldelem_i8: case ILOpcode.ldelem_r4: case ILOpcode.ldelem_r8: case ILOpcode.ldelem_ref: case ILOpcode.ldelem_u1: case ILOpcode.ldelem_u2: case ILOpcode.ldelem_u4: case ILOpcode.ldelema: case ILOpcode.mkrefany: case ILOpcode.mul: case ILOpcode.mul_ovf: case ILOpcode.mul_ovf_un: case ILOpcode.or: case ILOpcode.pop: case ILOpcode.rem: case ILOpcode.rem_un: case ILOpcode.shl: case ILOpcode.shr: case ILOpcode.shr_un: case ILOpcode.stsfld: case ILOpcode.sub: case ILOpcode.sub_ovf: case ILOpcode.sub_ovf_un: case ILOpcode.xor: case ILOpcode.starg: case ILOpcode.starg_s: case ILOpcode.stloc: case ILOpcode.stloc_0: case ILOpcode.stloc_1: case ILOpcode.stloc_2: case ILOpcode.stloc_3: case ILOpcode.stloc_s: Debug.Assert(stackHeight > 0); stackHeight -= 1; break; case ILOpcode.throw_: Debug.Assert(stackHeight > 0); stackHeight = StackHeightNotSet; break; case ILOpcode.br: case ILOpcode.leave: case ILOpcode.brfalse: case ILOpcode.brtrue: case ILOpcode.beq: case ILOpcode.bge: case ILOpcode.bge_un: case ILOpcode.bgt: case ILOpcode.bgt_un: case ILOpcode.ble: case ILOpcode.ble_un: case ILOpcode.blt: case ILOpcode.blt_un: case ILOpcode.bne_un: { int target = currentOffset + ReadInt32(ilbytes, currentOffset + 1) + 5; int adjustment; bool isConditional; if (opcode == ILOpcode.br || opcode == ILOpcode.leave) { isConditional = false; adjustment = 0; } else if (opcode == ILOpcode.brfalse || opcode == ILOpcode.brtrue) { isConditional = true; adjustment = 1; } else { isConditional = true; adjustment = 2; } Debug.Assert(stackHeight >= adjustment); stackHeight -= adjustment; Debug.Assert(stackHeights[target] == StackHeightNotSet || stackHeights[target] == stackHeight); // Forward branch carries information about stack height at a future // offset. We need to remember it. if (target > currentOffset) stackHeights[target] = stackHeight; if (!isConditional) stackHeight = StackHeightNotSet; } break; case ILOpcode.br_s: case ILOpcode.leave_s: case ILOpcode.brfalse_s: case ILOpcode.brtrue_s: case ILOpcode.beq_s: case ILOpcode.bge_s: case ILOpcode.bge_un_s: case ILOpcode.bgt_s: case ILOpcode.bgt_un_s: case ILOpcode.ble_s: case ILOpcode.ble_un_s: case ILOpcode.blt_s: case ILOpcode.blt_un_s: case ILOpcode.bne_un_s: { int target = currentOffset + (sbyte)ilbytes[currentOffset + 1] + 2; int adjustment; bool isConditional; if (opcode == ILOpcode.br_s || opcode == ILOpcode.leave_s) { isConditional = false; adjustment = 0; } else if (opcode == ILOpcode.brfalse_s || opcode == ILOpcode.brtrue_s) { isConditional = true; adjustment = 1; } else { isConditional = true; adjustment = 2; } Debug.Assert(stackHeight >= adjustment); stackHeight -= adjustment; Debug.Assert(stackHeights[target] == StackHeightNotSet || stackHeights[target] == stackHeight); // Forward branch carries information about stack height at a future // offset. We need to remember it. if (target > currentOffset) stackHeights[target] = stackHeight; if (!isConditional) stackHeight = StackHeightNotSet; } break; case ILOpcode.call: case ILOpcode.calli: case ILOpcode.callvirt: case ILOpcode.newobj: { int token = ReadILToken(ilbytes, currentOffset + 1); object obj = methodIL.GetObject(token); MethodSignature sig = obj is MethodSignature ? (MethodSignature)obj : ((MethodDesc)obj).Signature; int adjustment = sig.Length; if (opcode == ILOpcode.newobj) { adjustment--; } else { if (opcode == ILOpcode.calli) adjustment++; if (!sig.IsStatic) adjustment++; if (!sig.ReturnType.IsVoid) adjustment--; } Debug.Assert(stackHeight >= adjustment); stackHeight -= adjustment; } break; case ILOpcode.ret: { bool hasReturnValue = !methodIL.OwningMethod.Signature.ReturnType.IsVoid; if (hasReturnValue) stackHeight -= 1; Debug.Assert(stackHeight == 0); stackHeight = StackHeightNotSet; } break; case ILOpcode.cpobj: case ILOpcode.stfld: case ILOpcode.stind_i: case ILOpcode.stind_i1: case ILOpcode.stind_i2: case ILOpcode.stind_i4: case ILOpcode.stind_i8: case ILOpcode.stind_r4: case ILOpcode.stind_r8: case ILOpcode.stind_ref: case ILOpcode.stobj: Debug.Assert(stackHeight > 1); stackHeight -= 2; break; case ILOpcode.cpblk: case ILOpcode.initblk: case ILOpcode.stelem: case ILOpcode.stelem_i: case ILOpcode.stelem_i1: case ILOpcode.stelem_i2: case ILOpcode.stelem_i4: case ILOpcode.stelem_i8: case ILOpcode.stelem_r4: case ILOpcode.stelem_r8: case ILOpcode.stelem_ref: Debug.Assert(stackHeight > 2); stackHeight -= 3; break; case ILOpcode.break_: case ILOpcode.constrained: case ILOpcode.no: case ILOpcode.nop: case ILOpcode.readonly_: case ILOpcode.tail: case ILOpcode.unaligned: case ILOpcode.volatile_: break; case ILOpcode.endfilter: Debug.Assert(stackHeight > 0); stackHeight = StackHeightNotSet; break; case ILOpcode.jmp: case ILOpcode.rethrow: case ILOpcode.endfinally: stackHeight = StackHeightNotSet; break; case ILOpcode.box: case ILOpcode.castclass: case ILOpcode.ckfinite: case ILOpcode.conv_i: case ILOpcode.conv_i1: case ILOpcode.conv_i2: case ILOpcode.conv_i4: case ILOpcode.conv_i8: case ILOpcode.conv_ovf_i: case ILOpcode.conv_ovf_i_un: case ILOpcode.conv_ovf_i1: case ILOpcode.conv_ovf_i1_un: case ILOpcode.conv_ovf_i2: case ILOpcode.conv_ovf_i2_un: case ILOpcode.conv_ovf_i4: case ILOpcode.conv_ovf_i4_un: case ILOpcode.conv_ovf_i8: case ILOpcode.conv_ovf_i8_un: case ILOpcode.conv_ovf_u: case ILOpcode.conv_ovf_u_un: case ILOpcode.conv_ovf_u1: case ILOpcode.conv_ovf_u1_un: case ILOpcode.conv_ovf_u2: case ILOpcode.conv_ovf_u2_un: case ILOpcode.conv_ovf_u4: case ILOpcode.conv_ovf_u4_un: case ILOpcode.conv_ovf_u8: case ILOpcode.conv_ovf_u8_un: case ILOpcode.conv_r_un: case ILOpcode.conv_r4: case ILOpcode.conv_r8: case ILOpcode.conv_u: case ILOpcode.conv_u1: case ILOpcode.conv_u2: case ILOpcode.conv_u4: case ILOpcode.conv_u8: case ILOpcode.isinst: case ILOpcode.ldfld: case ILOpcode.ldflda: case ILOpcode.ldind_i: case ILOpcode.ldind_i1: case ILOpcode.ldind_i2: case ILOpcode.ldind_i4: case ILOpcode.ldind_i8: case ILOpcode.ldind_r4: case ILOpcode.ldind_r8: case ILOpcode.ldind_ref: case ILOpcode.ldind_u1: case ILOpcode.ldind_u2: case ILOpcode.ldind_u4: case ILOpcode.ldlen: case ILOpcode.ldobj: case ILOpcode.ldvirtftn: case ILOpcode.localloc: case ILOpcode.neg: case ILOpcode.newarr: case ILOpcode.not: case ILOpcode.refanytype: case ILOpcode.refanyval: case ILOpcode.unbox: case ILOpcode.unbox_any: Debug.Assert(stackHeight > 0); break; case ILOpcode.switch_: Debug.Assert(stackHeight > 0); isVariableSize = true; stackHeight -= 1; currentOffset += 1 + (ReadInt32(ilbytes, currentOffset + 1) * 4) + 4; break; default: Debug.Fail("Unknown instruction"); break; } if (!isVariableSize) currentOffset += opcode.GetSize(); maxStack = Math.Max(maxStack, stackHeight); } return maxStack; } private static int ReadInt32(byte[] ilBytes, int offset) { return ilBytes[offset] + (ilBytes[offset + 1] << 8) + (ilBytes[offset + 2] << 16) + (ilBytes[offset + 3] << 24); } private static int ReadILToken(byte[] ilBytes, int offset) { return ReadInt32(ilBytes, offset); } } }