[tests] Improve the Blittable P/Invoke test to report signatures with MarshalAs attributes. (#17879)

Also augment the test to use existing helper methods to compare actual
failures with known failures.
This commit is contained in:
Rolf Bjarne Kvinge 2023-03-28 12:44:55 +02:00 коммит произвёл GitHub
Родитель 61ff4277ea
Коммит d5ad7db296
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 1373 добавлений и 39 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -28,6 +28,7 @@ namespace Cecil.Tests {
// 3. ref/out types
// 4. structs with non-blittable types
// 5. arrays
// 6. any type with [MarshalAs]
// How do I fix these?
// 1. use a TransientString from ObjCRuntime. This is an IDisposable type
// that will allocate an unmanaged string in the appropriate encoding.
@ -136,26 +137,34 @@ namespace Cecil.Tests {
// and:
// FreeStringArray (IntPtr arr, int count);
// both can handle a null array or null strings in the array.
// 6. [MarshalAs] types - this depends on the declared type. This is typically
// bool, which must be changed to 'byte' instead, and the consuming code has
// to change to compare the parameter/return value to 0 (non-zero = true,
// zero = false)
[TestFixture]
public class BlittablePInvokes {
public partial class BlittablePInvokes {
struct MethodBlitResult {
public MethodBlitResult (bool isBlittable)
public MethodBlitResult (bool isBlittable, MethodDefinition method)
{
IsBlittable = isBlittable;
Result = new StringBuilder ();
Method = method;
}
public bool IsBlittable;
public StringBuilder Result;
public MethodDefinition Method;
}
struct TypeAndIndex {
public TypeAndIndex (TypeReference type, int index)
public TypeAndIndex (TypeReference type, int index, IMarshalInfoProvider provider)
{
Type = type;
Index = index;
Provider = provider;
}
public TypeReference Type;
public int Index;
public IMarshalInfoProvider Provider;
}
struct BlitAndReason {
public BlitAndReason (bool isBlittable, string reason)
@ -167,34 +176,33 @@ namespace Cecil.Tests {
public string Reason;
}
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckForNonBlittablePInvokes (AssemblyInfo info)
[Test]
public void CheckForNonBlittablePInvokes ()
{
var assembly = info.Assembly;
var pinvokes = AllPInvokes (assembly).Where (IsPInvokeOK);
Assert.IsTrue (pinvokes.Count () > 0);
var failures = new Dictionary<string, (string Message, string Location)> ();
var pinvokes = new List<(AssemblyDefinition Assembly, MethodDefinition Method)> ();
foreach (var info in Helper.NetPlatformImplementationAssemblyDefinitions)
pinvokes.AddRange (AllPInvokes (info.Assembly).Select (v => (info.Assembly, v)));
Assert.That (pinvokes.Count, Is.GreaterThan (0), "Must have some P/Invokes at least");
var blitCache = new Dictionary<string, BlitAndReason> ();
var results = pinvokes.Select (pi => IsMethodBlittable (assembly, pi, blitCache)).Where (r => !r.IsBlittable);
if (results.Count () > 0) {
var failString = new StringBuilder ();
failString.Append ($"There is an issue with {results.Count ()} pinvokes in {assembly.Name} ({info.Path}):\n");
foreach (var sb in results.Select (r => r.Result)) {
failString.Append (sb.ToString ());
}
failString.Append ("In the file tests/cecil-tests/BlittablePInvokes.cs, read the guide carefully.");
Assert.Fail (failString.ToString ());
var results = pinvokes.Select (pi => IsMethodBlittable (pi.Assembly, pi.Method, blitCache)).Where (r => !r.IsBlittable).ToArray ();
foreach (var result in results) {
failures [result.Method.FullName] = new (result.Method.FullName, result.Method.RenderLocation ());
}
Helper.AssertFailures (failures, knownFailuresPInvokes, nameof (knownFailuresPInvokes), "In the file tests/cecil-tests/BlittablePInvokes.cs, read the guide carefully.", (v) => $"{v.Location}: {v.Message}");
}
MethodBlitResult IsMethodBlittable (AssemblyDefinition assembly, MethodReference method, Dictionary<string, BlitAndReason> blitCache)
MethodBlitResult IsMethodBlittable (AssemblyDefinition assembly, MethodDefinition method, Dictionary<string, BlitAndReason> blitCache)
{
var result = new MethodBlitResult (true);
var result = new MethodBlitResult (true, method);
var localResult = new StringBuilder ();
var types = TypesFromMethod (method);
foreach (var typeIndex in types) {
if (!IsTypeBlittable (assembly, typeIndex.Type, localResult, blitCache)) {
if (!IsTypeBlittable (assembly, typeIndex.Type, typeIndex.Provider, localResult, blitCache)) {
if (result.IsBlittable) {
result.IsBlittable = false;
result.Result.Append ($" The P/Invoke {method.FullName} has been marked as non-blittable for the following reasons:\n");
@ -213,14 +221,19 @@ namespace Cecil.Tests {
IEnumerable<TypeAndIndex> TypesFromMethod (MethodReference method)
{
if (method.ReturnType is not null)
yield return new TypeAndIndex (method.ReturnType, -1);
yield return new TypeAndIndex (method.ReturnType, -1, method.MethodReturnType);
var i = 0;
foreach (var parameter in method.Parameters)
yield return new TypeAndIndex (parameter.ParameterType, i++);
yield return new TypeAndIndex (parameter.ParameterType, i++, parameter);
}
bool IsTypeBlittable (AssemblyDefinition assembly, TypeReference type, StringBuilder result, Dictionary<string, BlitAndReason> blitCache)
bool IsTypeBlittable (AssemblyDefinition assembly, TypeReference type, IMarshalInfoProvider provider, StringBuilder result, Dictionary<string, BlitAndReason> blitCache)
{
if (provider.HasMarshalInfo) {
result.Append ($" has a [MarshalAs] attribute");
return false;
}
if (blitCache.TryGetValue (type.Name, out var cachedResult)) {
if (!cachedResult.IsBlittable)
result.Append ($" {cachedResult.Reason}");
@ -307,7 +320,7 @@ namespace Cecil.Tests {
if (f.IsStatic)
continue;
var localResult = new StringBuilder ();
if (!IsTypeBlittable (assembly, f.FieldType, localResult, blitCache)) {
if (!IsTypeBlittable (assembly, f.FieldType, f, localResult, blitCache)) {
if (!allBlittable)
fieldsResult.Append ($" {type.Name}:");
fieldsResult.Append ($" ({f.Name}: {localResult})");
@ -327,21 +340,6 @@ namespace Cecil.Tests {
(method.Attributes & MethodAttributes.PInvokeImpl) != 0);
}
static bool IsPInvokeOK (MethodDefinition method)
{
var fullName = method.FullName;
switch (fullName) {
case "System.IntPtr ObjCRuntime.Selector::GetHandle(System.String)":
#if !NET8_0_OR_GREATER
case "System.Boolean CoreFoundation.CFReadStream::CFReadStreamSetClient(System.IntPtr,System.IntPtr,CoreFoundation.CFStream/CFStreamCallback,System.IntPtr)":
case "System.Boolean CoreFoundation.CFWriteStream::CFWriteStreamSetClient(System.IntPtr,System.IntPtr,CoreFoundation.CFStream/CFStreamCallback,System.IntPtr)":
#endif
return false;
default:
return true;
}
}
[Test]
public void CheckForBlockLiterals ()
{