2016-05-26 16:06:52 +03:00
/ /
// The rule reports
/ /
// !missing-selector!
// if headers defines a selector for which we have no bindings
/ /
using System ;
using System.Collections.Generic ;
using Mono.Cecil ;
using Clang.Ast ;
namespace Extrospection {
public class SelectorCheck : BaseVisitor {
HashSet < string > qualified_selectors = new HashSet < string > ( ) ;
2020-08-04 15:25:24 +03:00
Dictionary < string , List < Tuple < MethodDefinition , Helpers . ArgumentSemantic > > > qualified_properties = new Dictionary < string , List < Tuple < MethodDefinition , Helpers . ArgumentSemantic > > > ( ) ;
2016-05-26 16:06:52 +03:00
// most selectors will be found in [Export] attribtues
public override void VisitManagedMethod ( MethodDefinition method )
{
if ( ! method . HasCustomAttributes )
return ;
var type = method . DeclaringType ;
// we do not process protocols here
if ( type . IsProtocol ( ) )
return ;
foreach ( var ca in method . CustomAttributes ) {
switch ( ca . Constructor . DeclaringType . Name ) {
case "ExportAttribute" :
2018-11-29 19:33:37 +03:00
var methodDefinition = method . GetName ( ) ;
if ( ! string . IsNullOrEmpty ( methodDefinition ) ) {
var argumentSemantic = Helpers . ArgumentSemantic . Assign ; // Default
if ( ca . ConstructorArguments . Count > 1 ) {
argumentSemantic = ( Helpers . ArgumentSemantic ) ca . ConstructorArguments [ 1 ] . Value ;
2020-08-04 15:25:24 +03:00
if ( ! qualified_properties . TryGetValue ( methodDefinition , out var list ) )
qualified_properties [ methodDefinition ] = list = new List < Tuple < MethodDefinition , Helpers . ArgumentSemantic > > ( ) ;
list . Add ( new Tuple < MethodDefinition , Helpers . ArgumentSemantic > ( method , argumentSemantic ) ) ;
2018-11-29 19:33:37 +03:00
}
qualified_selectors . Add ( methodDefinition ) ;
}
2016-05-26 16:06:52 +03:00
break ;
}
}
}
2018-11-29 19:33:37 +03:00
public override void VisitObjCPropertyDecl ( ObjCPropertyDecl decl )
{
// protocol members are checked in ObjCProtocolCheck
if ( decl . DeclContext is ObjCProtocolDecl )
return ;
// check availability macros to see if the API is available on the OS and not deprecated
if ( ! decl . IsAvailable ( ) )
return ;
var framework = Helpers . GetFramework ( decl ) ;
if ( framework = = null )
return ;
var nativeArgumentSemantic = decl . Attributes . ToArgumentSemantic ( ) ;
var nativeMethodDefinition = decl . QualifiedName ;
2020-08-04 15:25:24 +03:00
if ( qualified_properties . TryGetValue ( nativeMethodDefinition , out var managedArgumentSemanticList ) ) {
foreach ( var entry in managedArgumentSemanticList ) {
var method = entry . Item1 ;
var managedArgumentSemantic = entry . Item2 ;
if ( managedArgumentSemantic ! = nativeArgumentSemantic ) {
// FIXME: only Copy mistakes are reported now
if ( managedArgumentSemantic = = Helpers . ArgumentSemantic . Copy | | nativeArgumentSemantic = = Helpers . ArgumentSemantic . Copy ) {
// FIXME: rule disactivated for now
// Log.On (framework).Add ($"!incorrect-argument-semantic! Native '{nativeMethodDefinition}' is declared as ({nativeArgumentSemantic.ToUsableString ().ToLowerInvariant ()}) but mapped to 'ArgumentSemantic.{managedArgumentSemantic.ToUsableString ()}' in '{method}'");
}
}
2018-11-29 19:33:37 +03:00
}
}
}
2016-05-26 16:06:52 +03:00
public override void VisitObjCMethodDecl ( ObjCMethodDecl decl , VisitKind visitKind )
{
if ( visitKind ! = VisitKind . Enter )
return ;
// protocol members are checked in ObjCProtocolCheck
if ( decl . DeclContext is ObjCProtocolDecl )
return ;
// don't process methods (or types) that are unavailable for the current platform
if ( ! decl . IsAvailable ( ) | | ! ( decl . DeclContext as Decl ) . IsAvailable ( ) )
return ;
2017-12-15 22:08:09 +03:00
var framework = Helpers . GetFramework ( decl ) ;
if ( framework = = null )
return ;
2016-05-26 16:06:52 +03:00
string selector = decl . GetSelector ( ) ;
if ( String . IsNullOrEmpty ( selector ) )
return ;
2018-08-21 21:21:12 +03:00
var name = decl . QualifiedName ;
if ( decl . IsClassMethod ) {
// we do not bind `+{type}:new` just instance `init`
if ( selector = = "new" )
return ;
name = "+" + name ;
}
2016-05-26 16:06:52 +03:00
bool found = qualified_selectors . Contains ( name ) ;
if ( ! found ) {
// a category could be inlined into the type it extend
var category = decl . DeclContext as ObjCCategoryDecl ;
if ( category ! = null ) {
var cname = category . Name ;
if ( cname = = null )
name = GetCategoryBase ( category ) + name ;
else
name = name . ReplaceFirstInstance ( cname , GetCategoryBase ( category ) ) ;
found = qualified_selectors . Contains ( name ) ;
}
}
if ( ! found )
2017-12-15 22:08:09 +03:00
Log . On ( framework ) . Add ( $"!missing-selector! {name} not bound" ) ;
2016-05-26 16:06:52 +03:00
}
static string GetCategoryBase ( ObjCCategoryDecl category )
{
// I really dislike doing this
switch ( category . Name ) {
case "UIResponderStandardEditActions" :
// we inlined this protocol in UIResponder but Apple has it on NSObject
return "UIResponder" ;
case "UIAccessibility" :
// we inlined this protocol in UIView... but Apple has it on NSObject
return "UIView" ;
case "UIAccessibilityAction" :
// we inlined this protocol in UIResponder but Apple has it on NSObject
return "UIResponder" ;
default :
2016-12-13 05:26:23 +03:00
return Helpers . GetManagedName ( category . ClassInterface . Name ) ;
2016-05-26 16:06:52 +03:00
}
}
}
}