2021-08-11 11:06:46 +03:00
using System ;
2021-03-04 18:22:24 +03:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2021-09-28 15:09:23 +03:00
using System.Runtime.InteropServices ;
2021-03-04 18:22:24 +03:00
using NUnit.Framework ;
using Mono.Cecil ;
using Mono.Cecil.Cil ;
#nullable enable
namespace Cecil.Tests {
[TestFixture]
2021-03-05 09:22:14 +03:00
public class MarshalAsTest {
2022-12-13 11:23:25 +03:00
[TestCaseSource (typeof (Helper), nameof (Helper.PlatformAssemblyDefinitions))]
[TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))]
public void TestAssembly ( AssemblyInfo info )
2021-03-04 18:22:24 +03:00
{
2022-12-13 11:23:25 +03:00
var assembly = info . Assembly ;
2021-03-04 18:22:24 +03:00
var failedMethods = new List < string > ( ) ;
List < string > ? failures = null ;
var checkedTypes = new List < TypeReference > ( ) ;
2022-12-13 19:09:45 +03:00
foreach ( var m in assembly . EnumerateMethods ( ( m ) = > m . HasPInvokeInfo ) ) {
2021-03-04 18:22:24 +03:00
failures = null ;
2021-09-28 15:09:23 +03:00
checkedTypes . Clear ( ) ;
2021-03-04 18:22:24 +03:00
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 ) ) {
2021-03-05 09:22:14 +03:00
AddFailure ( ref failures , $"The {GetTypeName (method.ReturnType)} return type does not have a MarshalAs attribute: {method.FullName}" ) ;
2021-03-04 18:22:24 +03:00
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 ) ) {
2021-03-05 09:22:14 +03:00
AddFailure ( ref failures , $"The {GetTypeName (paramType)} parameter #{i + 1} ({param.Name}) does not have a MarshalAs attribute: {method.FullName}" ) ;
2021-03-04 18:22:24 +03:00
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 ( ) ;
2022-09-19 12:52:23 +03:00
// System.Runtime.InteropServices.NFloat is in a custom assembly in .NET 6, so add a special case.
if ( type is null & & tr . FullName = = "System.Runtime.InteropServices.NFloat" )
return true ;
2022-12-12 19:14:29 +03:00
if ( type is null )
throw new Exception ( $"Unable to resolve {tr.FullName}" ) ;
2021-03-04 18:22:24 +03:00
if ( type . IsEnum )
return true ;
2021-09-28 15:09:23 +03:00
if ( ( type . Attributes & TypeAttributes . ExplicitLayout ) = = TypeAttributes . ExplicitLayout )
return true ;
2021-03-04 18:22:24 +03:00
foreach ( var field in type . Fields ) {
if ( field . IsStatic )
continue ;
if ( ! CheckMarshalAs ( checkedTypes , method , field . FieldType , field , ref failures ) ) {
2021-03-05 09:22:14 +03:00
AddFailure ( ref failures , $"The {GetTypeName (field.FieldType)} field '{field.Name}' in {tr.FullName} does not have a MarshalAs attribute. Original method: {method.FullName}" ) ;
2021-03-04 18:22:24 +03:00
rv = false ;
}
}
return rv ;
}
2021-03-05 09:22:14 +03:00
static string GetTypeName ( TypeReference type )
{
2021-09-28 15:09:23 +03:00
return type . Resolve ( ) . FullName ;
2021-03-05 09:22:14 +03:00
}
2021-03-04 18:22:24 +03:00
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 ;
2021-03-05 09:22:14 +03:00
// boolean or char type without MarshalAs directive
if ( type . Namespace = = "System" ) {
switch ( type . Name ) {
case "Boolean" :
case "Char" :
return false ;
}
}
2021-03-04 18:22:24 +03:00
2021-03-05 09:22:14 +03:00
return true ;
2021-03-04 18:22:24 +03:00
}
}
}