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.
358 lines
14 KiB
358 lines
14 KiB
(*
|
|
* Copyright (c) 2017-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
|
|
module F = Format
|
|
module L = Logging
|
|
|
|
(** Forward analysis to compute uninitialized variables at each program point *)
|
|
module D = UninitDomain.Domain
|
|
|
|
module MaybeUninitVars = UninitDomain.MaybeUninitVars
|
|
module AliasedVars = AbstractDomain.FiniteSet (UninitDomain.VarPair)
|
|
module RecordDomain = UninitDomain.Record (MaybeUninitVars) (AliasedVars) (D)
|
|
|
|
module Payload = SummaryPayload.Make (struct
|
|
type t = UninitDomain.Summary.t
|
|
|
|
let update_payloads sum (payloads : Payloads.t) = {payloads with uninit= Some sum}
|
|
|
|
let of_payloads (payloads : Payloads.t) = payloads.uninit
|
|
end)
|
|
|
|
module Models = struct
|
|
let initializing_all_args = [BuiltinDecl.__set_array_length]
|
|
|
|
let is_initializing_all_args pname =
|
|
List.exists initializing_all_args ~f:(fun fname -> Typ.Procname.equal pname fname)
|
|
end
|
|
|
|
let should_report_on_type t =
|
|
match t.Typ.desc with
|
|
| Tptr (_, Pk_reference) ->
|
|
false
|
|
| Tint _ | Tfloat _ | Tvoid | Tptr _ ->
|
|
true
|
|
| _ ->
|
|
false
|
|
|
|
|
|
type extras = {formals: FormalMap.t; summary: Summary.t}
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) = struct
|
|
module CFG = CFG
|
|
module Domain = RecordDomain
|
|
|
|
type nonrec extras = extras
|
|
|
|
let report_intra access_expr loc summary =
|
|
let message =
|
|
F.asprintf "The value read from %a was never initialized" AccessExpression.pp access_expr
|
|
in
|
|
let ltr = [Errlog.make_trace_element 0 loc "" []] in
|
|
Reporting.log_error summary ~loc ~ltr IssueType.uninitialized_value message
|
|
|
|
|
|
let is_struct t = match t.Typ.desc with Typ.Tstruct _ -> true | _ -> false
|
|
|
|
let is_array t = match t.Typ.desc with Typ.Tarray _ -> true | _ -> false
|
|
|
|
let get_formals pname = Ondemand.get_proc_desc pname |> Option.map ~f:Procdesc.get_formals
|
|
|
|
let should_report_var pdesc tenv maybe_uninit_vars access_expr =
|
|
let base = AccessExpression.get_base access_expr in
|
|
match (AccessExpression.get_typ access_expr tenv, base) with
|
|
| Some typ, (Var.ProgramVar pv, _) ->
|
|
(not (Pvar.is_frontend_tmp pv))
|
|
&& (not (Procdesc.is_captured_var pdesc pv))
|
|
&& MaybeUninitVars.mem access_expr maybe_uninit_vars
|
|
&& should_report_on_type typ
|
|
| _, _ ->
|
|
false
|
|
|
|
|
|
let nth_formal_param callee_pname idx =
|
|
get_formals callee_pname |> Option.bind ~f:(fun formals -> List.nth formals idx)
|
|
|
|
|
|
let function_expects_a_pointer_as_nth_param callee_formals idx =
|
|
match List.nth callee_formals idx with Some (_, typ) -> Typ.is_pointer typ | _ -> false
|
|
|
|
|
|
let is_struct_field_passed_by_ref callee_formals t access_expr idx =
|
|
is_struct t
|
|
&& (not (AccessExpression.is_base access_expr))
|
|
&& function_expects_a_pointer_as_nth_param callee_formals idx
|
|
|
|
|
|
let is_array_element_passed_by_ref callee_formals t access_expr idx =
|
|
is_array t
|
|
&& (not (AccessExpression.is_base access_expr))
|
|
&& function_expects_a_pointer_as_nth_param callee_formals idx
|
|
|
|
|
|
let is_fld_or_array_elem_passed_by_ref t access_expr idx callee_formals =
|
|
is_struct_field_passed_by_ref callee_formals t access_expr idx
|
|
|| is_array_element_passed_by_ref callee_formals t access_expr idx
|
|
|
|
|
|
let report_on_function_params pdesc tenv maybe_uninit_vars actuals loc summary callee_formals_opt
|
|
=
|
|
List.iteri actuals ~f:(fun idx e ->
|
|
match e with
|
|
| HilExp.AccessExpression access_expr ->
|
|
let _, t = AccessExpression.get_base access_expr in
|
|
if
|
|
should_report_var pdesc tenv maybe_uninit_vars access_expr
|
|
&& (not (Typ.is_pointer t))
|
|
&& not
|
|
(Option.exists callee_formals_opt ~f:(fun callee_formals ->
|
|
is_struct_field_passed_by_ref callee_formals t access_expr idx ))
|
|
then report_intra access_expr loc summary
|
|
| _ ->
|
|
() )
|
|
|
|
|
|
let is_dummy_constructor_of_a_struct call =
|
|
let is_dummy_constructor_of_struct =
|
|
match get_formals call with
|
|
| Some [(_, {Typ.desc= Typ.Tptr ({Typ.desc= Tstruct _}, _)})] ->
|
|
true
|
|
| _ ->
|
|
false
|
|
in
|
|
Typ.Procname.is_constructor call && is_dummy_constructor_of_struct
|
|
|
|
|
|
let is_pointer_assignment tenv lhs rhs =
|
|
let _, base_typ = AccessExpression.get_base lhs in
|
|
HilExp.is_null_literal rhs
|
|
(* the rhs has type int when assigning the lhs to null *)
|
|
|| Option.equal Typ.equal (AccessExpression.get_typ lhs tenv) (HilExp.get_typ tenv rhs)
|
|
&& Typ.is_pointer base_typ
|
|
|
|
|
|
(* checks that the set of initialized formal parameters defined in the precondition of
|
|
the function (init_formal_params) contains the (base of) nth formal parameter of the function *)
|
|
let init_nth_actual_param callee_pname idx init_formal_params =
|
|
match nth_formal_param callee_pname idx with
|
|
| None ->
|
|
None
|
|
| Some (fparam, t) ->
|
|
let var_fparam = Var.of_pvar (Pvar.mk fparam callee_pname) in
|
|
if
|
|
D.exists
|
|
(fun access_expr ->
|
|
let base = AccessExpression.get_base access_expr in
|
|
AccessPath.equal_base base (var_fparam, t) )
|
|
init_formal_params
|
|
then Some var_fparam
|
|
else None
|
|
|
|
|
|
let remove_initialized_params pdesc call maybe_uninit_vars idx access_expr remove_fields =
|
|
match Payload.read pdesc call with
|
|
| Some {pre= init_formals; post= _} -> (
|
|
match init_nth_actual_param call idx init_formals with
|
|
| Some var_formal ->
|
|
let maybe_uninit_vars = MaybeUninitVars.remove access_expr maybe_uninit_vars in
|
|
if remove_fields then
|
|
let base = AccessExpression.get_base access_expr in
|
|
MaybeUninitVars.remove_init_fields base var_formal maybe_uninit_vars init_formals
|
|
else maybe_uninit_vars
|
|
| _ ->
|
|
maybe_uninit_vars )
|
|
| _ ->
|
|
maybe_uninit_vars
|
|
|
|
|
|
(* true if a function initializes at least a param or a field of a struct param *)
|
|
let function_initializes_some_formal_params pdesc call =
|
|
match Payload.read pdesc call with
|
|
| Some {pre= initialized_formal_params; post= _} ->
|
|
not (D.is_empty initialized_formal_params)
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let exec_instr (astate : Domain.astate) {ProcData.pdesc; extras= {formals; summary}; tenv} _
|
|
(instr : HilInstr.t) =
|
|
let check_access_expr ~loc rhs_access_expr =
|
|
if should_report_var pdesc tenv astate.maybe_uninit_vars rhs_access_expr then
|
|
report_intra rhs_access_expr loc summary
|
|
in
|
|
let check_hil_expr ~loc = function
|
|
| HilExp.AccessExpression access_expr ->
|
|
check_access_expr ~loc access_expr
|
|
| _ ->
|
|
()
|
|
in
|
|
let update_prepost access_expr rhs =
|
|
let lhs_base = AccessExpression.get_base access_expr in
|
|
if
|
|
FormalMap.is_formal lhs_base formals
|
|
&& Typ.is_pointer (snd lhs_base)
|
|
&& ( (not (is_pointer_assignment tenv access_expr rhs))
|
|
|| not (AccessExpression.is_base access_expr) )
|
|
then
|
|
let pre = D.add access_expr astate.prepost.UninitDomain.pre in
|
|
{astate.prepost with pre}
|
|
else astate.prepost
|
|
in
|
|
match instr with
|
|
| Assign (lhs_access_expr, rhs_expr, loc) ->
|
|
(* check on lhs_typ to avoid false positive when assigning a pointer to another *)
|
|
( match AccessExpression.get_typ lhs_access_expr tenv with
|
|
| Some lhs_typ when not (Typ.is_reference lhs_typ) ->
|
|
check_hil_expr ~loc rhs_expr
|
|
| _ ->
|
|
() ) ;
|
|
let maybe_uninit_vars = MaybeUninitVars.remove lhs_access_expr astate.maybe_uninit_vars in
|
|
let maybe_uninit_vars =
|
|
if AccessExpression.is_base lhs_access_expr then
|
|
(* if we assign to the root of a struct then we need to remove all the fields *)
|
|
let lhs_base = AccessExpression.get_base lhs_access_expr in
|
|
MaybeUninitVars.remove_all_fields tenv lhs_base maybe_uninit_vars
|
|
|> MaybeUninitVars.remove_dereference_access lhs_base
|
|
else maybe_uninit_vars
|
|
in
|
|
let prepost = update_prepost lhs_access_expr rhs_expr in
|
|
{astate with maybe_uninit_vars; prepost}
|
|
| Call (_, Direct callee_pname, _, _, _)
|
|
when Typ.Procname.equal callee_pname BuiltinDecl.objc_cpp_throw ->
|
|
{astate with maybe_uninit_vars= MaybeUninitVars.empty}
|
|
| Call (_, HilInstr.Direct call, [HilExp.AccessExpression (AddressOf (Base base))], _, _)
|
|
when is_dummy_constructor_of_a_struct call ->
|
|
(* if it's a default constructor, we use the following heuristic: we assume that it initializes
|
|
correctly all fields when there is an implementation of the constructor that initilizes at least one
|
|
field. If there is no explicit implementation we cannot assume fields are initialized *)
|
|
if function_initializes_some_formal_params pdesc call then
|
|
let maybe_uninit_vars =
|
|
(* in HIL/SIL the default constructor has only one param: the struct *)
|
|
MaybeUninitVars.remove_all_fields tenv base astate.maybe_uninit_vars
|
|
in
|
|
{astate with maybe_uninit_vars}
|
|
else astate
|
|
| Call (_, call, actuals, _, loc) ->
|
|
(* in case of intraprocedural only analysis we assume that parameters passed by reference
|
|
to a function will be initialized inside that function *)
|
|
let pname_opt = match call with Direct pname -> Some pname | Indirect _ -> None in
|
|
let callee_formals_opt = Option.bind pname_opt ~f:get_formals in
|
|
let is_initializing_all_args =
|
|
match call with
|
|
| Direct pname ->
|
|
Models.is_initializing_all_args pname
|
|
| Indirect _ ->
|
|
false
|
|
in
|
|
let maybe_uninit_vars =
|
|
List.foldi ~init:astate.maybe_uninit_vars actuals ~f:(fun idx acc actual_exp ->
|
|
match actual_exp with
|
|
| HilExp.AccessExpression access_expr -> (
|
|
let access_expr_to_remove =
|
|
match access_expr with AddressOf ae -> ae | _ -> access_expr
|
|
in
|
|
match AccessExpression.get_base access_expr with
|
|
| _, {Typ.desc= Tarray _} when is_initializing_all_args ->
|
|
MaybeUninitVars.remove access_expr acc
|
|
| _, t
|
|
(* Access to a field of a struct or an element of an array by reference *)
|
|
when Option.exists callee_formals_opt
|
|
~f:(is_fld_or_array_elem_passed_by_ref t access_expr idx) -> (
|
|
match pname_opt with
|
|
| Some pname when Config.uninit_interproc ->
|
|
remove_initialized_params pdesc pname acc idx access_expr_to_remove false
|
|
| _ ->
|
|
MaybeUninitVars.remove access_expr_to_remove acc )
|
|
| base when Option.exists pname_opt ~f:Typ.Procname.is_constructor ->
|
|
MaybeUninitVars.remove_all_fields tenv base
|
|
(MaybeUninitVars.remove access_expr_to_remove acc)
|
|
| _, {Typ.desc= Tptr _} -> (
|
|
match pname_opt with
|
|
| Some pname when Config.uninit_interproc ->
|
|
remove_initialized_params pdesc pname acc idx access_expr_to_remove true
|
|
| _ ->
|
|
MaybeUninitVars.remove_everything_under tenv access_expr_to_remove acc )
|
|
| _ ->
|
|
acc )
|
|
| HilExp.Closure (_, apl) ->
|
|
(* remove the captured variables of a block/lambda *)
|
|
List.fold apl ~init:acc ~f:(fun acc (base, _) ->
|
|
MaybeUninitVars.remove (AccessExpression.Base base) acc )
|
|
| _ ->
|
|
acc )
|
|
in
|
|
( match call with
|
|
| Direct _ ->
|
|
report_on_function_params pdesc tenv maybe_uninit_vars actuals loc summary
|
|
callee_formals_opt
|
|
| Indirect _ ->
|
|
() ) ;
|
|
{astate with maybe_uninit_vars}
|
|
| Assume (expr, _, _, loc) ->
|
|
check_hil_expr expr ~loc ; astate
|
|
|
|
|
|
let pp_session_name node fmt = F.fprintf fmt "uninit %a" CFG.Node.pp_id (CFG.Node.id node)
|
|
end
|
|
|
|
module CFG = ProcCfg.Normal
|
|
module Analyzer = LowerHil.MakeAbstractInterpreter (CFG) (TransferFunctions)
|
|
|
|
module Initial = struct
|
|
let get_locals tenv pdesc =
|
|
List.fold (Procdesc.get_locals pdesc) ~init:[]
|
|
~f:(fun acc (var_data : ProcAttributes.var_data) ->
|
|
let pvar = Pvar.mk var_data.name (Procdesc.get_proc_name pdesc) in
|
|
let base_access_expr = AccessExpression.Base (Var.of_pvar pvar, var_data.typ) in
|
|
match var_data.typ.Typ.desc with
|
|
| Typ.Tstruct qual_name
|
|
(* T30105165 remove filtering after we improve union translation *)
|
|
when not (Typ.Name.is_union qual_name) -> (
|
|
match Tenv.lookup tenv qual_name with
|
|
| Some {fields} ->
|
|
let flist =
|
|
List.fold
|
|
~f:(fun acc' (fn, _, _) ->
|
|
AccessExpression.FieldOffset (base_access_expr, fn) :: acc' )
|
|
~init:acc fields
|
|
in
|
|
base_access_expr :: flist
|
|
(* for struct we take the struct address, and the access_path
|
|
to the fields one level down *)
|
|
| _ ->
|
|
acc )
|
|
| Typ.Tarray {elt} ->
|
|
AccessExpression.ArrayOffset (base_access_expr, elt, []) :: acc
|
|
| Typ.Tptr _ ->
|
|
base_access_expr :: AccessExpression.Dereference base_access_expr :: acc
|
|
| _ ->
|
|
base_access_expr :: acc )
|
|
end
|
|
|
|
let checker {Callbacks.tenv; summary; proc_desc} : Summary.t =
|
|
(* start with empty set of uninit local vars and empty set of init formal params *)
|
|
let maybe_uninit_vars = Initial.get_locals tenv proc_desc in
|
|
let initial =
|
|
{ RecordDomain.maybe_uninit_vars= MaybeUninitVars.of_list maybe_uninit_vars
|
|
; aliased_vars= AliasedVars.empty
|
|
; prepost= {UninitDomain.pre= D.empty; post= D.empty} }
|
|
in
|
|
let proc_data =
|
|
let formals = FormalMap.make proc_desc in
|
|
ProcData.make proc_desc tenv {formals; summary}
|
|
in
|
|
match Analyzer.compute_post proc_data ~initial with
|
|
| Some {RecordDomain.prepost} ->
|
|
Payload.update_summary prepost summary
|
|
| None ->
|
|
if Procdesc.Node.get_succs (Procdesc.get_start_node proc_desc) <> [] then (
|
|
L.internal_error "Uninit analyzer failed to compute post for %a" Typ.Procname.pp
|
|
(Procdesc.get_proc_name proc_desc) ;
|
|
summary )
|
|
else summary
|