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.
200 lines
8.1 KiB
200 lines
8.1 KiB
(*
|
|
* Copyright (c) 2017 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
|
|
module L = Logging
|
|
module MF = MarkupFormatter
|
|
module CallSites = AbstractDomain.FiniteSet (CallSite)
|
|
module NullableAP = AbstractDomain.Map (AccessPath) (CallSites)
|
|
module NullCheckedPname = AbstractDomain.InvertedSet (Typ.Procname)
|
|
module Domain = AbstractDomain.Pair (NullableAP) (NullCheckedPname)
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
module CFG = CFG
|
|
module Domain = Domain
|
|
|
|
type extras = Specs.summary
|
|
|
|
let is_instance_method callee_pname =
|
|
if Typ.Procname.is_java callee_pname then not (Typ.Procname.java_is_static callee_pname)
|
|
else
|
|
Option.exists
|
|
~f:(fun attributes -> attributes.ProcAttributes.is_cpp_instance_method)
|
|
(Specs.proc_resolve_attributes callee_pname)
|
|
|
|
|
|
let report_nullable_dereference ap call_sites {ProcData.pdesc; extras} loc =
|
|
let pname = Procdesc.get_proc_name pdesc in
|
|
let annotation = Localise.nullable_annotation_name pname in
|
|
let issue_kind = IssueType.nullable_dereference.unique_id in
|
|
let call_site =
|
|
try CallSites.min_elt call_sites
|
|
with Not_found ->
|
|
L.(die InternalError)
|
|
"Expecting a least one element in the set of call sites when analyzing %a"
|
|
Typ.Procname.pp pname
|
|
in
|
|
let message =
|
|
match ap with
|
|
| (Var.LogicalVar _, _), _ ->
|
|
(* direct dereference without intermediate variable *)
|
|
Format.asprintf
|
|
"The return value of %s is annotated with %a and is dereferenced without being checked for null at %a"
|
|
(MF.monospaced_to_string
|
|
(Typ.Procname.to_simplified_string ~withclass:true (CallSite.pname call_site)))
|
|
MF.pp_monospaced annotation Location.pp loc
|
|
| _ ->
|
|
(* dereference with intermediate variable *)
|
|
Format.asprintf
|
|
"Variable %a is indirectly annotated with %a (source %a) and is dereferenced without being checked for null at %a"
|
|
(MF.wrap_monospaced AccessPath.pp)
|
|
ap MF.pp_monospaced annotation (MF.wrap_monospaced CallSite.pp) call_site Location.pp
|
|
loc
|
|
in
|
|
let exn = Exceptions.Checkers (issue_kind, Localise.verbatim_desc message) in
|
|
let summary = extras in
|
|
let trace =
|
|
let with_origin_site =
|
|
let callee_pname = CallSite.pname call_site in
|
|
match Specs.proc_resolve_attributes callee_pname with
|
|
| None ->
|
|
[]
|
|
| Some attributes ->
|
|
let description =
|
|
Format.asprintf "definition of %s" (Typ.Procname.get_method callee_pname)
|
|
in
|
|
let trace_element =
|
|
Errlog.make_trace_element 1 attributes.ProcAttributes.loc description []
|
|
in
|
|
[trace_element]
|
|
in
|
|
let with_assignment_site =
|
|
let call_site_loc = CallSite.loc call_site in
|
|
if Location.equal call_site_loc loc then with_origin_site
|
|
else
|
|
let trace_element =
|
|
Errlog.make_trace_element 0 call_site_loc "assignment of the nullable value" []
|
|
in
|
|
trace_element :: with_origin_site
|
|
in
|
|
let dereference_site =
|
|
let description = Format.asprintf "deference of %a" AccessPath.pp ap in
|
|
Errlog.make_trace_element 0 loc description []
|
|
in
|
|
dereference_site :: with_assignment_site
|
|
in
|
|
Reporting.log_error summary ~loc ~ltr:trace exn
|
|
|
|
|
|
let add_nullable_ap ap call_sites (aps, pnames) = (NullableAP.add ap call_sites aps, pnames)
|
|
|
|
let remove_nullable_ap ap (aps, pnames) = (NullableAP.remove ap aps, pnames)
|
|
|
|
let find_nullable_ap ap (aps, _) = NullableAP.find ap aps
|
|
|
|
let assume_pnames_notnull ap (aps, checked_pnames) : Domain.astate =
|
|
let updated_pnames =
|
|
try
|
|
let call_sites = NullableAP.find ap aps in
|
|
CallSites.fold
|
|
(fun call_site s -> NullCheckedPname.add (CallSite.pname call_site) s)
|
|
call_sites checked_pnames
|
|
with Not_found -> checked_pnames
|
|
in
|
|
(NullableAP.remove ap aps, updated_pnames)
|
|
|
|
|
|
let rec longest_nullable_prefix ap ((nulable_aps, _) as astate) =
|
|
try Some (ap, NullableAP.find ap nulable_aps)
|
|
with Not_found ->
|
|
match ap with _, [] -> None | p -> longest_nullable_prefix (AccessPath.truncate p) astate
|
|
|
|
|
|
let exec_instr ((_, checked_pnames) as astate) proc_data _ (instr: HilInstr.t) : Domain.astate =
|
|
let is_pointer_assignment tenv lhs rhs =
|
|
HilExp.is_null_literal rhs
|
|
(* the rhs has type int when assigning the lhs to null *)
|
|
|| Option.equal Typ.equal (AccessPath.get_typ lhs tenv) (HilExp.get_typ tenv rhs)
|
|
(* the lhs and rhs have the same type in the case of pointer assignment
|
|
but the types are different when assigning the pointee *)
|
|
in
|
|
match instr with
|
|
| Call (Some ret_var, Direct callee_pname, _, _, _)
|
|
when NullCheckedPname.mem callee_pname checked_pnames ->
|
|
(* Do not report nullable when the method has already been checked for null *)
|
|
remove_nullable_ap (ret_var, []) astate
|
|
| Call (_, Direct callee_pname, (HilExp.AccessPath receiver) :: _, _, _)
|
|
when Models.is_check_not_null callee_pname ->
|
|
assume_pnames_notnull receiver astate
|
|
| Call (Some ret_var, Direct callee_pname, _, _, loc)
|
|
when Annotations.pname_has_return_annot callee_pname
|
|
~attrs_of_pname:Specs.proc_resolve_attributes Annotations.ia_is_nullable ->
|
|
let call_site = CallSite.make callee_pname loc in
|
|
add_nullable_ap (ret_var, []) (CallSites.singleton call_site) astate
|
|
| Call (_, Direct callee_pname, (HilExp.AccessPath receiver) :: _, _, loc)
|
|
when is_instance_method callee_pname -> (
|
|
match longest_nullable_prefix receiver astate with
|
|
| None ->
|
|
astate
|
|
| Some (nullable_ap, call_sites) ->
|
|
report_nullable_dereference nullable_ap call_sites proc_data loc ;
|
|
assume_pnames_notnull receiver astate )
|
|
| Call (Some ret_var, _, _, _, _) ->
|
|
remove_nullable_ap (ret_var, []) astate
|
|
| Assign (lhs, rhs, loc)
|
|
-> (
|
|
Option.iter
|
|
~f:(fun (nullable_ap, call_sites) ->
|
|
if not (is_pointer_assignment proc_data.ProcData.tenv nullable_ap rhs) then
|
|
(* TODO (T22426288): Undertand why the pointer derference and the pointer
|
|
assignment have the same HIL representation *)
|
|
report_nullable_dereference nullable_ap call_sites proc_data loc)
|
|
(longest_nullable_prefix lhs astate) ;
|
|
match rhs with
|
|
| HilExp.AccessPath ap -> (
|
|
try
|
|
(* Add the lhs to the list of nullable values if the rhs is nullable *)
|
|
add_nullable_ap lhs (find_nullable_ap ap astate) astate
|
|
with Not_found ->
|
|
(* Remove the lhs from the list of nullable values if the rhs is not nullable *)
|
|
remove_nullable_ap lhs astate )
|
|
| _ ->
|
|
(* Remove the lhs from the list of nullable values if the rhs is not an access path *)
|
|
remove_nullable_ap lhs astate )
|
|
| Assume (HilExp.AccessPath ap, _, _, _) ->
|
|
assume_pnames_notnull ap astate
|
|
| Assume
|
|
( ( HilExp.BinaryOperator (Binop.Ne, HilExp.AccessPath ap, exp)
|
|
| HilExp.BinaryOperator (Binop.Ne, exp, HilExp.AccessPath ap) )
|
|
, _
|
|
, _
|
|
, _ )
|
|
| Assume
|
|
( HilExp.UnaryOperator
|
|
( Unop.LNot
|
|
, ( HilExp.BinaryOperator (Binop.Eq, HilExp.AccessPath ap, exp)
|
|
| HilExp.BinaryOperator (Binop.Eq, exp, HilExp.AccessPath ap) )
|
|
, _ )
|
|
, _
|
|
, _
|
|
, _ ) ->
|
|
if HilExp.is_null_literal exp then assume_pnames_notnull ap astate else astate
|
|
| _ ->
|
|
astate
|
|
|
|
end
|
|
|
|
module Analyzer = LowerHil.MakeAbstractInterpreter (ProcCfg.Exceptional) (TransferFunctions)
|
|
|
|
let checker {Callbacks.summary; proc_desc; tenv} =
|
|
let initial = (NullableAP.empty, NullCheckedPname.empty) in
|
|
let proc_data = ProcData.make proc_desc tenv summary in
|
|
ignore (Analyzer.compute_post proc_data ~initial) ;
|
|
summary
|
|
|