diff --git a/infer/annotations/src/main/java/com/facebook/infer/annotation/Cleanup.java b/infer/annotations/src/main/java/com/facebook/infer/annotation/Cleanup.java new file mode 100644 index 000000000..87f2b80db --- /dev/null +++ b/infer/annotations/src/main/java/com/facebook/infer/annotation/Cleanup.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A method annotated with @Cleanup should always be permitted to nullify fields, even if they are + * not nullable. Combined with the @Initializer annotation, this allows devs to specify + * acquire/release methods. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface Cleanup {} diff --git a/infer/src/checkers/annotations.ml b/infer/src/checkers/annotations.ml index 176a8b213..1601a9371 100644 --- a/infer/src/checkers/annotations.ml +++ b/infer/src/checkers/annotations.ml @@ -27,6 +27,8 @@ let bind_string = "BindString" let camel_nonnull = "NonNull" +let cleanup = "Cleanup" + let expensive = "Expensive" let false_on_null = "FalseOnNull" @@ -181,6 +183,8 @@ let ia_is_nonblocking ia = ia_ends_with ia nonblocking let ia_is_initializer ia = ia_ends_with ia initializer_ +let ia_is_cleanup ia = ia_ends_with ia cleanup + let ia_is_volatile ia = ia_contains ia volatile let field_injector_readwrite_list = diff --git a/infer/src/checkers/annotations.mli b/infer/src/checkers/annotations.mli index 69df06165..734c08747 100644 --- a/infer/src/checkers/annotations.mli +++ b/infer/src/checkers/annotations.mli @@ -65,6 +65,8 @@ val ia_is_false_on_null : Annot.Item.t -> bool val ia_is_initializer : Annot.Item.t -> bool +val ia_is_cleanup : Annot.Item.t -> bool + val ia_is_field_injector_readonly : Annot.Item.t -> bool (** Annotations for readonly injectors. The injector framework initializes the field but does not write null into it. *) diff --git a/infer/src/nullsafe/eradicateChecks.ml b/infer/src/nullsafe/eradicateChecks.ml index 9232181d1..f17447e90 100644 --- a/infer/src/nullsafe/eradicateChecks.ml +++ b/infer/src/nullsafe/eradicateChecks.ml @@ -33,6 +33,7 @@ let explain_expr tenv node e = | None -> None + let is_virtual = function (p, _, _) :: _ when Mangled.is_this p -> true | _ -> false (** Check an access (read or write) to a field. *) @@ -143,6 +144,7 @@ let check_nonzero tenv find_canonical_duplicate = let check_field_assignment tenv find_canonical_duplicate curr_pdesc node instr_ref typestate exp_lhs exp_rhs typ loc fname t_ia_opt typecheck_expr : unit = let curr_pname = Procdesc.get_proc_name curr_pdesc in + let curr_pattrs = Procdesc.get_attributes curr_pdesc in let t_lhs, ta_lhs, _ = typecheck_expr node instr_ref curr_pdesc typestate exp_lhs (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) @@ -153,20 +155,25 @@ let check_field_assignment tenv find_canonical_duplicate curr_pdesc node instr_r (typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc]) loc in + let field_is_injector_readwrite () = + match t_ia_opt with + | Some (_, ia) -> + Annotations.ia_is_field_injector_readwrite ia + | _ -> + false + in + let field_is_in_cleanup_context () = + let ia, _ = (Models.get_modelled_annotated_signature curr_pattrs).AnnotatedSignature.ret in + Annotations.ia_is_cleanup ia + in let should_report_nullable = - let field_is_field_injector_readwrite () = - match t_ia_opt with - | Some (_, ia) -> - Annotations.ia_is_field_injector_readwrite ia - | _ -> - false - in (not (AndroidFramework.is_destroy_method curr_pname)) && (not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta_lhs)) && TypeAnnotation.get_value AnnotatedSignature.Nullable ta_rhs && PatternMatch.type_is_class t_lhs && (not (Typ.Fieldname.Java.is_outer_instance fname)) - && not (field_is_field_injector_readwrite ()) + && (not (field_is_injector_readwrite ())) + && not (field_is_in_cleanup_context ()) in let should_report_absent = Config.eradicate_optional_present diff --git a/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java b/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java index 68e07a844..8444a1cac 100644 --- a/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java +++ b/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java @@ -12,6 +12,7 @@ import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.View; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Cleanup; import com.facebook.infer.annotation.Initializer; import javax.annotation.Nullable; @@ -120,6 +121,13 @@ class TestInitializerBuilder { return new TestInitializer(this); } + + @Cleanup + void testCleanup() { + this.required1 = null; + this.required2 = null; + this.optional = null; + } } class TestInitializer { diff --git a/infer/tests/codetoanalyze/java/eradicate/issues.exp b/infer/tests/codetoanalyze/java/eradicate/issues.exp index 727c7c497..a39bf3cad 100644 --- a/infer/tests/codetoanalyze/java/eradicate/issues.exp +++ b/infer/tests/codetoanalyze/java/eradicate/issues.exp @@ -16,18 +16,18 @@ codetoanalyze/java/eradicate/FieldNotInitialized.java, codetoanalyze.java.eradic codetoanalyze/java/eradicate/FieldNotInitialized.java, codetoanalyze.java.eradicate.FieldNotInitialized$Swap.(codetoanalyze.java.eradicate.FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, no_bucket, WARNING, [Field `FieldNotInitialized$Swap.o1` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/FieldNotInitialized.java, codetoanalyze.java.eradicate.FieldNotInitialized$WriteItself.(codetoanalyze.java.eradicate.FieldNotInitialized), 0, ERADICATE_FIELD_NOT_INITIALIZED, no_bucket, WARNING, [Field `FieldNotInitialized$WriteItself.o` is not initialized in the constructor and is not declared `@Nullable`] codetoanalyze/java/eradicate/FieldNotInitialized.java, codetoanalyze.java.eradicate.FieldNotInitialized.(), 0, ERADICATE_FIELD_NOT_INITIALIZED, no_bucket, WARNING, [Field `FieldNotInitialized.a` is not initialized in the constructor and is not declared `@Nullable`] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.(java.lang.Integer), -25, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.static_s` can be null but is not declared `@Nullable`. (Origin: null constant at line 43)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.(java.lang.String), -2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.static_s` can be null but is not declared `@Nullable`. (Origin: null constant at line 43)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.setYNull():void, 1, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.y` can be null but is not declared `@Nullable`. (Origin: null constant at line 61)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.(java.lang.Integer), -25, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.static_s` can be null but is not declared `@Nullable`. (Origin: null constant at line 44)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.(java.lang.String), -2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.static_s` can be null but is not declared `@Nullable`. (Origin: null constant at line 44)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.setYNull():void, 1, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.y` can be null but is not declared `@Nullable`. (Origin: null constant at line 62)] codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FieldNotNullable.setYNullable(java.lang.String):void, 1, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `FieldNotNullable.y` can be null but is not declared `@Nullable`. (Origin: method parameter s)] codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.FragmentExample.(), 0, ERADICATE_FIELD_NOT_INITIALIZED, no_bucket, WARNING, [Field `FragmentExample.view` is not initialized in the constructor and is not declared `@Nullable`] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.FlatBAD1(codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent):void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 250)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.FlatBAD2(codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent):void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 256)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD1():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 274)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD2():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 280)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD3():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 286)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestPut.putNull(java.util.Map,java.lang.String):void, 2, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`Map.put(...)` needs a non-null value in parameter 2 but argument `null` can be null. (Origin: null constant at line 324)] -codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestPut.putNull(java.util.Map,java.lang.String):void, 3, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestPut.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: null constant at line 324)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.FlatBAD1(codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent):void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 258)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.FlatBAD2(codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent):void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 264)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD1():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 282)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD2():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 288)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestFunctionsIdempotent.NestedBAD3():void, 2, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestFunctionsIdempotent.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: call to getS(...) at line 294)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestPut.putNull(java.util.Map,java.lang.String):void, 2, ERADICATE_PARAMETER_NOT_NULLABLE, no_bucket, WARNING, [`Map.put(...)` needs a non-null value in parameter 2 but argument `null` can be null. (Origin: null constant at line 332)] +codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.NestedFieldAccess$TestPut.putNull(java.util.Map,java.lang.String):void, 3, ERADICATE_FIELD_NOT_NULLABLE, no_bucket, WARNING, [Field `NestedFieldAccess$TestPut.dontAssignNull` can be null but is not declared `@Nullable`. (Origin: null constant at line 332)] codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.TestInitializerBuilder.build():codetoanalyze.java.eradicate.TestInitializer, 2, ERADICATE_CONDITION_REDUNDANT, no_bucket, WARNING, [The condition TestInitializerBuilder.required1 is always true according to the existing annotations.] codetoanalyze/java/eradicate/FieldNotNullable.java, codetoanalyze.java.eradicate.TestInitializerBuilder.build():codetoanalyze.java.eradicate.TestInitializer, 2, ERADICATE_CONDITION_REDUNDANT, no_bucket, WARNING, [The condition TestInitializerBuilder.required2 is always true according to the existing annotations.] codetoanalyze/java/eradicate/InconsistentSubclassAnnotation.java, codetoanalyze.java.eradicate.ExtendsExternalLibrary.externalMethod2(java.lang.Object):void, 0, ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, no_bucket, WARNING, [First parameter `object` of method `ExtendsExternalLibrary.externalMethod2(...)` is not `@Nullable` but is declared `@Nullable`in the parent class method `SomeExternalClass.externalMethod2(...)`.]