// dnlib: See LICENSE.txt for more info
using System.Collections.Generic;
using dnlib.DotNet.Emit;
namespace dnlib.DotNet.Writer {
///
/// Calculates max stack usage by using a simple pass over all instructions. This value
/// can be placed in the fat method header's MaxStack field.
///
public struct MaxStackCalculator {
IList instructions;
IList exceptionHandlers;
readonly Dictionary stackHeights;
bool hasError;
int currentMaxStack;
///
/// Gets max stack value
///
/// All instructions
/// All exception handlers
/// Max stack value
public static uint GetMaxStack(IList instructions, IList exceptionHandlers) {
new MaxStackCalculator(instructions, exceptionHandlers).Calculate(out uint maxStack);
return maxStack;
}
///
/// Gets max stack value
///
/// All instructions
/// All exception handlers
/// Updated with max stack value
/// true if no errors were detected, false otherwise
public static bool GetMaxStack(IList instructions, IList exceptionHandlers, out uint maxStack) =>
new MaxStackCalculator(instructions, exceptionHandlers).Calculate(out maxStack);
internal static MaxStackCalculator Create() => new MaxStackCalculator(true);
MaxStackCalculator(bool dummy) {
instructions = null;
exceptionHandlers = null;
stackHeights = new Dictionary();
hasError = false;
currentMaxStack = 0;
}
MaxStackCalculator(IList instructions, IList exceptionHandlers) {
this.instructions = instructions;
this.exceptionHandlers = exceptionHandlers;
stackHeights = new Dictionary();
hasError = false;
currentMaxStack = 0;
}
internal void Reset(IList instructions, IList exceptionHandlers) {
this.instructions = instructions;
this.exceptionHandlers = exceptionHandlers;
stackHeights.Clear();
hasError = false;
currentMaxStack = 0;
}
internal bool Calculate(out uint maxStack) {
var exceptionHandlers = this.exceptionHandlers;
var stackHeights = this.stackHeights;
for (int i = 0; i < exceptionHandlers.Count; i++) {
var eh = exceptionHandlers[i];
if (eh is null)
continue;
Instruction instr;
if ((instr = eh.TryStart) is not null)
stackHeights[instr] = 0;
if ((instr = eh.FilterStart) is not null) {
stackHeights[instr] = 1;
currentMaxStack = 1;
}
if ((instr = eh.HandlerStart) is not null) {
bool pushed = eh.IsCatch || eh.IsFilter;
if (pushed) {
stackHeights[instr] = 1;
currentMaxStack = 1;
}
else
stackHeights[instr] = 0;
}
}
int stack = 0;
bool resetStack = false;
var instructions = this.instructions;
for (int i = 0; i < instructions.Count; i++) {
var instr = instructions[i];
if (instr is null)
continue;
if (resetStack) {
stackHeights.TryGetValue(instr, out stack);
resetStack = false;
}
stack = WriteStack(instr, stack);
var opCode = instr.OpCode;
var code = opCode.Code;
if (code == Code.Jmp) {
if (stack != 0)
hasError = true;
}
else {
instr.CalculateStackUsage(out int pushes, out int pops);
if (pops == -1)
stack = 0;
else {
stack -= pops;
if (stack < 0) {
hasError = true;
stack = 0;
}
stack += pushes;
}
}
if (stack < 0) {
hasError = true;
stack = 0;
}
switch (opCode.FlowControl) {
case FlowControl.Branch:
WriteStack(instr.Operand as Instruction, stack);
resetStack = true;
break;
case FlowControl.Call:
if (code == Code.Jmp)
resetStack = true;
break;
case FlowControl.Cond_Branch:
if (code == Code.Switch) {
if (instr.Operand is IList targets) {
for (int j = 0; j < targets.Count; j++)
WriteStack(targets[j], stack);
}
}
else
WriteStack(instr.Operand as Instruction, stack);
break;
case FlowControl.Return:
case FlowControl.Throw:
resetStack = true;
break;
}
}
maxStack = (uint)currentMaxStack;
return !hasError;
}
int WriteStack(Instruction instr, int stack) {
if (instr is null) {
hasError = true;
return stack;
}
var stackHeights = this.stackHeights;
if (stackHeights.TryGetValue(instr, out int stack2)) {
if (stack != stack2)
hasError = true;
return stack2;
}
stackHeights[instr] = stack;
if (stack > currentMaxStack)
currentMaxStack = stack;
return stack;
}
}
}