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.

258 lines
12 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 L = Logging
module F = Format
(* make sure that this is initialized *)
let () = NullsafeInit.init ()
let callback1 ({IntraproceduralAnalysis.proc_desc= curr_pdesc; _} as analysis_data) ~curr_java_pname
find_canonical_duplicate calls_this checks idenv annotated_signature linereader proc_loc :
bool * TypeState.t option =
let curr_pname = Procdesc.get_proc_name curr_pdesc in
let add_formal typestate (param_signature : AnnotatedSignature.param_signature) =
let pvar = Pvar.mk param_signature.mangled curr_pname in
let formal_name = param_signature.mangled in
let origin =
if Mangled.is_this formal_name then
(* `this` is technically an implicit method param, but from syntactic and semantic points of view it
has very special meaning and nullability limitations (it can never be null).
*)
TypeOrigin.This
(* An extra feature of Nullsafe. If the method overrides `Object.equals()`, but does not specify @Nullable annotation for the param,
a normal "inconsistent subclass parameter annotation" will be issued.
But apart from this we want to issue additional violations every time the value is used as a non-nullable.
This is to distinct "benign" cases where the value is simply not annotated from real bugs where e.g. the value is actually dereferenced.
We achieve this via having a special TypeOrigin for this param, which will be considered as nullable in relevant places.
*)
else if
PatternMatch.Java.is_override_of_lang_object_equals curr_pname
&& (* Turn this feature off for annotation graph mode. In this mode not annotated, but potentially nullable params
are treated in special way, and this conflicts with the trick that is being made here.
*)
not Config.nullsafe_annotation_graph
then TypeOrigin.CurrMethodParameter ObjectEqualsOverride
else TypeOrigin.CurrMethodParameter (Normal param_signature)
in
let inferred_nullability = InferredNullability.create origin in
TypeState.add pvar
(param_signature.param_annotated_type.typ, inferred_nullability)
typestate ~descr:"registering formal param"
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 ~java_pname find_canonical_duplicate exit_node final_typestate
annotated_signature loc : unit =
let {AnnotatedSignature.ret_annotated_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
(* TODO(T54088319): model this in AnnotatedNullability *)
let ret_implicitly_nullable =
String.equal (PatternMatch.get_type_name ret_annotated_type.typ) "java.lang.Void"
in
AnalysisState.set_node exit_node ;
if not (List.is_empty checks.TypeCheck.check_ret_type) then
List.iter
~f:(fun f -> f curr_pdesc ret_annotated_type.typ typ_found_opt loc)
checks.TypeCheck.check_ret_type ;
if checks.TypeCheck.eradicate then
EradicateChecks.check_return_annotation analysis_data ~java_pname find_canonical_duplicate
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 ~java_pname find_canonical_duplicate final_typestate =
let exit_node = Procdesc.get_exit_node curr_pdesc in
check_return ~java_pname find_canonical_duplicate exit_node final_typestate annotated_signature
proc_loc
in
let module DFTypeCheck = DataFlow.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 node typestate =
AnalysisCallbacks.html_debug_new_node_session ~pp_name node ~f:(fun () ->
AnalysisState.set_node node ;
if Config.write_html then L.d_printfln "before:@\n%a@\n" TypeState.pp typestate ;
let {TypeCheck.normal_flow_typestate; exception_flow_typestates} =
TypeCheck.typecheck_node analysis_data calls_this checks idenv find_canonical_duplicate
annotated_signature typestate node linereader
in
let normal_flow_typestates =
Option.value_map normal_flow_typestate ~f:(fun a -> [a]) ~default:[]
in
(normal_flow_typestates, exception_flow_typestates) )
let proc_throws _ = DataFlow.DontKnow
end) in
let initial_typestate = get_initial_typestate () in
do_before_dataflow initial_typestate ;
let transitions = DFTypeCheck.run curr_pdesc initial_typestate in
match transitions (Procdesc.get_exit_node curr_pdesc) with
| DFTypeCheck.Transition (final_typestate, _, _) ->
do_after_dataflow ~java_pname:curr_java_pname find_canonical_duplicate final_typestate ;
(!calls_this, Some final_typestate)
| DFTypeCheck.Dead_state ->
(!calls_this, None)
let analyze_one_procedure ~java_pname
({IntraproceduralAnalysis.tenv; proc_desc; _} as analysis_data) calls_this checks
annotated_signature linereader proc_loc : unit =
let idenv = IDEnv.create proc_desc in
let find_duplicate_nodes = State.mk_find_duplicate_nodes proc_desc 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 proc_name = Procdesc.get_proc_name proc_desc in
let caller_nullsafe_mode = NullsafeMode.of_procname tenv proc_name 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 is_callee_in_trust_list =
let callee_class = Procname.get_class_type_name pname in
Option.value_map callee_class
~f:(fun class_name ->
Typ.Name.Java.get_java_class_name_exn class_name
|> NullsafeMode.is_in_trust_list caller_nullsafe_mode )
~default:false
in
let ann_sig =
Models.get_modelled_annotated_signature ~is_callee_in_trust_list tenv
(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
(* NOTE: [analysis_data] has the new [pdesc] *)
callback1
{analysis_data with proc_desc= pdesc}
find_canonical_duplicate calls_this' checks' idenv_pn ann_sig linereader loc
in
let do_final_typestate typestate_opt calls_this =
let do_typestate typestate =
let start_node = Procdesc.get_start_node proc_desc in
if
(not calls_this)
(* if 'this(...)' is called, no need to check initialization *)
&& checks.TypeCheck.eradicate
then (
let typestates_for_curr_constructor_and_all_initializer_methods =
Initializers.final_initializer_typestates_lazy tenv proc_name proc_desc
(typecheck_proc ~curr_java_pname:java_pname)
in
let typestates_for_all_constructors_incl_current =
Initializers.final_constructor_typestates_lazy tenv proc_name
(typecheck_proc ~curr_java_pname:java_pname)
in
EradicateChecks.check_constructor_initialization analysis_data find_canonical_duplicate
start_node ~nullsafe_mode:annotated_signature.AnnotatedSignature.nullsafe_mode
~typestates_for_curr_constructor_and_all_initializer_methods
~typestates_for_all_constructors_incl_current 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
let calls_this, final_typestate_opt =
typecheck_proc ~curr_java_pname:java_pname true proc_name proc_desc
(Some (annotated_signature, proc_loc, idenv))
in
do_final_typestate final_typestate_opt calls_this ;
if checks.TypeCheck.eradicate then
EradicateChecks.check_overridden_annotations analysis_data find_canonical_duplicate
~proc_name:java_pname annotated_signature ;
()
(* If we need to skip analysis, returns a string reason for debug, otherwise None *)
let find_reason_to_skip_analysis proc_name proc_desc =
match proc_name with
| Procname.Java java_pname ->
if Procname.Java.is_access_method java_pname then Some "access method"
else if
ThirdPartyAnnotationInfo.is_third_party_proc
(ThirdPartyAnnotationGlobalRepo.get_repo ())
java_pname
then Some "third party method"
else if (Procdesc.get_attributes proc_desc).ProcAttributes.is_bridge_method then
Some "bridge method"
else if Procname.Java.is_autogen_method java_pname then Some "autogenerated method"
else None
| _ ->
Some "not a Java method"
(** Entry point for the nullsafe procedure-level analysis. *)
let analyze checks ({IntraproceduralAnalysis.proc_desc; tenv; _} as analysis_data) :
NullsafeSummary.t option =
let proc_name = Procdesc.get_proc_name proc_desc in
L.debug Analysis Medium "Analysis of %a@\n" Procname.pp proc_name ;
match find_reason_to_skip_analysis proc_name proc_desc with
| Some reason ->
L.debug Analysis Medium "Skipping analysis: %s@\n" reason ;
None
| None ->
(* start the analysis! *)
let calls_this = ref false in
let java_pname =
Procname.as_java_exn ~explanation:"Would have skipped the analysis otherwise" proc_name
in
let annotated_signature =
AnnotatedSignature.get_for_class_under_analysis tenv (Procdesc.get_attributes proc_desc)
in
L.debug Analysis Medium "Signature: %a@\n" (AnnotatedSignature.pp proc_name)
annotated_signature ;
let loc = Procdesc.get_loc proc_desc in
let linereader = LineReader.create () in
(* Initializing TypeErr signleton. *)
TypeErr.reset () ;
(* The main method - during this the actual analysis will happen and TypeErr will be populated with
issues (and some of them - reported).
*)
analyze_one_procedure ~java_pname analysis_data calls_this checks annotated_signature
linereader loc ;
(* Collect issues that were detected during analysis and put them in summary for further processing *)
let issues = TypeErr.get_errors () |> List.map ~f:(fun (issues, _) -> issues) in
(* Report errors of "farall" class - those could not be reported during analysis phase. *)
TypeErr.report_forall_issues_and_reset analysis_data
~nullsafe_mode:annotated_signature.nullsafe_mode ;
Some {NullsafeSummary.issues}
let analyze_procedure analysis_data =
let checks = {TypeCheck.eradicate= true; check_ret_type= []} in
analyze checks analysis_data
let analyze_for_immutable_cast_checker check_return_type analysis_data =
let checks = {TypeCheck.eradicate= false; check_ret_type= [check_return_type]} in
analyze checks analysis_data