diff --git a/infer/man/man1/infer-full.txt b/infer/man/man1/infer-full.txt index a2d5da27d..3b36f46a4 100644 --- a/infer/man/man1/infer-full.txt +++ b/infer/man/man1/infer-full.txt @@ -1633,6 +1633,11 @@ INTERNAL OPTIONS typechecker, use `--eradicate` for now. (Conversely: --no-nullsafe) + --nullsafe-annotation-graph + Activates: Nullsafe: an experimental mode for calculating the + dependency graph between potential annotations to add in the + source code. (Conversely: --no-nullsafe-annotation-graph) + --nullsafe-disable-field-not-initialized-in-nonstrict-classes Activates: Nullsafe: In this mode field not initialized issues won't be reported unless the class is marked as @NullsafeStrict. diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index d1446d01b..2a7b10f7c 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -1617,6 +1617,12 @@ and nullable_annotation = CLOpt.mk_string_opt ~long:"nullable-annotation-name" "Specify custom nullable annotation name" +and nullsafe_annotation_graph = + CLOpt.mk_bool ~long:"nullsafe-annotation-graph" + "Nullsafe: an experimental mode for calculating the dependency graph between potential \ + annotations to add in the source code." + + and nullsafe_disable_field_not_initialized_in_nonstrict_classes = CLOpt.mk_bool ~long:"nullsafe-disable-field-not-initialized-in-nonstrict-classes" ~default:false "Nullsafe: In this mode field not initialized issues won't be reported unless the class is \ @@ -2942,6 +2948,8 @@ and nelseg = !nelseg and nullable_annotation = !nullable_annotation +and nullsafe_annotation_graph = !nullsafe_annotation_graph + and nullsafe_disable_field_not_initialized_in_nonstrict_classes = !nullsafe_disable_field_not_initialized_in_nonstrict_classes diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 861fde995..6ea8197e7 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -393,6 +393,8 @@ val no_translate_libs : bool val nullable_annotation : string option +val nullsafe_annotation_graph : bool + val nullsafe_disable_field_not_initialized_in_nonstrict_classes : bool val nullsafe_optimistic_third_party_params_in_non_strict : bool diff --git a/infer/src/nullsafe/AnnotatedNullability.ml b/infer/src/nullsafe/AnnotatedNullability.ml index f51a50c32..66e1e2d77 100644 --- a/infer/src/nullsafe/AnnotatedNullability.ml +++ b/infer/src/nullsafe/AnnotatedNullability.ml @@ -17,6 +17,7 @@ module F = Format (** See {!Nullability.t} for explanation *) type t = | Nullable of nullable_origin + | ProvisionallyNullable of ProvisionalAnnotation.t | ThirdPartyNonnull | UncheckedNonnull of unchecked_nonnull_origin | LocallyTrustedNonnull @@ -45,6 +46,8 @@ and strict_nonnull_origin = let get_nullability = function | Nullable _ -> Nullability.Nullable + | ProvisionallyNullable _ -> + Nullability.Nullable | ThirdPartyNonnull -> Nullability.ThirdPartyNonnull | UncheckedNonnull _ -> @@ -90,6 +93,8 @@ let pp fmt t = match t with | Nullable origin -> F.fprintf fmt "Nullable[%s]" (string_of_nullable_origin origin) + | ProvisionallyNullable _ -> + F.fprintf fmt "ProvisionallyNullable" | ThirdPartyNonnull -> F.fprintf fmt "ThirdPartyNonnull" | UncheckedNonnull origin -> diff --git a/infer/src/nullsafe/AnnotatedNullability.mli b/infer/src/nullsafe/AnnotatedNullability.mli index fb95e9efe..152248583 100644 --- a/infer/src/nullsafe/AnnotatedNullability.mli +++ b/infer/src/nullsafe/AnnotatedNullability.mli @@ -19,6 +19,7 @@ open! IStd (** See {!Nullability.t} for explanation *) type t = | Nullable of nullable_origin + | ProvisionallyNullable of ProvisionalAnnotation.t (** Exist only for specical run modes *) | ThirdPartyNonnull | UncheckedNonnull of unchecked_nonnull_origin | LocallyTrustedNonnull diff --git a/infer/src/nullsafe/AnnotatedSignature.ml b/infer/src/nullsafe/AnnotatedSignature.ml index 52717d1ba..107379863 100644 --- a/infer/src/nullsafe/AnnotatedSignature.ml +++ b/infer/src/nullsafe/AnnotatedSignature.ml @@ -41,8 +41,8 @@ let get_non_virtual_params {params} = (* get nullability of method's return type given its annotations and information about its params *) -let nullability_for_return ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type - ret_annotations ~has_propagates_nullable_in_param = +let nullability_for_return ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party + ~is_provisional_annotation_mode ret_type ret_annotations ~has_propagates_nullable_in_param = let nullability = AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type ret_annotations @@ -54,18 +54,37 @@ let nullability_for_return ~is_callee_in_trust_list ~nullsafe_mode ~is_third_par | _ when has_propagates_nullable_in_param -> (* if any params is propagates nullable, the return type can be only nullable *) AnnotatedNullability.Nullable AnnotatedNullability.HasPropagatesNullableInParam + | _ when is_provisional_annotation_mode -> + (* Not explicitly annotated with [@Nullable] - make it provisionally nullable *) + AnnotatedNullability.ProvisionallyNullable (ProvisionalAnnotation.Method proc_name) + | _ -> + nullability + + +let nullability_for_param ~proc_name ~param_num ~is_callee_in_trust_list ~nullsafe_mode + ~is_third_party ~is_provisional_annotation_mode param_type param_annotations = + let nullability = + AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode + ~is_third_party param_type param_annotations + in + match nullability with + | AnnotatedNullability.Nullable _ -> + nullability + | _ when is_provisional_annotation_mode -> + AnnotatedNullability.ProvisionallyNullable + (ProvisionalAnnotation.Param {method_info= proc_name; num= param_num}) | _ -> nullability (* Given annotations for method signature, extract nullability information for return type and params *) -let extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type - ret_annotations param_annotated_types = +let extract_nullability ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party + ~is_provisional_annotation_mode ret_type ret_annotations param_annotated_types = let params_nullability = - List.map param_annotated_types ~f:(fun (typ, annotations) -> - AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode - ~is_third_party typ annotations ) + List.mapi param_annotated_types ~f:(fun param_num (param_type, param_annotations) -> + nullability_for_param ~proc_name ~param_num ~is_callee_in_trust_list ~nullsafe_mode + ~is_third_party ~is_provisional_annotation_mode param_type param_annotations ) in let has_propagates_nullable_in_param = List.exists params_nullability ~f:(function @@ -75,13 +94,13 @@ let extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party false ) in let return_nullability = - nullability_for_return ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type - ret_annotations ~has_propagates_nullable_in_param + nullability_for_return ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party + ret_type ~is_provisional_annotation_mode ret_annotations ~has_propagates_nullable_in_param in (return_nullability, params_nullability) -let get ~is_callee_in_trust_list ~nullsafe_mode +let get_impl ~is_callee_in_trust_list ~nullsafe_mode ~is_provisional_annotation_mode ( {ProcAttributes.proc_name; ret_type; method_annotation= {return= ret_annotation}} as proc_attributes ) : t = let proc_name = @@ -98,8 +117,8 @@ let get ~is_callee_in_trust_list ~nullsafe_mode List.map params_with_annotations ~f:(fun ((_, typ), annotations) -> (typ, annotations)) in let return_nullability, params_nullability = - extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type - ret_annotation param_annotated_types + extract_nullability ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party + ~is_provisional_annotation_mode ret_type ret_annotation param_annotated_types in let ret = { ret_annotation_deprecated= ret_annotation @@ -116,6 +135,8 @@ let get ~is_callee_in_trust_list ~nullsafe_mode {nullsafe_mode; kind; ret; params} +let get = get_impl ~is_provisional_annotation_mode:false + let get_for_class_under_analysis tenv proc_attributes = (* Signature makes special meaning when the method is inside the class we are currently analysing. Various non-nullable levels (as dictated by nullsafe mode of the class) @@ -125,7 +146,8 @@ let get_for_class_under_analysis tenv proc_attributes = We achieve it via passing Strict mode to the signature extractor. *) let result = - get ~is_callee_in_trust_list:false ~nullsafe_mode:NullsafeMode.Strict proc_attributes + get_impl ~is_callee_in_trust_list:false ~nullsafe_mode:NullsafeMode.Strict proc_attributes + ~is_provisional_annotation_mode:Config.nullsafe_annotation_graph in (* Don't forget about the original mode *) let nullsafe_mode = NullsafeMode.of_procname tenv proc_attributes.ProcAttributes.proc_name in diff --git a/infer/src/nullsafe/ProvisionalAnnotation.ml b/infer/src/nullsafe/ProvisionalAnnotation.ml new file mode 100644 index 000000000..49579fb87 --- /dev/null +++ b/infer/src/nullsafe/ProvisionalAnnotation.ml @@ -0,0 +1,13 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd + +type t = + | Field of {field_name: string} + | Method of Procname.Java.t + | Param of {method_info: Procname.Java.t; num: int} +[@@deriving compare] diff --git a/infer/src/nullsafe/ProvisionalAnnotation.mli b/infer/src/nullsafe/ProvisionalAnnotation.mli new file mode 100644 index 000000000..79187938f --- /dev/null +++ b/infer/src/nullsafe/ProvisionalAnnotation.mli @@ -0,0 +1,18 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd + +(** Provisional annotation is an imaginary [@Nullable] annotation that is added to a class element. + * (It implies the corresponding element is NOT annotated as [@Nullable] in the source code.) * + The purpose of provisional annotation is to compute Nullability graph: what would happen if such + and such * element was annotated as [@Nullable]. *) + +type t = + | Field of {field_name: string} + | Method of Procname.Java.t + | Param of {method_info: Procname.Java.t; num: int} +[@@deriving compare] diff --git a/infer/src/nullsafe/typeOrigin.ml b/infer/src/nullsafe/typeOrigin.ml index f48990889..c99c2b57d 100644 --- a/infer/src/nullsafe/typeOrigin.ml +++ b/infer/src/nullsafe/typeOrigin.ml @@ -64,6 +64,8 @@ let get_nullability = function | AnnotatedNullability.Nullable _ -> (* Annotated as Nullable explicitly or implicitly *) Nullability.Nullable + | AnnotatedNullability.ProvisionallyNullable _ -> + Nullability.Nullable | AnnotatedNullability.UncheckedNonnull _ | AnnotatedNullability.ThirdPartyNonnull | AnnotatedNullability.LocallyTrustedNonnull @@ -134,6 +136,10 @@ let get_method_ret_description pname call_loc match nullability with | AnnotatedNullability.Nullable _ -> "nullable" + | AnnotatedNullability.ProvisionallyNullable _ -> + (* There should not be scenario where this is explained to the end user. *) + Logging.die InternalError + "get_method_ret_description: Unexpected nullability: ProvisionallyNullable" | AnnotatedNullability.ThirdPartyNonnull | AnnotatedNullability.UncheckedNonnull _ | AnnotatedNullability.LocallyTrustedNonnull