bizinvoker - fix a bug that caused the CLR to reject any [BizInvoke, Compatibility = false] method with a string parameter in any position except the first

According to the rules (which are hard to find), there must be nothing on the evaluation stack when localloc is called (except its own size parameter).  The generated string interop code didn't respect that.  This didn't cause any problems in existing code.
This commit is contained in:
nattthebear 2020-06-30 17:53:26 -04:00
parent ef311576ee
commit 6e938718f5
1 changed files with 123 additions and 66 deletions

View File

@ -287,6 +287,19 @@ namespace BizHawk.BizInvoke
}; };
} }
private class ParameterLoadInfo
{
/// <summary>
/// The native type for this parameter, to pass to calli
/// </summary>
public Type NativeType;
/// <summary>
/// Closure that will actually emit the parameter load to the il stream. The evaluation stack will
/// already have other parameters on it at this time.
/// </summary>
public Action EmitLoad;
}
/// <summary> /// <summary>
/// create a method implementation that uses calli internally /// create a method implementation that uses calli internally
/// </summary> /// </summary>
@ -296,7 +309,7 @@ namespace BizHawk.BizInvoke
{ {
var paramInfos = baseMethod.GetParameters(); var paramInfos = baseMethod.GetParameters();
var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
var nativeParamTypes = new List<Type>(); var paramLoadInfos = new List<ParameterLoadInfo>();
var returnType = baseMethod.ReturnType; var returnType = baseMethod.ReturnType;
if (returnType != typeof(void) && !returnType.IsPrimitive && !returnType.IsPointer && !returnType.IsEnum) if (returnType != typeof(void) && !returnType.IsPrimitive && !returnType.IsPointer && !returnType.IsEnum)
{ {
@ -327,10 +340,16 @@ namespace BizHawk.BizInvoke
exc = il.BeginExceptionBlock(); exc = il.BeginExceptionBlock();
} }
// phase 1: empty eval stack and each parameter load thunk does any prep work it needs to do
for (int i = 0; i < paramTypes.Length; i++) for (int i = 0; i < paramTypes.Length; i++)
{ {
// arg 0 is this, so + 1 // arg 0 is this, so + 1
nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField)); paramLoadInfos.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField));
}
// phase 2: actually load the individual params, leaving each one on the stack
foreach (var pli in paramLoadInfos)
{
pli.EmitLoad();
} }
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_0);
@ -338,7 +357,7 @@ namespace BizHawk.BizInvoke
il.EmitCalli(OpCodes.Calli, il.EmitCalli(OpCodes.Calli,
nativeCall, nativeCall,
returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage
nativeParamTypes.ToArray()); paramLoadInfos.Select(p => p.NativeType).ToArray());
if (monitorField != null) // monitor: finally exit if (monitorField != null) // monitor: finally exit
{ {
@ -419,9 +438,10 @@ namespace BizHawk.BizInvoke
} }
/// <summary> /// <summary>
/// emit a single parameter load with unmanaged conversions /// emit a single parameter load with unmanaged conversions. The evaluation stack will be empty when the IL generated here runs,
/// and should end as empty.
/// </summary> /// </summary>
private static Type EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField) private static ParameterLoadInfo EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField)
{ {
if (type.IsGenericType) if (type.IsGenericType)
{ {
@ -435,13 +455,18 @@ namespace BizHawk.BizInvoke
{ {
throw new NotImplementedException("Only refs of primitive or enum types are supported!"); throw new NotImplementedException("Only refs of primitive or enum types are supported!");
} }
return new ParameterLoadInfo
var loc = il.DeclareLocal(type, true); {
il.Emit(OpCodes.Ldarg, (short)idx); NativeType = typeof(IntPtr),
il.Emit(OpCodes.Dup); EmitLoad = () =>
il.Emit(OpCodes.Stloc, loc); {
il.Emit(OpCodes.Conv_I); var loc = il.DeclareLocal(type, true);
return typeof(IntPtr); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I);
}
};
} }
if (type.IsArray) if (type.IsArray)
@ -463,48 +488,59 @@ namespace BizHawk.BizInvoke
throw new NotImplementedException("Only 0-based 1-dimensional arrays are supported!"); throw new NotImplementedException("Only 0-based 1-dimensional arrays are supported!");
} }
var loc = il.DeclareLocal(type, true); return new ParameterLoadInfo
var end = il.DefineLabel(); {
var isNull = il.DefineLabel(); NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull); il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc); il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldelema, et); il.Emit(OpCodes.Ldelema, et);
il.Emit(OpCodes.Conv_I); il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Br, end); il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull); il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero); LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end); il.MarkLabel(end);
}
return typeof(IntPtr); };
} }
if (typeof(Delegate).IsAssignableFrom(type)) if (typeof(Delegate).IsAssignableFrom(type))
{ {
// callback -- use the same callingconventionadapter on it that the invoker is being made from // callback -- use the same callingconventionadapter on it that the invoker is being made from
var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate"); return new ParameterLoadInfo
var end = il.DefineLabel(); {
var isNull = il.DefineLabel(); NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate");
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull); il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, adapterField); il.Emit(OpCodes.Ldfld, adapterField);
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes); il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes);
il.Emit(OpCodes.Br, end); il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull); il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero); LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end); il.MarkLabel(end);
return typeof(IntPtr); }
};
} }
if (type == typeof(string)) if (type == typeof(string))
@ -534,6 +570,8 @@ namespace BizHawk.BizInvoke
il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add); // +1 for null byte il.Emit(OpCodes.Add); // +1 for null byte
il.Emit(OpCodes.Conv_U); il.Emit(OpCodes.Conv_U);
// NB: The evaluation stack must be entirely empty, except for the size argument, when calling localloc.
// That's why we have to split every parameter load into two parts, the first of which runs on an empty stack.
il.Emit(OpCodes.Localloc); il.Emit(OpCodes.Localloc);
il.Emit(OpCodes.Stloc, bytes); il.Emit(OpCodes.Stloc, bytes);
@ -556,46 +594,65 @@ namespace BizHawk.BizInvoke
// unused ret // unused ret
il.Emit(OpCodes.Pop); il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldloc, bytes);
il.Emit(OpCodes.Br, end); il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull); il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero); LoadConstant(il, IntPtr.Zero);
il.Emit(OpCodes.Stloc, bytes);
il.MarkLabel(end); il.MarkLabel(end);
return typeof(IntPtr);
return new ParameterLoadInfo
{
NativeType = typeof(IntPtr),
EmitLoad = () =>
{
il.Emit(OpCodes.Ldloc, bytes);
}
};
} }
if (type.IsClass) if (type.IsClass)
{ {
// non ref of class can just be passed as pointer // non ref of class can just be passed as pointer
var loc = il.DeclareLocal(type, true); return new ParameterLoadInfo
var end = il.DefineLabel(); {
var isNull = il.DefineLabel(); NativeType = typeof(IntPtr),
EmitLoad = () =>
{
var loc = il.DeclareLocal(type, true);
var end = il.DefineLabel();
var isNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Brfalse, isNull); il.Emit(OpCodes.Brfalse, isNull);
il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Ldarg, (short)idx);
il.Emit(OpCodes.Dup); il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, loc); il.Emit(OpCodes.Stloc, loc);
il.Emit(OpCodes.Conv_I); il.Emit(OpCodes.Conv_I);
// skip past the methodtable pointer to the first field // skip past the methodtable pointer to the first field
il.Emit(OpCodes.Ldc_I4, ClassFieldOffset); il.Emit(OpCodes.Ldc_I4, ClassFieldOffset);
il.Emit(OpCodes.Conv_I); il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Add); il.Emit(OpCodes.Add);
il.Emit(OpCodes.Br, end); il.Emit(OpCodes.Br, end);
il.MarkLabel(isNull); il.MarkLabel(isNull);
LoadConstant(il, IntPtr.Zero); LoadConstant(il, IntPtr.Zero);
il.MarkLabel(end); il.MarkLabel(end);
}
return typeof(IntPtr); };
} }
if (type.IsPrimitive || type.IsEnum || type.IsPointer) if (type.IsPrimitive || type.IsEnum || type.IsPointer)
{ {
il.Emit(OpCodes.Ldarg, (short)idx); return new ParameterLoadInfo
return type; {
NativeType = type,
EmitLoad = () =>
{
il.Emit(OpCodes.Ldarg, (short)idx);
}
};
} }
throw new NotImplementedException("Unrecognized parameter type!"); throw new NotImplementedException("Unrecognized parameter type!");