Merge pull request #149 from Washi1337/feature/branch-verification
Feature/branch verification
This commit is contained in:
Коммит
7145b30842
|
@ -153,6 +153,10 @@ Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to
|
|||
|
||||
The ``switch`` operation uses a ``IList<ICilLabel>`` instead.
|
||||
|
||||
.. note::
|
||||
|
||||
When a branching instruction contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will by default report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``.
|
||||
|
||||
|
||||
Finding instructions by offset
|
||||
------------------------------
|
||||
|
@ -245,6 +249,11 @@ Exception handlers are regions in the method body that are protected from except
|
|||
|
||||
Depending on the value of ``HandlerType``, either ``FilterStart`` or ``ExceptionType``, or neither has a value.
|
||||
|
||||
.. note::
|
||||
|
||||
Similar to branch instructions, when an exception handler contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``.
|
||||
|
||||
|
||||
Maximum stack depth
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
[DebuggerDisplay("Count = {" + nameof(Count) + "}")]
|
||||
public partial class CilInstructionCollection : IList<CilInstruction>
|
||||
{
|
||||
private readonly List<CilInstruction> _items = new List<CilInstruction>();
|
||||
private readonly List<CilInstruction> _items = new();
|
||||
private ICilLabel _endLabel;
|
||||
|
||||
/// <summary>
|
||||
|
@ -44,7 +44,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
/// Gets the size in bytes of the collection.
|
||||
/// </summary>
|
||||
public int Size => _items.Sum(x => x.Size);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public CilInstruction this[int index]
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
return _endLabel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(CilInstruction item) => _items.Add(item);
|
||||
|
||||
|
@ -135,7 +135,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
/// </exception>
|
||||
public void RemoveAt(int baseIndex, params int[] relativeIndices) =>
|
||||
RemoveAt(baseIndex, relativeIndices.AsEnumerable());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a set of CIL instructions based on a list of indices that are relative to a starting index.
|
||||
/// </summary>
|
||||
|
@ -158,14 +158,14 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
}
|
||||
|
||||
absoluteIndices.Sort();
|
||||
|
||||
|
||||
// Remove indices.
|
||||
for (int i = 0; i < absoluteIndices.Count; i++)
|
||||
{
|
||||
int index = absoluteIndices[i];
|
||||
_items.RemoveAt(index);
|
||||
|
||||
// Removal of instruction offsets all remaining indices by one. Update remaining indices.
|
||||
|
||||
// Removal of instruction offsets all remaining indices by one. Update remaining indices.
|
||||
for (int j = i+1; j < absoluteIndices.Count; j++)
|
||||
absoluteIndices[j]--;
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Searches for an instruction with the given offset.
|
||||
/// </summary>
|
||||
|
@ -221,7 +221,9 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
public CilInstruction GetByOffset(int offset)
|
||||
{
|
||||
int index = GetIndexByOffset(offset);
|
||||
return index == -1 ? null : _items[index];
|
||||
return index == -1
|
||||
? null
|
||||
: _items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -236,7 +238,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
if (offset >= EndLabel.Offset)
|
||||
return EndLabel;
|
||||
|
||||
|
||||
var instruction = GetByOffset(offset);
|
||||
return instruction is null
|
||||
? new CilOffsetLabel(offset)
|
||||
|
@ -244,7 +246,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the offsets of each instruction in the list.
|
||||
/// Calculates the offsets of each instruction in the list.
|
||||
/// </summary>
|
||||
public void CalculateOffsets()
|
||||
{
|
||||
|
@ -388,14 +390,14 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
// Repeat until no more optimizations can be done.
|
||||
}
|
||||
|
||||
|
||||
CalculateOffsets();
|
||||
}
|
||||
|
||||
private bool OptimizeMacrosPass()
|
||||
{
|
||||
CalculateOffsets();
|
||||
|
||||
|
||||
bool changed = false;
|
||||
foreach (var instruction in _items)
|
||||
changed |= TryOptimizeMacro(instruction);
|
||||
|
@ -483,7 +485,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
int value = instruction.GetLdcI4Constant();
|
||||
var (code, operand) = CilInstruction.GetLdcI4OpCodeOperand(value);
|
||||
|
||||
|
||||
if (code != instruction.OpCode)
|
||||
{
|
||||
instruction.OpCode = code;
|
||||
|
@ -497,7 +499,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
private bool TryOptimizeVariable(CilInstruction instruction)
|
||||
{
|
||||
var variable = instruction.GetLocalVariable(Owner.LocalVariables);
|
||||
|
||||
|
||||
CilOpCode code = instruction.OpCode;
|
||||
object operand = instruction.Operand;
|
||||
|
||||
|
@ -558,7 +560,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
else if (instruction.IsStarg())
|
||||
{
|
||||
code = parameter.MethodSignatureIndex <= byte.MaxValue
|
||||
? CilOpCodes.Starg_S
|
||||
? CilOpCodes.Starg_S
|
||||
: CilOpCodes.Starg;
|
||||
}
|
||||
|
||||
|
@ -632,4 +634,4 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
case CilStackBehaviour.Pop0:
|
||||
return 0;
|
||||
|
||||
|
||||
case CilStackBehaviour.Pop1:
|
||||
case CilStackBehaviour.PopI:
|
||||
case CilStackBehaviour.PopRef:
|
||||
return 1;
|
||||
|
||||
|
||||
case CilStackBehaviour.Pop1_Pop1:
|
||||
case CilStackBehaviour.PopI_Pop1:
|
||||
case CilStackBehaviour.PopI_PopI:
|
||||
|
@ -56,7 +56,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilStackBehaviour.PopRef_Pop1:
|
||||
case CilStackBehaviour.PopRef_PopI:
|
||||
return 2;
|
||||
|
||||
|
||||
case CilStackBehaviour.PopI_PopI_PopI:
|
||||
case CilStackBehaviour.PopRef_PopI_PopI:
|
||||
case CilStackBehaviour.PopRef_PopI_PopI8:
|
||||
|
@ -65,13 +65,13 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilStackBehaviour.PopRef_PopI_PopRef:
|
||||
case CilStackBehaviour.PopRef_PopI_Pop1:
|
||||
return 3;
|
||||
|
||||
|
||||
case CilStackBehaviour.VarPop:
|
||||
return DetermineVarPopCount(instruction, isVoid);
|
||||
|
||||
|
||||
case CilStackBehaviour.PopAll:
|
||||
return -1;
|
||||
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
// NewObj produces instead of consumes the this parameter.
|
||||
count--;
|
||||
break;
|
||||
|
||||
|
||||
case CilCode.Calli:
|
||||
// Calli consumes an extra parameter (the address to call).
|
||||
count++;
|
||||
|
@ -126,7 +126,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
case CilStackBehaviour.Push0:
|
||||
return 0;
|
||||
|
||||
|
||||
case CilStackBehaviour.Push1:
|
||||
case CilStackBehaviour.PushI:
|
||||
case CilStackBehaviour.PushI8:
|
||||
|
@ -134,13 +134,13 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilStackBehaviour.PushR8:
|
||||
case CilStackBehaviour.PushRef:
|
||||
return 1;
|
||||
|
||||
|
||||
case CilStackBehaviour.Push1_Push1:
|
||||
return 2;
|
||||
|
||||
|
||||
case CilStackBehaviour.VarPush:
|
||||
return DetermineVarPushCount(instruction);
|
||||
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
|
||||
if (signature == null)
|
||||
return 0;
|
||||
|
||||
|
||||
if (!signature.ReturnType.IsTypeOf("System", "Void") || instruction.OpCode.Code == CilCode.Newobj)
|
||||
return 1;
|
||||
|
||||
|
@ -183,23 +183,23 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilCode.Ldloca:
|
||||
case CilCode.Ldloca_S:
|
||||
return (CilLocalVariable) instruction.Operand;
|
||||
|
||||
|
||||
case CilCode.Ldloc_0:
|
||||
case CilCode.Stloc_0:
|
||||
return variables[0];
|
||||
|
||||
|
||||
case CilCode.Ldloc_1:
|
||||
case CilCode.Stloc_1:
|
||||
return variables[1];
|
||||
|
||||
|
||||
case CilCode.Ldloc_2:
|
||||
case CilCode.Stloc_2:
|
||||
return variables[2];
|
||||
|
||||
|
||||
case CilCode.Ldloc_3:
|
||||
case CilCode.Stloc_3:
|
||||
return variables[3];
|
||||
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Instruction is not a ldloc or stloc instruction.");
|
||||
}
|
||||
|
@ -224,23 +224,23 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilCode.Starg:
|
||||
case CilCode.Starg_S:
|
||||
return (Parameter) instruction.Operand;
|
||||
|
||||
|
||||
case CilCode.Ldarg_0:
|
||||
return parameters.GetBySignatureIndex(0);
|
||||
|
||||
|
||||
case CilCode.Ldarg_1:
|
||||
return parameters.GetBySignatureIndex(1);
|
||||
|
||||
|
||||
case CilCode.Ldarg_2:
|
||||
return parameters.GetBySignatureIndex(2);
|
||||
|
||||
|
||||
case CilCode.Ldarg_3:
|
||||
return parameters.GetBySignatureIndex(3);
|
||||
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Instruction is not a ldarg or starg instruction.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AsmResolver.PE.DotNet.Cil;
|
||||
|
||||
namespace AsmResolver.DotNet.Code.Cil
|
||||
{
|
||||
internal struct CilLabelVerifier
|
||||
{
|
||||
private readonly CilMethodBody _body;
|
||||
private List<Exception> _diagnostics;
|
||||
|
||||
public CilLabelVerifier(CilMethodBody body)
|
||||
{
|
||||
_body = body ?? throw new ArgumentNullException(nameof(body));
|
||||
_diagnostics = null;
|
||||
}
|
||||
|
||||
public void Verify()
|
||||
{
|
||||
VerifyInstructions();
|
||||
VerifyExceptionHandlers();
|
||||
|
||||
if (_diagnostics is null)
|
||||
return;
|
||||
|
||||
switch (_diagnostics.Count)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
throw _diagnostics[0];
|
||||
default:
|
||||
throw new AggregateException("Method body contains multiple invalid branch targets.", _diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyInstructions()
|
||||
{
|
||||
foreach (var instruction in _body.Instructions)
|
||||
{
|
||||
switch (instruction.OpCode.OperandType)
|
||||
{
|
||||
case CilOperandType.InlineBrTarget:
|
||||
case CilOperandType.ShortInlineBrTarget:
|
||||
VerifyBranchLabel(instruction.Offset, (ICilLabel) instruction.Operand);
|
||||
break;
|
||||
|
||||
case CilOperandType.InlineSwitch:
|
||||
var targets = (IList<ICilLabel>) instruction.Operand;
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
VerifyBranchLabel(instruction.Offset, targets[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyExceptionHandlers()
|
||||
{
|
||||
for (int i = 0; i < _body.ExceptionHandlers.Count; i++)
|
||||
{
|
||||
var handler = _body.ExceptionHandlers[i];
|
||||
VerifyHandlerLabel(i, "Try Start", handler.TryStart);
|
||||
VerifyHandlerLabel(i, "Try End", handler.TryEnd);
|
||||
VerifyHandlerLabel(i, "Handler Start", handler.HandlerStart);
|
||||
VerifyHandlerLabel(i, "Handler End", handler.HandlerEnd);
|
||||
|
||||
if (handler.HandlerType == CilExceptionHandlerType.Filter)
|
||||
VerifyHandlerLabel(i, "Filter Start", handler.FilterStart);
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyBranchLabel(int offset, ICilLabel label)
|
||||
{
|
||||
switch (label)
|
||||
{
|
||||
case null:
|
||||
AddDiagnostic($"Branch target of IL_{offset:X4} is null.");
|
||||
break;
|
||||
|
||||
case CilInstructionLabel {Instruction: { } instruction}:
|
||||
if (!IsPresentInBody(instruction))
|
||||
AddDiagnostic($"IL_{offset:X4} references an instruction that is not present in the method body.");
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!IsPresentInBody(label.Offset))
|
||||
AddDiagnostic($"IL_{offset:X4} references an offset that is not present in the method body.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyHandlerLabel(int handlerIndex, string labelName, ICilLabel label)
|
||||
{
|
||||
switch (label)
|
||||
{
|
||||
case null:
|
||||
AddDiagnostic($"{labelName} of exception handler {handlerIndex.ToString()} is null.");
|
||||
break;
|
||||
|
||||
case CilInstructionLabel {Instruction: { } instruction}:
|
||||
if (!IsPresentInBody(instruction))
|
||||
AddDiagnostic($"{labelName} of exception handler {handlerIndex.ToString()} references an instruction that is not present in the method body.");
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!IsPresentInBody(label.Offset))
|
||||
AddDiagnostic($"{labelName} of exception handler {handlerIndex.ToString()} references an offset that is not present in the method body.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsPresentInBody(CilInstruction instruction) =>
|
||||
ReferenceEquals(_body.Instructions.GetByOffset(instruction.Offset), instruction);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsPresentInBody(int offset) =>
|
||||
_body.Instructions.GetIndexByOffset(offset) > 0;
|
||||
|
||||
private void AddDiagnostic(string message)
|
||||
{
|
||||
_diagnostics ??= new List<Exception>();
|
||||
_diagnostics.Add(new InvalidCilInstructionException(message));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AsmResolver.PE.DotNet.Cil;
|
||||
|
||||
namespace AsmResolver.DotNet.Code.Cil
|
||||
{
|
||||
internal readonly ref struct CilMaxStackCalculator
|
||||
{
|
||||
private readonly CilMethodBody _body;
|
||||
private readonly Stack<StackState> _agenda;
|
||||
private readonly int?[] _recordedStackSizes;
|
||||
|
||||
public CilMaxStackCalculator(CilMethodBody body)
|
||||
{
|
||||
_body = body ?? throw new ArgumentNullException(nameof(body));
|
||||
|
||||
if (_body.Instructions.Count > 0)
|
||||
{
|
||||
_agenda = new Stack<StackState>();
|
||||
_recordedStackSizes = new int?[_body.Instructions.Count];
|
||||
}
|
||||
else
|
||||
{
|
||||
_agenda = null;
|
||||
_recordedStackSizes = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int Compute()
|
||||
{
|
||||
if (_body.Instructions.Count == 0)
|
||||
return 0;
|
||||
|
||||
int result = 0;
|
||||
|
||||
// Add entry points to agenda.
|
||||
ScheduleEntryPoints();
|
||||
|
||||
while (_agenda.Count > 0)
|
||||
{
|
||||
var currentState = _agenda.Pop();
|
||||
|
||||
// Check if we got passed the end of the method body. This only happens if the CIL code is invalid.
|
||||
if (currentState.InstructionIndex >= _body.Instructions.Count)
|
||||
{
|
||||
var last = _body.Instructions[_body.Instructions.Count - 1];
|
||||
throw new StackImbalanceException(_body, last.Offset + last.Size);
|
||||
}
|
||||
|
||||
int? recordedStackSize = _recordedStackSizes[currentState.InstructionIndex];
|
||||
if (recordedStackSize.HasValue)
|
||||
{
|
||||
// Check if previously visited state is consistent with current observation.
|
||||
if (recordedStackSize.Value != currentState.StackSize)
|
||||
throw new StackImbalanceException(_body, _body.Instructions[currentState.InstructionIndex].Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark instruction as visited and store current state.
|
||||
_recordedStackSizes[currentState.InstructionIndex] = currentState.StackSize;
|
||||
|
||||
// Schedule successors of current instruction.
|
||||
ScheduleSuccessors(currentState);
|
||||
}
|
||||
|
||||
// Maintain largest found stack size.
|
||||
if (currentState.StackSize > result)
|
||||
result = currentState.StackSize;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ScheduleEntryPoints()
|
||||
{
|
||||
// Schedule offset 0.
|
||||
_agenda.Push(new StackState(0, 0));
|
||||
|
||||
// Handler blocks are not referenced explicitly by instructions.
|
||||
// Therefore we need to schedule them explicitly as well.
|
||||
var instructions = _body.Instructions;
|
||||
|
||||
for (int i = 0; i < _body.ExceptionHandlers.Count; i++)
|
||||
{
|
||||
var handler = _body.ExceptionHandlers[i];
|
||||
|
||||
// Determine stack size at the start of the handler block.
|
||||
int stackDelta = handler.HandlerType switch
|
||||
{
|
||||
CilExceptionHandlerType.Exception => 1,
|
||||
CilExceptionHandlerType.Filter => 1,
|
||||
CilExceptionHandlerType.Finally => 0,
|
||||
CilExceptionHandlerType.Fault => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(handler.HandlerType))
|
||||
};
|
||||
|
||||
_agenda.Push(new StackState(instructions.GetIndexByOffset(handler.TryStart.Offset), 0));
|
||||
_agenda.Push(new StackState(instructions.GetIndexByOffset(handler.HandlerStart.Offset), stackDelta));
|
||||
|
||||
if (handler.FilterStart is {Offset: { } offset})
|
||||
_agenda.Push(new StackState(instructions.GetIndexByOffset(offset), 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleSuccessors(in StackState currentState)
|
||||
{
|
||||
var instruction = _body.Instructions[currentState.InstructionIndex];
|
||||
|
||||
// Pop values from stack.
|
||||
int popCount = instruction.GetStackPopCount(_body);
|
||||
int nextStackSize = popCount == -1 ? 0 : currentState.StackSize - popCount;
|
||||
if (nextStackSize < 0)
|
||||
throw new StackImbalanceException(_body, instruction.Offset);
|
||||
|
||||
// Push values on the stack.
|
||||
nextStackSize += instruction.GetStackPushCount();
|
||||
|
||||
// Add outgoing edges to agenda.
|
||||
if (instruction.OpCode.Code == CilCode.Jmp)
|
||||
{
|
||||
// jmp instructions need special treatment:
|
||||
// Upon execution of a jmp instruction, the stack must be empty.
|
||||
// Besides, jmps have no outgoing edges, even though they are classified as FlowControl.Call.
|
||||
if (nextStackSize != 0)
|
||||
throw new StackImbalanceException(_body, instruction.Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (instruction.OpCode.FlowControl)
|
||||
{
|
||||
case CilFlowControl.Branch:
|
||||
// Schedule branch target.
|
||||
ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch:
|
||||
// Schedule all switch targets for processing.
|
||||
var targets = (IList<ICilLabel>) instruction.Operand;
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
ScheduleLabel(instruction.Offset, targets[i], nextStackSize);
|
||||
|
||||
// Schedule default case (= fallthrough instruction).
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.ConditionalBranch:
|
||||
// Schedule branch target.
|
||||
ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand, nextStackSize);
|
||||
|
||||
// Schedule fallthrough instruction.
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.Call:
|
||||
case CilFlowControl.Break:
|
||||
case CilFlowControl.Meta:
|
||||
case CilFlowControl.Phi:
|
||||
case CilFlowControl.Next:
|
||||
// Schedule fallthrough instruction.
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.Throw:
|
||||
// Throw instructions just stop execution and clear any remaining values on stack.
|
||||
// => no stack imbalance if too many values are pushed on the stack.
|
||||
break;
|
||||
|
||||
case CilFlowControl.Return:
|
||||
// Verify final stack size is correct.
|
||||
if (nextStackSize != 0)
|
||||
throw new StackImbalanceException(_body, instruction.Offset);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
$"Invalid or unsupported operand type at offset IL_{instruction.Offset:X4}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ScheduleLabel(int currentIndex, ICilLabel label, int nextStackSize)
|
||||
{
|
||||
int nextIndex = _body.Instructions.GetIndexByOffset(label.Offset);
|
||||
ScheduleIndex(currentIndex, nextIndex, label.Offset, nextStackSize);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ScheduleNext(int currentIndex, int nextStackSize)
|
||||
{
|
||||
var instruction = _body.Instructions[currentIndex];
|
||||
ScheduleIndex(currentIndex, currentIndex + 1, instruction.Offset + instruction.Size, nextStackSize);
|
||||
}
|
||||
|
||||
private void ScheduleIndex(int currentIndex, int nextIndex, int nextOffset, int nextStackSize)
|
||||
{
|
||||
if (nextIndex < 0 && nextIndex >= _body.Instructions.Count)
|
||||
{
|
||||
var instruction = _body.Instructions[currentIndex];
|
||||
throw new InvalidProgramException(
|
||||
$"Instruction at offset IL_{instruction.Offset:X4} transfers control to a non-existing offset IL_{nextOffset:X4}.");
|
||||
}
|
||||
|
||||
_agenda.Push(new StackState(nextIndex, nextStackSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides information about the state of the stack at a particular point of execution in a method.
|
||||
/// </summary>
|
||||
private readonly struct StackState
|
||||
{
|
||||
/// <summary>
|
||||
/// The index of the instruction the state is associated to.
|
||||
/// </summary>
|
||||
public readonly int InstructionIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The number of values currently on the stack.
|
||||
/// </summary>
|
||||
public readonly int StackSize;
|
||||
|
||||
public StackState(int instructionIndex, int stackSize)
|
||||
{
|
||||
InstructionIndex = instructionIndex;
|
||||
StackSize = stackSize;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
{
|
||||
return $"InstructionIndex: {InstructionIndex}, StackSize: {StackSize}";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AsmResolver.DotNet.Serialized;
|
||||
using AsmResolver.DotNet.Signatures;
|
||||
using AsmResolver.PE.DotNet.Cil;
|
||||
|
@ -9,203 +9,10 @@ using AsmResolver.PE.DotNet.Metadata.Tables;
|
|||
namespace AsmResolver.DotNet.Code.Cil
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a method body of a method defined in a .NET assembly, implemented using the Common Intermediate Language (CIL).
|
||||
/// Represents a method body of a method defined in a .NET assembly, implemented using the Common Intermediate Language (CIL).
|
||||
/// </summary>
|
||||
public class CilMethodBody : MethodBody
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a CIL method body from a dynamic method.
|
||||
/// </summary>
|
||||
/// <param name="method">The method that owns the method body.</param>
|
||||
/// <param name="dynamicMethodObj">The Dynamic Method/Delegate/DynamicResolver.</param>
|
||||
/// <param name="operandResolver">
|
||||
/// The object instance to use for resolving operands of an instruction in the
|
||||
/// method body.
|
||||
/// </param>
|
||||
/// <param name="importer">
|
||||
/// The object instance to use for importing operands of an instruction in the
|
||||
/// method body.
|
||||
/// </param>
|
||||
/// <returns>The method body.</returns>
|
||||
public static CilMethodBody FromDynamicMethod(
|
||||
MethodDefinition method,
|
||||
object dynamicMethodObj,
|
||||
ICilOperandResolver operandResolver = null,
|
||||
ReferenceImporter importer = null)
|
||||
{
|
||||
if (!(method.Module is SerializedModuleDefinition module))
|
||||
throw new ArgumentException("Method body should reference a serialized module.");
|
||||
|
||||
var result = new CilMethodBody(method);
|
||||
|
||||
operandResolver ??= new CilOperandResolver(method.Module, result);
|
||||
importer ??= new ReferenceImporter(method.Module);
|
||||
|
||||
dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
|
||||
|
||||
//Get Runtime Fields
|
||||
var code = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_code");
|
||||
var scope = FieldReader.ReadField<object>(dynamicMethodObj, "m_scope");
|
||||
var tokenList = FieldReader.ReadField<List<object>>(scope, "m_tokens");
|
||||
var localSig = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_localSignature");
|
||||
var ehHeader = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_exceptionHeader");
|
||||
var ehInfos = FieldReader.ReadField<IList<object>>(dynamicMethodObj, "m_exceptions");
|
||||
|
||||
// Read raw instructions.
|
||||
var reader = new ByteArrayReader(code);
|
||||
var disassembler = new CilDisassembler(reader);
|
||||
result.Instructions.AddRange(disassembler.ReadAllInstructions());
|
||||
|
||||
//Local Variables
|
||||
DynamicMethodHelper.ReadLocalVariables(result, method, localSig);
|
||||
|
||||
//Exception Handlers
|
||||
DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, importer);
|
||||
|
||||
// Resolve all operands.
|
||||
foreach (var instruction in result.Instructions)
|
||||
{
|
||||
instruction.Operand =
|
||||
DynamicMethodHelper.ResolveOperandReflection(module.ReaderContext, result, instruction, operandResolver, tokenList, importer) ??
|
||||
instruction.Operand;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CIL method body from a raw CIL method body.
|
||||
/// </summary>
|
||||
/// <param name="context">The reader context.</param>
|
||||
/// <param name="method">The method that owns the method body.</param>
|
||||
/// <param name="rawBody">The raw method body.</param>
|
||||
/// <param name="operandResolver">The object instance to use for resolving operands of an instruction in the
|
||||
/// method body.</param>
|
||||
/// <returns>The method body.</returns>
|
||||
public static CilMethodBody FromRawMethodBody(
|
||||
ModuleReaderContext context,
|
||||
MethodDefinition method,
|
||||
CilRawMethodBody rawBody,
|
||||
ICilOperandResolver operandResolver = null)
|
||||
{
|
||||
var result = new CilMethodBody(method);
|
||||
|
||||
if (operandResolver is null)
|
||||
operandResolver = new CilOperandResolver(context.ParentModule, result);
|
||||
|
||||
// Read raw instructions.
|
||||
var reader = new ByteArrayReader(rawBody.Code);
|
||||
var disassembler = new CilDisassembler(reader);
|
||||
result.Instructions.AddRange(disassembler.ReadAllInstructions());
|
||||
|
||||
// Read out extra metadata.
|
||||
if (rawBody is CilRawFatMethodBody fatBody)
|
||||
{
|
||||
result.MaxStack = fatBody.MaxStack;
|
||||
result.InitializeLocals = fatBody.InitLocals;
|
||||
|
||||
ReadLocalVariables(method.Module, result, fatBody);
|
||||
ReadExceptionHandlers(fatBody, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.MaxStack = 8;
|
||||
result.InitializeLocals = false;
|
||||
}
|
||||
|
||||
// Resolve operands.
|
||||
foreach (var instruction in result.Instructions)
|
||||
instruction.Operand = ResolveOperand(result, instruction, operandResolver) ?? instruction.Operand;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object ResolveOperand(CilMethodBody methodBody, CilInstruction instruction,
|
||||
ICilOperandResolver resolver)
|
||||
{
|
||||
switch (instruction.OpCode.OperandType)
|
||||
{
|
||||
case CilOperandType.InlineBrTarget:
|
||||
case CilOperandType.ShortInlineBrTarget:
|
||||
return new CilInstructionLabel(
|
||||
methodBody.Instructions.GetByOffset(((ICilLabel) instruction.Operand).Offset));
|
||||
|
||||
case CilOperandType.InlineField:
|
||||
case CilOperandType.InlineMethod:
|
||||
case CilOperandType.InlineSig:
|
||||
case CilOperandType.InlineTok:
|
||||
case CilOperandType.InlineType:
|
||||
return resolver.ResolveMember((MetadataToken) instruction.Operand);
|
||||
|
||||
case CilOperandType.InlineString:
|
||||
return resolver.ResolveString((MetadataToken) instruction.Operand);
|
||||
|
||||
case CilOperandType.InlineSwitch:
|
||||
var result = new List<ICilLabel>();
|
||||
var labels = (IEnumerable<ICilLabel>) instruction.Operand;
|
||||
foreach (var label in labels)
|
||||
{
|
||||
var target = methodBody.Instructions.GetByOffset(label.Offset);
|
||||
result.Add(target == null ? label : new CilInstructionLabel(target));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
case CilOperandType.InlineVar:
|
||||
case CilOperandType.ShortInlineVar:
|
||||
return resolver.ResolveLocalVariable(Convert.ToInt32(instruction.Operand));
|
||||
|
||||
case CilOperandType.InlineArgument:
|
||||
case CilOperandType.ShortInlineArgument:
|
||||
return resolver.ResolveParameter(Convert.ToInt32(instruction.Operand));
|
||||
|
||||
case CilOperandType.InlineI:
|
||||
case CilOperandType.InlineI8:
|
||||
case CilOperandType.InlineNone:
|
||||
case CilOperandType.InlineR:
|
||||
case CilOperandType.ShortInlineI:
|
||||
case CilOperandType.ShortInlineR:
|
||||
return instruction.Operand;
|
||||
|
||||
case CilOperandType.InlinePhi:
|
||||
throw new NotSupportedException();
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadLocalVariables(
|
||||
ModuleDefinition module,
|
||||
CilMethodBody result,
|
||||
CilRawFatMethodBody fatBody)
|
||||
{
|
||||
if (fatBody.LocalVarSigToken != MetadataToken.Zero
|
||||
&& module.TryLookupMember(fatBody.LocalVarSigToken, out var member)
|
||||
&& member is StandAloneSignature {Signature: LocalVariablesSignature localVariablesSignature})
|
||||
{
|
||||
foreach (var type in localVariablesSignature.VariableTypes)
|
||||
result.LocalVariables.Add(new CilLocalVariable(type));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadExceptionHandlers(CilRawFatMethodBody fatBody, CilMethodBody result)
|
||||
{
|
||||
foreach (var section in fatBody.ExtraSections)
|
||||
{
|
||||
if (section.IsEHTable)
|
||||
{
|
||||
var reader = new ByteArrayReader(section.Data);
|
||||
int size = section.IsFat
|
||||
? CilExceptionHandler.FatExceptionHandlerSize
|
||||
: CilExceptionHandler.TinyExceptionHandlerSize;
|
||||
|
||||
while (reader.CanRead(size))
|
||||
result.ExceptionHandlers.Add(CilExceptionHandler.FromReader(result, reader, section.IsFat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new method body.
|
||||
/// </summary>
|
||||
|
@ -233,16 +40,6 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a .NET assembly builder should automatically compute and update the
|
||||
/// <see cref="MaxStack"/> property according to the contents of the method body.
|
||||
/// </summary>
|
||||
public bool ComputeMaxStackOnBuild
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether all local variables should be initialized to zero by the runtime
|
||||
/// upon execution of the method body.
|
||||
|
@ -288,7 +85,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
public CilLocalVariableCollection LocalVariables
|
||||
{
|
||||
get;
|
||||
} = new CilLocalVariableCollection();
|
||||
} = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of regions protected by exception handlers, finally or faulting clauses defined in the method body.
|
||||
|
@ -299,188 +96,272 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
} = new List<CilExceptionHandler>();
|
||||
|
||||
/// <summary>
|
||||
/// Computes the maximum values pushed onto the stack by this method body.
|
||||
/// Gets or sets a value indicating whether a .NET assembly builder should automatically compute and update the
|
||||
/// <see cref="MaxStack"/> property according to the contents of the method body.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="StackImbalanceException">Occurs when the method body will result in an unbalanced stack.</exception>
|
||||
/// <remarks>This method will force the offsets of each instruction to be calculated.</remarks>
|
||||
public int ComputeMaxStack()
|
||||
public bool ComputeMaxStackOnBuild
|
||||
{
|
||||
if (Instructions.Count == 0)
|
||||
return 0;
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
Instructions.CalculateOffsets();
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a .NET assembly builder should verify branch instructions and
|
||||
/// exception handler labels in this method body for validity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of this property will be ignored if <see cref="ComputeMaxStackOnBuild"/> is set to <c>true</c>.
|
||||
/// </remarks>
|
||||
public bool VerifyLabelsOnBuild
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
var recordedStackSizes = new int?[Instructions.Count];
|
||||
var agenda = new Stack<StackState>();
|
||||
/// <summary>
|
||||
/// Creates a CIL method body from a dynamic method.
|
||||
/// </summary>
|
||||
/// <param name="method">The method that owns the method body.</param>
|
||||
/// <param name="dynamicMethodObj">The Dynamic Method/Delegate/DynamicResolver.</param>
|
||||
/// <param name="operandResolver">
|
||||
/// The object instance to use for resolving operands of an instruction in the
|
||||
/// method body.
|
||||
/// </param>
|
||||
/// <param name="importer">
|
||||
/// The object instance to use for importing operands of an instruction in the
|
||||
/// method body.
|
||||
/// </param>
|
||||
/// <returns>The method body.</returns>
|
||||
public static CilMethodBody FromDynamicMethod(
|
||||
MethodDefinition method,
|
||||
object dynamicMethodObj,
|
||||
ICilOperandResolver operandResolver = null,
|
||||
ReferenceImporter importer = null)
|
||||
{
|
||||
if (!(method.Module is SerializedModuleDefinition module))
|
||||
throw new ArgumentException("Method body should reference a serialized module.");
|
||||
|
||||
// Add entrypoints to agenda.
|
||||
agenda.Push(new StackState(0, 0));
|
||||
foreach (var handler in ExceptionHandlers)
|
||||
var result = new CilMethodBody(method);
|
||||
|
||||
operandResolver ??= new CilOperandResolver(method.Module, result);
|
||||
importer ??= new ReferenceImporter(method.Module);
|
||||
|
||||
dynamicMethodObj = DynamicMethodHelper.ResolveDynamicResolver(dynamicMethodObj);
|
||||
|
||||
//Get Runtime Fields
|
||||
byte[] code = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_code");
|
||||
var scope = FieldReader.ReadField<object>(dynamicMethodObj, "m_scope");
|
||||
var tokenList = FieldReader.ReadField<List<object>>(scope, "m_tokens");
|
||||
byte[] localSig = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_localSignature");
|
||||
byte[] ehHeader = FieldReader.ReadField<byte[]>(dynamicMethodObj, "m_exceptionHeader");
|
||||
var ehInfos = FieldReader.ReadField<IList<object>>(dynamicMethodObj, "m_exceptions");
|
||||
|
||||
// Read raw instructions.
|
||||
var reader = new ByteArrayReader(code);
|
||||
var disassembler = new CilDisassembler(reader);
|
||||
result.Instructions.AddRange(disassembler.ReadAllInstructions());
|
||||
|
||||
//Local Variables
|
||||
DynamicMethodHelper.ReadLocalVariables(result, method, localSig);
|
||||
|
||||
//Exception Handlers
|
||||
DynamicMethodHelper.ReadReflectionExceptionHandlers(result, ehInfos, ehHeader, importer);
|
||||
|
||||
// Resolve all operands.
|
||||
foreach (var instruction in result.Instructions)
|
||||
{
|
||||
// Determine stack size at the start of the handler block.
|
||||
int stackDelta = handler.HandlerType switch
|
||||
{
|
||||
CilExceptionHandlerType.Exception => 1,
|
||||
CilExceptionHandlerType.Filter => 1,
|
||||
CilExceptionHandlerType.Finally => 0,
|
||||
CilExceptionHandlerType.Fault => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(handler.HandlerType))
|
||||
};
|
||||
|
||||
agenda.Push(new StackState(Instructions.GetIndexByOffset(handler.TryStart.Offset), 0));
|
||||
agenda.Push(new StackState(Instructions.GetIndexByOffset(handler.HandlerStart.Offset), stackDelta));
|
||||
|
||||
if (handler.FilterStart != null)
|
||||
agenda.Push(new StackState(Instructions.GetIndexByOffset(handler.FilterStart.Offset), 1));
|
||||
instruction.Operand =
|
||||
DynamicMethodHelper.ResolveOperandReflection(
|
||||
module.ReaderContext,
|
||||
result,
|
||||
instruction,
|
||||
operandResolver,
|
||||
tokenList,
|
||||
importer)
|
||||
?? instruction.Operand;
|
||||
}
|
||||
|
||||
while (agenda.Count > 0)
|
||||
{
|
||||
var currentState = agenda.Pop();
|
||||
if (currentState.InstructionIndex >= Instructions.Count)
|
||||
{
|
||||
var last = Instructions[Instructions.Count - 1];
|
||||
throw new StackImbalanceException(this, last.Offset + last.Size);
|
||||
}
|
||||
|
||||
var instruction = Instructions[currentState.InstructionIndex];
|
||||
|
||||
var recordedStackSize = recordedStackSizes[currentState.InstructionIndex];
|
||||
if (recordedStackSize.HasValue)
|
||||
{
|
||||
// Check if previously visited state is consistent with current observation.
|
||||
if (recordedStackSize.Value != currentState.StackSize)
|
||||
throw new StackImbalanceException(this, instruction.Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark instruction as visited and store current state.
|
||||
recordedStackSizes[currentState.InstructionIndex] = currentState.StackSize;
|
||||
|
||||
// Compute next stack size.
|
||||
int popCount = instruction.GetStackPopCount(this);
|
||||
int nextStackSize = popCount == -1 ? 0 : currentState.StackSize - popCount;
|
||||
if (nextStackSize < 0)
|
||||
throw new StackImbalanceException(this, instruction.Offset);
|
||||
nextStackSize += instruction.GetStackPushCount();
|
||||
|
||||
// Add outgoing edges to agenda.
|
||||
|
||||
if (instruction.OpCode.Code == CilCode.Jmp)
|
||||
{
|
||||
// jmp instructions need special treatment:
|
||||
// Upon execution of a jmp instruction, the stack must be empty.
|
||||
// Besides, jmps have no outgoing edges, even though they are classified as FlowControl.Call.
|
||||
if (nextStackSize != 0)
|
||||
throw new StackImbalanceException(this, instruction.Offset);
|
||||
}
|
||||
else switch (instruction.OpCode.FlowControl)
|
||||
{
|
||||
case CilFlowControl.Branch:
|
||||
// Schedule branch target.
|
||||
ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.ConditionalBranch when instruction.OpCode.Code == CilCode.Switch:
|
||||
// Schedule all switch targets for processing.
|
||||
foreach (var target in (IEnumerable<ICilLabel>) instruction.Operand)
|
||||
ScheduleLabel(instruction.Offset, target, nextStackSize);
|
||||
|
||||
// Schedule default case (= fallthrough instruction).
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.ConditionalBranch:
|
||||
// Schedule branch target.
|
||||
ScheduleLabel(instruction.Offset, (ICilLabel) instruction.Operand, nextStackSize);
|
||||
|
||||
// Schedule fallthrough instruction.
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.Call:
|
||||
case CilFlowControl.Break:
|
||||
case CilFlowControl.Meta:
|
||||
case CilFlowControl.Phi:
|
||||
case CilFlowControl.Next:
|
||||
// Schedule fallthrough instruction.
|
||||
ScheduleNext(currentState.InstructionIndex, nextStackSize);
|
||||
break;
|
||||
|
||||
case CilFlowControl.Throw:
|
||||
// Throw instructions just stop execution and clear any remaining values on stack.
|
||||
// => no stack imbalance if too many values are pushed on the stack.
|
||||
break;
|
||||
|
||||
case CilFlowControl.Return:
|
||||
// Verify final stack size is correct.
|
||||
if (nextStackSize != 0)
|
||||
throw new StackImbalanceException(this, instruction.Offset);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
$"Invalid or unsupported operand type at offset IL_{instruction.Offset:X4}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScheduleLabel(int currentIndex, ICilLabel label, int nextStackSize)
|
||||
{
|
||||
int nextIndex = Instructions.GetIndexByOffset(label.Offset);
|
||||
ScheduleIndex(currentIndex, nextIndex, label.Offset, nextStackSize);
|
||||
}
|
||||
|
||||
void ScheduleNext(int currentIndex, int nextStackSize)
|
||||
{
|
||||
var instruction = Instructions[currentIndex];
|
||||
ScheduleIndex(currentIndex, currentIndex + 1, instruction.Offset + instruction.Size, nextStackSize);
|
||||
}
|
||||
|
||||
void ScheduleIndex(int currentIndex, int nextIndex, int nextOffset, int nextStackSize)
|
||||
{
|
||||
if (nextIndex < 0 && nextIndex >= Instructions.Count)
|
||||
{
|
||||
var instruction = Instructions[currentIndex];
|
||||
throw new InvalidProgramException(
|
||||
$"Instruction at offset IL_{instruction.Offset:X4} transfers control to a non-existing offset IL_{nextOffset:X4}.");
|
||||
}
|
||||
|
||||
agenda.Push(new StackState(nextIndex, nextStackSize));
|
||||
}
|
||||
|
||||
return recordedStackSizes.Max(x => x.GetValueOrDefault());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides information about the state of the stack at a particular point of execution in a method.
|
||||
/// Creates a CIL method body from a raw CIL method body.
|
||||
/// </summary>
|
||||
private readonly struct StackState
|
||||
/// <param name="context">The reader context.</param>
|
||||
/// <param name="method">The method that owns the method body.</param>
|
||||
/// <param name="rawBody">The raw method body.</param>
|
||||
/// <param name="operandResolver">The object instance to use for resolving operands of an instruction in the
|
||||
/// method body.</param>
|
||||
/// <returns>The method body.</returns>
|
||||
public static CilMethodBody FromRawMethodBody(
|
||||
ModuleReaderContext context,
|
||||
MethodDefinition method,
|
||||
CilRawMethodBody rawBody,
|
||||
ICilOperandResolver operandResolver = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// The index of the instruction the state is associated to.
|
||||
/// </summary>
|
||||
public readonly int InstructionIndex;
|
||||
var result = new CilMethodBody(method);
|
||||
|
||||
/// <summary>
|
||||
/// The number of values currently on the stack.
|
||||
/// </summary>
|
||||
public readonly int StackSize;
|
||||
operandResolver ??= new CilOperandResolver(context.ParentModule, result);
|
||||
|
||||
public StackState(int instructionIndex, int stackSize)
|
||||
// Read raw instructions.
|
||||
var reader = new ByteArrayReader(rawBody.Code);
|
||||
var disassembler = new CilDisassembler(reader);
|
||||
result.Instructions.AddRange(disassembler.ReadAllInstructions());
|
||||
|
||||
// Read out extra metadata.
|
||||
if (rawBody is CilRawFatMethodBody fatBody)
|
||||
{
|
||||
InstructionIndex = instructionIndex;
|
||||
StackSize = stackSize;
|
||||
result.MaxStack = fatBody.MaxStack;
|
||||
result.InitializeLocals = fatBody.InitLocals;
|
||||
|
||||
ReadLocalVariables(method.Module, result, fatBody);
|
||||
ReadExceptionHandlers(fatBody, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.MaxStack = 8;
|
||||
result.InitializeLocals = false;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
// Resolve operands.
|
||||
foreach (var instruction in result.Instructions)
|
||||
instruction.Operand = ResolveOperand(result, instruction, operandResolver) ?? instruction.Operand;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object ResolveOperand(
|
||||
CilMethodBody methodBody,
|
||||
CilInstruction instruction,
|
||||
ICilOperandResolver resolver)
|
||||
{
|
||||
switch (instruction.OpCode.OperandType)
|
||||
{
|
||||
return $"InstructionIndex: {InstructionIndex}, StackSize: {StackSize}";
|
||||
case CilOperandType.InlineBrTarget:
|
||||
case CilOperandType.ShortInlineBrTarget:
|
||||
return new CilInstructionLabel(
|
||||
methodBody.Instructions.GetByOffset(((ICilLabel) instruction.Operand).Offset));
|
||||
|
||||
case CilOperandType.InlineField:
|
||||
case CilOperandType.InlineMethod:
|
||||
case CilOperandType.InlineSig:
|
||||
case CilOperandType.InlineTok:
|
||||
case CilOperandType.InlineType:
|
||||
return resolver.ResolveMember((MetadataToken) instruction.Operand);
|
||||
|
||||
case CilOperandType.InlineString:
|
||||
return resolver.ResolveString((MetadataToken) instruction.Operand);
|
||||
|
||||
case CilOperandType.InlineSwitch:
|
||||
var result = new List<ICilLabel>();
|
||||
var labels = (IList<ICilLabel>) instruction.Operand;
|
||||
for (int i = 0; i < labels.Count; i++)
|
||||
{
|
||||
var label = labels[i];
|
||||
var targetInstruction = methodBody.Instructions.GetByOffset(label.Offset);
|
||||
|
||||
result.Add(targetInstruction is null ? label : new CilInstructionLabel(targetInstruction));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
case CilOperandType.InlineVar:
|
||||
case CilOperandType.ShortInlineVar:
|
||||
return resolver.ResolveLocalVariable(Convert.ToInt32(instruction.Operand));
|
||||
|
||||
case CilOperandType.InlineArgument:
|
||||
case CilOperandType.ShortInlineArgument:
|
||||
return resolver.ResolveParameter(Convert.ToInt32(instruction.Operand));
|
||||
|
||||
case CilOperandType.InlineI:
|
||||
case CilOperandType.InlineI8:
|
||||
case CilOperandType.InlineNone:
|
||||
case CilOperandType.InlineR:
|
||||
case CilOperandType.ShortInlineI:
|
||||
case CilOperandType.ShortInlineR:
|
||||
return instruction.Operand;
|
||||
|
||||
case CilOperandType.InlinePhi:
|
||||
throw new NotSupportedException();
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void ReadLocalVariables(
|
||||
ModuleDefinition module,
|
||||
CilMethodBody result,
|
||||
CilRawFatMethodBody fatBody)
|
||||
{
|
||||
if (fatBody.LocalVarSigToken != MetadataToken.Zero
|
||||
&& module.TryLookupMember(fatBody.LocalVarSigToken, out var member)
|
||||
&& member is StandAloneSignature {Signature: LocalVariablesSignature localVariablesSignature})
|
||||
{
|
||||
var variableTypes = localVariablesSignature.VariableTypes;
|
||||
for (int i = 0; i < variableTypes.Count; i++)
|
||||
result.LocalVariables.Add(new CilLocalVariable(variableTypes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReadExceptionHandlers(CilRawFatMethodBody fatBody, CilMethodBody result)
|
||||
{
|
||||
for (int i = 0; i < fatBody.ExtraSections.Count; i++)
|
||||
{
|
||||
var section = fatBody.ExtraSections[i];
|
||||
if (section.IsEHTable)
|
||||
{
|
||||
var reader = new ByteArrayReader(section.Data);
|
||||
int size = section.IsFat
|
||||
? CilExceptionHandler.FatExceptionHandlerSize
|
||||
: CilExceptionHandler.TinyExceptionHandlerSize;
|
||||
|
||||
while (reader.CanRead(size))
|
||||
result.ExceptionHandlers.Add(CilExceptionHandler.FromReader(result, reader, section.IsFat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies all branch targets and exception handler labels in the method body for validity.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidCilInstructionException">Occurs when one branch instruction in the method body is invalid.</exception>
|
||||
/// <exception cref="AggregateException">Occurs when multiple branch instructions in the method body are invalid.</exception>
|
||||
/// <remarks>This method will force the offsets of each instruction to be calculated.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void VerifyLabels() => VerifyLabels(true);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies all branch targets and exception handler labels in the method body for validity.
|
||||
/// </summary>
|
||||
/// <param name="calculateOffsets">Determines whether offsets should be calculated beforehand.</param>
|
||||
/// <exception cref="InvalidCilInstructionException">Occurs when one branch instruction in the method body is invalid.</exception>
|
||||
/// <exception cref="AggregateException">Occurs when multiple branch instructions in the method body are invalid.</exception>
|
||||
public void VerifyLabels(bool calculateOffsets)
|
||||
{
|
||||
if (calculateOffsets)
|
||||
Instructions.CalculateOffsets();
|
||||
new CilLabelVerifier(this).Verify();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the maximum values pushed onto the stack by this method body.
|
||||
/// </summary>
|
||||
/// <exception cref="StackImbalanceException">Occurs when the method body will result in an unbalanced stack.</exception>
|
||||
/// <remarks>This method will force the offsets of each instruction to be calculated.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ComputeMaxStack() => ComputeMaxStack(true);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the maximum values pushed onto the stack by this method body.
|
||||
/// </summary>
|
||||
/// <param name="calculateOffsets">Determines whether offsets should be calculated beforehand.</param>
|
||||
/// <exception cref="StackImbalanceException">Occurs when the method body will result in an unbalanced stack.</exception>
|
||||
public int ComputeMaxStack(bool calculateOffsets)
|
||||
{
|
||||
if (calculateOffsets)
|
||||
Instructions.CalculateOffsets();
|
||||
return new CilMaxStackCalculator(this).Compute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,26 +16,48 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of an override switch indicating whether the max stack should always be recalculated
|
||||
/// or should always be preserved.
|
||||
/// or should always be preserved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When this property is set to <c>true</c>, the maximum stack depth of all method bodies will be recaculated.
|
||||
/// </para>
|
||||
/// When this property is set to <c>true</c>, the maximum stack depth of all method bodies will be recaculated.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When this property is set to <c>false</c>, the maximum stack depth of all method bodies will be preserved.
|
||||
/// </para>
|
||||
/// When this property is set to <c>false</c>, the maximum stack depth of all method bodies will be preserved.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When this property is set to <c>null</c>, the maximum stack depth will only be recalculated if
|
||||
/// <see cref="CilMethodBody.ComputeMaxStackOnBuild"/> is set to <c>true</c>.
|
||||
/// </para>
|
||||
/// <see cref="CilMethodBody.ComputeMaxStackOnBuild"/> is set to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool? ComputeMaxStackOnBuildOverride
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of an override switch indicating whether labels should always be verified for
|
||||
/// validity or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When this property is set to <c>true</c>, all method bodies will be verified for branch validity.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When this property is set to <c>false</c>, no method body will be verified for branch validity.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When this property is set to <c>null</c>, a method body will only be verified if
|
||||
/// <see cref="CilMethodBody.VerifyLabelsOnBuild"/> is set to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool? VerifyLabelsOnBuildOverride
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISegmentReference SerializeMethodBody(MethodBodySerializationContext context, MethodDefinition method)
|
||||
{
|
||||
|
@ -43,37 +65,39 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
return SegmentReference.Null;
|
||||
|
||||
var body = method.CilMethodBody;
|
||||
|
||||
// Compute max stack when specified, otherwise just calculate offsets only.
|
||||
if (ComputeMaxStackOnBuildOverride ?? body.ComputeMaxStackOnBuild)
|
||||
|
||||
body.Instructions.CalculateOffsets();
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
if (ComputeMaxStackOnBuildOverride ?? body.ComputeMaxStackOnBuild)
|
||||
{
|
||||
body.MaxStack = body.ComputeMaxStack();
|
||||
// Max stack computation requires branches to be correct.
|
||||
body.VerifyLabels(false);
|
||||
body.MaxStack = body.ComputeMaxStack(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (VerifyLabelsOnBuildOverride ?? body.VerifyLabelsOnBuild)
|
||||
{
|
||||
context.DiagnosticBag.RegisterException(ex);
|
||||
body.Instructions.CalculateOffsets();
|
||||
body.VerifyLabels(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
body.Instructions.CalculateOffsets();
|
||||
context.DiagnosticBag.RegisterException(ex);
|
||||
}
|
||||
|
||||
// Serialize CIL stream.
|
||||
var code = BuildRawCodeStream(context, body);
|
||||
|
||||
|
||||
// Build method body.
|
||||
var rawBody = body.IsFat
|
||||
? BuildFatMethodBody(context, body, code)
|
||||
var rawBody = body.IsFat
|
||||
? BuildFatMethodBody(context, body, code)
|
||||
: BuildTinyMethodBody(code);
|
||||
|
||||
return new SegmentReference(rawBody);
|
||||
}
|
||||
|
||||
private static CilRawMethodBody BuildTinyMethodBody(byte[] code) =>
|
||||
private static CilRawMethodBody BuildTinyMethodBody(byte[] code) =>
|
||||
new CilRawTinyMethodBody(code);
|
||||
|
||||
private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext context, CilMethodBody body, byte[] code)
|
||||
|
@ -93,7 +117,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
|
||||
var fatBody = new CilRawFatMethodBody(CilMethodBodyAttributes.Fat, (ushort) body.MaxStack, token, code);
|
||||
fatBody.InitLocals = body.InitializeLocals;
|
||||
|
||||
|
||||
// Build up EH table section.
|
||||
if (body.ExceptionHandlers.Count > 0)
|
||||
{
|
||||
|
@ -115,11 +139,11 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
private static byte[] BuildRawCodeStream(MethodBodySerializationContext context, CilMethodBody body)
|
||||
{
|
||||
using var codeStream = new MemoryStream();
|
||||
|
||||
|
||||
var writer = new BinaryStreamWriter(codeStream);
|
||||
var assembler = new CilAssembler(writer, new CilOperandBuilder(context.TokenProvider, context.DiagnosticBag));
|
||||
assembler.WriteInstructions(body.Instructions);
|
||||
|
||||
|
||||
return codeStream.ToArray();
|
||||
}
|
||||
|
||||
|
@ -141,7 +165,7 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
{
|
||||
if (handler.IsFat && !useFatFormat)
|
||||
throw new InvalidOperationException("Can only serialize fat exception handlers in fat format.");
|
||||
|
||||
|
||||
// Write handler type and boundaries.
|
||||
if (useFatFormat)
|
||||
{
|
||||
|
@ -183,12 +207,12 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
case CilExceptionHandlerType.Filter:
|
||||
writer.WriteUInt32((uint) handler.FilterStart.Offset);
|
||||
break;
|
||||
|
||||
|
||||
case CilExceptionHandlerType.Finally:
|
||||
case CilExceptionHandlerType.Fault:
|
||||
writer.WriteUInt32(0);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
context.DiagnosticBag.RegisterException(new ArgumentOutOfRangeException(
|
||||
$"Invalid or unsupported handler type ({handler.HandlerType.SafeToString()}"));
|
||||
|
@ -196,4 +220,4 @@ namespace AsmResolver.DotNet.Code.Cil
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
namespace AsmResolver.PE.DotNet.Cil
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label to a CIL instruction referenced by a fixed offset relative to the start of the CIL method body.
|
||||
/// </summary>
|
||||
public class CilOffsetLabel : ICilLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new fixed offset CIL label.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the instruction to reference.</param>
|
||||
public CilOffsetLabel(int offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Offset
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => "IL_" + Offset.ToString("X4");
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ICilLabel other) => other != null && Offset == other.Offset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj) => Equals(obj as ICilLabel);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => Offset;
|
||||
}
|
||||
}
|
||||
namespace AsmResolver.PE.DotNet.Cil
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label to a CIL instruction referenced by a fixed offset relative to the start of the CIL method body.
|
||||
/// </summary>
|
||||
public class CilOffsetLabel : ICilLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new fixed offset CIL label.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the instruction to reference.</param>
|
||||
public CilOffsetLabel(int offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Offset
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => "IL_" + Offset.ToString("X4");
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ICilLabel other) => other != null && Offset == other.Offset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj) => Equals(obj as ICilLabel);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => Offset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
var module = ModuleDefinition.FromFile(typeof(MethodBodyTypes).Assembly.Location);
|
||||
return GetMethodBodyInModule(module, name);
|
||||
}
|
||||
|
||||
|
||||
private static CilMethodBody GetMethodBodyInModule(ModuleDefinition module, string name)
|
||||
{
|
||||
var type = module.TopLevelTypes.First(t => t.Name == nameof(MethodBodyTypes));
|
||||
|
@ -32,13 +32,13 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
private CilMethodBody RebuildAndLookup(CilMethodBody methodBody)
|
||||
{
|
||||
var module = methodBody.Owner.Module;
|
||||
|
||||
|
||||
string tempFile = Path.GetTempFileName();
|
||||
module.Write(tempFile);
|
||||
|
||||
|
||||
var stream = new MemoryStream();
|
||||
module.Write(stream);
|
||||
|
||||
|
||||
var newModule = ModuleDefinition.FromBytes(stream.ToArray());
|
||||
return GetMethodBodyInModule(newModule, methodBody.Owner.Name);
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
public void ReadFatMethodWithManyLocals()
|
||||
{
|
||||
// https://github.com/Washi1337/AsmResolver/issues/55
|
||||
|
||||
|
||||
var body = ReadMethodBody(nameof(MethodBodyTypes.FatMethodWithManyLocals));
|
||||
int expectedIndex = 0;
|
||||
foreach (var instruction in body.Instructions)
|
||||
|
@ -122,28 +122,28 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
Assert.True(body.IsFat);
|
||||
Assert.Single(body.ExceptionHandlers);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ReadDynamicMethod()
|
||||
{
|
||||
var module = ModuleDefinition.FromFile(typeof(TDynamicMethod).Assembly.Location);
|
||||
|
||||
|
||||
var type = module.TopLevelTypes.First(t => t.Name == nameof(TDynamicMethod));
|
||||
|
||||
|
||||
var method = type.Methods.FirstOrDefault(m => m.Name == nameof(TDynamicMethod.GenerateDynamicMethod));
|
||||
|
||||
|
||||
DynamicMethod generateDynamicMethod = TDynamicMethod.GenerateDynamicMethod();
|
||||
|
||||
//Dynamic method => CilMethodBody
|
||||
var body = CilMethodBody.FromDynamicMethod(method, generateDynamicMethod);
|
||||
|
||||
|
||||
Assert.NotNull(body);
|
||||
|
||||
|
||||
Assert.NotEmpty(body.Instructions);
|
||||
|
||||
|
||||
Assert.Equal(body.Instructions.Select(q=>q.OpCode),new CilOpCode[]
|
||||
{
|
||||
CilOpCodes.Ldarg_0,
|
||||
CilOpCodes.Ldarg_0,
|
||||
CilOpCodes.Call,
|
||||
CilOpCodes.Ldarg_1,
|
||||
CilOpCodes.Ret
|
||||
|
@ -160,7 +160,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
module.GetOrCreateModuleType().Methods.Add(method);
|
||||
return method.CilMethodBody = new CilMethodBody(method);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void MaxStackComputationOnNonVoidShouldFailIfNoValueOnStack()
|
||||
{
|
||||
|
@ -169,7 +169,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
Assert.ThrowsAny<StackImbalanceException>(() => body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void MaxStackComputationOnNonVoid()
|
||||
{
|
||||
|
@ -179,7 +179,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
Assert.Equal(1, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void MaxStackComputationOnVoidShouldFailIfValueOnStack()
|
||||
{
|
||||
|
@ -189,7 +189,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
Assert.ThrowsAny<StackImbalanceException>(() => body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void JoiningPathsWithSameStackSizeShouldSucceed()
|
||||
{
|
||||
|
@ -212,7 +212,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
Assert.Equal(1, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void JoiningPathsWithDifferentStackSizesShouldFail()
|
||||
{
|
||||
|
@ -222,7 +222,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
var branchTarget1 = new CilInstructionLabel();
|
||||
var branchTarget2 = new CilInstructionLabel();
|
||||
var end = new CilInstructionLabel();
|
||||
|
||||
|
||||
instructions.Add(CilOpCodes.Ldarg_0);
|
||||
instructions.Add(CilOpCodes.Brtrue, branchTarget1);
|
||||
|
||||
|
@ -232,12 +232,12 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
branchTarget1.Instruction = instructions.Add(CilOpCodes.Ldc_I4_0);
|
||||
branchTarget2.Instruction = instructions.Add(CilOpCodes.Nop);
|
||||
end.Instruction = instructions.Add(CilOpCodes.Ret);
|
||||
end.Instruction = instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
var exception = Assert.ThrowsAny<StackImbalanceException>(() => body.ComputeMaxStack());
|
||||
Assert.Equal(end.Offset, exception.Offset);
|
||||
Assert.Equal(end.Offset, exception.Offset);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ThrowsInstructionShouldTerminateTraversal()
|
||||
{
|
||||
|
@ -305,13 +305,13 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
HandlerStart = handlerStart,
|
||||
HandlerEnd = handlerEnd,
|
||||
});
|
||||
|
||||
|
||||
tryStart.Instruction = body.Instructions.Add(CilOpCodes.Nop);
|
||||
tryEnd.Instruction = body.Instructions.Add(CilOpCodes.Leave, end);
|
||||
handlerStart.Instruction = body.Instructions.Add(CilOpCodes.Nop);
|
||||
handlerEnd.Instruction = body.Instructions.Add(CilOpCodes.Leave, end);
|
||||
end.Instruction = body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
|
||||
Assert.Equal(0, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
@ -319,7 +319,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
public void LeaveInstructionShouldClearStackAndNotFail()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
|
||||
|
||||
var end = new CilInstructionLabel();
|
||||
|
||||
var tryStart = new CilInstructionLabel();
|
||||
|
@ -336,10 +336,10 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
HandlerStart = handlerStart,
|
||||
HandlerEnd = handlerEnd,
|
||||
});
|
||||
|
||||
|
||||
tryStart.Instruction = body.Instructions.Add(CilOpCodes.Nop);
|
||||
tryEnd.Instruction = body.Instructions.Add(CilOpCodes.Leave, end);
|
||||
|
||||
|
||||
handlerStart.Instruction = body.Instructions.Add(CilOpCodes.Nop);
|
||||
// Push junk values on the stack.
|
||||
body.Instructions.Add(CilOpCodes.Ldc_I4_0);
|
||||
|
@ -347,9 +347,9 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
body.Instructions.Add(CilOpCodes.Ldc_I4_2);
|
||||
// Leave should clear.
|
||||
handlerEnd.Instruction = body.Instructions.Add(CilOpCodes.Leave, end);
|
||||
|
||||
|
||||
end.Instruction = body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
|
||||
Assert.Equal(4, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
@ -357,13 +357,13 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
public void LazyInitializationTest()
|
||||
{
|
||||
// https://github.com/Washi1337/AsmResolver/issues/97
|
||||
|
||||
|
||||
var module = ModuleDefinition.FromFile(typeof(MethodBodyTypes).Assembly.Location);
|
||||
var method = (MethodDefinition) module.LookupMember(new MetadataToken(TableIndex.Method, 1));
|
||||
var body = method.CilMethodBody;
|
||||
method.DeclaringType.Methods.Remove(method);
|
||||
Assert.NotNull(body);
|
||||
|
||||
|
||||
var module2 = ModuleDefinition.FromFile(typeof(MethodBodyTypes).Assembly.Location);
|
||||
var method2 = (MethodDefinition) module2.LookupMember(new MetadataToken(TableIndex.Method, 1));
|
||||
method2.DeclaringType.Methods.Remove(method2);
|
||||
|
@ -379,7 +379,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
body.Instructions.Add(CilOpCodes.Ldc_I4_0);
|
||||
body.Instructions.Add(CilOpCodes.Ldnull);
|
||||
body.Instructions.Add(CilOpCodes.Throw);
|
||||
|
||||
|
||||
Assert.Equal(2, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
@ -393,12 +393,12 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
|
||||
Assert.Equal(0, body.ComputeMaxStack());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void NonEmptyStackAtJmpShouldThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
|
||||
|
||||
body.Instructions.Add(CilOpCodes.Ldnull);
|
||||
body.Instructions.Add(CilOpCodes.Jmp, body.Owner);
|
||||
|
||||
|
@ -409,7 +409,7 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
public void ReadInvalidMethodBodyErrorShouldAppearInDiagnostics()
|
||||
{
|
||||
var bag = new DiagnosticBag();
|
||||
|
||||
|
||||
// Read module with diagnostic bag as error listener.
|
||||
var module = ModuleDefinition.FromBytes(
|
||||
Properties.Resources.HelloWorld_InvalidMethodBody,
|
||||
|
@ -429,6 +429,110 @@ namespace AsmResolver.DotNet.Tests.Code.Cil
|
|||
var module = ModuleDefinition.FromBytes(Properties.Resources.HandlerEndAtEndOfMethodBody);
|
||||
var body = module.ManagedEntrypointMethod.CilMethodBody;
|
||||
Assert.Same(body.Instructions.EndLabel, body.ExceptionHandlers[0].HandlerEnd);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullBranchTargetShouldThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
body.Instructions.Add(new CilInstruction(CilOpCodes.Br, null));
|
||||
Assert.Throws<InvalidCilInstructionException>(() => body.VerifyLabels());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonExistingBranchTargetShouldThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
body.Instructions.Add(CilOpCodes.Br, new CilOffsetLabel(1337));
|
||||
Assert.Throws<InvalidCilInstructionException>(() => body.VerifyLabels());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExistingOffsetBranchTargetShouldNotThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
body.Instructions.Add(CilOpCodes.Br_S, new CilOffsetLabel(2));
|
||||
body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
body.VerifyLabels();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExistingInstructionBranchTargetShouldNotThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
var label = new CilInstructionLabel();
|
||||
body.Instructions.Add(CilOpCodes.Br_S, label);
|
||||
label.Instruction = body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
body.VerifyLabels();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullHandlerShouldThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
|
||||
var handler = new CilExceptionHandler();
|
||||
body.ExceptionHandlers.Add(handler);
|
||||
Assert.Throws<AggregateException>(() => body.VerifyLabels());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidHandlerShouldNotThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
|
||||
var tryStart = new CilInstructionLabel();
|
||||
var tryEnd = new CilInstructionLabel();
|
||||
var handlerStart = new CilInstructionLabel();
|
||||
var handlerEnd = new CilInstructionLabel();
|
||||
|
||||
var handler = new CilExceptionHandler
|
||||
{
|
||||
TryStart = tryStart,
|
||||
TryEnd = tryEnd,
|
||||
HandlerStart = handlerStart,
|
||||
HandlerEnd = handlerEnd,
|
||||
HandlerType = CilExceptionHandlerType.Exception
|
||||
};
|
||||
|
||||
body.Instructions.Add(CilOpCodes.Nop);
|
||||
tryStart.Instruction = body.Instructions.Add(CilOpCodes.Leave, handlerEnd);
|
||||
handlerStart.Instruction = tryEnd.Instruction = body.Instructions.Add(CilOpCodes.Leave, handlerEnd);
|
||||
handlerEnd.Instruction = body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
body.ExceptionHandlers.Add(handler);
|
||||
body.VerifyLabels();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullFilterOnFilterHandlerShouldThrow()
|
||||
{
|
||||
var body = CreateDummyBody(true);
|
||||
|
||||
var tryStart = new CilInstructionLabel();
|
||||
var tryEnd = new CilInstructionLabel();
|
||||
var handlerStart = new CilInstructionLabel();
|
||||
var handlerEnd = new CilInstructionLabel();
|
||||
|
||||
var handler = new CilExceptionHandler
|
||||
{
|
||||
TryStart = tryStart,
|
||||
TryEnd = tryEnd,
|
||||
HandlerStart = handlerStart,
|
||||
HandlerEnd = handlerEnd,
|
||||
HandlerType = CilExceptionHandlerType.Filter
|
||||
};
|
||||
|
||||
body.Instructions.Add(CilOpCodes.Nop);
|
||||
tryStart.Instruction = body.Instructions.Add(CilOpCodes.Leave, handlerEnd);
|
||||
tryEnd.Instruction = body.Instructions.Add(CilOpCodes.Endfilter);
|
||||
handlerStart.Instruction = body.Instructions.Add(CilOpCodes.Leave, handlerEnd);
|
||||
handlerEnd.Instruction = body.Instructions.Add(CilOpCodes.Ret);
|
||||
|
||||
body.ExceptionHandlers.Add(handler);
|
||||
Assert.Throws<InvalidCilInstructionException>(() => body.VerifyLabels());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче