void ProcessObjcSelector(string fullname, VersionTuple objcVersion) { string[] nameParts = fullname.Split(new string[] { "::" }, StringSplitOptions.None); string objcClassName = nameParts[0]; string selector = nameParts[1]; TypeDefinition managedType = ManagedTypes.FirstOrDefault(x => Helpers.GetName(x) == objcClassName); if (managedType != null) { var framework = Helpers.GetFramework(managedType); if (framework == null) { return; } // If the entire type is deprecated, call it good enough if (AttributeHelpers.HasAnyDeprecationForCurrentPlatform(managedType)) { return; } var matchingMethod = managedType.Methods.FirstOrDefault(x => x.GetSelector() == selector && x.IsPublic); if (matchingMethod != null) { ProcessItem(matchingMethod, fullname, objcVersion, framework); } } }
public override void VisitManagedType(TypeDefinition type) { // exclude non enum and nested enums, e.g. bunch of Selector enums in CTFont if (!type.IsEnum || type.IsNested) { return; } var name = type.Name; // e.g. WatchKit.WKErrorCode and WebKit.WKErrorCode :-( if (!enums.TryGetValue(name, out var td)) { enums.Add(name, type); } else { var(t1, t2) = Helpers.Sort(type, td); if (t1.Namespace.StartsWith("OpenTK.", StringComparison.Ordinal)) { // OpenTK duplicate a lots of enums between it's versions } else if (t1.IsNotPublic && String.IsNullOrEmpty(t1.Namespace)) { // ignore special, non exposed types } else { var framework = Helpers.GetFramework(t1); Log.On(framework).Add($"!duplicate-type-name! {name} enum exists as both {t1.FullName} and {t2.FullName}"); } } }
public override void End() { // looking for extra [Appearance] attributes foreach (var t in appearance_types) { if (!t.HasMethods) { continue; } foreach (var m in t.Methods) { if (m.IsConstructor) { continue; } if (appearance_methods.Contains(m)) { continue; } var framework = Helpers.GetFramework(m); var fn = m.FullName; // don't report on the *Appearance type - but where the attribute was used int ns = fn.IndexOf('/'); int ms = fn.IndexOf("::", ns); fn = fn.Remove(ns, ms - ns); Log.On(framework).Add($"!extra-ui-appearance-support! {fn} should NOT be decorated with [Appearance]"); } } }
void Visit(ObjCMethodDecl decl) { // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } var method = GetMethod(decl); if (method == null) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } if (!decl.HasAttr <ObjCRequiresSuperAttr> ()) { if (method.RequiresSuper()) { Log.On(framework).Add($"!extra-requires-super! {method.GetName ()} is incorrectly decorated with an [RequiresSuper] attribute"); } } else if (!method.RequiresSuper()) { Log.On(framework).Add($"!missing-requires-super! {method.GetName ()} is missing an [RequiresSuper] attribute"); } }
public override void VisitManagedType(TypeDefinition type) { // exclude non enum and nested enums, e.g. bunch of Selector enums in CTFont if (!type.IsEnum || type.IsNested) { return; } var name = type.Name; // e.g. WatchKit.WKErrorCode and WebKit.WKErrorCode :-( if (!enums.TryGetValue(name, out var td)) { enums.Add(name, type); } else if (td.Namespace.StartsWith("OpenTK.", StringComparison.Ordinal)) { // OpenTK duplicate a lots of enums between it's versions } else { var sorted = Helpers.Sort(type.FullName, td.FullName); var framework = Helpers.GetFramework(type); Log.On(framework).Add($"!duplicate-type-name! {name} enum exists as both {sorted.Item1} and {sorted.Item2}"); } }
public override void VisitVarDecl(VarDecl decl) { if (!decl.IsExternC) { return; } if (!decl.PresumedLoc.FileName.Contains(".framework")) { return; } if (!decl.IsAvailable()) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var name = decl.ToString(); if (!fields.TryGetValue(name, out var mr)) { Log.On(framework).Add($"!missing-field! {name} not bound"); } else { fields.Remove(name); } }
void ProcessObjcSelector(string fullname, VersionTuple objcVersion) { var class_method = fullname [0] == '+'; var n = fullname.IndexOf("::"); string objcClassName = fullname.Substring(class_method ? 1: 0, n); string selector = fullname.Substring(n + 2); TypeDefinition managedType = ManagedTypes.FirstOrDefault(x => Helpers.GetName(x) == objcClassName); if (managedType != null) { var framework = Helpers.GetFramework(managedType); if (framework == null) { return; } // If the entire type is deprecated, call it good enough if (AttributeHelpers.HasAnyDeprecationForCurrentPlatform(managedType)) { return; } var matchingMethod = managedType.Methods.FirstOrDefault(x => x.GetSelector() == selector && x.IsPublic && x.IsStatic == class_method); if (matchingMethod != null) { ProcessItem(matchingMethod, fullname, objcVersion, framework); } } }
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; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } string selector = decl.GetSelector(); if (String.IsNullOrEmpty(selector)) { return; } var name = (decl.IsClassMethod ? "+" : String.Empty) + decl.QualifiedName; 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) { Log.On(framework).Add($"!missing-selector! {name} not bound"); } }
bool IsExtVector(Decl decl, QualType type, ref string simd_type) { var rv = false; var t = type.CanonicalQualType.Type; // Unpoint the type var pointerType = t as Clang.Ast.PointerType; if (pointerType != null) { t = pointerType.PointeeQualType.Type; } if (t.Kind == TypeKind.ExtVector) { rv = true; } else { var r = (t as RecordType)?.Decl; if (r != null) { foreach (var f in r.Fields) { var qt = f.QualType.CanonicalQualType.Type; if (qt.Kind == TypeKind.ExtVector) { rv = true; break; } var at = qt as ConstantArrayType; if (at != null) { if (at.ElementType.Type.Kind == TypeKind.ExtVector) { rv = true; break; } } } } } var typeName = type.ToString(); if (!rv && typeName.Contains("simd")) { var framework = Helpers.GetFramework(decl); Log.On(framework).Add($"!unknown-simd-type! Could not detect that {typeName} is a Simd type, but its name contains 'simd'. Something needs fixing in SimdCheck.cs"); } if (rv) { simd_type = typeName; } return(rv); }
public override void VisitObjCInterfaceDecl(ObjCInterfaceDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } var name = decl.Name; // 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; } if (!type_map.TryGetValue(name, out var td)) { Log.On(framework).Add($"!missing-type! {name} not bound"); // other checks can't be done without an actual type to inspect return; } // check base type var nbt = decl.SuperClass?.Name; var mbt = td.BaseType?.Resolve().GetName(); if (nbt != mbt) { Log.On(framework).Add($"!wrong-base-type! {name} expected {nbt} actual {mbt}"); } // check protocols foreach (var protocol in decl.Protocols) { var pname = protocol.Name; if (!ImplementProtocol(pname, td)) { Log.On(framework).Add($"!missing-protocol-conformance! {name} should conform to {pname}"); } } // TODO : check for extraneous protocols type_map.Remove(name); }
public override void End() { // at this stage anything else we have is not something we could find in Apple's headers foreach (var kvp in fields) { var extra = kvp.Key; var framework = Helpers.GetFramework(kvp.Value); Log.On(framework).Add($"!unknown-field! {extra} bound"); } }
public override void End() { // at this stage anything else we have is not something we could find in Apple's headers // e.g. a typo in the name foreach (var kvp in dllimports) { var extra = kvp.Key; var framework = Helpers.GetFramework(kvp.Value); Log.On(framework).Add($"!unknown-pinvoke! {extra} bound"); } }
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 method = GetMethod(decl); // don't report missing [DesignatedInitializer] for types that are not bound - that's a different problem if (method == null) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var designated_initializer = method.IsDesignatedInitializer(); if (!method.IsConstructor) { if (designated_initializer) { Log.On(framework).Add($"!incorrect-designated-initializer! {method.GetName ()} is not a constructor"); } } else if (decl.IsDesignatedInitializer) { if (!designated_initializer) { Log.On(framework).Add($"!missing-designated-initializer! {method.GetName ()} is missing an [DesignatedInitializer] attribute"); } } else { if (designated_initializer) { Log.On(framework).Add($"!extra-designated-initializer! {method.GetName ()} is incorrectly decorated with an [DesignatedInitializer] attribute"); } } }
void ProcessObjcEntry(string objcClassName, VersionTuple objcVersion) { TypeDefinition managedType = ManagedTypes.FirstOrDefault(x => Helpers.GetName(x) == objcClassName && x.IsPublic); if (managedType != null) { var framework = Helpers.GetFramework(managedType); if (framework != null) { ProcessItem(managedType, Helpers.GetName(managedType), objcVersion, framework); } } }
public override void End() { // report any [Native] decorated enum for which we could not find a match in the header files // e.g. a typo in the name foreach (var extra in enums) { var t = extra.Value; if (!IsNative(t)) { continue; } var framework = Helpers.GetFramework(t); Log.On(framework).Add($"!unknown-native-enum! {extra.Key} bound"); } }
public override void VisitFunctionDecl(FunctionDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // skip macros : we generally implement them but their name is lost (so no matching is possible) if (!decl.IsExternC) { return; } var name = decl.Name; // do not consider _* or __* as public API that should be bound if (name [0] == '_') { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } // check availability macros to see if the API is available on the OS and not deprecated if (!decl.IsAvailable()) { return; } if (!dllimports.ContainsKey(name)) { // if we find functions without matching DllImport then we report them // but don't report deprecated functions if (!decl.IsDeprecated()) { Log.On(framework).Add($"!missing-pinvoke! {name} is not bound"); } return; } dllimports.Remove(name); }
public override void VisitObjCCategoryDecl(ObjCCategoryDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } var categoryName = decl.Name; if (categoryName == null) { 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 ciName = decl.ClassInterface.Name; if (!type_map_copy.TryGetValue(ciName, out var td)) { // other checks can't be done without an actual type to inspect return; } // check protocols foreach (var protocol in decl.Protocols) { var pname = protocol.Name; if (!ImplementProtocol(pname, td)) { Log.On(framework).Add($"!missing-protocol-conformance! {ciName} should conform to {pname} (defined in '{categoryName}' category)"); } } }
void VisitObjCMethodDecl(ObjCMethodDecl decl) { var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var method = GetMethod(decl); if (method == null) { return; } var dt = method.DeclaringType; if (dt.HasNestedTypes) { foreach (var nt in dt.NestedTypes) { if (nt.Name != dt.Name + "Appearance") { continue; } // find matching method, including parameters (we have overloads) var fn = method.FullName; int ms = fn.IndexOf("::"); fn = fn.Insert(ms, $"/{dt.Name}Appearance"); foreach (var m in nt.Methods) { if (m.FullName != fn) { continue; } appearance_methods.Add(m); // legit one return; } } } Log.On(framework).Add($"!missing-ui-appearance-support! {method.GetName ()} is missing [Appearance]"); }
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; 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}'"); } } } } }
bool IsSimdType(Decl decl, QualType type, ref string simd_type, ref bool requires_marshal_directive) { var str = Undecorate(type.ToString()); if (type_mapping.TryGetValue(str, out var info)) { requires_marshal_directive = true; simd_type = str; return(true); } if (IsExtVector(decl, type, ref simd_type)) { var framework = Helpers.GetFramework(decl); Log.On(framework).Add($"!unknown-simd-type-mapping! The Simd type {simd_type} does not have a mapping to a managed type. Please add one in SimdCheck.cs"); } return(false); }
void ProcessCFunction(string fullname, VersionTuple objcVersion) { if (dllimports.TryGetValue(fullname, out var method)) { var dt = method.DeclaringType; var framework = Helpers.GetFramework(dt); if (framework == null) { return; } // If the entire type is deprecated, call it good enough if (AttributeHelpers.HasAnyDeprecationForCurrentPlatform(dt)) { return; } ProcessItem(method, fullname, objcVersion, framework); } }
public override void VisitManagedType(TypeDefinition type) { if (!type.HasCustomAttributes) { return; } string rname = null; bool wrapper = true; bool skip = false; foreach (var ca in type.CustomAttributes) { switch (ca.Constructor.DeclaringType.Name) { case "RegisterAttribute": rname = type.Name; if (ca.HasConstructorArguments) { rname = (ca.ConstructorArguments [0].Value as string); if (ca.ConstructorArguments.Count > 1) { wrapper = (bool)ca.ConstructorArguments [1].Value; } } if (ca.HasProperties) { foreach (var arg in ca.Properties) { switch (arg.Name) { case "Wrapper": wrapper = (bool)arg.Argument.Value; break; case "SkipRegistration": skip = (bool)arg.Argument.Value; break; } } } break; case "ProtocolAttribute": // exclude protocols return; } } if (!skip && wrapper && !String.IsNullOrEmpty(rname)) { TypeDefinition td; if (!type_map.TryGetValue(rname, out td)) { type_map.Add(rname, type); type_map_copy.Add(rname, type); } else { // always report in the same order (for unique error messages) var sorted = Helpers.Sort(type, td); var framework = Helpers.GetFramework(sorted.Item1); Log.On(framework).Add($"!duplicate-register! {rname} exists as both {sorted.Item1.FullName} and {sorted.Item2.FullName}"); } } }
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 method = GetMethod(decl); // don't report missing nullability on types that are not bound - that's a different problem if (method == null) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var t = method.DeclaringType; // look for [NullableContext] for defaults var managed_default_nullability = GetNullableContext(method); if (managed_default_nullability == Null.Oblivious) { managed_default_nullability = GetNullableContext(t); } // check parameters // categories have an offset of 1 for the extension method type (spotted as static types) int i = t.IsSealed && t.IsAbstract ? 1 : 0; foreach (var p in decl.Parameters) { var mp = method.Parameters [i++]; // a managed `out` value does not need to be inialized, won't be null (but can be ignored) if (mp.IsOut) { continue; } var pt = mp.ParameterType; // if bound as `IntPtr` then nullability attributes won't be present if (pt.IsValueType) { continue; } Null parameter_nullable; // if we used a type by reference (e.g. `ref float foo`); or a nullable type (e.g. `[BindAs]`) // then assume it's meant as a nullable type) without additional decorations if (pt.IsByReference || pt.FullName.StartsWith("System.Nullable`1<", StringComparison.Ordinal)) { parameter_nullable = Null.Annotated; } else { // check C# 8 compiler attributes var nullable = GetNullable(mp); if (nullable.Length > 1) { // check the type itself, TODO check the generics (don't think we have such cases yet) parameter_nullable = nullable [0]; } else if (nullable.Length == 0) { parameter_nullable = managed_default_nullability; } else { parameter_nullable = nullable [0]; } } // match with native and, if needed, report discrepancies p.QualType.Type.GetNullability(p.AstContext, out var nullability); switch (nullability) { case NullabilityKind.NonNull: if (parameter_nullable == Null.Annotated) { Log.On(framework).Add($"!extra-null-allowed! '{method.FullName}' has a extraneous [NullAllowed] on parameter #{i-1}"); } break; case NullabilityKind.Nullable: if (parameter_nullable != Null.Annotated) { Log.On(framework).Add($"!missing-null-allowed! '{method.FullName}' is missing an [NullAllowed] on parameter #{i-1}"); } break; case NullabilityKind.Unspecified: break; } } // with .net a constructor will always return something (or throw) // that's not the case in ObjC where `init*` can return `nil` if (method.IsConstructor) { return; } var mrt = method.ReturnType; // if bound as an `IntPtr` then the nullability will not be visible in the metadata if (mrt.IsValueType) { return; } Null return_nullable; // if we used a nullable type (e.g. [BindAs] then assume it's meant as a nullable type) without additional decorations if (mrt.FullName.StartsWith("System.Nullable`1<", StringComparison.Ordinal)) { return_nullable = Null.Annotated; } else { ICustomAttributeProvider cap; // the managed attributes are on the property, not the special methods if (method.IsGetter) { var property = method.FindProperty(); // also `null_resettable` will only show something (natively) on the setter (since it does not return null, but accept it) // in this case we'll trust xtro checking the setter only (if it exists, if not then it can't be `null_resettable`) if (property.SetMethod != null) { return; } cap = property; } else { cap = method.MethodReturnType; } Null [] mrt_nullable = GetNullable(cap); if (mrt_nullable.Length > 1) { // check the type itself, TODO check the generics (don't think we have such cases yet) return_nullable = mrt_nullable [0]; } else if (mrt_nullable.Length == 0) { return_nullable = managed_default_nullability; } else { return_nullable = mrt_nullable [0]; } } var rt = decl.ReturnQualType; rt.Type.GetNullability(decl.AstContext, out var rnull); switch (rnull) { case NullabilityKind.NonNull: if (return_nullable == Null.Annotated) { Log.On(framework).Add($"!extra-null-allowed! '{method}' has a extraneous [NullAllowed] on return type"); } break; case NullabilityKind.Nullable: if (return_nullable != Null.Annotated) { Log.On(framework).Add($"!missing-null-allowed! '{method}' is missing an [NullAllowed] on return type"); } break; case NullabilityKind.Unspecified: break; } }
public override void VisitObjCProtocolDecl(ObjCProtocolDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { 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 name = decl.Name; TypeDefinition td; if (!protocol_map.TryGetValue(name, out td)) { Log.On(framework).Add($"!missing-protocol! {name} not bound"); // other checks can't be done without an actual protocol to inspect return; } // build type selector-required map var map = new Dictionary <string, bool> (); foreach (var ca in td.CustomAttributes) { string export = null; string g_export = null; string s_export = null; bool is_required = false; bool is_property = false; bool is_static = false; switch (ca.Constructor.DeclaringType.Name) { case "ProtocolMemberAttribute": foreach (var p in ca.Properties) { switch (p.Name) { case "Selector": export = p.Argument.Value as string; break; case "GetterSelector": g_export = p.Argument.Value as string; break; case "SetterSelector": s_export = p.Argument.Value as string; break; case "IsRequired": is_required = (bool)p.Argument.Value; break; case "IsProperty": is_property = (bool)p.Argument.Value; break; case "IsStatic": is_static = (bool)p.Argument.Value; break; } } break; } if (is_property) { if (g_export != null) { if (is_static) { g_export = "+" + g_export; } map.Add(g_export, is_required); } if (s_export != null) { if (is_static) { s_export = "+" + s_export; } map.Add(s_export, is_required); } } else if (export != null) { if (is_static) { export = "+" + export; } map.Add(export, is_required); } } var remaining = new Dictionary <string, bool> (map); // check that required members match the [Abstract] members foreach (ObjCMethodDecl method in decl.Methods) { // some members might not be part of the current platform if (!method.IsAvailable()) { continue; } var selector = GetSelector(method); if (selector == null) { continue; } // a .NET interface cannot have constructors - so we cannot enforce that on the interface if (IsInit(selector)) { continue; } if (method.IsClassMethod) { selector = "+" + selector; } bool is_abstract; if (map.TryGetValue(selector, out is_abstract)) { bool required = method.ImplementationControl == ObjCImplementationControl.Required; if (required) { if (!is_abstract) { Log.On(framework).Add($"!incorrect-protocol-member! {GetName (decl, method)} is REQUIRED and should be abstract"); } } else { if (is_abstract) { Log.On(framework).Add($"!incorrect-protocol-member! {GetName (decl, method)} is OPTIONAL and should NOT be abstract"); } } remaining.Remove(selector); } else if (!method.IsClassMethod) { // a .NET interface cannot have static methods - so we can only report missing instance methods Log.On(framework).Add($"!missing-protocol-member! {GetName (decl, method)} not found"); remaining.Remove(selector); } } foreach (var selector in remaining.Keys) { Log.On(framework).Add($"!extra-protocol-member! unexpected selector {decl.Name}::{selector} found"); } remaining.Clear(); map.Clear(); protocol_map.Remove(name); }
public override void VisitEnumDecl(EnumDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } string name = decl.Name; if (name == null) { 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 mname = Helpers.GetManagedName(name); if (!enums.TryGetValue(mname, out var type)) { Log.On(framework).Add($"!missing-enum! {name} not bound"); return; } else { enums.Remove(mname); } int native_size = 4; bool native = false; // FIXME: this can be simplified switch (decl.IntegerQualType.ToString()) { case "NSInteger": case "NSUInteger": case "CFIndex": case "CFOptionFlags": case "AVAudioInteger": native_size = 8; // in managed code it's always the largest size native = true; break; case "unsigned long": case "unsigned int": case "int32_t": case "uint32_t": case "int": case "GLint": case "GLuint": case "GLenum": case "SInt32": case "UInt32": case "OptionBits": // UInt32 case "long": case "FourCharCode": case "OSStatus": break; case "int64_t": case "uint64_t": case "unsigned long long": case "CVOptionFlags": // uint64_t native_size = 8; break; case "UInt16": case "int16_t": case "uint16_t": case "short": native_size = 2; break; case "UInt8": case "int8_t": case "uint8_t": native_size = 1; break; default: throw new NotImplementedException(decl.IntegerQualType.ToString()); } // check correct [Native] decoration if (native) { if (!IsNative(type)) { Log.On(framework).Add($"!missing-enum-native! {name}"); } } else { if (IsNative(type)) { Log.On(framework).Add($"!extra-enum-native! {name}"); } } int managed_size = 4; switch (GetEnumUnderlyingType(type).Name) { case "Byte": case "SByte": managed_size = 1; break; case "Int16": case "UInt16": managed_size = 2; break; case "Int32": case "UInt32": break; case "Int64": case "UInt64": managed_size = 8; break; default: throw new NotImplementedException(); } if (native_size != managed_size) { Log.On(framework).Add($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}"); } }
// 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; } }
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; } var simd_type = string.Empty; var requires_marshal_directive = false; var native_simd = ContainsSimdTypes(decl, ref simd_type, ref requires_marshal_directive); ManagedSimdInfo info; managed_methods.TryGetValue(decl.GetName(), out info); var method = info?.Method; if (!native_simd) { if (method != null) { // The managed method uses types that were incorrectly used in place of the correct Simd types, // but the native method doesn't use the native Simd types. This means the binding is correct. } else { // Neither the managed nor the native method have anything to do with Simd types. } return; } if (method == null) { // Could not map the native method to a managed method. // This needs investigation, to see why the native method couldn't be mapped. // Check if this is new API, in which case it probably couldn't be mapped because we haven't bound it. var is_new = false; var attrs = decl.Attrs.ToList(); var parentClass = decl.DeclContext as Decl; if (parentClass != null) { attrs.AddRange(parentClass.Attrs); } foreach (var attr in attrs) { var av_attr = attr as AvailabilityAttr; if (av_attr == null) { continue; } if (av_attr.Platform.Name != "ios") { continue; } if (av_attr.Introduced.Major >= 11) { is_new = true; break; } } if (is_new && !very_strict) { return; } if (!strict) { return; } Log.On(framework).Add($"!missing-simd-managed-method! {decl}: could not find a managed method for the native method {decl.GetName ()} (selector: {decl.Selector}). Found the simd type '{simd_type}' in the native signature."); return; } if (!info.ContainsInvalidMappingForSimd) { // The managed method does not have any types that are incorrect for Simd. if (requires_marshal_directive) { CheckMarshalDirective(method, simd_type); } return; } if (method.IsObsolete()) { // We have a potentially broken managed method, but it's obsolete. That's fine. return; } if (requires_marshal_directive) { CheckMarshalDirective(method, simd_type); } // We have a potentially broken managed method. This needs fixing/investigation. Log.On(framework).Add($"!unknown-simd-type-in-signature! {method}: the native signature has a simd type ({simd_type}), while the corresponding managed method is using an incorrect (non-simd) type."); }
public override void VisitEnumDecl(EnumDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } string name = decl.Name; if (name == null) { 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 mname = Helpers.GetManagedName(name); // If our enum is obsoleted, then don't process it. if (obsoleted_enums.ContainsKey(mname)) { return; } if (!enums.TryGetValue(mname, out var type)) { Log.On(framework).Add($"!missing-enum! {name} not bound"); return; } else { enums.Remove(mname); } int native_size = 4; bool native = false; // FIXME: this can be simplified switch (decl.IntegerQualType.ToString()) { case "NSInteger": case "NSUInteger": case "CFIndex": case "CFOptionFlags": case "AVAudioInteger": native_size = 8; // in managed code it's always the largest size native = true; break; case "unsigned long": case "unsigned int": case "int32_t": case "uint32_t": case "int": case "GLint": case "GLuint": case "GLenum": case "SInt32": case "UInt32": case "OptionBits": // UInt32 case "long": case "FourCharCode": case "OSStatus": break; case "int64_t": case "uint64_t": case "unsigned long long": case "CVOptionFlags": // uint64_t native_size = 8; break; case "UInt16": case "int16_t": case "uint16_t": case "short": native_size = 2; break; case "UInt8": case "int8_t": case "uint8_t": native_size = 1; break; default: throw new NotImplementedException(decl.IntegerQualType.ToString()); } // check correct [Native] decoration if (native) { if (!IsNative(type)) { Log.On(framework).Add($"!missing-enum-native! {name}"); } } else { if (IsNative(type)) { Log.On(framework).Add($"!extra-enum-native! {name}"); } } int managed_size = 4; bool signed = true; switch (GetEnumUnderlyingType(type).Name) { case "Byte": signed = false; managed_size = 1; break; case "SByte": managed_size = 1; break; case "Int16": managed_size = 2; break; case "UInt16": signed = false; managed_size = 2; break; case "Int32": break; case "UInt32": signed = false; break; case "Int64": managed_size = 8; break; case "UInt64": signed = false; managed_size = 8; break; default: throw new NotImplementedException(); } var fields = type.Fields; if (signed) { managed_signed_values.Clear(); native_signed_values.Clear(); foreach (var f in fields) { // skip special `value__` if (f.IsRuntimeSpecialName && !f.IsStatic) { continue; } if (!f.IsObsolete()) { managed_signed_values [Convert.ToInt64(f.Constant)] = f; } } long n = 0; foreach (var value in decl.Values) { if ((value.InitExpr != null) && value.InitExpr.EvaluateAsInt(decl.AstContext, out var integer)) { n = integer.SExtValue; } native_signed_values [n] = value.ToString(); // assume, sequentially assigned (in case next `value.InitExpr` is null) n++; } foreach (var value in native_signed_values.Keys) { if (!managed_signed_values.ContainsKey(value)) { Log.On(framework).Add($"!missing-enum-value! {type.Name} native value {native_signed_values [value]} = {value} not bound"); } else { managed_signed_values.Remove(value); } } foreach (var value in managed_signed_values.Keys) { if ((value == 0) && IsExtraZeroValid(type.Name, managed_signed_values [0].Name)) { continue; } // value could be decorated with `[No*]` and those should not be reported if (managed_signed_values [value].IsAvailable()) { Log.On(framework).Add($"!extra-enum-value! Managed value {value} for {type.Name}.{managed_signed_values [value].Name} not found in native headers"); } } } else { managed_unsigned_values.Clear(); native_unsigned_values.Clear(); foreach (var f in fields) { // skip special `value__` if (f.IsRuntimeSpecialName && !f.IsStatic) { continue; } if (!f.IsObsolete()) { managed_unsigned_values [Convert.ToUInt64(f.Constant)] = f; } } ulong n = 0; foreach (var value in decl.Values) { if ((value.InitExpr != null) && value.InitExpr.EvaluateAsInt(decl.AstContext, out var integer)) { n = integer.ZExtValue; } native_unsigned_values [n] = value.ToString(); // assume, sequentially assigned (in case next `value.InitExpr` is null) n++; } foreach (var value in native_unsigned_values.Keys) { if (!managed_unsigned_values.ContainsKey(value)) { // only for unsigned (flags) native enums we allow all bits set on 32 bits (UInt32.MaxValue) // to be equal to all bit set on 64 bits (UInt64.MaxValue) since the MaxValue differs between // 32bits (e.g. watchOS) and 64bits (all others) platforms var log = true; if (native && (value == UInt32.MaxValue)) { log = !managed_unsigned_values.ContainsKey(UInt64.MaxValue); managed_unsigned_values.Remove(UInt64.MaxValue); } if (log) { Log.On(framework).Add($"!missing-enum-value! {type.Name} native value {native_unsigned_values [value]} = {value} not bound"); } } else { managed_unsigned_values.Remove(value); } } foreach (var value in managed_unsigned_values.Keys) { if ((value == 0) && IsExtraZeroValid(type.Name, managed_unsigned_values [0].Name)) { continue; } // value could be decorated with `[No*]` and those should not be reported if (managed_unsigned_values [value].IsAvailable()) { Log.On(framework).Add($"!extra-enum-value! Managed value {value} for {type.Name}.{managed_unsigned_values [value].Name} not found in native headers"); } } } if (native_size != managed_size) { Log.On(framework).Add($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}"); } }