|
|
|
(*
|
|
|
|
* 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
|