obfuz/Editor/Emit/EvalStackCalculator.cs

1447 lines
54 KiB
C#
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// Copyright 2025 Code Philosophy
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Obfuz.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Assertions;
namespace Obfuz.Emit
{
enum EvalDataType
{
None,
Int32,
Int64,
Float,
Double,
I,
Ref,
ValueType,
Token,
Unknown,
}
struct EvalDataTypeWithSig
{
public readonly EvalDataType type;
public readonly TypeSig typeSig;
public EvalDataTypeWithSig(EvalDataType type, TypeSig typeSig)
{
this.type = type;
this.typeSig = typeSig;
}
public override string ToString()
{
return $"{type} ({typeSig})";
}
}
class InstructionParameterInfo
{
public readonly EvalDataType op1;
public readonly EvalDataType op2;
public readonly EvalDataType retType;
public InstructionParameterInfo(EvalDataType op1, EvalDataType op2, EvalDataType retType)
{
this.op1 = op1;
this.op2 = op2;
this.retType = retType;
}
}
class EvalStackState
{
public bool visited;
public readonly List<EvalDataTypeWithSig> inputStackDatas = new List<EvalDataTypeWithSig>();
public readonly List<EvalDataTypeWithSig> runStackDatas = new List<EvalDataTypeWithSig>();
}
class EvalStackCalculator
{
private readonly MethodDef _method;
private readonly ICorLibTypes _corLibTypes;
private readonly BasicBlockCollection _basicBlocks;
private readonly Dictionary<Instruction, InstructionParameterInfo> _instructionParameterInfos = new Dictionary<Instruction, InstructionParameterInfo>();
private readonly Dictionary<Instruction, EvalDataType> _evalStackTopDataTypeAfterInstructions = new Dictionary<Instruction, EvalDataType>();
private readonly Dictionary<BasicBlock, EvalStackState> _blockEvalStackStates;
public EvalStackCalculator(MethodDef method)
{
_method = method;
_corLibTypes = method.Module.CorLibTypes;
_basicBlocks = new BasicBlockCollection(method, false);
_blockEvalStackStates = _basicBlocks.Blocks.ToDictionary(b => b, b => new EvalStackState());
SimulateRunAllBlocks();
}
public BasicBlockCollection BasicBlockCollection => _basicBlocks;
public bool TryGetParameterInfo(Instruction inst, out InstructionParameterInfo info)
{
return _instructionParameterInfos.TryGetValue(inst, out info);
}
public bool TryGetPushResult(Instruction inst, out EvalDataType result)
{
return _evalStackTopDataTypeAfterInstructions.TryGetValue(inst, out result);
}
public EvalStackState GetEvalStackState(BasicBlock basicBlock)
{
return _blockEvalStackStates[basicBlock];
}
private void PushStack(List<EvalDataTypeWithSig> datas, TypeSig type)
{
type = type.RemovePinnedAndModifiers();
switch (type.ElementType)
{
case ElementType.Void: break;
case ElementType.Boolean:
case ElementType.Char:
case ElementType.I1:
case ElementType.U1:
case ElementType.I2:
case ElementType.U2:
case ElementType.I4:
case ElementType.U4:
datas.Add(new EvalDataTypeWithSig(EvalDataType.Int32, null));
break;
case ElementType.I8:
case ElementType.U8:
datas.Add(new EvalDataTypeWithSig(EvalDataType.Int64, null));
break;
case ElementType.R4:
datas.Add(new EvalDataTypeWithSig(EvalDataType.Float, null));
break;
case ElementType.R8:
datas.Add(new EvalDataTypeWithSig(EvalDataType.Double, null));
break;
case ElementType.I:
case ElementType.U:
case ElementType.Ptr:
case ElementType.FnPtr:
case ElementType.ByRef:
datas.Add(new EvalDataTypeWithSig(EvalDataType.I, type));
break;
case ElementType.String:
case ElementType.Class:
case ElementType.Array:
case ElementType.SZArray:
case ElementType.Object:
datas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, type));
break;
case ElementType.ValueType:
{
TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow();
if (typeDef.IsEnum)
{
PushStack(datas, typeDef.GetEnumUnderlyingType());
}
else
{
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type));
}
break;
}
case ElementType.GenericInst:
{
GenericInstSig genericInstSig = (GenericInstSig)type;
TypeDef typeDef = genericInstSig.GenericType.ToTypeDefOrRef().ResolveTypeDefThrow();
if (!typeDef.IsValueType)
{
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.Ref, type));
}
else if (typeDef.IsEnum)
{
PushStack(datas, typeDef.GetEnumUnderlyingType());
}
else
{
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type));
}
break;
}
case ElementType.TypedByRef:
{
// TypedByRef is a special type used in dynamic method invocation and reflection.
// It is treated as a reference type in the evaluation stack.
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type));
break;
}
case ElementType.Var:
case ElementType.MVar:
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.ValueType, type));
break;
case ElementType.ValueArray:
case ElementType.R:
case ElementType.CModOpt:
case ElementType.CModReqd:
case ElementType.Internal:
case ElementType.Module:
case ElementType.Sentinel:
PushStack(datas, EvalDataType.Unknown);
break;
default: throw new Exception($"Unsupported type: {type} in method: {_method.FullName}.");
}
}
private void PushStack(List<EvalDataTypeWithSig> datas, ITypeDefOrRef type)
{
PushStack(datas, type.ToTypeSig());
}
private void PushStack(List<EvalDataTypeWithSig> datas, EvalDataType type)
{
Assert.IsTrue(type != EvalDataType.ValueType, "Cannot push EvalDataType.Value without type sig onto the stack.");
datas.Add(new EvalDataTypeWithSig(type, null));
}
private void PushStack(List<EvalDataTypeWithSig> datas, EvalDataTypeWithSig type)
{
datas.Add(type);
}
private void PushStackObject(List<EvalDataTypeWithSig> datas, TypeSig type)
{
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.Ref, type));
}
private void PushStackPointer(List<EvalDataTypeWithSig> datas, TypeSig type)
{
PushStack(datas, new EvalDataTypeWithSig(EvalDataType.I, type));
}
//private void PushStackObject(List<EvalDataTypeWithSig> datas)
//{
// datas.Add(new EvalDataTypeWithSig(EvalDataType.Ref, _method.Module.CorLibTypes.Object));
//}
private EvalDataType CalcBasicBinOpRetType(EvalDataType op1, EvalDataType op2)
{
switch (op1)
{
case EvalDataType.Int32:
{
switch (op2)
{
case EvalDataType.Int32: return EvalDataType.Int32;
case EvalDataType.Int64: return EvalDataType.Int64;
case EvalDataType.I: return EvalDataType.I;
default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation.");
}
}
case EvalDataType.Int64:
{
switch (op2)
{
case EvalDataType.Int32: return EvalDataType.Int64;
case EvalDataType.Int64:
case EvalDataType.I:
return EvalDataType.Int64;
default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation.");
}
}
case EvalDataType.I:
{
switch (op2)
{
case EvalDataType.Int32: return EvalDataType.I;
case EvalDataType.Int64: return EvalDataType.Int64;
case EvalDataType.I: return EvalDataType.I;
default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation.");
}
}
case EvalDataType.Float:
{
switch (op2)
{
case EvalDataType.Float: return EvalDataType.Float;
case EvalDataType.Double: return EvalDataType.Double;
default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation.");
}
}
case EvalDataType.Double:
{
switch (op2)
{
case EvalDataType.Float:
case EvalDataType.Double: return EvalDataType.Double;
default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation.");
}
}
default: throw new Exception($"Unsupported operand type: {op1} in binary operation.");
}
}
private void SimulateRunAllBlocks()
{
bool methodHasReturnValue = !MetaUtil.IsVoidType(_method.ReturnType);
CilBody body = _method.Body;
if (body.HasExceptionHandlers)
{
foreach (ExceptionHandler handler in body.ExceptionHandlers)
{
if (handler.IsFilter)
{
BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.FilterStart);
var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas;
if (inputStackDatas.Count == 0)
{
PushStackObject(inputStackDatas, handler.CatchType.ToTypeSig());
}
}
if (handler.IsCatch || handler.IsFilter)
{
BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.HandlerStart);
var inputStackDatas = _blockEvalStackStates[bb].inputStackDatas;
if (inputStackDatas.Count == 0)
{
PushStackObject(inputStackDatas, handler.CatchType.ToTypeSig());
}
}
}
}
var newPushedDatas = new List<EvalDataTypeWithSig>();
IList<TypeSig> methodTypeGenericArgument = _method.DeclaringType.GenericParameters.Count > 0
? (IList<TypeSig>)_method.DeclaringType.GenericParameters.Select(p => (TypeSig)new GenericVar(p.Number)).ToList()
: null;
IList<TypeSig> methodMethodGenericArgument = _method.GenericParameters.Count > 0
? (IList<TypeSig>)_method.GenericParameters.Select(p => (TypeSig)new GenericMVar(p.Number)).ToList()
: null;
var gac = new GenericArgumentContext(methodTypeGenericArgument, methodMethodGenericArgument);
var corLibTypes = _method.Module.CorLibTypes;
var blockWalkStack = new Stack<BasicBlock>(_basicBlocks.Blocks.Reverse());
while (blockWalkStack.Count > 0)
{
BasicBlock block = blockWalkStack.Pop();
EvalStackState state = _blockEvalStackStates[block];
if (state.visited)
continue;
state.visited = true;
state.runStackDatas.AddRange(state.inputStackDatas);
List<EvalDataTypeWithSig> stackDatas = state.runStackDatas;
foreach (var inst in block.instructions)
{
int stackSize = stackDatas.Count;
newPushedDatas.Clear();
switch (inst.OpCode.Code)
{
case Code.Nop: break;
case Code.Break: break;
case Code.Ldarg_0:
case Code.Ldarg_1:
case Code.Ldarg_2:
case Code.Ldarg_3:
case Code.Ldarg:
case Code.Ldarg_S:
{
PushStack(newPushedDatas, inst.GetParameter(_method.Parameters).Type);
break;
}
case Code.Ldarga:
case Code.Ldarga_S:
{
PushStackPointer(newPushedDatas, new ByRefSig(inst.GetParameter(_method.Parameters).Type));
break;
}
case Code.Ldloc_0:
case Code.Ldloc_1:
case Code.Ldloc_2:
case Code.Ldloc_3:
case Code.Ldloc:
case Code.Ldloc_S:
{
PushStack(newPushedDatas, inst.GetLocal(body.Variables).Type);
break;
}
case Code.Ldloca:
case Code.Ldloca_S:
{
PushStackPointer(newPushedDatas, new ByRefSig(inst.GetLocal(body.Variables).Type));
break;
}
case Code.Stloc_0:
case Code.Stloc_1:
case Code.Stloc_2:
case Code.Stloc_3:
case Code.Stloc:
case Code.Stloc_S:
{
Assert.IsTrue(stackSize > 0);
break;
}
case Code.Starg:
case Code.Starg_S:
{
Assert.IsTrue(stackSize > 0);
break;
}
case Code.Ldnull:
{
PushStackObject(newPushedDatas, corLibTypes.Object);
break;
}
case Code.Ldc_I4_M1:
case Code.Ldc_I4_0:
case Code.Ldc_I4_1:
case Code.Ldc_I4_2:
case Code.Ldc_I4_3:
case Code.Ldc_I4_4:
case Code.Ldc_I4_5:
case Code.Ldc_I4_6:
case Code.Ldc_I4_7:
case Code.Ldc_I4_8:
case Code.Ldc_I4:
case Code.Ldc_I4_S:
{
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
case Code.Ldc_I8:
{
PushStack(newPushedDatas, EvalDataType.Int64);
break;
}
case Code.Ldc_R4:
{
PushStack(newPushedDatas, EvalDataType.Float);
break;
}
case Code.Ldc_R8:
{
PushStack(newPushedDatas, EvalDataType.Double);
break;
}
case Code.Dup:
{
Assert.IsTrue(stackSize > 0);
EvalDataTypeWithSig type = stackDatas[stackSize - 1];
PushStack(newPushedDatas, type);
PushStack(newPushedDatas, type);
break;
}
case Code.Pop:
{
break;
}
case Code.Jmp:
{
break;
}
case Code.Call:
case Code.Callvirt:
{
IMethod calledMethod = (IMethod)inst.Operand;
MethodSig methodSig = MetaUtil.GetInflatedMethodSig(calledMethod, gac);
PushStack(newPushedDatas, methodSig.RetType);
break;
}
case Code.Calli:
{
MethodSig methodSig = (MethodSig)inst.Operand;
PushStack(newPushedDatas, methodSig.RetType);
break;
}
case Code.Ret:
{
break;
}
case Code.Br:
case Code.Br_S:
case Code.Brfalse:
case Code.Brfalse_S:
case Code.Brtrue:
case Code.Brtrue_S:
case Code.Beq:
case Code.Beq_S:
case Code.Bge:
case Code.Bge_S:
case Code.Bge_Un:
case Code.Bge_Un_S:
case Code.Bgt:
case Code.Bgt_S:
case Code.Bgt_Un:
case Code.Bgt_Un_S:
case Code.Ble:
case Code.Ble_S:
case Code.Ble_Un:
case Code.Ble_Un_S:
case Code.Blt:
case Code.Blt_S:
case Code.Blt_Un:
case Code.Blt_Un_S:
case Code.Bne_Un:
case Code.Bne_Un_S:
{
// Branch instructions do not change the stack.
break;
}
case Code.Ceq:
case Code.Cgt:
case Code.Cgt_Un:
case Code.Clt:
case Code.Clt_Un:
{
Assert.IsTrue(stackSize >= 2);
EvalDataType op2 = stackDatas[stackSize - 1].type;
EvalDataType op1 = stackDatas[stackSize - 2].type;
EvalDataType ret = EvalDataType.Int32;
_instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret));
PushStack(newPushedDatas, ret);
break;
}
case Code.Switch:
{
// Switch instruction does not change the stack.
break;
}
case Code.Ldind_I1:
case Code.Ldind_U1:
case Code.Ldind_I2:
case Code.Ldind_U2:
case Code.Ldind_I4:
case Code.Ldind_U4:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
case Code.Ldind_I8:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Int64);
break;
}
case Code.Ldind_I:
{
Assert.IsTrue(stackSize > 0);
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Ldind_Ref:
{
Assert.IsTrue(stackSize > 0);
PushStackObject(newPushedDatas, stackDatas[stackSize - 1].typeSig?.RemovePinnedAndModifiers().Next ?? corLibTypes.Object);
break;
}
case Code.Ldind_R4:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Float);
break;
}
case Code.Ldind_R8:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.Double);
break;
}
case Code.Stind_I1:
case Code.Stind_I2:
case Code.Stind_I4:
case Code.Stind_I8:
case Code.Stind_I:
case Code.Stind_R4:
case Code.Stind_R8:
case Code.Stind_Ref:
{
Assert.IsTrue(stackSize >= 2);
break;
}
case Code.Add:
case Code.Add_Ovf:
case Code.Add_Ovf_Un:
case Code.Sub:
case Code.Sub_Ovf:
case Code.Sub_Ovf_Un:
case Code.Mul:
case Code.Mul_Ovf:
case Code.Mul_Ovf_Un:
case Code.Div:
case Code.Div_Un:
case Code.Rem:
case Code.Rem_Un:
case Code.And:
case Code.Or:
case Code.Xor:
{
Assert.IsTrue(stackSize >= 2);
EvalDataType op2 = stackDatas[stackSize - 1].type;
EvalDataType op1 = stackDatas[stackSize - 2].type;
EvalDataType ret = CalcBasicBinOpRetType(op1, op2);
_instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret));
PushStack(newPushedDatas, ret);
break;
}
case Code.Shl:
case Code.Shr:
case Code.Shr_Un:
{
Assert.IsTrue(stackSize >= 2);
EvalDataType op2 = stackDatas[stackSize - 1].type;
EvalDataType op1 = stackDatas[stackSize - 2].type;
if (op1 != EvalDataType.Int32 && op1 != EvalDataType.Int64 && op1 != EvalDataType.I)
throw new Exception($"Unsupported operand type: {op1} in shift operation.");
if (op2 != EvalDataType.Int32 && op2 != EvalDataType.Int64)
throw new Exception($"Unsupported operand type: {op2} for {op1} in shift operation.");
EvalDataType ret = op1;
_instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret));
PushStack(newPushedDatas, ret);
break;
}
case Code.Neg:
{
Assert.IsTrue(stackSize > 0);
EvalDataType op = stackDatas[stackSize - 1].type;
EvalDataType ret = op;
switch (op)
{
case EvalDataType.Int32:
case EvalDataType.Int64:
case EvalDataType.I:
case EvalDataType.Float:
case EvalDataType.Double:
break;
default:
throw new Exception($"Unsupported operand type: {op} in unary operation.");
}
_instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret));
PushStack(newPushedDatas, ret);
break;
}
case Code.Not:
{
Assert.IsTrue(stackSize > 0);
EvalDataType op = stackDatas[stackSize - 1].type;
EvalDataType ret = op;
if (op != EvalDataType.Int32 && op != EvalDataType.Int64 && op != EvalDataType.I)
throw new Exception($"Unsupported operand type: {op} in unary operation.");
_instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret));
PushStack(newPushedDatas, ret);
break;
}
case Code.Conv_I1:
case Code.Conv_U1:
case Code.Conv_I2:
case Code.Conv_U2:
case Code.Conv_I4:
case Code.Conv_U4:
{
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
case Code.Conv_I8:
case Code.Conv_U8:
{
PushStack(newPushedDatas, EvalDataType.Int64);
break;
}
case Code.Conv_I:
case Code.Conv_U:
{
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Conv_R4:
{
PushStack(newPushedDatas, EvalDataType.Float);
break;
}
case Code.Conv_R8:
{
PushStack(newPushedDatas, EvalDataType.Double);
break;
}
case Code.Conv_Ovf_I1:
case Code.Conv_Ovf_I1_Un:
case Code.Conv_Ovf_U1:
case Code.Conv_Ovf_U1_Un:
case Code.Conv_Ovf_I2:
case Code.Conv_Ovf_I2_Un:
case Code.Conv_Ovf_U2:
case Code.Conv_Ovf_U2_Un:
case Code.Conv_Ovf_I4:
case Code.Conv_Ovf_I4_Un:
case Code.Conv_Ovf_U4:
case Code.Conv_Ovf_U4_Un:
{
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
case Code.Conv_Ovf_I8:
case Code.Conv_Ovf_I8_Un:
case Code.Conv_Ovf_U8:
case Code.Conv_Ovf_U8_Un:
{
PushStack(newPushedDatas, EvalDataType.Int64);
break;
}
case Code.Conv_Ovf_I:
case Code.Conv_Ovf_I_Un:
case Code.Conv_Ovf_U:
case Code.Conv_Ovf_U_Un:
{
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Conv_R_Un:
{
PushStack(newPushedDatas, EvalDataType.Double);
break;
}
case Code.Cpobj:
case Code.Initobj:
case Code.Stobj:
{
break;
}
case Code.Ldobj:
{
PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand);
break;
}
case Code.Ldstr:
{
PushStackObject(newPushedDatas, corLibTypes.String);
break;
}
case Code.Newobj:
{
IMethod ctor = (IMethod)inst.Operand;
PushStack(newPushedDatas, ctor.DeclaringType);
break;
}
case Code.Castclass:
{
Assert.IsTrue(stackSize > 0);
var obj = stackDatas[stackSize - 1];
//Assert.IsTrue(obj.type == EvalDataType.Ref);
PushStackObject(newPushedDatas, ((ITypeDefOrRef)inst.Operand).ToTypeSig());
break;
}
case Code.Isinst:
{
Assert.IsTrue(stackSize > 0);
var obj = stackDatas[stackSize - 1];
//Assert.IsTrue(obj.type == EvalDataType.Ref);
PushStackObject(newPushedDatas, ((ITypeDefOrRef)inst.Operand).ToTypeSig());
break;
}
case Code.Unbox:
{
Assert.IsTrue(stackSize > 0);
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Unbox_Any:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand);
break;
}
case Code.Box:
{
Assert.IsTrue(stackSize > 0);
//PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand);
PushStackObject(newPushedDatas, ((ITypeDefOrRef)inst.Operand).ToTypeSig());
break;
}
case Code.Throw:
{
// Throw instruction does not change the stack.
break;
}
case Code.Rethrow:
{
// Rethrow instruction does not change the stack.
break;
}
case Code.Ldfld:
case Code.Ldsfld:
{
IField field = (IField)inst.Operand;
TypeSig fieldType = MetaUtil.InflateFieldSig(field, gac);
PushStack(newPushedDatas, fieldType);
break;
}
case Code.Ldflda:
case Code.Ldsflda:
{
IField field = (IField)inst.Operand;
TypeSig fieldType = MetaUtil.InflateFieldSig(field, gac);
PushStackPointer(newPushedDatas, new ByRefSig(fieldType));
break;
}
case Code.Stfld:
case Code.Stsfld:
{
break;
}
case Code.Newarr:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, new SZArraySig(((ITypeDefOrRef)inst.Operand).ToTypeSig()));
break;
}
case Code.Ldlen:
{
Assert.IsTrue(stackSize > 0);
PushStack(newPushedDatas, EvalDataType.I);
break;
}
case Code.Ldelema:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.I);
break;
}
case Code.Ldelem_I1:
case Code.Ldelem_U1:
case Code.Ldelem_I2:
case Code.Ldelem_U2:
case Code.Ldelem_I4:
case Code.Ldelem_U4:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
case Code.Ldelem_I8:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.Int64);
break;
}
case Code.Ldelem_I:
{
Assert.IsTrue(stackSize >= 2);
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Ldelem_R4:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.Float);
break;
}
case Code.Ldelem_R8:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, EvalDataType.Double);
break;
}
case Code.Ldelem_Ref:
{
Assert.IsTrue(stackSize >= 2);
PushStackObject(newPushedDatas, stackDatas[stackSize - 2].typeSig.RemovePinnedAndModifiers().Next ?? corLibTypes.Object);
break;
}
case Code.Ldelem:
{
Assert.IsTrue(stackSize >= 2);
PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand);
break;
}
case Code.Stelem_I1:
case Code.Stelem_I2:
case Code.Stelem_I4:
case Code.Stelem_I8:
case Code.Stelem_I:
case Code.Stelem_R4:
case Code.Stelem_R8:
case Code.Stelem_Ref:
case Code.Stelem:
{
Assert.IsTrue(stackSize >= 3);
break;
}
case Code.Mkrefany:
{
PushStack(newPushedDatas, new EvalDataTypeWithSig(EvalDataType.ValueType, _method.Module.CorLibTypes.TypedReference));
break;
}
case Code.Refanytype:
{
PushStack(newPushedDatas, EvalDataType.Token);
break;
}
case Code.Refanyval:
{
Assert.IsTrue(stackSize > 0);
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Ldtoken:
{
PushStack(newPushedDatas, EvalDataType.Token);
break;
}
case Code.Endfinally:
case Code.Leave:
case Code.Leave_S:
{
break;
}
case Code.Endfilter:
{
break;
}
case Code.Arglist:
{
break;
}
case Code.Ldftn:
case Code.Ldvirtftn:
{
PushStack(newPushedDatas, EvalDataType.Unknown);
break;
}
case Code.Localloc:
{
PushStackPointer(newPushedDatas, corLibTypes.IntPtr);
break;
}
case Code.Unaligned:
case Code.Volatile:
case Code.Tailcall:
case Code.No:
case Code.Readonly:
case Code.Constrained:
{
break;
}
case Code.Cpblk:
case Code.Initblk:
{
break;
}
case Code.Sizeof:
{
PushStack(newPushedDatas, EvalDataType.Int32);
break;
}
default: throw new Exception($"not supported opcode: {inst} in method: {_method.FullName}.");
}
inst.CalculateStackUsage(methodHasReturnValue, out var pushed, out var pops);
if (pushed != newPushedDatas.Count)
{
throw new Exception($"Instruction {inst} in method {_method.FullName} pushed {newPushedDatas.Count} items, but expected {pushed} items.");
}
if (pops == -1)
{
stackDatas.Clear();
}
else
{
if (stackSize < pops)
{
throw new Exception($"Instruction {inst} in method {_method.FullName} pops {pops} items, but only {stackSize} items are available on the stack.");
}
stackDatas.RemoveRange(stackDatas.Count - pops, pops);
stackDatas.AddRange(newPushedDatas);
Assert.AreEqual(stackSize + pushed - pops, stackDatas.Count);
}
if (pushed > 0 && stackDatas.Count > 0)
{
_evalStackTopDataTypeAfterInstructions[inst] = stackDatas.Last().type;
}
}
ReduceOutputArgsToBaseType(block, stackDatas);
foreach (BasicBlock outBb in block.outBlocks)
{
EvalStackState outState = _blockEvalStackStates[outBb];
if (!outState.visited)
{
blockWalkStack.Push(outBb);
}
}
}
}
private void ReduceOutputArgsToBaseType(BasicBlock block, List<EvalDataTypeWithSig> stackDatas)
{
foreach (BasicBlock outBb in block.outBlocks)
{
if (outBb == block)
{
continue;
}
EvalStackState outState = _blockEvalStackStates[outBb];
if (outState.inputStackDatas.Count == stackDatas.Count)
{
ReduceOutputArgsToBaseType(stackDatas, outState.inputStackDatas);
}
else
{
if (outState.inputStackDatas.Count > 0 || outState.visited)
{
throw new Exception($"Block {outBb} in method {_method.FullName} has inconsistent stack data. Expected {outState.inputStackDatas.Count}, but got {stackDatas.Count}.");
}
}
}
foreach (BasicBlock outBb in block.outBlocks)
{
if (outBb == block)
{
continue;
}
EvalStackState outState = _blockEvalStackStates[outBb];
outState.inputStackDatas.Clear();
outState.inputStackDatas.AddRange(stackDatas);
}
}
private TypeSig ReduceBaseType(TypeSig type1, TypeSig type2, bool forceRefType)
{
if (TypeEqualityComparer.Instance.Equals(type1, type2))
{
return type1;
}
type1 = type1.RemovePinnedAndModifiers();
type2 = type2.RemovePinnedAndModifiers();
ElementType t1 = type1.ElementType;
ElementType t2 = type2.ElementType;
if (t1 > t2)
{
(t1, t2) = (t2, t1);
(type1, type2) = (type2, type1);
}
switch (t1)
{
case ElementType.Void:
case ElementType.TypedByRef:
case ElementType.ValueArray:
{
if (t1 != t2)
{
break;
}
return type1;
}
case ElementType.R4:
case ElementType.R8:
case ElementType.R:
{
if (t1 == t2)
{
return type1;
}
if (forceRefType)
{
switch (t2)
{
case ElementType.Class:
case ElementType.GenericInst:
return type2;
default: return _corLibTypes.Object;
}
}
break;
}
case ElementType.Boolean:
case ElementType.Char:
case ElementType.I1:
case ElementType.U1:
case ElementType.I2:
case ElementType.U2:
case ElementType.I4:
case ElementType.U4:
{
switch (t2)
{
case ElementType.Boolean:
case ElementType.Char:
case ElementType.I1:
case ElementType.U1:
case ElementType.I2:
case ElementType.U2:
case ElementType.I4:
case ElementType.U4:
{
return _corLibTypes.Int32;
}
case ElementType.I:
case ElementType.U:
case ElementType.I8:
case ElementType.U8:
{
return type2;
}
default:
{
if (forceRefType)
{
switch (t2)
{
case ElementType.Class:
case ElementType.GenericInst:
return type2;
default: return _corLibTypes.Object;
}
}
break;
}
}
break;
}
case ElementType.I8:
case ElementType.U8:
{
switch (t2)
{
case ElementType.I:
case ElementType.U:
case ElementType.I8:
case ElementType.U8:
{
return type1;
}
default:
{
if (forceRefType)
{
switch (t2)
{
case ElementType.Class:
case ElementType.GenericInst:
return type2;
default: return _corLibTypes.Object;
}
}
break;
}
}
break;
}
case ElementType.I:
case ElementType.U:
{
switch (t2)
{
case ElementType.I:
case ElementType.U:
{
return type1;
}
case ElementType.FnPtr:
{
return type2;
}
default:
{
if (forceRefType)
{
switch (t2)
{
case ElementType.Class:
case ElementType.GenericInst:
return type2;
default: return _corLibTypes.Object;
}
}
break;
}
}
break;
}
case ElementType.String:
{
if (t1 == t2)
{
return type1;
}
return _corLibTypes.Object;
}
case ElementType.Ptr:
{
switch (t2)
{
case ElementType.Ptr: return new PtrSig(ReduceBaseType(type1.Next, type2.Next, false));
case ElementType.ByRef: return new ByRefSig(ReduceBaseType(type1.Next, type2.Next, false));
case ElementType.I:
case ElementType.U: return type1;
}
break;
}
case ElementType.ByRef:
{
switch (t2)
{
case ElementType.ByRef: return new ByRefSig(ReduceBaseType(type1.Next, type2.Next, false));
case ElementType.I:
case ElementType.U: return type1;
}
break;
}
case ElementType.ValueType:
{
if (forceRefType)
{
return type2;
}
break;
}
case ElementType.Class:
{
switch (t2)
{
case ElementType.Class:
{
TypeDef typeDef1 = type1.ToTypeDefOrRef().ResolveTypeDefThrow();
TypeDef typeDef2 = type2.ToTypeDefOrRef().ResolveTypeDefThrow();
while (typeDef2 != null)
{
if (MetaUtil.IsAssignableFrom(typeDef1, typeDef2))
{
return _method.Module.Import(typeDef2).ToTypeSig();
}
typeDef2 = typeDef2.BaseType?.ResolveTypeDefThrow();
}
return _corLibTypes.Object;
}
case ElementType.Array:
case ElementType.SZArray:
{
TypeDef typeDef1 = type1.ToTypeDefOrRef().ResolveTypeDefThrow();
if (typeDef1.Namespace.StartsWith("System"))
{
switch (typeDef1.Name.ToString())
{
case "Array":
case "ICloneable":
case "IList":
case "IList`1":
case "ICollection":
case "ICollection`1":
case "IEnumerable":
case "IEnumerable`1":
case "IStructuralComparable":
case "IStructuralEquatable":
{
return type1;
}
}
}
return _corLibTypes.Object;
}
case ElementType.GenericInst:
{
return type2;
}
case ElementType.Var:
case ElementType.Object:
case ElementType.MVar:
{
return _corLibTypes.Object;
}
}
break;
}
case ElementType.Array:
{
switch (t2)
{
case ElementType.Array:
return new ArraySig(ReduceBaseType(type1.Next, type2.Next, false));
case ElementType.SZArray:
return _method.Module.ImportAsTypeSig(typeof(Array));
case ElementType.GenericInst:
{
var gis = (GenericInstSig)type2;
switch (gis.GenericType.GetName())
{
case "IList`1":
case "ICollection`1":
return type2;
}
return _corLibTypes.Object;
}
case ElementType.Object:
{
return _corLibTypes.Object;
}
}
break;
}
case ElementType.GenericInst:
{
var gis = (GenericInstSig)type1;
switch (t2)
{
case ElementType.Object:
{
return _corLibTypes.Object;
}
case ElementType.MVar:
{
return type2;
}
case ElementType.SZArray:
{
switch (gis.GenericType.GetName())
{
case "IList`1":
case "ICollection`1":
{
return type1;
}
// FIXME
default: return _corLibTypes.Object;
}
}
case ElementType.GenericInst:
{
// FIXME
return type1;
}
}
break;
}
case ElementType.FnPtr:
{
switch (t2)
{
case ElementType.FnPtr:
{
// FIXME
return type1;
}
}
break;
}
case ElementType.Object:
{
return type1;
}
case ElementType.SZArray:
{
switch (t2)
{
case ElementType.SZArray:
return new SZArraySig(ReduceBaseType(type1.Next, type2.Next, false));
case ElementType.MVar:
return _corLibTypes.Object;
}
break;
}
case ElementType.Var:
case ElementType.MVar:
{
if (t1 == t2)
{
// FIXME!!!
return type1;
}
if (t2 == ElementType.Class || t2 == ElementType.GenericInst)
{
return type2;
}
return _corLibTypes.Object;
}
}
UnityEngine.Debug.LogError($"reduce base type of {type1} and {type2} is unsupported");
return type1;
}
private EvalDataTypeWithSig ReduceBaseType(EvalDataTypeWithSig type1, EvalDataTypeWithSig type2)
{
if (type1.type != type2.type)
{
throw new Exception($"Method {_method.FullName} has inconsistent stack data. Expected {type1.type}, but got {type2.type}.");
}
switch (type1.type)
{
case EvalDataType.Int32:
case EvalDataType.Int64:
case EvalDataType.Float:
case EvalDataType.Double:
case EvalDataType.Token:
case EvalDataType.Unknown:
{
return type1;
}
case EvalDataType.I:
case EvalDataType.Ref:
{
if (type1.typeSig == null)
{
return type2;
}
if (type2.typeSig == null)
{
return type1;
}
if (TypeEqualityComparer.Instance.Equals(type1.typeSig, type2.typeSig))
{
return type1;
}
return new EvalDataTypeWithSig(type1.type, ReduceBaseType(type1.typeSig, type2.typeSig, type1.type == EvalDataType.Ref));
}
case EvalDataType.ValueType:
{
if (!TypeEqualityComparer.Instance.Equals(type1.typeSig, type2.typeSig))
{
throw new Exception($"Method {_method.FullName} has inconsistent stack data. Expected {type1.typeSig}, but got {type2.typeSig}.");
}
return type1;
}
default: throw new Exception($"not supported EvalDataType: {type1.type} in method: {_method.FullName}.");
}
}
private void ReduceOutputArgsToBaseType(List<EvalDataTypeWithSig> stackDatasRet, List<EvalDataTypeWithSig> stackDatasNew)
{
if (stackDatasNew.Count == 0)
{
return;
}
if (stackDatasRet.Count != stackDatasNew.Count)
{
throw new Exception($"Method {_method.FullName} has inconsistent stack data. Expected {stackDatasRet.Count}, but got {stackDatasNew.Count}.");
}
for (int i = 0; i < stackDatasRet.Count; i++)
{
EvalDataTypeWithSig oldType = stackDatasRet[i];
EvalDataTypeWithSig newType = stackDatasNew[i];
stackDatasRet[i] = ReduceBaseType(oldType, newType);
}
}
}
}