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.

363 lines
14 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
(** 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 add_formal typestate (param_signature : AnnotatedSignature.param_signature) =
let pvar = Pvar.mk param_signature.mangled curr_pname in
let inferred_nullability =
let origin = TypeOrigin.Formal param_signature.mangled in
InferredNullability.from_nullsafe_type param_signature.param_nullsafe_type origin
in
TypeState.add pvar
(param_signature.param_nullsafe_type.typ, inferred_nullability, [])
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 AnnotatedSignature.{ret_nullsafe_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
[nullsafe] Introduce NullsafeType abstraction Summary: This is a central abstraction for coming future unknown nullability support. # Context Annot.ml is a low-level module: - it contains lists of raw (string) annotations - no algebraic datatypes for annotations - it mixes annotations that Nullsafe should be aware of with all sorts of other annotations - some annotations make sense for return values, some make sense for params, and some make sense for methods. But, most importantly, it does not contain information about source of an annotation, making it hard to distinct things like "Nonnull as default" vs "Nonnull as explicitly annotated" vs "Nonnull as modelled". Ditto for nullable. Because of this, it is tricky to introduce unknown nullability in an elegant way. Let's get rid of using Annot.Item.t in nullsafe code in the following way: - Move nullability information associated with the Java type to a dedicated algebraic DT. - Split other annotations that are important for nullsafe into param flags, ret value flags, and method flags, and introduce corresponding datatypes. # This diff This diff introduces NullsafeType and adds this to AnnotatedSignature. It is not used yet, hence the diff is a no-op. In future diffs, we are going to (see also TODOs in the code): - actually use this information instead of accessing Annot.item - add more information to AnnotatedSignature - remove Annot.item from AnnnotatedSignature - when this is done, introduce notion of unknown nullability. Reviewed By: ngorogiannis Differential Revision: D17420595 fbshipit-source-id: b30706d9b
6 years ago
(* TODO(T54088319): model this in NullsafeType.nullability *)
let ret_implicitly_nullable =
[nullsafe] Introduce NullsafeType abstraction Summary: This is a central abstraction for coming future unknown nullability support. # Context Annot.ml is a low-level module: - it contains lists of raw (string) annotations - no algebraic datatypes for annotations - it mixes annotations that Nullsafe should be aware of with all sorts of other annotations - some annotations make sense for return values, some make sense for params, and some make sense for methods. But, most importantly, it does not contain information about source of an annotation, making it hard to distinct things like "Nonnull as default" vs "Nonnull as explicitly annotated" vs "Nonnull as modelled". Ditto for nullable. Because of this, it is tricky to introduce unknown nullability in an elegant way. Let's get rid of using Annot.Item.t in nullsafe code in the following way: - Move nullability information associated with the Java type to a dedicated algebraic DT. - Split other annotations that are important for nullsafe into param flags, ret value flags, and method flags, and introduce corresponding datatypes. # This diff This diff introduces NullsafeType and adds this to AnnotatedSignature. It is not used yet, hence the diff is a no-op. In future diffs, we are going to (see also TODOs in the code): - actually use this information instead of accessing Annot.item - add more information to AnnotatedSignature - remove Annot.item from AnnnotatedSignature - when this is done, introduce notion of unknown nullability. Reviewed By: ngorogiannis Differential Revision: D17420595 fbshipit-source-id: b30706d9b
6 years ago
String.equal (PatternMatch.get_type_name ret_nullsafe_type.typ) "java.lang.Void"
in
State.set_node exit_node ;
if checks.TypeCheck.check_ret_type <> [] then
List.iter
[nullsafe] Introduce NullsafeType abstraction Summary: This is a central abstraction for coming future unknown nullability support. # Context Annot.ml is a low-level module: - it contains lists of raw (string) annotations - no algebraic datatypes for annotations - it mixes annotations that Nullsafe should be aware of with all sorts of other annotations - some annotations make sense for return values, some make sense for params, and some make sense for methods. But, most importantly, it does not contain information about source of an annotation, making it hard to distinct things like "Nonnull as default" vs "Nonnull as explicitly annotated" vs "Nonnull as modelled". Ditto for nullable. Because of this, it is tricky to introduce unknown nullability in an elegant way. Let's get rid of using Annot.Item.t in nullsafe code in the following way: - Move nullability information associated with the Java type to a dedicated algebraic DT. - Split other annotations that are important for nullsafe into param flags, ret value flags, and method flags, and introduce corresponding datatypes. # This diff This diff introduces NullsafeType and adds this to AnnotatedSignature. It is not used yet, hence the diff is a no-op. In future diffs, we are going to (see also TODOs in the code): - actually use this information instead of accessing Annot.item - add more information to AnnotatedSignature - remove Annot.item from AnnnotatedSignature - when this is done, introduce notion of unknown nullability. Reviewed By: ngorogiannis Differential Revision: D17420595 fbshipit-source-id: b30706d9b
6 years ago
~f:(fun f -> f curr_pname curr_pdesc ret_nullsafe_type.typ 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.summary; exe_env; get_procs_in_file}
annotated_signature linereader proc_loc : unit =
let curr_pdesc = Summary.get_proc_desc summary in
let idenv = Idenv.create curr_pdesc in
let curr_pname = Summary.get_proc_name summary in
let tenv = Exe_env.get_tenv exe_env curr_pname 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
.ret_annotation_deprecated
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.summary} as callback_args) : Summary.t =
let proc_desc = Summary.get_proc_desc summary in
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