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.
577 lines
27 KiB
577 lines
27 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 for the checks called by Eradicate. *)
|
|
|
|
module L = Logging
|
|
|
|
let explain_expr tenv node e =
|
|
match Errdesc.exp_rv_dexp tenv node e with
|
|
| Some de ->
|
|
Some (DecompiledExp.to_string de)
|
|
| None ->
|
|
None
|
|
|
|
|
|
let is_virtual = function
|
|
| AnnotatedSignature.{mangled} :: _ when Mangled.is_this mangled ->
|
|
true
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let check_object_dereference ({IntraproceduralAnalysis.tenv; _} as analysis_data) ~nullsafe_mode
|
|
find_canonical_duplicate node instr_ref object_exp dereference_type inferred_nullability loc =
|
|
Result.iter_error
|
|
(DereferenceRule.check (InferredNullability.get_nullability inferred_nullability))
|
|
~f:(fun dereference_violation ->
|
|
let nullable_object_origin = InferredNullability.get_origin inferred_nullability in
|
|
let nullable_object_descr = explain_expr tenv node object_exp in
|
|
let type_error =
|
|
TypeErr.Nullable_dereference
|
|
{ dereference_violation
|
|
; dereference_location= loc
|
|
; nullable_object_descr
|
|
; dereference_type
|
|
; nullable_object_origin }
|
|
in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate type_error (Some instr_ref)
|
|
~nullsafe_mode loc )
|
|
|
|
|
|
(** [expr] is an expression that was explicitly compared with `null`. At the same time, [expr] had
|
|
[inferred_nullability] before the comparision. Check if the comparision is redundant and emit an
|
|
issue, if this is the case. *)
|
|
let check_condition_for_redundancy
|
|
({IntraproceduralAnalysis.tenv; proc_desc= curr_pdesc; _} as analysis_data) ~is_always_true
|
|
find_canonical_duplicate node ~nullsafe_mode expr typ inferred_nullability idenv linereader loc
|
|
instr_ref : unit =
|
|
let contains_instanceof_throwable pdesc node =
|
|
(* Check if the current procedure has a catch Throwable. *)
|
|
(* That always happens in the bytecode generated by try-with-resources. *)
|
|
let loc = Procdesc.Node.get_loc node in
|
|
let throwable_found = ref false in
|
|
let typ_is_throwable {Typ.desc} =
|
|
match desc with
|
|
| Typ.Tstruct (Typ.JavaClass _ as name) ->
|
|
String.equal (Typ.Name.name name) "java.lang.Throwable"
|
|
| _ ->
|
|
false
|
|
in
|
|
let do_instr = function
|
|
| Sil.Call (_, Exp.Const (Const.Cfun pn), [_; (Exp.Sizeof {typ}, _)], _, _)
|
|
when Procname.equal pn BuiltinDecl.__instanceof && typ_is_throwable typ ->
|
|
throwable_found := true
|
|
| _ ->
|
|
()
|
|
in
|
|
let do_node n =
|
|
if Location.equal loc (Procdesc.Node.get_loc n) then
|
|
Instrs.iter ~f:do_instr (Procdesc.Node.get_instrs n)
|
|
in
|
|
Procdesc.iter_nodes do_node pdesc ; !throwable_found
|
|
in
|
|
let from_try_with_resources () : bool =
|
|
(* heuristic to check if the condition is the translation of try-with-resources *)
|
|
match LineReader.from_loc linereader loc with
|
|
| Some line ->
|
|
(not (String.is_substring ~substring:"==" line || String.is_substring ~substring:"!=" line))
|
|
&& String.is_substring ~substring:"}" line
|
|
&& contains_instanceof_throwable curr_pdesc node
|
|
| None ->
|
|
false
|
|
in
|
|
let is_temp = Idenv.exp_is_temp idenv expr in
|
|
let should_report =
|
|
(* NOTE: We have different levels of non-nullability. In practice there is a big difference
|
|
between certainty for different cases: whether expression is the result of something that can not be null
|
|
due to language semantics, or comes from something that is merely not declared as nullable.
|
|
We report both types of issues now, which is OK since this warning is turned off by default.
|
|
In the future we might as well start reporting only for cases where we are more confident.
|
|
Hovewer keep in mind that for code analysis purposes all condition redudant warnings can be useful,
|
|
particularly they help to find popular functions that should have been annotated as nullable, but were not made so.
|
|
*)
|
|
(* NOTE: We don't report the opposite case, namely when the expression is known to be always `null`. Consider changing this.
|
|
*)
|
|
InferredNullability.is_nonnullish inferred_nullability
|
|
&& Config.eradicate_condition_redundant && (not is_temp) && PatternMatch.type_is_class typ
|
|
&& (not (from_try_with_resources ()))
|
|
&& not (InferredNullability.origin_is_fun_defined inferred_nullability)
|
|
in
|
|
if should_report then
|
|
(* TODO(T61051649) this is not really condition. This is an expression.
|
|
This leads to inconsistent messaging like "condition `some_variable` is always true".
|
|
So Condition_redundant should either accept expression, or this should pass a condition.
|
|
*)
|
|
let condition_descr = explain_expr tenv node expr in
|
|
let nonnull_origin = InferredNullability.get_origin inferred_nullability in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(TypeErr.Condition_redundant {is_always_true; condition_descr; nonnull_origin})
|
|
(Some instr_ref) ~nullsafe_mode loc
|
|
|
|
|
|
(** Check an assignment to a field. *)
|
|
let check_field_assignment
|
|
({IntraproceduralAnalysis.tenv; proc_desc= curr_pdesc; _} as analysis_data) ~nullsafe_mode
|
|
find_canonical_duplicate node instr_ref typestate ~expr_rhs ~field_type loc fname
|
|
(annotated_field : AnnotatedField.t) typecheck_expr : unit =
|
|
L.d_with_indent ~name:"check_field_assignment" (fun () ->
|
|
let curr_pname = Procdesc.get_proc_name curr_pdesc in
|
|
let curr_pattrs = Procdesc.get_attributes curr_pdesc in
|
|
let _, inferred_nullability_rhs =
|
|
L.d_strln "Typechecking rhs" ;
|
|
typecheck_expr node instr_ref typestate expr_rhs
|
|
(* TODO(T54687014) optimistic default might be an unsoundness issue - investigate *)
|
|
(field_type, InferredNullability.create TypeOrigin.OptimisticFallback)
|
|
loc
|
|
in
|
|
let field_is_injector_readwrite () =
|
|
Annotations.ia_is_field_injector_readwrite annotated_field.annotation_deprecated
|
|
in
|
|
let field_is_in_cleanup_context () =
|
|
let AnnotatedSignature.{ret_annotation_deprecated} =
|
|
(* TODO(T62825735): support trusted callees for fields *)
|
|
(Models.get_modelled_annotated_signature ~is_callee_in_trust_list:false tenv curr_pattrs)
|
|
.ret
|
|
in
|
|
Annotations.ia_is_cleanup ret_annotation_deprecated
|
|
in
|
|
let declared_nullability =
|
|
AnnotatedNullability.get_nullability annotated_field.annotated_type.nullability
|
|
in
|
|
let assignment_check_result =
|
|
AssignmentRule.check ~lhs:declared_nullability
|
|
~rhs:(InferredNullability.get_nullability inferred_nullability_rhs)
|
|
in
|
|
Result.iter_error assignment_check_result ~f:(fun assignment_violation ->
|
|
let should_report =
|
|
(not (AndroidFramework.is_destroy_method curr_pname))
|
|
&& PatternMatch.type_is_class field_type
|
|
&& (not (Fieldname.is_java_outer_instance fname))
|
|
&& (not (field_is_injector_readwrite ()))
|
|
&& not (field_is_in_cleanup_context ())
|
|
in
|
|
if should_report then
|
|
let rhs_origin = InferredNullability.get_origin inferred_nullability_rhs in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(TypeErr.Bad_assignment
|
|
{ assignment_violation
|
|
; assignment_location= loc
|
|
; rhs_origin
|
|
; assignment_type= AssignmentRule.ReportableViolation.AssigningToField fname })
|
|
(Some instr_ref) ~nullsafe_mode loc ) )
|
|
|
|
|
|
(* Check if the field declared as not nullable (implicitly or explicitly). If the field is
|
|
absent, we optimistically assume it is not nullable.
|
|
|
|
TODO(T54687014) investigate if this leads to unsoundness issues in practice *)
|
|
let is_field_declared_as_nonnull annotated_field_opt =
|
|
let is_nonnullish AnnotatedField.{annotated_type= {nullability}} =
|
|
Nullability.is_nonnullish (AnnotatedNullability.get_nullability nullability)
|
|
in
|
|
Option.exists annotated_field_opt ~f:is_nonnullish
|
|
|
|
|
|
let lookup_field_in_typestate pname field_name typestate =
|
|
let pvar = Pvar.mk (Mangled.from_string (Fieldname.to_string field_name)) pname in
|
|
TypeState.lookup_pvar pvar typestate
|
|
|
|
|
|
(* Given a predicate over field name, look ups the field and returns a predicate
|
|
over this field value in a typestate, or true if there is no such a field in typestate *)
|
|
let convert_predicate predicate_over_field_name field_name (pname, typestate) =
|
|
let range_for_field = lookup_field_in_typestate pname field_name typestate in
|
|
Option.exists range_for_field ~f:predicate_over_field_name
|
|
|
|
|
|
(* Given a list of typestates, does a given predicate about the field hold for at least one of them? *)
|
|
let predicate_holds_for_some_typestate typestate_list field_name ~predicate =
|
|
List.exists typestate_list ~f:(convert_predicate predicate field_name)
|
|
|
|
|
|
(* Given the typestate and the field, what is the upper bound of field nullability in this typestate?
|
|
*)
|
|
let get_nullability_upper_bound_for_typestate proc_name field_name typestate =
|
|
let range_for_field = lookup_field_in_typestate proc_name field_name typestate in
|
|
match range_for_field with
|
|
| None ->
|
|
(* There is no information about the field type in typestate (field was not assigned in all paths).
|
|
It gives the most generic upper bound.
|
|
*)
|
|
Nullability.top
|
|
(* We were able to lookup the field. Its nullability gives precise upper bound. *)
|
|
| Some (_, inferred_nullability) ->
|
|
InferredNullability.get_nullability inferred_nullability
|
|
|
|
|
|
(* Given the list of typestates (each corresponding to the final result of executing of some function),
|
|
and the field, what is the upper bound of field nullability joined over all typestates?
|
|
*)
|
|
let get_nullability_upper_bound field_name typestate_list =
|
|
(* Join upper bounds for all typestates in the list *)
|
|
List.fold typestate_list ~init:Nullability.StrictNonnull ~f:(fun acc (proc_name, typestate) ->
|
|
Nullability.join acc
|
|
(get_nullability_upper_bound_for_typestate proc_name field_name typestate) )
|
|
|
|
|
|
let is_generated_field field_name =
|
|
(* Annotation transformers might generate hidden fields in the class.
|
|
We distinguish such fields by their prefix.
|
|
*)
|
|
String.is_prefix ~prefix:"$" (Fieldname.get_field_name field_name)
|
|
|
|
|
|
(** Check field initialization for a given constructor *)
|
|
let check_constructor_initialization
|
|
({IntraproceduralAnalysis.tenv; proc_desc= curr_constructor_pdesc; _} as analysis_data)
|
|
find_canonical_duplicate start_node ~nullsafe_mode
|
|
~typestates_for_curr_constructor_and_all_initializer_methods
|
|
~typestates_for_all_constructors_incl_current loc : unit =
|
|
AnalysisState.set_node start_node ;
|
|
if Procname.is_constructor (Procdesc.get_proc_name curr_constructor_pdesc) then
|
|
match
|
|
PatternMatch.get_this_type_nonstatic_methods_only
|
|
(Procdesc.get_attributes curr_constructor_pdesc)
|
|
with
|
|
| Some {desc= Tptr (({desc= Tstruct name} as ts), _)} -> (
|
|
match Tenv.lookup tenv name with
|
|
| Some {fields} ->
|
|
let do_field (field_name, field_type, _) =
|
|
let annotated_field = AnnotatedField.get tenv field_name ts in
|
|
let is_initialized_by_framework =
|
|
match annotated_field with
|
|
| None ->
|
|
false
|
|
| Some {annotation_deprecated} ->
|
|
(* Initialized by Dependency Injection framework *)
|
|
Annotations.ia_is_field_injector_readonly annotation_deprecated
|
|
(* Initialized by Json framework *)
|
|
|| Annotations.ia_is_json_field annotation_deprecated
|
|
in
|
|
let is_initialized_in_either_constructor_or_initializer =
|
|
let is_initialized = function
|
|
| TypeOrigin.Field {object_origin= TypeOrigin.This} ->
|
|
(* Circular initialization - does not count *)
|
|
false
|
|
| _ ->
|
|
(* Found in typestate, hence the field was initialized *)
|
|
true
|
|
in
|
|
predicate_holds_for_some_typestate
|
|
(Lazy.force typestates_for_curr_constructor_and_all_initializer_methods) field_name
|
|
~predicate:(fun (_, nullability) ->
|
|
is_initialized (InferredNullability.get_origin nullability) )
|
|
in
|
|
(* TODO(T54584721) This check is completely independent of the current constuctor we check.
|
|
This check should be moved out of this function. Until it is done,
|
|
we issue one over-annotated warning per constructor, which does not make a lot of sense. *)
|
|
let field_nullability_upper_bound_over_all_typestates () =
|
|
get_nullability_upper_bound field_name
|
|
(Lazy.force typestates_for_all_constructors_incl_current)
|
|
in
|
|
let should_check_field_initialization =
|
|
let in_current_class =
|
|
let fld_cname = Fieldname.get_class_name field_name in
|
|
Typ.Name.equal name fld_cname
|
|
in
|
|
(* various frameworks initialize spefic fields behind the scenes,
|
|
we assume they are doing it well
|
|
*)
|
|
(not is_initialized_by_framework)
|
|
(* primitive types can not be null so initialization check is not needed *)
|
|
&& PatternMatch.type_is_class field_type
|
|
&& in_current_class
|
|
&& (not (Fieldname.is_java_outer_instance field_name))
|
|
(* not user code, unactionable errors *)
|
|
&& not (is_generated_field field_name)
|
|
in
|
|
if should_check_field_initialization then (
|
|
(* Check if non-null field is not initialized. *)
|
|
if
|
|
is_field_declared_as_nonnull annotated_field
|
|
&& not is_initialized_in_either_constructor_or_initializer
|
|
then
|
|
if
|
|
Config.nullsafe_disable_field_not_initialized_in_nonstrict_classes
|
|
&& NullsafeMode.equal nullsafe_mode NullsafeMode.Default
|
|
then
|
|
(* Behavior needed for backward compatibility, where we are not ready to surface this type of errors by default.
|
|
Hovewer, this error should be always turned on for @NullsafeStrict classes.
|
|
*)
|
|
()
|
|
else
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(TypeErr.Field_not_initialized {field_name})
|
|
None ~nullsafe_mode loc ;
|
|
(* Check if field is over-annotated. *)
|
|
match annotated_field with
|
|
| None ->
|
|
()
|
|
| Some annotated_field ->
|
|
if Config.eradicate_field_over_annotated then
|
|
let what =
|
|
AnnotatedNullability.get_nullability
|
|
annotated_field.annotated_type.nullability
|
|
in
|
|
let by_rhs_upper_bound = field_nullability_upper_bound_over_all_typestates () in
|
|
Result.iter_error (OverAnnotatedRule.check ~what ~by_rhs_upper_bound)
|
|
~f:(fun over_annotated_violation ->
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(TypeErr.Over_annotation
|
|
{ over_annotated_violation
|
|
; violation_type= OverAnnotatedRule.FieldOverAnnoted field_name })
|
|
~nullsafe_mode None loc ) )
|
|
in
|
|
List.iter ~f:do_field fields
|
|
| None ->
|
|
() )
|
|
| _ ->
|
|
()
|
|
|
|
|
|
let check_return_not_nullable ({IntraproceduralAnalysis.proc_desc= curr_pdesc; _} as analysis_data)
|
|
~nullsafe_mode find_canonical_duplicate loc (ret_signature : AnnotatedSignature.ret_signature)
|
|
ret_inferred_nullability =
|
|
(* Returning from a function is essentially an assignment the actual return value to the formal `return` *)
|
|
let lhs = AnnotatedNullability.get_nullability ret_signature.ret_annotated_type.nullability in
|
|
let rhs = InferredNullability.get_nullability ret_inferred_nullability in
|
|
Result.iter_error (AssignmentRule.check ~lhs ~rhs) ~f:(fun assignment_violation ->
|
|
let rhs_origin = InferredNullability.get_origin ret_inferred_nullability in
|
|
let curr_pname = Procdesc.get_proc_name curr_pdesc in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(Bad_assignment
|
|
{ assignment_violation
|
|
; assignment_location= loc
|
|
; rhs_origin
|
|
; assignment_type= ReturningFromFunction curr_pname })
|
|
None ~nullsafe_mode loc )
|
|
|
|
|
|
let check_return_overrannotated
|
|
({IntraproceduralAnalysis.proc_desc= curr_pdesc; _} as analysis_data) find_canonical_duplicate
|
|
loc ~nullsafe_mode (ret_signature : AnnotatedSignature.ret_signature) ret_inferred_nullability =
|
|
(* Returning from a function is essentially an assignment the actual return value to the formal `return` *)
|
|
let what = AnnotatedNullability.get_nullability ret_signature.ret_annotated_type.nullability in
|
|
(* In our CFG implementation, there is only one place where we return from a function
|
|
(all execution flow joins are already made), hence inferreed nullability of returns gives us
|
|
correct upper bound.
|
|
*)
|
|
let by_rhs_upper_bound = InferredNullability.get_nullability ret_inferred_nullability in
|
|
Result.iter_error (OverAnnotatedRule.check ~what ~by_rhs_upper_bound)
|
|
~f:(fun over_annotated_violation ->
|
|
let curr_pname = Procdesc.get_proc_name curr_pdesc in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(Over_annotation {over_annotated_violation; violation_type= ReturnOverAnnotated curr_pname})
|
|
None ~nullsafe_mode loc )
|
|
|
|
|
|
(** Check the annotations when returning from a method. *)
|
|
let check_return_annotation ({IntraproceduralAnalysis.proc_desc= curr_pdesc; _} as analysis_data)
|
|
find_canonical_duplicate ret_range (annotated_signature : AnnotatedSignature.t)
|
|
ret_implicitly_nullable loc : unit =
|
|
let curr_pname = Procdesc.get_proc_name curr_pdesc in
|
|
match ret_range with
|
|
(* Disables the warnings since it is not clear how to annotate the return value of lambdas *)
|
|
| Some _
|
|
when match curr_pname with Java java_pname -> Procname.Java.is_lambda java_pname | _ -> false ->
|
|
()
|
|
| Some (_, ret_inferred_nullability) ->
|
|
(* TODO(T54308240) Model ret_implicitly_nullable in AnnotatedNullability *)
|
|
if not ret_implicitly_nullable then
|
|
check_return_not_nullable analysis_data ~nullsafe_mode:annotated_signature.nullsafe_mode
|
|
find_canonical_duplicate loc annotated_signature.ret ret_inferred_nullability ;
|
|
if Config.eradicate_return_over_annotated then
|
|
check_return_overrannotated analysis_data find_canonical_duplicate loc
|
|
annotated_signature.ret ~nullsafe_mode:annotated_signature.nullsafe_mode
|
|
ret_inferred_nullability
|
|
| None ->
|
|
()
|
|
|
|
|
|
(** Check the receiver of a virtual call. *)
|
|
let check_call_receiver analysis_data ~nullsafe_mode find_canonical_duplicate node typestate
|
|
call_params callee_pname (instr_ref : TypeErr.InstrRef.t) loc typecheck_expr : unit =
|
|
match call_params with
|
|
| ((original_this_e, this_e), typ) :: _ ->
|
|
let _, this_inferred_nullability =
|
|
typecheck_expr node instr_ref typestate this_e
|
|
(* TODO(T54687014) optimistic default might be an unsoundness issue - investigate *)
|
|
(typ, InferredNullability.create TypeOrigin.OptimisticFallback)
|
|
loc
|
|
in
|
|
check_object_dereference analysis_data ~nullsafe_mode find_canonical_duplicate node instr_ref
|
|
original_this_e (DereferenceRule.ReportableViolation.MethodCall callee_pname)
|
|
this_inferred_nullability loc
|
|
| [] ->
|
|
()
|
|
|
|
|
|
type resolved_param =
|
|
{ num: int
|
|
; formal: AnnotatedSignature.param_signature
|
|
; actual: Exp.t * InferredNullability.t
|
|
; is_formal_propagates_nullable: bool }
|
|
|
|
(** Check the parameters of a call. *)
|
|
let check_call_parameters ({IntraproceduralAnalysis.tenv; _} as analysis_data) ~nullsafe_mode
|
|
~callee_annotated_signature find_canonical_duplicate node callee_attributes resolved_params loc
|
|
instr_ref : unit =
|
|
let callee_pname = callee_attributes.ProcAttributes.proc_name in
|
|
let check {num= param_position; formal; actual= orig_e2, nullability_actual} =
|
|
let report ~nullsafe_mode assignment_violation =
|
|
let actual_param_expression =
|
|
match explain_expr tenv node orig_e2 with
|
|
| Some descr ->
|
|
descr
|
|
| None ->
|
|
"formal parameter " ^ Mangled.to_string formal.mangled
|
|
in
|
|
let rhs_origin = InferredNullability.get_origin nullability_actual in
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(Bad_assignment
|
|
{ assignment_violation
|
|
; assignment_location= loc
|
|
; rhs_origin
|
|
; assignment_type=
|
|
PassingParamToFunction
|
|
{ param_signature= formal
|
|
; model_source= callee_annotated_signature.AnnotatedSignature.model_source
|
|
; actual_param_expression
|
|
; param_position
|
|
; function_procname= callee_pname } })
|
|
(Some instr_ref) ~nullsafe_mode loc
|
|
in
|
|
if PatternMatch.type_is_class formal.param_annotated_type.typ then
|
|
(* Passing a param to a function is essentially an assignment the actual param value
|
|
to the formal param *)
|
|
let lhs = AnnotatedNullability.get_nullability formal.param_annotated_type.nullability in
|
|
let rhs = InferredNullability.get_nullability nullability_actual in
|
|
Result.iter_error (AssignmentRule.check ~lhs ~rhs) ~f:(report ~nullsafe_mode)
|
|
in
|
|
List.iter ~f:check resolved_params
|
|
|
|
|
|
let check_inheritance_rule_for_return
|
|
({IntraproceduralAnalysis.proc_desc= overridden_proc_desc; _} as analysis_data)
|
|
find_canonical_duplicate loc ~nullsafe_mode ~base_proc_name ~base_nullability
|
|
~overridden_nullability =
|
|
Result.iter_error
|
|
(InheritanceRule.check InheritanceRule.Ret ~base:base_nullability
|
|
~overridden:overridden_nullability) ~f:(fun inheritance_violation ->
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(Inconsistent_subclass
|
|
{ inheritance_violation
|
|
; violation_type= InconsistentReturn
|
|
; overridden_proc_name= Procdesc.get_proc_name overridden_proc_desc
|
|
; base_proc_name })
|
|
None ~nullsafe_mode loc )
|
|
|
|
|
|
let check_inheritance_rule_for_param
|
|
({IntraproceduralAnalysis.proc_desc= overridden_proc_desc; _} as analysis_data)
|
|
find_canonical_duplicate loc ~nullsafe_mode ~overridden_param_name ~base_proc_name
|
|
~param_position ~base_nullability ~overridden_nullability =
|
|
Result.iter_error
|
|
(InheritanceRule.check InheritanceRule.Param ~base:base_nullability
|
|
~overridden:overridden_nullability) ~f:(fun inheritance_violation ->
|
|
TypeErr.register_error analysis_data find_canonical_duplicate
|
|
(Inconsistent_subclass
|
|
{ inheritance_violation
|
|
; violation_type=
|
|
InconsistentParam
|
|
{param_position; param_description= Mangled.to_string overridden_param_name}
|
|
; base_proc_name
|
|
; overridden_proc_name= Procdesc.get_proc_name overridden_proc_desc })
|
|
None ~nullsafe_mode loc )
|
|
|
|
|
|
let check_inheritance_rule_for_params analysis_data find_canonical_duplicate loc ~nullsafe_mode
|
|
~base_proc_name ~base_signature ~overridden_signature =
|
|
let base_params = base_signature.AnnotatedSignature.params in
|
|
let overridden_params = overridden_signature.AnnotatedSignature.params in
|
|
let zipped_params = List.zip base_params overridden_params in
|
|
match zipped_params with
|
|
| Ok base_and_overridden_params ->
|
|
let should_index_from_zero = is_virtual base_params in
|
|
(* Check the rule for each pair of base and overridden param *)
|
|
List.iteri base_and_overridden_params
|
|
~f:(fun index
|
|
( AnnotatedSignature.{param_annotated_type= {nullability= annotated_nullability_base}}
|
|
, AnnotatedSignature.
|
|
{ mangled= overridden_param_name
|
|
; param_annotated_type= {nullability= annotated_nullability_overridden} } )
|
|
->
|
|
check_inheritance_rule_for_param analysis_data find_canonical_duplicate loc ~nullsafe_mode
|
|
~overridden_param_name ~base_proc_name
|
|
~param_position:(if should_index_from_zero then index else index + 1)
|
|
~base_nullability:(AnnotatedNullability.get_nullability annotated_nullability_base)
|
|
~overridden_nullability:
|
|
(AnnotatedNullability.get_nullability annotated_nullability_overridden) )
|
|
| Unequal_lengths ->
|
|
(* Skip checking.
|
|
TODO (T5280249): investigate why argument lists can be of different length. *)
|
|
()
|
|
|
|
|
|
(** Check both params and return values for complying for co- and contravariance *)
|
|
let check_inheritance_rule_for_signature analysis_data find_canonical_duplicate loc ~nullsafe_mode
|
|
~base_proc_name ~base_signature ~overridden_signature =
|
|
(* Check params *)
|
|
check_inheritance_rule_for_params analysis_data find_canonical_duplicate loc ~nullsafe_mode
|
|
~base_proc_name ~base_signature ~overridden_signature ;
|
|
(* Check return value *)
|
|
match base_proc_name with
|
|
(* TODO model this as unknown nullability and get rid of that check *)
|
|
| Procname.Java java_pname when not (Procname.Java.is_external java_pname) ->
|
|
(* Check if return value is consistent with the base *)
|
|
let base_nullability =
|
|
AnnotatedNullability.get_nullability
|
|
base_signature.AnnotatedSignature.ret.ret_annotated_type.nullability
|
|
in
|
|
let overridden_nullability =
|
|
AnnotatedNullability.get_nullability
|
|
overridden_signature.AnnotatedSignature.ret.ret_annotated_type.nullability
|
|
in
|
|
check_inheritance_rule_for_return analysis_data find_canonical_duplicate loc ~nullsafe_mode
|
|
~base_proc_name ~base_nullability ~overridden_nullability
|
|
| _ ->
|
|
(* the analysis should not report return type inconsistencies with external code *)
|
|
()
|
|
|
|
|
|
(** Checks if the annotations are consistent with the derived classes and with the implemented
|
|
interfaces *)
|
|
let check_overridden_annotations ({IntraproceduralAnalysis.tenv; proc_desc; _} as analysis_data)
|
|
find_canonical_duplicate annotated_signature =
|
|
let start_node = Procdesc.get_start_node proc_desc in
|
|
let loc = Procdesc.Node.get_loc start_node in
|
|
let check_if_base_signature_matches_current base_proc_name =
|
|
match PatternMatch.lookup_attributes tenv base_proc_name with
|
|
| Some base_attributes ->
|
|
let base_signature =
|
|
(* TODO(T62825735): fully support trusted callees. Note that for inheritance
|
|
rule it doesn't make much difference, but would be nice to refactor anyway. *)
|
|
Models.get_modelled_annotated_signature ~is_callee_in_trust_list:false tenv
|
|
base_attributes
|
|
in
|
|
check_inheritance_rule_for_signature analysis_data
|
|
~nullsafe_mode:annotated_signature.AnnotatedSignature.nullsafe_mode
|
|
find_canonical_duplicate loc ~base_proc_name ~base_signature
|
|
~overridden_signature:annotated_signature
|
|
| None ->
|
|
(* Could not find the attributes - optimistically skipping the check *)
|
|
(* TODO(T54687014) ensure this is not an issue in practice *)
|
|
()
|
|
in
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
(* Iterate over all methods the current method overrides and see the current
|
|
method is compatible with all of them *)
|
|
PatternMatch.override_iter check_if_base_signature_matches_current tenv proc_name
|