[nullsafe][annotation graph] Annotation graph: introduce ProvisionalAnnotation

Summary:
This is the basic building blocks for the future annotation graph. This
diff introduces the first abstraction to be iterated on.

Reviewed By: artempyanykh

Differential Revision: D24621414

fbshipit-source-id: 19acdf216
master
Mitya Lyubarskiy 4 years ago committed by Facebook GitHub Bot
parent b17861b1c8
commit 19d1d2a678

@ -1633,6 +1633,11 @@ INTERNAL OPTIONS
typechecker, use `--eradicate` for now. (Conversely: typechecker, use `--eradicate` for now. (Conversely:
--no-nullsafe) --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 --nullsafe-disable-field-not-initialized-in-nonstrict-classes
Activates: Nullsafe: In this mode field not initialized issues Activates: Nullsafe: In this mode field not initialized issues
won't be reported unless the class is marked as @NullsafeStrict. won't be reported unless the class is marked as @NullsafeStrict.

@ -1617,6 +1617,12 @@ and nullable_annotation =
CLOpt.mk_string_opt ~long:"nullable-annotation-name" "Specify custom nullable annotation name" 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 = and nullsafe_disable_field_not_initialized_in_nonstrict_classes =
CLOpt.mk_bool ~long:"nullsafe-disable-field-not-initialized-in-nonstrict-classes" ~default:false 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 \ "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 nullable_annotation = !nullable_annotation
and nullsafe_annotation_graph = !nullsafe_annotation_graph
and nullsafe_disable_field_not_initialized_in_nonstrict_classes = and nullsafe_disable_field_not_initialized_in_nonstrict_classes =
!nullsafe_disable_field_not_initialized_in_nonstrict_classes !nullsafe_disable_field_not_initialized_in_nonstrict_classes

@ -393,6 +393,8 @@ val no_translate_libs : bool
val nullable_annotation : string option val nullable_annotation : string option
val nullsafe_annotation_graph : bool
val nullsafe_disable_field_not_initialized_in_nonstrict_classes : bool val nullsafe_disable_field_not_initialized_in_nonstrict_classes : bool
val nullsafe_optimistic_third_party_params_in_non_strict : bool val nullsafe_optimistic_third_party_params_in_non_strict : bool

@ -17,6 +17,7 @@ module F = Format
(** See {!Nullability.t} for explanation *) (** See {!Nullability.t} for explanation *)
type t = type t =
| Nullable of nullable_origin | Nullable of nullable_origin
| ProvisionallyNullable of ProvisionalAnnotation.t
| ThirdPartyNonnull | ThirdPartyNonnull
| UncheckedNonnull of unchecked_nonnull_origin | UncheckedNonnull of unchecked_nonnull_origin
| LocallyTrustedNonnull | LocallyTrustedNonnull
@ -45,6 +46,8 @@ and strict_nonnull_origin =
let get_nullability = function let get_nullability = function
| Nullable _ -> | Nullable _ ->
Nullability.Nullable Nullability.Nullable
| ProvisionallyNullable _ ->
Nullability.Nullable
| ThirdPartyNonnull -> | ThirdPartyNonnull ->
Nullability.ThirdPartyNonnull Nullability.ThirdPartyNonnull
| UncheckedNonnull _ -> | UncheckedNonnull _ ->
@ -90,6 +93,8 @@ let pp fmt t =
match t with match t with
| Nullable origin -> | Nullable origin ->
F.fprintf fmt "Nullable[%s]" (string_of_nullable_origin origin) F.fprintf fmt "Nullable[%s]" (string_of_nullable_origin origin)
| ProvisionallyNullable _ ->
F.fprintf fmt "ProvisionallyNullable"
| ThirdPartyNonnull -> | ThirdPartyNonnull ->
F.fprintf fmt "ThirdPartyNonnull" F.fprintf fmt "ThirdPartyNonnull"
| UncheckedNonnull origin -> | UncheckedNonnull origin ->

@ -19,6 +19,7 @@ open! IStd
(** See {!Nullability.t} for explanation *) (** See {!Nullability.t} for explanation *)
type t = type t =
| Nullable of nullable_origin | Nullable of nullable_origin
| ProvisionallyNullable of ProvisionalAnnotation.t (** Exist only for specical run modes *)
| ThirdPartyNonnull | ThirdPartyNonnull
| UncheckedNonnull of unchecked_nonnull_origin | UncheckedNonnull of unchecked_nonnull_origin
| LocallyTrustedNonnull | LocallyTrustedNonnull

@ -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 *) (* 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 let nullability_for_return ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
ret_annotations ~has_propagates_nullable_in_param = ~is_provisional_annotation_mode ret_type ret_annotations ~has_propagates_nullable_in_param =
let nullability = let nullability =
AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode
~is_third_party ret_type ret_annotations ~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 -> | _ when has_propagates_nullable_in_param ->
(* if any params is propagates nullable, the return type can be only nullable *) (* if any params is propagates nullable, the return type can be only nullable *)
AnnotatedNullability.Nullable AnnotatedNullability.HasPropagatesNullableInParam 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 nullability
(* Given annotations for method signature, extract nullability information (* Given annotations for method signature, extract nullability information
for return type and params *) for return type and params *)
let extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type let extract_nullability ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
ret_annotations param_annotated_types = ~is_provisional_annotation_mode ret_type ret_annotations param_annotated_types =
let params_nullability = let params_nullability =
List.map param_annotated_types ~f:(fun (typ, annotations) -> List.mapi param_annotated_types ~f:(fun param_num (param_type, param_annotations) ->
AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode nullability_for_param ~proc_name ~param_num ~is_callee_in_trust_list ~nullsafe_mode
~is_third_party typ annotations ) ~is_third_party ~is_provisional_annotation_mode param_type param_annotations )
in in
let has_propagates_nullable_in_param = let has_propagates_nullable_in_param =
List.exists params_nullability ~f:(function List.exists params_nullability ~f:(function
@ -75,13 +94,13 @@ let extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
false ) false )
in in
let return_nullability = let return_nullability =
nullability_for_return ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type nullability_for_return ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
ret_annotations ~has_propagates_nullable_in_param ret_type ~is_provisional_annotation_mode ret_annotations ~has_propagates_nullable_in_param
in in
(return_nullability, params_nullability) (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 ( {ProcAttributes.proc_name; ret_type; method_annotation= {return= ret_annotation}} as
proc_attributes ) : t = proc_attributes ) : t =
let proc_name = 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)) List.map params_with_annotations ~f:(fun ((_, typ), annotations) -> (typ, annotations))
in in
let return_nullability, params_nullability = let return_nullability, params_nullability =
extract_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party ret_type extract_nullability ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
ret_annotation param_annotated_types ~is_provisional_annotation_mode ret_type ret_annotation param_annotated_types
in in
let ret = let ret =
{ ret_annotation_deprecated= ret_annotation { ret_annotation_deprecated= ret_annotation
@ -116,6 +135,8 @@ let get ~is_callee_in_trust_list ~nullsafe_mode
{nullsafe_mode; kind; ret; params} {nullsafe_mode; kind; ret; params}
let get = get_impl ~is_provisional_annotation_mode:false
let get_for_class_under_analysis tenv proc_attributes = let get_for_class_under_analysis tenv proc_attributes =
(* Signature makes special meaning when the method is inside the class we are currently analysing. (* 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) 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. We achieve it via passing Strict mode to the signature extractor.
*) *)
let result = 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 in
(* Don't forget about the original mode *) (* Don't forget about the original mode *)
let nullsafe_mode = NullsafeMode.of_procname tenv proc_attributes.ProcAttributes.proc_name in let nullsafe_mode = NullsafeMode.of_procname tenv proc_attributes.ProcAttributes.proc_name in

@ -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]

@ -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]

@ -64,6 +64,8 @@ let get_nullability = function
| AnnotatedNullability.Nullable _ -> | AnnotatedNullability.Nullable _ ->
(* Annotated as Nullable explicitly or implicitly *) (* Annotated as Nullable explicitly or implicitly *)
Nullability.Nullable Nullability.Nullable
| AnnotatedNullability.ProvisionallyNullable _ ->
Nullability.Nullable
| AnnotatedNullability.UncheckedNonnull _ | AnnotatedNullability.UncheckedNonnull _
| AnnotatedNullability.ThirdPartyNonnull | AnnotatedNullability.ThirdPartyNonnull
| AnnotatedNullability.LocallyTrustedNonnull | AnnotatedNullability.LocallyTrustedNonnull
@ -134,6 +136,10 @@ let get_method_ret_description pname call_loc
match nullability with match nullability with
| AnnotatedNullability.Nullable _ -> | AnnotatedNullability.Nullable _ ->
"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.ThirdPartyNonnull
| AnnotatedNullability.UncheckedNonnull _ | AnnotatedNullability.UncheckedNonnull _
| AnnotatedNullability.LocallyTrustedNonnull | AnnotatedNullability.LocallyTrustedNonnull

Loading…
Cancel
Save