2022-09-20 17:23:53 +03:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Linq ;
using System.Text ;
using NUnit.Framework ;
using Mono.Cecil ;
using Mono.Cecil.Cil ;
2022-12-12 17:29:47 +03:00
using Xamarin.Utils ;
2022-09-20 17:23:53 +03:00
#nullable enable
namespace Cecil.Tests {
[TestFixture]
public class GenericPInvokesTest {
2022-12-13 11:23:25 +03:00
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckSetupBlockUnsafeUsage ( AssemblyInfo info )
2022-09-20 17:23:53 +03:00
{
2023-03-14 12:08:28 +03:00
// We should not call BlockLiteral.SetupBlockUnsafe in our code at all.
// All our code should use the function pointer syntax for block creation:
// var block = new BlockLiteral (&function, nameof (<type where function is defined), nameof (function))
2022-09-20 17:23:53 +03:00
2022-12-13 11:23:25 +03:00
var assembly = info . Assembly ;
2022-09-20 17:23:53 +03:00
var callsToSetupBlock = AllSetupBlocks ( assembly ) ;
2023-03-14 12:08:28 +03:00
Assert . That ( callsToSetupBlock . Select ( v = > v . FullName ) , Is . Empty , "No calls at all to BlockLiteral.SetupBlockUnsafe" ) ;
2022-09-20 17:23:53 +03:00
}
2022-12-13 11:23:25 +03:00
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformImplementationAssemblyDefinitions))]
public void CheckAllPInvokes ( AssemblyInfo info )
2022-09-20 17:23:53 +03:00
{
2022-12-13 11:23:25 +03:00
var assembly = info . Assembly ;
2022-09-20 17:23:53 +03:00
var pinvokes = AllPInvokes ( assembly ) . Where ( IsPInvokeOK ) ;
Assert . IsTrue ( pinvokes . Count ( ) > 0 ) ;
var failures = pinvokes . Where ( ContainsGenerics ) . ToList ( ) ;
var failingMethods = ListOfFailingMethods ( failures ) ;
Assert . IsTrue ( failures . Count ( ) = = 0 ,
$"There are {failures.Count ()} pinvoke methods that contain generics. This will not work in .NET 7 and above (see https://github.com/xamarin/xamarin-macios/issues/11771 ):{failingMethods}" ) ;
}
string ListOfFailingMethods ( IEnumerable < MethodDefinition > methods )
{
var list = new StringBuilder ( ) ;
foreach ( var method in methods ) {
list . Append ( '\n' ) . Append ( method . FullName ) ;
}
return list . ToString ( ) ;
}
static bool ContainsGenerics ( MethodDefinition method )
{
return method . ContainsGenericParameter ;
}
IEnumerable < MethodDefinition > AllPInvokes ( AssemblyDefinition assembly )
{
2022-12-13 19:09:45 +03:00
return assembly . EnumerateMethods ( method = >
2022-09-20 17:23:53 +03:00
( method . Attributes & MethodAttributes . PInvokeImpl ) ! = 0 ) ;
}
static bool IsPInvokeOK ( MethodDefinition method )
{
var fullName = method . FullName ;
switch ( fullName ) {
default :
return true ;
}
}
IEnumerable < MethodDefinition > AllSetupBlocks ( AssemblyDefinition assembly )
{
2022-12-13 19:09:45 +03:00
return assembly . EnumerateMethods ( method = > {
2022-12-12 17:29:47 +03:00
if ( ! method . HasBody )
2022-09-20 17:23:53 +03:00
return false ;
return method . Body . Instructions . Any ( IsCallToSetupBlockUnsafe ) ;
2022-09-26 22:00:28 +03:00
} ) ;
2022-09-20 17:23:53 +03:00
}
static bool IsCallToSetupBlockUnsafe ( Instruction instr )
{
2022-12-12 17:29:47 +03:00
if ( ! IsCall ( instr ) )
return false ;
var operand = instr . Operand ;
if ( ! ( operand is MethodReference mr ) )
return false ;
if ( ! mr . DeclaringType . Is ( "ObjCRuntime" , "BlockLiteral" ) )
return false ;
if ( mr . Name ! = "SetupBlockUnsafe" )
return false ;
if ( ! mr . ReturnType . Is ( "System" , "Void" ) )
return false ;
if ( ! mr . HasParameters | | mr . Parameters . Count ! = 2 )
return false ;
if ( ! mr . Parameters [ 0 ] . ParameterType . Is ( "System" , "Delegate" ) | | ! mr . Parameters [ 1 ] . ParameterType . Is ( "System" , "Delegate" ) )
return false ;
return true ;
2022-09-20 17:23:53 +03:00
}
2022-09-26 22:00:28 +03:00
static bool IsCall ( Instruction instr )
{
2022-09-20 17:23:53 +03:00
return instr . OpCode = = OpCodes . Call | |
instr . OpCode = = OpCodes . Calli ;
}
}
}