|
|
|
(*
|
|
|
|
* Copyright (c) 2013-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
|
|
|
|
|
|
|
|
(** Eradicate NPEs. *)
|
|
|
|
|
|
|
|
module L = Logging
|
|
|
|
module F = Format
|
|
|
|
open Dataflow
|
|
|
|
|
|
|
|
(* ERADICATE CHECKER. TODOS:*)
|
|
|
|
(* 1) add support for constructors for anonymous inner classes (currently not checked) *)
|
|
|
|
|
|
|
|
(** Type for a module that provides a main callback function *)
|
|
|
|
module type CallBackT = sig
|
|
|
|
val callback : TypeCheck.checks -> Callbacks.proc_callback_t
|
|
|
|
end
|
|
|
|
|
|
|
|
(* CallBackT *)
|
|
|
|
|
|
|
|
(** Extension to the type checker. *)
|
|
|
|
module type ExtensionT = sig
|
|
|
|
val update_payloads : TypeState.t option -> Payloads.t -> Payloads.t
|
|
|
|
end
|
|
|
|
|
|
|
|
(** Create a module with the toplevel callback. *)
|
|
|
|
module MkCallback (Extension : ExtensionT) : CallBackT = struct
|
|
|
|
let callback1 tenv find_canonical_duplicate calls_this checks idenv curr_pname curr_pdesc
|
|
|
|
annotated_signature linereader proc_loc : bool * TypeState.t option =
|
|
|
|
let mk s = Pvar.mk s curr_pname in
|
|
|
|
let add_formal typestate (s, ia, typ) =
|
|
|
|
let pvar = mk s in
|
|
|
|
let ta =
|
|
|
|
let origin = TypeOrigin.Formal s in
|
|
|
|
TypeAnnotation.from_item_annotation ia origin
|
|
|
|
in
|
|
|
|
TypeState.add pvar (typ, ta, []) typestate
|
|
|
|
in
|
|
|
|
let get_initial_typestate () =
|
|
|
|
let typestate_empty = TypeState.empty in
|
|
|
|
List.fold ~f:add_formal ~init:typestate_empty annotated_signature.AnnotatedSignature.params
|
|
|
|
in
|
|
|
|
(* Check the nullable flag computed for the return value and report inconsistencies. *)
|
|
|
|
let check_return find_canonical_duplicate exit_node final_typestate annotated_signature loc :
|
|
|
|
unit =
|
|
|
|
let _, ret_type = annotated_signature.AnnotatedSignature.ret in
|
|
|
|
let ret_pvar = Procdesc.get_ret_var curr_pdesc in
|
|
|
|
let ret_range = TypeState.lookup_pvar ret_pvar final_typestate in
|
|
|
|
let typ_found_opt =
|
|
|
|
match ret_range with Some (typ_found, _, _) -> Some typ_found | None -> None
|
|
|
|
in
|
|
|
|
let ret_implicitly_nullable =
|
|
|
|
String.equal (PatternMatch.get_type_name ret_type) "java.lang.Void"
|
|
|
|
in
|
|
|
|
State.set_node exit_node ;
|
|
|
|
if checks.TypeCheck.check_ret_type <> [] then
|
|
|
|
List.iter
|
|
|
|
~f:(fun f -> f curr_pname curr_pdesc ret_type typ_found_opt loc)
|
|
|
|
checks.TypeCheck.check_ret_type ;
|
|
|
|
if checks.TypeCheck.eradicate then
|
|
|
|
EradicateChecks.check_return_annotation tenv find_canonical_duplicate curr_pdesc ret_range
|
|
|
|
annotated_signature ret_implicitly_nullable loc
|
|
|
|
in
|
|
|
|
let do_before_dataflow initial_typestate =
|
|
|
|
if Config.eradicate_verbose then
|
|
|
|
L.result "Initial Typestate@\n%a@." TypeState.pp initial_typestate
|
|
|
|
in
|
|
|
|
let do_after_dataflow find_canonical_duplicate final_typestate =
|
|
|
|
let exit_node = Procdesc.get_exit_node curr_pdesc in
|
|
|
|
check_return find_canonical_duplicate exit_node final_typestate annotated_signature proc_loc
|
|
|
|
in
|
|
|
|
let module DFTypeCheck = MakeDF (struct
|
|
|
|
type t = TypeState.t
|
|
|
|
|
|
|
|
let equal = TypeState.equal
|
|
|
|
|
|
|
|
let join = TypeState.join
|
|
|
|
|
|
|
|
let pp_name fmt = F.pp_print_string fmt "eradicate"
|
|
|
|
|
|
|
|
let do_node tenv node typestate =
|
|
|
|
NodePrinter.with_session ~pp_name node ~f:(fun () ->
|
|
|
|
State.set_node node ;
|
|
|
|
let typestates_succ, typestates_exn =
|
|
|
|
TypeCheck.typecheck_node tenv calls_this checks idenv curr_pname curr_pdesc
|
|
|
|
find_canonical_duplicate annotated_signature typestate node linereader
|
|
|
|
in
|
|
|
|
if Config.write_html then (
|
|
|
|
let d_typestate ts = L.d_printfln "%a" TypeState.pp ts in
|
|
|
|
L.d_strln "before:" ;
|
|
|
|
d_typestate typestate ;
|
|
|
|
L.d_strln "after:" ;
|
|
|
|
List.iter ~f:d_typestate typestates_succ ) ;
|
|
|
|
(typestates_succ, typestates_exn) )
|
|
|
|
|
|
|
|
|
|
|
|
let proc_throws _ = DontKnow
|
|
|
|
end) in
|
|
|
|
let initial_typestate = get_initial_typestate () in
|
|
|
|
do_before_dataflow initial_typestate ;
|
|
|
|
let transitions = DFTypeCheck.run tenv curr_pdesc initial_typestate in
|
|
|
|
match transitions (Procdesc.get_exit_node curr_pdesc) with
|
|
|
|
| DFTypeCheck.Transition (final_typestate, _, _) ->
|
|
|
|
do_after_dataflow find_canonical_duplicate final_typestate ;
|
|
|
|
(!calls_this, Some final_typestate)
|
|
|
|
| DFTypeCheck.Dead_state ->
|
|
|
|
(!calls_this, None)
|
|
|
|
|
|
|
|
|
|
|
|
let callback2 calls_this checks
|
|
|
|
{Callbacks.proc_desc= curr_pdesc; summary; tenv; get_procs_in_file} annotated_signature
|
|
|
|
linereader proc_loc : unit =
|
|
|
|
let idenv = Idenv.create curr_pdesc in
|
|
|
|
let curr_pname = Summary.get_proc_name summary in
|
|
|
|
let find_duplicate_nodes = State.mk_find_duplicate_nodes curr_pdesc in
|
|
|
|
let find_canonical_duplicate node =
|
|
|
|
let duplicate_nodes = find_duplicate_nodes node in
|
|
|
|
try Procdesc.NodeSet.min_elt duplicate_nodes with Caml.Not_found -> node
|
|
|
|
in
|
|
|
|
let typecheck_proc do_checks pname pdesc proc_details_opt =
|
|
|
|
let ann_sig, loc, idenv_pn =
|
|
|
|
match proc_details_opt with
|
|
|
|
| Some (ann_sig, loc, idenv_pn) ->
|
|
|
|
(ann_sig, loc, idenv_pn)
|
|
|
|
| None ->
|
|
|
|
let ann_sig =
|
|
|
|
Models.get_modelled_annotated_signature (Procdesc.get_attributes pdesc)
|
|
|
|
in
|
|
|
|
let loc = Procdesc.get_loc pdesc in
|
|
|
|
(ann_sig, loc, Idenv.create pdesc)
|
|
|
|
in
|
|
|
|
let checks', calls_this' =
|
|
|
|
if do_checks then (checks, calls_this)
|
|
|
|
else ({TypeCheck.eradicate= false; check_ret_type= []}, ref false)
|
|
|
|
in
|
|
|
|
callback1 tenv find_canonical_duplicate calls_this' checks' idenv_pn pname pdesc ann_sig
|
|
|
|
linereader loc
|
|
|
|
in
|
|
|
|
let module Initializers = struct
|
|
|
|
type init = Typ.Procname.t * Procdesc.t
|
|
|
|
|
|
|
|
let equal_class_opt = [%compare.equal: string option]
|
|
|
|
|
|
|
|
let final_typestates initializers_current_class =
|
|
|
|
(* Get the private methods, from the same class, directly called by the initializers. *)
|
|
|
|
let get_private_called (initializers : init list) : init list =
|
|
|
|
let res = ref [] in
|
|
|
|
let do_proc (init_pn, init_pd) =
|
|
|
|
let filter callee_pn callee_attributes =
|
|
|
|
let is_private =
|
|
|
|
PredSymb.equal_access callee_attributes.ProcAttributes.access PredSymb.Private
|
|
|
|
in
|
|
|
|
let same_class =
|
|
|
|
let get_class_opt pn =
|
|
|
|
match pn with
|
|
|
|
| Typ.Procname.Java pn_java ->
|
|
|
|
Some (Typ.Procname.Java.get_class_name pn_java)
|
|
|
|
| _ ->
|
|
|
|
None
|
|
|
|
in
|
|
|
|
equal_class_opt (get_class_opt init_pn) (get_class_opt callee_pn)
|
|
|
|
in
|
|
|
|
is_private && same_class
|
|
|
|
in
|
|
|
|
let private_called =
|
|
|
|
PatternMatch.proc_calls (PatternMatch.lookup_attributes tenv) init_pd filter
|
|
|
|
in
|
|
|
|
let do_called (callee_pn, _) =
|
|
|
|
match Ondemand.get_proc_desc callee_pn with
|
|
|
|
| Some callee_pd ->
|
|
|
|
res := (callee_pn, callee_pd) :: !res
|
|
|
|
| None ->
|
|
|
|
()
|
|
|
|
in
|
|
|
|
List.iter ~f:do_called private_called
|
|
|
|
in
|
|
|
|
List.iter ~f:do_proc initializers ; !res
|
|
|
|
in
|
|
|
|
(* Get the initializers recursively called by computing a fixpoint.
|
|
|
|
Start from the initializers of the current class and the current procedure. *)
|
|
|
|
let initializers_recursive : init list =
|
|
|
|
let initializers_base_case = initializers_current_class in
|
|
|
|
let res = ref [] in
|
|
|
|
let seen = ref Typ.Procname.Set.empty in
|
|
|
|
let mark_seen (initializers : init list) : unit =
|
|
|
|
List.iter ~f:(fun (pn, _) -> seen := Typ.Procname.Set.add pn !seen) initializers ;
|
|
|
|
res := !res @ initializers
|
|
|
|
in
|
|
|
|
let rec fixpoint initializers_old =
|
|
|
|
let initializers_new = get_private_called initializers_old in
|
|
|
|
let initializers_new' =
|
|
|
|
List.filter ~f:(fun (pn, _) -> not (Typ.Procname.Set.mem pn !seen)) initializers_new
|
|
|
|
in
|
|
|
|
mark_seen initializers_new' ;
|
|
|
|
if initializers_new' <> [] then fixpoint initializers_new'
|
|
|
|
in
|
|
|
|
mark_seen initializers_base_case ; fixpoint initializers_base_case ; !res
|
|
|
|
in
|
|
|
|
(* Get the final typestates of all the initializers. *)
|
|
|
|
let final_typestates = ref [] in
|
|
|
|
let get_final_typestate (pname, pdesc) =
|
|
|
|
match typecheck_proc false pname pdesc None with
|
|
|
|
| _, Some final_typestate ->
|
|
|
|
final_typestates := (pname, final_typestate) :: !final_typestates
|
|
|
|
| _, None ->
|
|
|
|
()
|
|
|
|
in
|
|
|
|
List.iter ~f:get_final_typestate initializers_recursive ;
|
|
|
|
List.rev !final_typestates
|
|
|
|
|
|
|
|
|
|
|
|
let pname_and_pdescs_with f =
|
|
|
|
let res = ref [] in
|
|
|
|
let filter pname =
|
|
|
|
match PatternMatch.lookup_attributes tenv pname with
|
|
|
|
| Some proc_attributes ->
|
|
|
|
f (pname, proc_attributes)
|
|
|
|
| None ->
|
|
|
|
false
|
|
|
|
in
|
|
|
|
let do_proc pname =
|
|
|
|
if filter pname then
|
|
|
|
match Ondemand.get_proc_desc pname with
|
|
|
|
| Some pdesc ->
|
|
|
|
res := (pname, pdesc) :: !res
|
|
|
|
| None ->
|
|
|
|
()
|
|
|
|
in
|
|
|
|
List.iter ~f:do_proc (get_procs_in_file curr_pname) ;
|
|
|
|
List.rev !res
|
|
|
|
|
|
|
|
|
|
|
|
let get_class pn =
|
|
|
|
match pn with
|
|
|
|
| Typ.Procname.Java pn_java ->
|
|
|
|
Some (Typ.Procname.Java.get_class_name pn_java)
|
|
|
|
| _ ->
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
|
|
(** Typestates after the current procedure and all initializer procedures. *)
|
|
|
|
let final_initializer_typestates_lazy =
|
|
|
|
lazy
|
|
|
|
(let is_initializer proc_attributes =
|
|
|
|
PatternMatch.method_is_initializer tenv proc_attributes
|
|
|
|
||
|
|
|
|
let ia, _ =
|
|
|
|
(Models.get_modelled_annotated_signature proc_attributes).AnnotatedSignature.ret
|
|
|
|
in
|
|
|
|
Annotations.ia_is_initializer ia
|
|
|
|
in
|
|
|
|
let initializers_current_class =
|
|
|
|
pname_and_pdescs_with (function pname, proc_attributes ->
|
|
|
|
is_initializer proc_attributes
|
|
|
|
&& equal_class_opt (get_class pname) (get_class curr_pname) )
|
|
|
|
in
|
|
|
|
final_typestates ((curr_pname, curr_pdesc) :: initializers_current_class))
|
|
|
|
|
|
|
|
|
|
|
|
(** Typestates after all constructors. *)
|
|
|
|
let final_constructor_typestates_lazy =
|
|
|
|
lazy
|
|
|
|
(let constructors_current_class =
|
|
|
|
pname_and_pdescs_with (fun (pname, _) ->
|
|
|
|
Typ.Procname.is_constructor pname
|
|
|
|
&& equal_class_opt (get_class pname) (get_class curr_pname) )
|
|
|
|
in
|
|
|
|
final_typestates constructors_current_class)
|
|
|
|
end
|
|
|
|
(* Initializers *) in
|
|
|
|
let do_final_typestate typestate_opt calls_this =
|
|
|
|
let do_typestate typestate =
|
|
|
|
let start_node = Procdesc.get_start_node curr_pdesc in
|
|
|
|
if
|
|
|
|
(not calls_this)
|
|
|
|
(* if 'this(...)' is called, no need to check initialization *)
|
|
|
|
&& checks.TypeCheck.eradicate
|
|
|
|
then
|
|
|
|
EradicateChecks.check_constructor_initialization tenv find_canonical_duplicate curr_pname
|
|
|
|
curr_pdesc start_node Initializers.final_initializer_typestates_lazy
|
|
|
|
Initializers.final_constructor_typestates_lazy proc_loc ;
|
|
|
|
if Config.eradicate_verbose then L.result "Final Typestate@\n%a@." TypeState.pp typestate
|
|
|
|
in
|
|
|
|
match typestate_opt with None -> () | Some typestate -> do_typestate typestate
|
|
|
|
in
|
|
|
|
TypeErr.reset () ;
|
|
|
|
let calls_this, final_typestate_opt =
|
|
|
|
typecheck_proc true curr_pname curr_pdesc (Some (annotated_signature, proc_loc, idenv))
|
|
|
|
in
|
|
|
|
do_final_typestate final_typestate_opt calls_this ;
|
|
|
|
if checks.TypeCheck.eradicate then
|
|
|
|
EradicateChecks.check_overridden_annotations find_canonical_duplicate tenv curr_pname
|
|
|
|
curr_pdesc annotated_signature ;
|
|
|
|
TypeErr.report_forall_checks_and_reset tenv (EradicateCheckers.report_error tenv) curr_pdesc ;
|
|
|
|
()
|
|
|
|
|
|
|
|
|
|
|
|
(** Entry point for the eradicate-based checker infrastructure. *)
|
|
|
|
let callback checks ({Callbacks.proc_desc; summary} as callback_args) : Summary.t =
|
|
|
|
let proc_name = Procdesc.get_proc_name proc_desc in
|
|
|
|
let calls_this = ref false in
|
|
|
|
let filter_special_cases () =
|
|
|
|
if
|
|
|
|
( match proc_name with
|
|
|
|
| Typ.Procname.Java java_pname ->
|
|
|
|
Typ.Procname.Java.is_access_method java_pname
|
|
|
|
|| Typ.Procname.Java.is_external java_pname
|
|
|
|
| _ ->
|
|
|
|
false )
|
|
|
|
|| (Procdesc.get_attributes proc_desc).ProcAttributes.is_bridge_method
|
|
|
|
then None
|
|
|
|
else
|
|
|
|
let annotated_signature =
|
|
|
|
Models.get_modelled_annotated_signature (Procdesc.get_attributes proc_desc)
|
|
|
|
in
|
|
|
|
Some annotated_signature
|
|
|
|
in
|
|
|
|
( match filter_special_cases () with
|
|
|
|
| None ->
|
|
|
|
()
|
|
|
|
| Some annotated_signature ->
|
|
|
|
let loc = Procdesc.get_loc proc_desc in
|
|
|
|
let linereader = Printer.LineReader.create () in
|
|
|
|
if Config.eradicate_verbose then
|
|
|
|
L.result "%a@." (AnnotatedSignature.pp proc_name) annotated_signature ;
|
|
|
|
callback2 calls_this checks callback_args annotated_signature linereader loc ) ;
|
|
|
|
summary
|
|
|
|
end
|
|
|
|
|
|
|
|
(* MkCallback *)
|
|
|
|
|
|
|
|
module EmptyExtension : ExtensionT = struct
|
|
|
|
let update_payloads typestate_opt (payloads : Payloads.t) =
|
|
|
|
{payloads with typestate= typestate_opt}
|
|
|
|
end
|
|
|
|
|
|
|
|
module Main = struct
|
|
|
|
module Callback = MkCallback (EmptyExtension)
|
|
|
|
|
|
|
|
let callback = Callback.callback
|
|
|
|
end
|
|
|
|
|
|
|
|
(** Eradicate checker for Java @Nullable annotations. *)
|
|
|
|
let callback_eradicate =
|
|
|
|
let checks = {TypeCheck.eradicate= true; check_ret_type= []} in
|
|
|
|
Main.callback checks
|
|
|
|
|
|
|
|
|
|
|
|
(** Call the given check_return_type at the end of every procedure. *)
|
|
|
|
let callback_check_return_type check_return_type callback_args =
|
|
|
|
let checks = {TypeCheck.eradicate= false; check_ret_type= [check_return_type]} in
|
|
|
|
Main.callback checks callback_args
|