374 lines
18 KiB
C#
374 lines
18 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Linq.Expressions;
|
|
using MonoMod.Utils;
|
|
using System.Collections.Generic;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using System.Linq;
|
|
|
|
namespace MonoMod.Utils {
|
|
// The following mostly qualifies as r/badcode material.
|
|
public sealed partial class DynamicMethodDefinition {
|
|
|
|
// Mono
|
|
private static readonly FastReflectionDelegate _ILGen_make_room =
|
|
typeof(ILGenerator).GetMethod("make_room", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
private static readonly FastReflectionDelegate _ILGen_emit_int =
|
|
typeof(ILGenerator).GetMethod("emit_int", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
private static readonly FastReflectionDelegate _ILGen_ll_emit =
|
|
typeof(ILGenerator).GetMethod("ll_emit", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
|
|
// .NET
|
|
private static readonly FastReflectionDelegate _ILGen_EnsureCapacity =
|
|
typeof(ILGenerator).GetMethod("EnsureCapacity", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
private static readonly FastReflectionDelegate _ILGen_PutInteger4 =
|
|
typeof(ILGenerator).GetMethod("PutInteger4", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
private static readonly FastReflectionDelegate _ILGen_InternalEmit =
|
|
typeof(ILGenerator).GetMethod("InternalEmit", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
private static readonly FastReflectionDelegate _ILGen_UpdateStackSize =
|
|
typeof(ILGenerator).GetMethod("UpdateStackSize", BindingFlags.NonPublic | BindingFlags.Instance)
|
|
?.CreateFastDelegate();
|
|
|
|
private static readonly FieldInfo f_DynILGen_m_scope =
|
|
typeof(ILGenerator).GetTypeInfo().Assembly
|
|
.GetType("System.Reflection.Emit.DynamicILGenerator")?.GetField("m_scope", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
private static readonly FieldInfo f_DynScope_m_tokens =
|
|
typeof(ILGenerator).GetTypeInfo().Assembly
|
|
.GetType("System.Reflection.Emit.DynamicScope")?.GetField("m_tokens", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
// Based on https://referencesource.microsoft.com/#mscorlib/system/reflection/mdimport.cs,74bfbae3c61889bc
|
|
private static readonly Type[] CorElementTypes = new Type[] {
|
|
null,
|
|
typeof(void),
|
|
typeof(bool),
|
|
typeof(char),
|
|
typeof(sbyte),
|
|
typeof(byte),
|
|
typeof(short),
|
|
typeof(ushort),
|
|
typeof(int),
|
|
typeof(uint),
|
|
typeof(long),
|
|
typeof(ulong),
|
|
typeof(string),
|
|
typeof(IntPtr)
|
|
};
|
|
|
|
internal static void _EmitCallSite(DynamicMethod dm, ILGenerator il, System.Reflection.Emit.OpCode opcode, CallSite csite) {
|
|
/* The mess in this method is heavily based off of the code available at the following links:
|
|
* https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicmethod.cs#L791
|
|
* https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicilgenerator.cs#L353
|
|
* https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/DynamicMethod.cs#L411
|
|
* https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/ILGenerator.cs#L800
|
|
* https://github.com/dotnet/coreclr/blob/0fbd855e38bc3ec269479b5f6bf561dcfd67cbb6/src/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs#L57
|
|
*/
|
|
|
|
List<object> _tokens = null;
|
|
int _GetTokenForType(Type v) {
|
|
_tokens.Add(v.TypeHandle);
|
|
return _tokens.Count - 1 | 0x02000000 /* (int) MetadataTokenType.TypeDef */;
|
|
}
|
|
int _GetTokenForSig(byte[] v) {
|
|
_tokens.Add(v);
|
|
return _tokens.Count - 1 | 0x11000000 /* (int) MetadataTokenType.Signature */;
|
|
}
|
|
#if !NETSTANDARD
|
|
DynamicILInfo _info = null;
|
|
if (_IsMono) {
|
|
// GetDynamicILInfo throws "invalid signature" in .NET - let's hope for the best for mono...
|
|
_info = dm.GetDynamicILInfo();
|
|
} else {
|
|
#endif
|
|
// For .NET, we need to access DynamicScope m_scope and its List<object> m_tokens
|
|
_tokens = f_DynScope_m_tokens.GetValue(f_DynILGen_m_scope.GetValue(il)) as List<object>;
|
|
#if !NETSTANDARD
|
|
}
|
|
|
|
int GetTokenForType(Type v) => _info != null ? _info.GetTokenFor(v.TypeHandle) : _GetTokenForType(v);
|
|
int GetTokenForSig(byte[] v) => _info != null ? _info.GetTokenFor(v) : _GetTokenForSig(v);
|
|
|
|
#else
|
|
int GetTokenForType(Type v) => _GetTokenForType(v);
|
|
int GetTokenForSig(byte[] v) => _GetTokenForSig(v);
|
|
#endif
|
|
|
|
byte[] signature = new byte[32];
|
|
int currSig = 0;
|
|
int sizeLoc = -1;
|
|
|
|
// This expects a MdSigCallingConvention
|
|
AddData((byte) csite.CallingConvention);
|
|
sizeLoc = currSig++;
|
|
|
|
List<Type> modReq = new List<Type>();
|
|
List<Type> modOpt = new List<Type>();
|
|
|
|
_EmitResolveWithModifiers(csite.ReturnType, out Type returnType, out Type[] returnTypeModReq, out Type[] returnTypeModOpt, modReq, modOpt);
|
|
AddArgument(returnType, returnTypeModReq, returnTypeModOpt);
|
|
|
|
foreach (ParameterDefinition param in csite.Parameters) {
|
|
if (param.ParameterType.IsSentinel)
|
|
AddElementType(0x41 /* CorElementType.Sentinel */);
|
|
|
|
if (param.ParameterType.IsPinned) {
|
|
AddElementType(0x45 /* CorElementType.Pinned */);
|
|
// AddArgument(param.ParameterType.ResolveReflection());
|
|
// continue;
|
|
}
|
|
|
|
_EmitResolveWithModifiers(param.ParameterType, out Type paramType, out Type[] paramTypeModReq, out Type[] paramTypeModOpt, modReq, modOpt);
|
|
AddArgument(paramType, paramTypeModReq, paramTypeModOpt);
|
|
}
|
|
|
|
AddElementType(0x00 /* CorElementType.End */);
|
|
|
|
// For most signatures, this will set the number of elements in a byte which we have reserved for it.
|
|
// However, if we have a field signature, we don't set the length and return.
|
|
// If we have a signature with more than 128 arguments, we can't just set the number of elements,
|
|
// we actually have to allocate more space (e.g. shift everything in the array one or more spaces to the
|
|
// right. We do this by making a copy of the array and leaving the correct number of blanks. This new
|
|
// array is now set to be m_signature and we use the AddData method to set the number of elements properly.
|
|
// The forceCopy argument can be used to force SetNumberOfSignatureElements to make a copy of
|
|
// the array. This is useful for GetSignature which promises to trim the array to be the correct size anyway.
|
|
|
|
byte[] temp;
|
|
int newSigSize;
|
|
int currSigHolder = currSig;
|
|
|
|
// We need to have more bytes for the size. Figure out how many bytes here.
|
|
// Since we need to copy anyway, we're just going to take the cost of doing a
|
|
// new allocation.
|
|
if (csite.Parameters.Count < 0x80) {
|
|
newSigSize = 1;
|
|
} else if (csite.Parameters.Count < 0x4000) {
|
|
newSigSize = 2;
|
|
} else {
|
|
newSigSize = 4;
|
|
}
|
|
|
|
// Allocate the new array.
|
|
temp = new byte[currSig + newSigSize - 1];
|
|
|
|
// Copy the calling convention. The calling convention is always just one byte
|
|
// so we just copy that byte. Then copy the rest of the array, shifting everything
|
|
// to make room for the new number of elements.
|
|
temp[0] = signature[0];
|
|
Buffer.BlockCopy(signature, sizeLoc + 1, temp, sizeLoc + newSigSize, currSigHolder - (sizeLoc + 1));
|
|
signature = temp;
|
|
|
|
//Use the AddData method to add the number of elements appropriately compressed.
|
|
currSig = sizeLoc;
|
|
AddData(csite.Parameters.Count);
|
|
currSig = currSigHolder + (newSigSize - 1);
|
|
|
|
// This case will only happen if the user got the signature through
|
|
// InternalGetSignature first and then called GetSignature.
|
|
if (signature.Length > currSig) {
|
|
temp = new byte[currSig];
|
|
Array.Copy(signature, temp, currSig);
|
|
signature = temp;
|
|
}
|
|
|
|
// Emit.
|
|
|
|
if (_ILGen_emit_int != null) {
|
|
// Mono
|
|
_ILGen_make_room(il, 6);
|
|
_ILGen_ll_emit(il, opcode);
|
|
_ILGen_emit_int(il, GetTokenForSig(signature));
|
|
|
|
} else {
|
|
// .NET
|
|
_ILGen_EnsureCapacity(il, 7);
|
|
_ILGen_InternalEmit(il, opcode);
|
|
|
|
// The only IL instruction that has VarPop behaviour, that takes a
|
|
// Signature token as a parameter is calli. Pop the parameters and
|
|
// the native function pointer. To be conservative, do not pop the
|
|
// this pointer since this information is not easily derived from
|
|
// SignatureHelper.
|
|
if (opcode.StackBehaviourPop == System.Reflection.Emit.StackBehaviour.Varpop) {
|
|
// Pop the arguments and native function pointer off the stack.
|
|
_ILGen_UpdateStackSize(il, opcode, -csite.Parameters.Count - 1);
|
|
}
|
|
|
|
_ILGen_PutInteger4(il, GetTokenForSig(signature));
|
|
}
|
|
|
|
void AddArgument(Type clsArgument, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers) {
|
|
if (optionalCustomModifiers != null)
|
|
foreach (Type t in optionalCustomModifiers)
|
|
InternalAddTypeToken(GetTokenForType(t), 0x20 /* CorElementType.CModOpt */);
|
|
|
|
if (requiredCustomModifiers != null)
|
|
foreach (Type t in requiredCustomModifiers)
|
|
InternalAddTypeToken(GetTokenForType(t), 0x1F /* CorElementType.CModReqd */);
|
|
|
|
AddOneArgTypeHelper(clsArgument);
|
|
}
|
|
|
|
void AddData(int data) {
|
|
// A managed representation of CorSigCompressData;
|
|
|
|
if (currSig + 4 > signature.Length) {
|
|
signature = ExpandArray(signature);
|
|
}
|
|
|
|
if (data <= 0x7F) {
|
|
signature[currSig++] = (byte) (data & 0xFF);
|
|
} else if (data <= 0x3FFF) {
|
|
signature[currSig++] = (byte) ((data >> 8) | 0x80);
|
|
signature[currSig++] = (byte) (data & 0xFF);
|
|
} else if (data <= 0x1FFFFFFF) {
|
|
signature[currSig++] = (byte) ((data >> 24) | 0xC0);
|
|
signature[currSig++] = (byte) ((data >> 16) & 0xFF);
|
|
signature[currSig++] = (byte) ((data >> 8) & 0xFF);
|
|
signature[currSig++] = (byte) ((data) & 0xFF);
|
|
} else {
|
|
throw new ArgumentException("Integer or token was too large to be encoded.");
|
|
}
|
|
}
|
|
|
|
byte[] ExpandArray(byte[] inArray, int requiredLength = -1) {
|
|
if (requiredLength < inArray.Length)
|
|
requiredLength = inArray.Length * 2;
|
|
|
|
byte[] outArray = new byte[requiredLength];
|
|
Buffer.BlockCopy(inArray, 0, outArray, 0, inArray.Length);
|
|
return outArray;
|
|
}
|
|
|
|
void AddElementType(byte cvt) {
|
|
// Adds an element to the signature. A managed represenation of CorSigCompressElement
|
|
if (currSig + 1 > signature.Length)
|
|
signature = ExpandArray(signature);
|
|
|
|
signature[currSig++] = cvt;
|
|
}
|
|
|
|
void AddToken(int token) {
|
|
// A managed represenation of CompressToken
|
|
// Pulls the token appart to get a rid, adds some appropriate bits
|
|
// to the token and then adds this to the signature.
|
|
|
|
int rid = (token & 0x00FFFFFF); //This is RidFromToken;
|
|
int type = (token & unchecked((int) 0xFF000000)); //This is TypeFromToken;
|
|
|
|
if (rid > 0x3FFFFFF) {
|
|
// token is too big to be compressed
|
|
throw new ArgumentException("Integer or token was too large to be encoded.");
|
|
}
|
|
|
|
rid = (rid << 2);
|
|
|
|
// TypeDef is encoded with low bits 00
|
|
// TypeRef is encoded with low bits 01
|
|
// TypeSpec is encoded with low bits 10
|
|
if (type == 0x01000000 /* MetadataTokenType.TypeRef */) {
|
|
//if type is mdtTypeRef
|
|
rid |= 0x1;
|
|
} else if (type == 0x1b000000 /* MetadataTokenType.TypeSpec */) {
|
|
//if type is mdtTypeSpec
|
|
rid |= 0x2;
|
|
}
|
|
|
|
AddData(rid);
|
|
}
|
|
|
|
void InternalAddTypeToken(int clsToken, byte CorType) {
|
|
// Add a type token into signature. CorType will be either CorElementType.Class or CorElementType.ValueType
|
|
AddElementType(CorType);
|
|
AddToken(clsToken);
|
|
}
|
|
|
|
void AddOneArgTypeHelper(Type clsArgument) { AddOneArgTypeHelperWorker(clsArgument, false); }
|
|
void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst) {
|
|
if (clsArgument.GetTypeInfo().IsGenericType && (!clsArgument.GetTypeInfo().IsGenericTypeDefinition || !lastWasGenericInst)) {
|
|
AddElementType(0x15 /* CorElementType.GenericInst */);
|
|
|
|
AddOneArgTypeHelperWorker(clsArgument.GetGenericTypeDefinition(), true);
|
|
|
|
Type[] genargs = clsArgument.GetGenericArguments();
|
|
|
|
AddData(genargs.Length);
|
|
|
|
foreach (Type t in genargs)
|
|
AddOneArgTypeHelper(t);
|
|
} else if (clsArgument.IsByRef) {
|
|
AddElementType(0x10 /* CorElementType.ByRef */);
|
|
clsArgument = clsArgument.GetElementType();
|
|
AddOneArgTypeHelper(clsArgument);
|
|
} else if (clsArgument.IsPointer) {
|
|
AddElementType(0x0F /* CorElementType.Ptr */);
|
|
AddOneArgTypeHelper(clsArgument.GetElementType());
|
|
} else if (clsArgument.IsArray) {
|
|
#if false
|
|
if (clsArgument.IsArray && clsArgument == clsArgument.GetElementType().MakeArrayType()) { // .IsSZArray unavailable.
|
|
AddElementType(0x1D /* CorElementType.SzArray */);
|
|
|
|
AddOneArgTypeHelper(clsArgument.GetElementType());
|
|
} else
|
|
#endif
|
|
{
|
|
AddElementType(0x14 /* CorElementType.Array */);
|
|
|
|
AddOneArgTypeHelper(clsArgument.GetElementType());
|
|
|
|
// put the rank information
|
|
int rank = clsArgument.GetArrayRank();
|
|
AddData(rank); // rank
|
|
AddData(0); // upper bounds
|
|
AddData(rank); // lower bound
|
|
for (int i = 0; i < rank; i++)
|
|
AddData(0);
|
|
}
|
|
} else {
|
|
// This isn't 100% accurate, but... oh well.
|
|
byte type = 0; // 0 is reserved anyway.
|
|
|
|
for (int i = 0; i < CorElementTypes.Length; i++) {
|
|
if (clsArgument == CorElementTypes[i]) {
|
|
type = (byte) i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (type == 0) {
|
|
if (clsArgument == typeof(object)) {
|
|
type = 0x1C /* CorElementType.Object */;
|
|
} else if (clsArgument.GetTypeInfo().IsValueType) {
|
|
type = 0x11 /* CorElementType.ValueType */;
|
|
} else {
|
|
// Let's hope for the best.
|
|
type = 0x12 /* CorElementType.Class */;
|
|
}
|
|
}
|
|
|
|
if (type <= 0x0E /* CorElementType.String */ ||
|
|
type == 0x16 /* CorElementType.TypedByRef */ ||
|
|
type == 0x18 /* CorElementType.I */ ||
|
|
type == 0x19 /* CorElementType.U */ ||
|
|
type == 0x1C /* CorElementType.Object */
|
|
) {
|
|
AddElementType(type);
|
|
} else if (clsArgument.GetTypeInfo().IsValueType) {
|
|
InternalAddTypeToken(GetTokenForType(clsArgument), 0x11 /* CorElementType.ValueType */);
|
|
} else {
|
|
InternalAddTypeToken(GetTokenForType(clsArgument), 0x12 /* CorElementType.Class */);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|