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.

222 lines
8.0 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.
*)
open! IStd
module F = Format
module L = Logging
(** Forward analysis to compute uninitialized variables at each program point *)
module D =
UninitDomain.Domain
module UninitVars = AbstractDomain.FiniteSet (AccessPath)
module AliasedVars = AbstractDomain.FiniteSet (UninitDomain.VarPair)
module PrePost = AbstractDomain.Pair (D) (D)
module RecordDomain = UninitDomain.Record (UninitVars) (AliasedVars) (D)
module Summary = Summary.Make (struct
type payload = UninitDomain.summary
let update_payload sum (summary: Specs.summary) =
{summary with payload= {summary.payload with uninit= Some sum}}
let read_payload (summary: Specs.summary) = summary.payload.uninit
end)
let intraprocedural_only = true
let blacklisted_functions = [BuiltinDecl.__set_array_length]
let is_type_pointer t = match t.Typ.desc with Typ.Tptr _ -> true | _ -> false
let rec is_basic_type t =
match t.Typ.desc with
| Tint _ | Tfloat _ | Tvoid ->
true
| Tptr (t', _) ->
is_basic_type t'
| _ ->
false
let is_blacklisted_function pname =
List.exists ~f:(fun fname -> Typ.Procname.equal pname fname) blacklisted_functions
module TransferFunctions (CFG : ProcCfg.S) = struct
module CFG = CFG
module Domain = RecordDomain
let report var loc summary =
let message = F.asprintf "The value read from %a was never initialized" Var.pp var in
let issue_id = IssueType.uninitialized_value.unique_id in
let ltr = [Errlog.make_trace_element 0 loc "" []] in
let exn = Exceptions.Checkers (issue_id, Localise.verbatim_desc message) in
Reporting.log_error summary ~loc ~ltr exn
type extras = FormalMap.t * Specs.summary
let should_report_var pdesc tenv uninit_vars ap =
match (AccessPath.get_typ ap tenv, ap) with
| Some typ, ((Var.ProgramVar pv, _), _) ->
not (Pvar.is_frontend_tmp pv) && not (Procdesc.is_captured_var pdesc pv)
&& D.mem ap uninit_vars && is_basic_type typ
| _, _ ->
false
let report_on_function_params pdesc tenv uninit_vars actuals loc extras =
List.iter
~f:(fun e ->
match e with
| HilExp.AccessPath ((var, t), al)
when should_report_var pdesc tenv uninit_vars ((var, t), al) && not (is_type_pointer t) ->
report var loc (snd extras)
| _ ->
())
actuals
let remove_fields tenv base uninit_vars =
match base with
| _, {Typ.desc= Tptr ({Typ.desc= Tstruct name_struct}, _)} -> (
match Tenv.lookup tenv name_struct with
| Some {fields} ->
List.fold
~f:(fun acc (fn, _, _) -> D.remove (base, [AccessPath.FieldAccess fn]) acc)
fields ~init:uninit_vars
| _ ->
uninit_vars )
| _ ->
uninit_vars
let remove_array_element base uninit_vars =
D.remove (base, [AccessPath.ArrayAccess (snd base, [])]) uninit_vars
let exec_instr (astate: Domain.astate) {ProcData.pdesc; ProcData.extras; ProcData.tenv} _
(instr: HilInstr.t) =
match instr with
| Assign
( (((_, lhs_typ), _) as lhs_ap)
, HilExp.AccessPath (((rhs_var, rhs_typ) as rhs_base), al)
, loc ) ->
let uninit_vars = D.remove lhs_ap astate.uninit_vars in
let prepost =
if FormalMap.is_formal rhs_base (fst extras)
&& match rhs_typ.desc with Typ.Tptr _ -> true | _ -> false
then
let pre' = D.add (rhs_base, al) (fst astate.prepost) in
let post = snd astate.prepost in
(pre', post)
else astate.prepost
in
(* check on lhs_typ to avoid false positive when assigning a pointer to another *)
if should_report_var pdesc tenv uninit_vars (rhs_base, al) && not (is_type_pointer lhs_typ)
then report rhs_var loc (snd extras) ;
{astate with uninit_vars; prepost}
| Assign (lhs, _, _) ->
let uninit_vars = D.remove lhs astate.uninit_vars in
{astate with uninit_vars}
| Call (_, Direct callee_pname, _, _, _)
when Typ.Procname.equal callee_pname BuiltinDecl.objc_cpp_throw ->
{astate with uninit_vars= D.empty}
| Call (_, HilInstr.Direct 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 uninit_vars =
List.fold
~f:(fun acc actual_exp ->
match actual_exp with
| HilExp.AccessPath (((_, {Typ.desc= Tarray _}) as base), al)
when is_blacklisted_function call ->
D.remove (base, al) acc
| HilExp.AccessPath ap when Typ.Procname.is_constructor call ->
remove_fields tenv (fst ap) (D.remove ap acc)
| HilExp.AccessPath (((_, {Typ.desc= Tptr _}) as base), al)
when not (Typ.Procname.is_constructor call) ->
let acc' = D.remove (base, al) acc in
remove_fields tenv base acc'
| HilExp.AccessPath (((_, {Typ.desc= Tptr (t', _)}) as base), al) ->
let acc' = D.remove (base, al) acc in
remove_array_element (fst base, t') acc'
| HilExp.Closure (_, apl) ->
(* remove the captured variables of a block/lambda *)
List.fold ~f:(fun acc' (base, _) -> D.remove (base, []) acc') ~init:acc apl
| _ ->
acc)
~init:astate.uninit_vars actuals
in
report_on_function_params pdesc tenv uninit_vars actuals loc extras ;
{astate with uninit_vars}
| Call _ | Assume _ ->
astate
end
module CFG = ProcCfg.OneInstrPerNode (ProcCfg.Normal)
module Analyzer =
AbstractInterpreter.Make (CFG) (LowerHil.Make (TransferFunctions) (LowerHil.DefaultConfig))
let get_locals cfg tenv pdesc =
List.fold
~f:(fun acc (name, t) ->
let pvar = Pvar.mk name (Procdesc.get_proc_name pdesc) in
let base_ap = ((Var.of_pvar pvar, t), []) in
match t.Typ.desc with
| Typ.Tstruct qual_name -> (
match Tenv.lookup tenv qual_name with
| Some {fields} ->
let flist =
List.fold
~f:(fun acc' (fn, _, _) -> (fst base_ap, [AccessPath.FieldAccess fn]) :: acc')
~init:acc fields
in
base_ap :: flist
(* for struct we take the struct address, and the access_path
to the fields one level down *)
| _ ->
acc )
| Typ.Tarray (t', _, _) ->
(fst base_ap, [AccessPath.ArrayAccess (t', [])]) :: acc
| _ ->
base_ap :: acc)
~init:[] (Procdesc.get_locals cfg)
let checker {Callbacks.tenv; summary; proc_desc} : Specs.summary =
let cfg = CFG.from_pdesc proc_desc in
(* start with empty set of uninit local vars and empty set of init formal params *)
let formal_map = FormalMap.make proc_desc in
let uninit_vars = get_locals cfg tenv proc_desc in
let init =
( { RecordDomain.uninit_vars= UninitVars.of_list uninit_vars
; RecordDomain.aliased_vars= AliasedVars.empty
; RecordDomain.prepost= (D.empty, D.empty) }
, IdAccessPathMapDomain.empty )
in
let invariant_map =
Analyzer.exec_cfg cfg
(ProcData.make proc_desc tenv (formal_map, summary))
~initial:init ~debug:false
in
match Analyzer.extract_post (CFG.id (CFG.exit_node cfg)) invariant_map with
| Some
( {RecordDomain.uninit_vars= _; RecordDomain.aliased_vars= _; RecordDomain.prepost= pre, post}
, _ ) ->
Summary.update_summary {pre; post} 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