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.
490 lines
20 KiB
490 lines
20 KiB
(*
|
|
* Copyright (c) 2014-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.
|
|
*)
|
|
|
|
open! IStd
|
|
|
|
(** Module for the checks called by Eradicate. *)
|
|
|
|
(* do not report RETURN_NOT_NULLABLE if the return is annotated @Nonnull *)
|
|
let return_nonnull_silent = true
|
|
|
|
let get_field_annotation tenv fn typ =
|
|
let lookup = Tenv.lookup tenv in
|
|
match Typ.Struct.get_field_type_and_annotation ~lookup fn typ with
|
|
| None ->
|
|
None
|
|
| Some (t, ia) ->
|
|
let ia' =
|
|
(* TODO (t4968422) eliminate not !Config.eradicate check by marking fields as nullified *)
|
|
(* outside of Eradicate in some other way *)
|
|
if not Config.eradicate then AnnotatedSignature.mk_ia AnnotatedSignature.Nullable ia
|
|
else ia
|
|
in
|
|
Some (t, ia')
|
|
|
|
|
|
let report_error tenv = TypeErr.report_error tenv (EradicateCheckers.report_error tenv)
|
|
|
|
let explain_expr tenv node e =
|
|
match Errdesc.exp_rv_dexp tenv node e with
|
|
| Some de ->
|
|
Some (DecompiledExp.to_string de)
|
|
| None ->
|
|
None
|
|
|
|
|
|
(** Classify a procedure. *)
|
|
let classify_procedure proc_attributes =
|
|
let pn = proc_attributes.ProcAttributes.proc_name in
|
|
let unique_id = Typ.Procname.to_unique_id pn in
|
|
let classification =
|
|
if Models.is_modelled_nullable pn then "M" (* modelled *)
|
|
else if Summary.proc_is_library proc_attributes then "L" (* library *)
|
|
else if not proc_attributes.ProcAttributes.is_defined then "S" (* skip *)
|
|
else if String.is_prefix ~prefix:"com.facebook" unique_id then "F" (* FB *)
|
|
else "?"
|
|
in
|
|
classification
|
|
|
|
|
|
let is_virtual = function (p, _, _) :: _ when Mangled.is_this p -> true | _ -> false
|
|
|
|
(** Check an access (read or write) to a field. *)
|
|
let check_field_access tenv find_canonical_duplicate curr_pname node instr_ref exp fname ta loc :
|
|
unit =
|
|
if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then
|
|
let origin_descr = TypeAnnotation.descr_origin ta in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Null_field_access (explain_expr tenv node exp, fname, origin_descr, false))
|
|
(Some instr_ref) loc curr_pname
|
|
|
|
|
|
(** Check an access to an array *)
|
|
let check_array_access tenv find_canonical_duplicate curr_pname node instr_ref array_exp fname ta
|
|
loc indexed =
|
|
if TypeAnnotation.get_value AnnotatedSignature.Nullable ta then
|
|
let origin_descr = TypeAnnotation.descr_origin ta in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Null_field_access (explain_expr tenv node array_exp, fname, origin_descr, indexed))
|
|
(Some instr_ref) loc curr_pname
|
|
|
|
|
|
(** Where the condition is coming from *)
|
|
type from_call =
|
|
| From_condition (** Direct condition *)
|
|
| From_instanceof (** x instanceof C *)
|
|
| From_is_false_on_null (** returns false on null *)
|
|
| From_is_true_on_null (** returns true on null *)
|
|
| From_optional_isPresent (** x.isPresent *)
|
|
| From_containsKey (** x.containsKey *)
|
|
[@@deriving compare]
|
|
|
|
let equal_from_call = [%compare.equal: from_call]
|
|
|
|
(** Check the normalized "is zero" or "is not zero" condition of a prune instruction. *)
|
|
let check_condition tenv case_zero find_canonical_duplicate curr_pdesc node e typ ta true_branch
|
|
from_call idenv linereader loc instr_ref : unit =
|
|
let is_fun_nonnull ta =
|
|
match TypeAnnotation.get_origin ta with
|
|
| TypeOrigin.Proc proc_origin ->
|
|
let ia, _ = proc_origin.TypeOrigin.annotated_signature.AnnotatedSignature.ret in
|
|
Annotations.ia_is_nonnull ia
|
|
| _ ->
|
|
false
|
|
in
|
|
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 Typ.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 Printer.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 e in
|
|
let nonnull = is_fun_nonnull ta in
|
|
let should_report =
|
|
(not (TypeAnnotation.get_value AnnotatedSignature.Nullable ta))
|
|
&& (Config.eradicate_condition_redundant || nonnull)
|
|
&& true_branch && ((not is_temp) || nonnull) && PatternMatch.type_is_class typ
|
|
&& (not (from_try_with_resources ()))
|
|
&& equal_from_call from_call From_condition
|
|
&& not (TypeAnnotation.origin_is_fun_library ta)
|
|
in
|
|
let is_always_true = not case_zero in
|
|
let nonnull = is_fun_nonnull ta in
|
|
if should_report then
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Condition_redundant (is_always_true, explain_expr tenv node e, nonnull))
|
|
(Some instr_ref) loc curr_pdesc
|
|
|
|
|
|
(** Check an "is zero" condition. *)
|
|
let check_zero tenv find_canonical_duplicate = check_condition tenv true find_canonical_duplicate
|
|
|
|
(** Check an "is not zero" condition. *)
|
|
let check_nonzero tenv find_canonical_duplicate =
|
|
check_condition tenv false find_canonical_duplicate
|
|
|
|
|
|
(** Check an assignment to a field. *)
|
|
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 t_lhs, ta_lhs, _ =
|
|
typecheck_expr node instr_ref curr_pdesc typestate exp_lhs
|
|
(typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc])
|
|
loc
|
|
in
|
|
let _, ta_rhs, _ =
|
|
typecheck_expr node instr_ref curr_pdesc typestate exp_rhs
|
|
(typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [loc])
|
|
loc
|
|
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 ())
|
|
in
|
|
let should_report_absent =
|
|
Config.eradicate_optional_present
|
|
&& TypeAnnotation.get_value AnnotatedSignature.Present ta_lhs
|
|
&& (not (TypeAnnotation.get_value AnnotatedSignature.Present ta_rhs))
|
|
&& not (Typ.Fieldname.Java.is_outer_instance fname)
|
|
in
|
|
let should_report_mutable =
|
|
let field_is_mutable () =
|
|
match t_ia_opt with Some (_, ia) -> Annotations.ia_is_mutable ia | _ -> false
|
|
in
|
|
Config.eradicate_field_not_mutable
|
|
&& (not (Typ.Procname.is_constructor curr_pname))
|
|
&& ( match curr_pname with
|
|
| Typ.Procname.Java java_pname ->
|
|
not (Typ.Procname.Java.is_class_initializer java_pname)
|
|
| _ ->
|
|
true )
|
|
&& not (field_is_mutable ())
|
|
in
|
|
( if should_report_nullable || should_report_absent then
|
|
let ann =
|
|
if should_report_nullable then AnnotatedSignature.Nullable else AnnotatedSignature.Present
|
|
in
|
|
let origin_descr = TypeAnnotation.descr_origin ta_rhs in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Field_annotation_inconsistent (ann, fname, origin_descr))
|
|
(Some instr_ref) loc curr_pdesc ) ;
|
|
if should_report_mutable then
|
|
let origin_descr = TypeAnnotation.descr_origin ta_rhs in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Field_not_mutable (fname, origin_descr))
|
|
(Some instr_ref) loc curr_pdesc
|
|
|
|
|
|
(** Check that nonnullable fields are initialized in constructors. *)
|
|
let check_constructor_initialization tenv find_canonical_duplicate curr_pname curr_pdesc start_node
|
|
final_initializer_typestates final_constructor_typestates loc : unit =
|
|
State.set_node start_node ;
|
|
if Typ.Procname.is_constructor curr_pname then
|
|
match PatternMatch.get_this_type (Procdesc.get_attributes curr_pdesc) with
|
|
| Some {desc= Tptr (({desc= Tstruct name} as ts), _)} -> (
|
|
match Tenv.lookup tenv name with
|
|
| Some {fields} ->
|
|
let do_field (fn, ft, _) =
|
|
let annotated_with f =
|
|
match get_field_annotation tenv fn ts with None -> false | Some (_, ia) -> f ia
|
|
in
|
|
let nullable_annotated = annotated_with Annotations.ia_is_nullable in
|
|
let nonnull_annotated = annotated_with Annotations.ia_is_nonnull in
|
|
let injector_readonly_annotated =
|
|
annotated_with Annotations.ia_is_field_injector_readonly
|
|
in
|
|
let final_type_annotation_with unknown list f =
|
|
let filter_range_opt = function Some (_, ta, _) -> f ta | None -> unknown in
|
|
List.exists
|
|
~f:(function
|
|
| pname, typestate ->
|
|
let pvar =
|
|
Pvar.mk (Mangled.from_string (Typ.Fieldname.to_string fn)) pname
|
|
in
|
|
filter_range_opt (TypeState.lookup_pvar pvar typestate))
|
|
list
|
|
in
|
|
let may_be_assigned_in_final_typestate =
|
|
let origin_is_initialized = function
|
|
| TypeOrigin.Undef ->
|
|
false
|
|
| TypeOrigin.Field (TypeOrigin.Formal name, _, _) ->
|
|
let circular = Mangled.is_this name in
|
|
not circular
|
|
| _ ->
|
|
true
|
|
in
|
|
final_type_annotation_with false (Lazy.force final_initializer_typestates) (fun ta ->
|
|
origin_is_initialized (TypeAnnotation.get_origin ta) )
|
|
in
|
|
let may_be_nullable_in_final_typestate () =
|
|
final_type_annotation_with true (Lazy.force final_constructor_typestates) (fun ta ->
|
|
TypeAnnotation.get_value AnnotatedSignature.Nullable ta )
|
|
in
|
|
let should_check_field_initialization =
|
|
let in_current_class =
|
|
let fld_cname = Typ.Fieldname.Java.get_class fn in
|
|
String.equal (Typ.Name.name name) fld_cname
|
|
in
|
|
(not injector_readonly_annotated) && PatternMatch.type_is_class ft
|
|
&& in_current_class
|
|
&& not (Typ.Fieldname.Java.is_outer_instance fn)
|
|
in
|
|
if should_check_field_initialization then (
|
|
(* Check if field is missing annotation. *)
|
|
if
|
|
(not (nullable_annotated || nonnull_annotated))
|
|
&& not may_be_assigned_in_final_typestate
|
|
then
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Field_not_initialized (fn, curr_pname))
|
|
None loc curr_pdesc ;
|
|
(* Check if field is over-annotated. *)
|
|
if
|
|
Config.eradicate_field_over_annotated && nullable_annotated
|
|
&& not (may_be_nullable_in_final_typestate ())
|
|
then
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Field_over_annotated (fn, curr_pname))
|
|
None loc curr_pdesc )
|
|
in
|
|
List.iter ~f:do_field fields
|
|
| None ->
|
|
() )
|
|
| _ ->
|
|
()
|
|
|
|
|
|
(** Check the annotations when returning from a method. *)
|
|
let check_return_annotation tenv find_canonical_duplicate curr_pdesc ret_range
|
|
(annotated_signature : AnnotatedSignature.t) ret_implicitly_nullable loc : unit =
|
|
let ret_ia, _ = annotated_signature.ret in
|
|
let curr_pname = Procdesc.get_proc_name curr_pdesc in
|
|
let ret_annotated_nullable =
|
|
Annotations.ia_is_nullable ret_ia
|
|
|| List.exists
|
|
~f:(fun (_, ia, _) -> Annotations.ia_is_propagates_nullable ia)
|
|
annotated_signature.params
|
|
in
|
|
let ret_annotated_present = Annotations.ia_is_present ret_ia in
|
|
let ret_annotated_nonnull = Annotations.ia_is_nonnull ret_ia 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
|
|
| Typ.Procname.Java java_pname ->
|
|
Typ.Procname.Java.is_lambda java_pname
|
|
| _ ->
|
|
false ->
|
|
()
|
|
| Some (_, final_ta, _) ->
|
|
let final_nullable = TypeAnnotation.get_value AnnotatedSignature.Nullable final_ta in
|
|
let final_present = TypeAnnotation.get_value AnnotatedSignature.Present final_ta in
|
|
let origin_descr = TypeAnnotation.descr_origin final_ta in
|
|
let return_not_nullable =
|
|
final_nullable && (not ret_annotated_nullable) && (not ret_implicitly_nullable)
|
|
&& not (return_nonnull_silent && ret_annotated_nonnull)
|
|
in
|
|
let return_value_not_present =
|
|
Config.eradicate_optional_present && (not final_present) && ret_annotated_present
|
|
in
|
|
let return_over_annotated =
|
|
(not final_nullable) && ret_annotated_nullable && Config.eradicate_return_over_annotated
|
|
in
|
|
( if return_not_nullable || return_value_not_present then
|
|
let ann =
|
|
if return_not_nullable then AnnotatedSignature.Nullable else AnnotatedSignature.Present
|
|
in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Return_annotation_inconsistent (ann, curr_pname, origin_descr))
|
|
None loc curr_pdesc ) ;
|
|
if return_over_annotated then
|
|
report_error tenv find_canonical_duplicate (TypeErr.Return_over_annotated curr_pname) None
|
|
loc curr_pdesc
|
|
| None ->
|
|
()
|
|
|
|
|
|
(** Check the receiver of a virtual call. *)
|
|
let check_call_receiver tenv find_canonical_duplicate curr_pdesc 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_ta, _ =
|
|
typecheck_expr tenv node instr_ref curr_pdesc typestate this_e
|
|
(typ, TypeAnnotation.const AnnotatedSignature.Nullable false TypeOrigin.ONone, [])
|
|
loc
|
|
in
|
|
let null_method_call = TypeAnnotation.get_value AnnotatedSignature.Nullable this_ta in
|
|
let optional_get_on_absent =
|
|
Config.eradicate_optional_present
|
|
&& Models.is_optional_get callee_pname
|
|
&& not (TypeAnnotation.get_value AnnotatedSignature.Present this_ta)
|
|
in
|
|
if null_method_call || optional_get_on_absent then
|
|
let ann =
|
|
if null_method_call then AnnotatedSignature.Nullable else AnnotatedSignature.Present
|
|
in
|
|
let descr = explain_expr tenv node original_this_e in
|
|
let origin_descr = TypeAnnotation.descr_origin this_ta in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Call_receiver_annotation_inconsistent (ann, descr, callee_pname, origin_descr))
|
|
(Some instr_ref) loc curr_pdesc
|
|
| [] ->
|
|
()
|
|
|
|
|
|
type resolved_param =
|
|
{ num: int
|
|
; formal: Mangled.t * TypeAnnotation.t * Typ.t
|
|
; actual: Exp.t * TypeAnnotation.t
|
|
; propagates_nullable: bool }
|
|
|
|
(** Check the parameters of a call. *)
|
|
let check_call_parameters tenv find_canonical_duplicate curr_pdesc node callee_attributes
|
|
resolved_params loc instr_ref : unit =
|
|
let callee_pname = callee_attributes.ProcAttributes.proc_name in
|
|
let check {num= param_num; formal= s1, ta1, t1; actual= orig_e2, ta2} =
|
|
let report ann =
|
|
let description =
|
|
match explain_expr tenv node orig_e2 with
|
|
| Some descr ->
|
|
descr
|
|
| None ->
|
|
"formal parameter " ^ Mangled.to_string s1
|
|
in
|
|
let origin_descr = TypeAnnotation.descr_origin ta2 in
|
|
let callee_loc = callee_attributes.ProcAttributes.loc in
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Parameter_annotation_inconsistent
|
|
(ann, description, param_num, callee_pname, callee_loc, origin_descr))
|
|
(Some instr_ref) loc curr_pdesc
|
|
in
|
|
let check_ann ann =
|
|
let b1 = TypeAnnotation.get_value ann ta1 in
|
|
let b2 = TypeAnnotation.get_value ann ta2 in
|
|
match (ann, b1, b2) with
|
|
| AnnotatedSignature.Nullable, false, true ->
|
|
report ann
|
|
| AnnotatedSignature.Present, true, false ->
|
|
report ann
|
|
| _ ->
|
|
()
|
|
in
|
|
if PatternMatch.type_is_class t1 then (
|
|
check_ann AnnotatedSignature.Nullable ;
|
|
if Config.eradicate_optional_present then check_ann AnnotatedSignature.Present )
|
|
in
|
|
let should_check_parameters =
|
|
match callee_pname with
|
|
| Typ.Procname.Java java_pname ->
|
|
(not (Typ.Procname.Java.is_external java_pname))
|
|
|| Models.is_modelled_nullable callee_pname
|
|
| _ ->
|
|
false
|
|
in
|
|
if should_check_parameters then
|
|
(* left to right to avoid guessing the different lengths *)
|
|
List.iter ~f:check resolved_params
|
|
|
|
|
|
(** Checks if the annotations are consistent with the inherited class or with the
|
|
implemented interfaces *)
|
|
let check_overridden_annotations find_canonical_duplicate tenv proc_name proc_desc
|
|
annotated_signature =
|
|
let start_node = Procdesc.get_start_node proc_desc in
|
|
let loc = Procdesc.Node.get_loc start_node in
|
|
let check_return overriden_proc_name overriden_signature =
|
|
let ret_is_nullable =
|
|
let ia, _ = annotated_signature.AnnotatedSignature.ret in
|
|
Annotations.ia_is_nullable ia
|
|
and ret_overridden_nullable =
|
|
let overriden_ia, _ = overriden_signature.AnnotatedSignature.ret in
|
|
Annotations.ia_is_nullable overriden_ia
|
|
in
|
|
if ret_is_nullable && not ret_overridden_nullable then
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Inconsistent_subclass_return_annotation (proc_name, overriden_proc_name))
|
|
None loc proc_desc
|
|
and check_params overriden_proc_name overriden_signature =
|
|
let compare pos current_param overriden_param : int =
|
|
let current_name, current_ia, _ = current_param in
|
|
let _, overriden_ia, _ = overriden_param in
|
|
let () =
|
|
if (not (Annotations.ia_is_nullable current_ia)) && Annotations.ia_is_nullable overriden_ia
|
|
then
|
|
report_error tenv find_canonical_duplicate
|
|
(TypeErr.Inconsistent_subclass_parameter_annotation
|
|
(Mangled.to_string current_name, pos, proc_name, overriden_proc_name))
|
|
None loc proc_desc
|
|
in
|
|
pos + 1
|
|
in
|
|
(* TODO (#5280249): investigate why argument lists can be of different length *)
|
|
let current_params = annotated_signature.AnnotatedSignature.params
|
|
and overridden_params = overriden_signature.AnnotatedSignature.params in
|
|
let initial_pos = if is_virtual current_params then 0 else 1 in
|
|
if Int.equal (List.length current_params) (List.length overridden_params) then
|
|
ignore (List.fold2_exn ~f:compare ~init:initial_pos current_params overridden_params)
|
|
in
|
|
let check overriden_proc_name =
|
|
match PatternMatch.lookup_attributes tenv overriden_proc_name with
|
|
| Some attributes -> (
|
|
let overridden_signature = Models.get_modelled_annotated_signature attributes in
|
|
check_params overriden_proc_name overridden_signature ;
|
|
(* the analysis should not report return type inconsistencies with external code *)
|
|
match overriden_proc_name with
|
|
| Typ.Procname.Java java_pname when not (Typ.Procname.Java.is_external java_pname) ->
|
|
check_return overriden_proc_name overridden_signature
|
|
| _ ->
|
|
() )
|
|
| None ->
|
|
()
|
|
in
|
|
PatternMatch.override_iter check tenv proc_name
|