xamarin-macios/tests/xtro-sharpie/ReleaseAttributeCheck.cs

128 строки
4.1 KiB
C#

//
// The rule reports
//
// !missing-release-attribute-on-return-value!
// for methods whose objc family indicates the returned value is retained, and the method doesn't have a [return: Release] attribute.
//
using System;
using System.Collections.Generic;
using Mono.Cecil;
using Clang.Ast;
namespace Extrospection {
public class ReleaseAttributeCheck : BaseVisitor {
// most selectors will be found in [Export] attributes
public override void VisitManagedMethod (MethodDefinition method)
{
// Don't care about methods that don't have [Export] attributes
if (!method.HasCustomAttributes)
return;
// We don't care about 'void' functions
if (method.ReturnType.FullName == "System.Void")
return;
// Value types can't need '[return: Release]'
if (method.ReturnType.IsValueType)
return;
string family = null;
string selector = null;
bool hasReleaseAttribute = false;
if (method.MethodReturnType.HasCustomAttributes) {
foreach (var ca in method.MethodReturnType.CustomAttributes) {
switch (ca.Constructor.DeclaringType.Name) {
case "ReleaseAttribute":
hasReleaseAttribute = true;
break;
}
}
}
foreach (var ca in method.CustomAttributes) {
switch (ca.Constructor.DeclaringType.Name) {
case "ExportAttribute":
selector = (string) ca.ConstructorArguments [0].Value;
// We need to compute the selector's method family
// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#method-families
// A selector is in a certain selector family if ignoring any leading underscore the first component of the selector either consists entirely
// of the name of the method family or it begins with that name followed by a character other than a lowercase letter
var firstLetter = 0;
var firstNonLowercaseLetter = selector.Length;
for (var i = 0; i < selector.Length; i++) {
var c = selector [i];
if (firstLetter == i && c == '_') {
// ... ignoring any leading underscores ...
firstLetter++;
} else if (c < 'a' || c > 'z') {
firstNonLowercaseLetter = i;
break;
}
}
family = selector.Substring (0, firstNonLowercaseLetter - firstLetter);
break;
}
}
switch (family) {
case "init": // in many cases we have custom init/constructor code, which seems to be correct, so ignore the 'init' family for now.
break;
case "alloc":
case "copy":
case "mutableCopy":
case "new":
if (!hasReleaseAttribute) {
var framework = Helpers.GetFramework (method);
Log.On (framework).Add ($"!missing-release-attribute-on-return-value! {method.FullName}'s selector's ('{selector}') Objective-C method family ('{family}') indicates that the native method returns a retained object, and as such a '[return: Release]' attribute is required.");
}
break;
default:
break;
}
}
// We should also look at the native definition for family attributes: __attribute__((objc_method_family(...))
// but unfortunately ObjectiveSharpie doesn't support getting the actual family from the attribute yet,
// so this will have to wait. In any case there only seems to be a single method with the family attribute in the SDKs,
// so we can live with a manual exception.
//public override void VisitObjCMethodDecl (ObjCMethodDecl decl, VisitKind visitKind)
//{
// if (visitKind != VisitKind.Enter)
// return;
// // don't process methods (or types) that are unavailable for the current platform
// if (!decl.IsAvailable () || !(decl.DeclContext as Decl).IsAvailable ())
// return;
// var framework = Helpers.GetFramework (decl);
// if (framework == null)
// return;
// string selector = decl.GetSelector ();
// if (string.IsNullOrEmpty (selector))
// return;
// foreach (var attr in decl.Attrs) {
// switch (attr.Kind) {
// case AttrKind.ObjCMethodFamily:
// ObjCMethodFamilyAttr familyAttr = (ObjCMethodFamilyAttr)attr;
// Console.WriteLine ("Family attribute {0} for {1}", familyAttr, selector);
// break;
// default:
// break;
// }
// }
//}
}
}