Example #1
0
        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;
            }

            // don't process deprecated methods (or types)
            if (decl.IsDeprecated() ||  (decl.DeclContext as Decl).IsDeprecated())
            {
                return;
            }

            var framework = Helpers.GetFramework(decl);

            if (framework == null)
            {
                return;
            }

            string selector = decl.GetSelector();

            if (String.IsNullOrEmpty(selector))
            {
                return;
            }

            var name = decl.QualifiedName;

            if (decl.IsClassMethod)
            {
                // we do not bind `+{type}:new` just instance `init`
                if (selector == "new")
                {
                    return;
                }
                name = "+" + name;
            }
            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");
            }
        }
        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;
            }

            // don't process deprecated methods (or types)
            if (decl.IsDeprecated() ||  (decl.DeclContext as Decl).IsDeprecated())
            {
                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;
            }
        }