xamarin-macios/tests/cecil-tests/MarshalAsTest.cs

144 строки
4.2 KiB
C#
Исходник Обычный вид История

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
#nullable enable
namespace Cecil.Tests {
[TestFixture]
public class MarshalAsTest {
[TestCaseSource (typeof (Helper), "PlatformAssemblies")]
public void TestAssembly (string assemblyPath)
{
var assembly = Helper.GetAssembly (assemblyPath);
if (assembly == null)
Assert.Ignore ($"{assemblyPath} could not be found (might be disabled in build)");
var failedMethods = new List<string> ();
List<string>? failures = null;
var checkedTypes = new List<TypeReference> ();
foreach (var m in Helper.FilterMethods (assembly!, (m) => m.HasPInvokeInfo)) {
failures = null;
if (!CheckMarshalAs (checkedTypes, m, ref failures)) {
failedMethods.Add ($"Found {failures!.Count} issues with {m.FullName}:\n\t{string.Join ("\n\t", failures)}");
}
}
Assert.That (failedMethods, Is.Empty, "Methods with bool return type / parameters and no MarshalAs attribute.");
}
static void AddFailure (ref List<string>? failures, string failure)
{
if (failures == null)
failures = new List<string> ();
failures.Add (failure);
Console.WriteLine (failure);
}
bool CheckMarshalAs (List<TypeReference> checkedTypes, MethodDefinition method, ref List<string>? failures)
{
var rv = true;
if (!CheckMarshalAs (checkedTypes, method, method.MethodReturnType.ReturnType, method.MethodReturnType, ref failures)) {
AddFailure (ref failures, $"The {GetTypeName (method.ReturnType)} return type does not have a MarshalAs attribute: {method.FullName}");
rv = false;
}
if (method.HasParameters) {
for (var i = 0; i < method.Parameters.Count; i++) {
var param = method.Parameters [i];
var paramType = param.ParameterType;
if (paramType.IsByReference)
paramType = paramType.GetElementType ();
if (!CheckMarshalAs (checkedTypes, method, paramType, param, ref failures)) {
AddFailure (ref failures, $"The {GetTypeName (paramType)} parameter #{i + 1} ({param.Name}) does not have a MarshalAs attribute: {method.FullName}");
rv = false;
}
}
}
return rv;
}
bool CheckMarshalAsDelegate (List<TypeReference> checkedTypes, MethodDefinition method, TypeReference type, IMarshalInfoProvider provider, ref List<string>? failures)
{
var invokeMethod = type.Resolve ().Methods.Single (v => v.Name == "Invoke");
if (!CheckMarshalAs (checkedTypes, invokeMethod, ref failures)) {
AddFailure (ref failures, $"For the delegate type {type.FullName}");
return false;
}
return true;
}
bool CheckValueType (List<TypeReference> checkedTypes, MethodDefinition method, TypeReference tr, ref List<string>? failures)
{
var rv = true;
var type = tr.Resolve ();
if (type.IsEnum)
return true;
foreach (var field in type.Fields) {
if (field.IsStatic)
continue;
if (!CheckMarshalAs (checkedTypes, method, field.FieldType, field, ref failures)) {
AddFailure (ref failures, $"The {GetTypeName (field.FieldType)} field '{field.Name}' in {tr.FullName} does not have a MarshalAs attribute. Original method: {method.FullName}");
rv = false;
}
}
return rv;
}
static string GetTypeName (TypeReference type)
{
return type.Name.ToLower ();
}
static bool IsDelegate (TypeReference tr)
{
var t = tr.Resolve ();
if (t == null)
return false;
var baseType = t.BaseType;
return baseType.Namespace == "System" && baseType.Name == "MulticastDelegate";
}
bool CheckMarshalAs (List<TypeReference> checkedTypes, MethodDefinition method, TypeReference type, IMarshalInfoProvider provider, ref List<string>? failures)
{
if (checkedTypes.Contains (type))
return true;
checkedTypes.Add (type);
if (type.IsValueType && !type.IsPrimitive)
return CheckValueType (checkedTypes, method, type, ref failures);
if (IsDelegate (type))
return CheckMarshalAsDelegate (checkedTypes, method, type, provider, ref failures);
if (provider.HasMarshalInfo)
return true;
// boolean or char type without MarshalAs directive
if (type.Namespace == "System") {
switch (type.Name) {
case "Boolean":
case "Char":
return false;
}
}
return true;
}
}
}