You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
11 KiB
259 lines
11 KiB
(*
|
|
* 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
|
|
module F = Format
|
|
module L = Logging
|
|
|
|
(* TODO(T54088319) remove Annot.Item.t from t:
|
|
For all helper annotations guiding Nullsafe behavior, introduce corresponding datatypes:
|
|
a. Known ret value annotations (if any)
|
|
b. Known param annotations
|
|
c. Known method-level annotations.
|
|
*)
|
|
|
|
type t =
|
|
{nullsafe_mode: NullsafeMode.t; kind: kind; ret: ret_signature; params: param_signature list}
|
|
[@@deriving compare]
|
|
|
|
and ret_signature = {ret_annotation_deprecated: Annot.Item.t; ret_annotated_type: AnnotatedType.t}
|
|
[@@deriving compare]
|
|
|
|
and param_signature =
|
|
{ param_annotation_deprecated: Annot.Item.t
|
|
; mangled: Mangled.t
|
|
; param_annotated_type: AnnotatedType.t }
|
|
[@@deriving compare]
|
|
|
|
and kind = FirstParty | ThirdParty of third_party_model_source [@deriving compare]
|
|
|
|
and third_party_model_source =
|
|
| Unregistered
|
|
| ModelledInternally
|
|
| InThirdPartyRepo of {filename: string; line_number: int}
|
|
[@@deriving compare]
|
|
|
|
let get_non_virtual_params {params} =
|
|
match params with x :: tail when Mangled.is_this x.mangled -> tail | _ -> params
|
|
|
|
|
|
(* get nullability of method's return type given its annotations and information about its params *)
|
|
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
|
|
in
|
|
(* if any param is annotated with propagates nullable, the return nullability is also nullable *)
|
|
let nullability =
|
|
match nullability with
|
|
| AnnotatedNullability.Nullable _ ->
|
|
nullability (* We already know it is nullable - lets not overwrite the origin *)
|
|
| _ when has_propagates_nullable_in_param ->
|
|
(* if any params is propagates nullable, the return type can be only nullable *)
|
|
AnnotatedNullability.Nullable AnnotatedNullability.HasPropagatesNullableInParam
|
|
| _ ->
|
|
nullability
|
|
in
|
|
let final_nullability =
|
|
if
|
|
is_provisional_annotation_mode
|
|
&& AnnotatedNullability.can_be_considered_for_provisional_annotation nullability
|
|
then AnnotatedNullability.ProvisionallyNullable (ProvisionalAnnotation.Method proc_name)
|
|
else nullability
|
|
in
|
|
final_nullability
|
|
|
|
|
|
(* Given annotations for method signature, extract nullability information
|
|
for return type and params *)
|
|
let extract_for_ret ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
|
|
~is_provisional_annotation_mode ret_type ret_annotations param_info =
|
|
let has_propagates_nullable_in_param =
|
|
List.exists param_info ~f:(fun {param_annotated_type= {nullability}} ->
|
|
match nullability with
|
|
| AnnotatedNullability.Nullable AnnotatedNullability.AnnotatedPropagatesNullable ->
|
|
true
|
|
| _ ->
|
|
false )
|
|
in
|
|
let return_nullability =
|
|
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
|
|
{ ret_annotation_deprecated= ret_annotations
|
|
; ret_annotated_type= AnnotatedType.{nullability= return_nullability; typ= ret_type} }
|
|
|
|
|
|
let get_param_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party param_name
|
|
param_type param_annotations =
|
|
if Mangled.is_this param_name then AnnotatedNullability.StrictNonnull ImplicitThis
|
|
else
|
|
AnnotatedNullability.of_type_and_annotation ~is_callee_in_trust_list ~nullsafe_mode
|
|
~is_third_party param_type param_annotations
|
|
|
|
|
|
(* When getting param indices, we might need to offset to account for synthetic virtual params *)
|
|
let get_param_index_offset param_nullabilities =
|
|
match param_nullabilities with
|
|
| AnnotatedNullability.StrictNonnull ImplicitThis :: _ ->
|
|
1
|
|
| _ ->
|
|
0
|
|
|
|
|
|
let correct_by_provisional_annotations ~proc_name param_nullabilities =
|
|
let index_offset = get_param_index_offset param_nullabilities in
|
|
List.mapi param_nullabilities ~f:(fun param_index nullability ->
|
|
if AnnotatedNullability.can_be_considered_for_provisional_annotation nullability then
|
|
AnnotatedNullability.ProvisionallyNullable
|
|
(ProvisionalAnnotation.Param {method_info= proc_name; num= param_index - index_offset})
|
|
else nullability )
|
|
|
|
|
|
let extract_for_params ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
|
|
~is_provisional_annotation_mode param_info =
|
|
let param_nullability =
|
|
List.map param_info ~f:(fun (param_name, typ, annotations) ->
|
|
get_param_nullability ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party param_name typ
|
|
annotations )
|
|
in
|
|
let corrected_nullability =
|
|
if is_provisional_annotation_mode then
|
|
correct_by_provisional_annotations ~proc_name param_nullability
|
|
else param_nullability
|
|
in
|
|
List.map2_exn param_info corrected_nullability
|
|
~f:(fun (mangled, typ, param_annotation_deprecated) nullability ->
|
|
{param_annotation_deprecated; mangled; param_annotated_type= AnnotatedType.{nullability; typ}} )
|
|
|
|
|
|
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 =
|
|
Procname.as_java_exn ~explanation:"AnnotatedSignature.get:: should call only for Java methods"
|
|
proc_name
|
|
in
|
|
let is_third_party =
|
|
ThirdPartyAnnotationInfo.is_third_party_proc
|
|
(ThirdPartyAnnotationGlobalRepo.get_repo ())
|
|
proc_name
|
|
in
|
|
let param_info =
|
|
ProcAttributes.get_annotated_formals proc_attributes
|
|
|> List.map ~f:(fun ((a, b), c) -> (a, b, c))
|
|
in
|
|
let params =
|
|
extract_for_params ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
|
|
~is_provisional_annotation_mode param_info
|
|
in
|
|
let ret =
|
|
extract_for_ret ~proc_name ~is_callee_in_trust_list ~nullsafe_mode ~is_third_party
|
|
~is_provisional_annotation_mode ret_type ret_annotation params
|
|
in
|
|
let kind = if is_third_party then ThirdParty Unregistered else FirstParty in
|
|
{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)
|
|
make sense only for external (for the class under analysis) methods.
|
|
But in context of currently analyzed class we effectively have two levels of nullability for signatures:
|
|
nullable and (strict) non-null.
|
|
We achieve it via passing Strict mode to the signature extractor.
|
|
*)
|
|
let result =
|
|
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
|
|
{result with nullsafe_mode}
|
|
|
|
|
|
let pp_ia fmt ia = if not (List.is_empty ia) then F.fprintf fmt "%a " Annot.Item.pp ia
|
|
|
|
let pp_annotated_param fmt {mangled; param_annotation_deprecated; param_annotated_type} =
|
|
F.fprintf fmt " %a%a %a" pp_ia param_annotation_deprecated AnnotatedType.pp param_annotated_type
|
|
Mangled.pp mangled
|
|
|
|
|
|
let pp proc_name fmt annotated_signature =
|
|
let {ret_annotation_deprecated; ret_annotated_type} = annotated_signature.ret in
|
|
F.fprintf fmt "[%a] %a%a %a (%a )" NullsafeMode.pp annotated_signature.nullsafe_mode pp_ia
|
|
ret_annotation_deprecated AnnotatedType.pp ret_annotated_type
|
|
(Procname.pp_simplified_string ~withclass:false)
|
|
proc_name (Pp.comma_seq pp_annotated_param) annotated_signature.params
|
|
|
|
|
|
let mk_ann_str s = {Annot.class_name= s; parameters= []}
|
|
|
|
let mk_ia_nullable ia =
|
|
if Annotations.ia_is_nullable ia then ia else (mk_ann_str Annotations.nullable, true) :: ia
|
|
|
|
|
|
let mark_ia_nullability ia x = if x then mk_ia_nullable ia else ia
|
|
|
|
(** Override existing information about nullability for a given type and set it to either nullable
|
|
or nonnull *)
|
|
let set_modelled_nullability_for_annotated_type annotated_type should_set_nullable =
|
|
let nullability =
|
|
if should_set_nullable then AnnotatedNullability.Nullable ModelledNullable
|
|
else AnnotatedNullability.StrictNonnull ModelledNonnull
|
|
in
|
|
AnnotatedType.{annotated_type with nullability}
|
|
|
|
|
|
let set_modelled_nullability proc_name asig model_source (nullability_for_ret, params_nullability) =
|
|
let set_modelled_nullability_for_param param should_set_nullable =
|
|
{ param with
|
|
param_annotation_deprecated=
|
|
mark_ia_nullability param.param_annotation_deprecated should_set_nullable
|
|
; param_annotated_type=
|
|
set_modelled_nullability_for_annotated_type param.param_annotated_type should_set_nullable
|
|
}
|
|
in
|
|
let set_modelled_nullability_for_ret ret should_set_nullable =
|
|
{ ret_annotation_deprecated=
|
|
mark_ia_nullability ret.ret_annotation_deprecated should_set_nullable
|
|
; ret_annotated_type=
|
|
set_modelled_nullability_for_annotated_type ret.ret_annotated_type should_set_nullable }
|
|
in
|
|
let final_params =
|
|
let fail () =
|
|
L.die InternalError
|
|
"Annotation for procedure %a has wrong number of arguments.@\n Annotated signature: %a"
|
|
Procname.pp_unique_id proc_name (pp proc_name) asig
|
|
in
|
|
let rec model_param_nullability original_params params_nullability =
|
|
match (original_params, params_nullability) with
|
|
| param :: params_tail, nullability_tail when Mangled.is_this param.mangled ->
|
|
(* Skip "this" param - there is no notion of "nullable this" *)
|
|
param :: model_param_nullability params_tail nullability_tail
|
|
| param :: params_tail, should_set_nullable :: nullability_tail ->
|
|
set_modelled_nullability_for_param param should_set_nullable
|
|
:: model_param_nullability params_tail nullability_tail
|
|
| [], _ :: _ | _ :: _, [] ->
|
|
(* One list extausted before the other one *)
|
|
fail ()
|
|
| [], [] ->
|
|
[]
|
|
in
|
|
model_param_nullability asig.params params_nullability
|
|
in
|
|
match model_source with
|
|
| Unregistered ->
|
|
Logging.die InternalError "the method should be either internally or externally modelled"
|
|
| ModelledInternally | InThirdPartyRepo _ ->
|
|
{ asig with
|
|
ret= set_modelled_nullability_for_ret asig.ret nullability_for_ret
|
|
; kind= ThirdParty model_source
|
|
; params= final_params }
|