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.

279 lines
10 KiB

(*
* Copyright (c) 2016 - 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
open! AbstractDomain.Types
module F = Format
module L = Logging
module GlobalVar = SiofTrace.GlobalVar
module GlobalVarSet = SiofTrace.GlobalVarSet
let methods_whitelist = QualifiedCppName.Match.of_fuzzy_qual_names Config.siof_safe_methods
let is_whitelisted (pname: Typ.Procname.t) =
Typ.Procname.get_qualifiers pname |> QualifiedCppName.Match.match_qualifiers methods_whitelist
type siof_model =
{ qual_name: string (** (fuzzy) name of the method, eg "std::ios_base::Init::Init" *)
; initialized_globals: string list
(** names of variables that are guaranteed to be initialized once the method is executed,
eg ["std::cerr"] *)
}
let parse_siof_model (qual_name, initialized_globals) = {qual_name; initialized_globals}
let standard_streams =
[ "std::cerr"
; "std::wcerr"
; "std::cin"
; "std::wcin"
; "std::clog"
; "std::wclog"
; "std::cout"
; "std::wcout" ]
let models = List.map ~f:parse_siof_model [("std::ios_base::Init::Init", standard_streams)]
let is_modelled =
let models_matcher =
List.map models ~f:(fun {qual_name} -> qual_name) |> QualifiedCppName.Match.of_fuzzy_qual_names
in
fun pname ->
Typ.Procname.get_qualifiers pname |> QualifiedCppName.Match.match_qualifiers models_matcher
module Summary = Summary.Make (struct
type payload = SiofDomain.astate
let update_payload astate (summary: Specs.summary) =
{summary with payload= {summary.payload with siof= Some astate}}
let read_payload (summary: Specs.summary) = summary.payload.siof
end)
module TransferFunctions (CFG : ProcCfg.S) = struct
module CFG = CFG
module Domain = SiofDomain
type extras = ProcData.no_extras
let is_compile_time_constructed pdesc pv =
let init_pname = Pvar.get_initializer_pname pv in
match Option.bind init_pname ~f:(Summary.read_summary pdesc) with
| Some (Bottom, _) ->
(* we analyzed the initializer for this global and found that it doesn't require any runtime
initialization so cannot participate in SIOF *)
true
| _ ->
false
let get_globals pdesc e =
let is_dangerous_global pv =
Pvar.is_global pv && not (Pvar.is_static_local pv) && not (Pvar.is_pod pv)
&& not (Pvar.is_compile_constant pv) && not (is_compile_time_constructed pdesc pv)
in
Exp.get_vars e |> snd |> List.filter ~f:is_dangerous_global |> GlobalVarSet.of_list
let filter_global_accesses initialized =
let initialized_matcher =
Domain.VarNames.elements initialized |> QualifiedCppName.Match.of_fuzzy_qual_names
in
Staged.stage (fun (* gvar \notin initialized, up to some fuzzing *)
gvar ->
QualifiedCppName.of_qual_string (Pvar.to_string gvar)
|> Fn.non (QualifiedCppName.Match.match_qualifiers initialized_matcher) )
let add_globals astate loc globals =
if GlobalVarSet.is_empty globals then astate
else
let trace = match fst astate with Bottom -> SiofTrace.empty | NonBottom t -> t in
let is_dangerous =
(* filter out variables that are known to be already initialized *)
let initialized = snd astate in
filter_global_accesses initialized |> Staged.unstage
in
let trace_with_non_init_globals =
GlobalVarSet.fold
(fun global acc ->
if is_dangerous global then SiofTrace.add_sink (SiofTrace.make_access global loc) acc
else acc)
globals trace
in
(NonBottom trace_with_non_init_globals, snd astate)
let add_actuals_globals astate0 pdesc call_loc actuals =
List.fold_left actuals ~init:astate0 ~f:(fun astate (e, _) ->
get_globals pdesc e |> add_globals astate call_loc )
let at_least_nonbottom = Domain.join (NonBottom SiofTrace.empty, Domain.VarNames.empty)
let exec_instr astate {ProcData.pdesc} _ (instr: Sil.instr) =
match instr with
| Load (_, exp, _, loc) | Store (_, _, exp, loc) | Prune (exp, loc, _, _) ->
get_globals pdesc exp |> add_globals astate loc
| Call (_, Const Cfun callee_pname, _, _, _) when is_whitelisted callee_pname ->
at_least_nonbottom astate
| Call (_, Const Cfun callee_pname, _, _, _) when is_modelled callee_pname ->
let init =
List.find_map_exn models ~f:(fun {qual_name; initialized_globals} ->
if QualifiedCppName.Match.of_fuzzy_qual_names [qual_name]
|> Fn.flip QualifiedCppName.Match.match_qualifiers
(Typ.Procname.get_qualifiers callee_pname)
then Some initialized_globals
else None )
in
Domain.join astate (NonBottom SiofTrace.empty, Domain.VarNames.of_list init)
| Call (_, Const Cfun callee_pname, _ :: actuals_without_self, loc, _)
when Typ.Procname.is_c_method callee_pname && Typ.Procname.is_constructor callee_pname
&& Typ.Procname.is_constexpr callee_pname ->
add_actuals_globals astate pdesc loc actuals_without_self
| Call (_, Const Cfun callee_pname, actuals, loc, _) ->
let callee_astate =
match Summary.read_summary pdesc callee_pname with
| Some (NonBottom trace, initialized_by_callee) ->
let already_initialized = snd astate in
let dangerous_accesses =
SiofTrace.sinks trace
|> SiofTrace.Sinks.filter (fun sink ->
SiofTrace.Sink.kind sink
|> Staged.unstage (filter_global_accesses already_initialized) )
in
let callsite = CallSite.make callee_pname loc in
let sinks =
SiofTrace.Sinks.map
(fun access -> SiofTrace.Sink.with_callsite access callsite)
dangerous_accesses
in
(NonBottom (SiofTrace.update_sinks trace sinks), initialized_by_callee)
| Some ((Bottom, _) as callee_astate) ->
callee_astate
| None ->
(Bottom, Domain.VarNames.empty)
in
add_actuals_globals astate pdesc loc actuals |> Domain.join callee_astate
|> (* make sure it's not Bottom: we made a function call so this needs initialization *)
at_least_nonbottom
| Call (_, _, actuals, loc, _) ->
add_actuals_globals astate pdesc loc actuals
|> (* make sure it's not Bottom: we made a function call so this needs initialization *)
at_least_nonbottom
| Declare_locals _ | Remove_temps _ | Abstract _ | Nullify _ ->
astate
end
module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions)
let is_foreign tu_opt v =
match (Pvar.get_translation_unit v, tu_opt) with
| TUFile v_tu, Some current_tu ->
not (SourceFile.equal current_tu v_tu)
| TUExtern, Some _ ->
true
| _, None ->
L.(die InternalError) "cannot be called with translation unit set to None"
let report_siof summary trace pdesc gname loc =
let trace_of_pname pname =
match Summary.read_summary pdesc pname with
| Some (NonBottom summary, _) ->
summary
| _ ->
SiofTrace.empty
in
let report_one_path ((_, path) as trace) =
let description =
match path with
| [] ->
assert false
| (final_sink, _) :: _ ->
F.asprintf
"Initializer of %s accesses global variable from a different translation unit: %a"
gname GlobalVar.pp (SiofTrace.Sink.kind final_sink)
in
let ltr = SiofTrace.trace_of_error loc gname trace in
let msg = IssueType.static_initialization_order_fiasco.unique_id in
let exn = Exceptions.Checkers (msg, Localise.verbatim_desc description) in
Reporting.log_error summary ~loc ~ltr exn
in
let reportable_paths = SiofTrace.get_reportable_sink_paths trace ~trace_of_pname in
if Config.filtering then List.hd reportable_paths |> Option.iter ~f:report_one_path
else List.iter ~f:report_one_path reportable_paths
let siof_check pdesc gname (summary: Specs.summary) =
match summary.payload.siof with
| Some (NonBottom post, _) ->
let attrs = Procdesc.get_attributes pdesc in
let tu_opt =
let attrs = Procdesc.get_attributes pdesc in
attrs.ProcAttributes.translation_unit
in
let foreign_sinks =
SiofTrace.Sinks.filter
(fun sink -> SiofTrace.Sink.kind sink |> is_foreign tu_opt)
(SiofTrace.sinks post)
in
if not (SiofTrace.Sinks.is_empty foreign_sinks) then
report_siof summary
(SiofTrace.update_sinks post foreign_sinks)
pdesc gname attrs.ProcAttributes.loc
| Some (Bottom, _) | None ->
()
let checker {Callbacks.proc_desc; tenv; summary; get_procs_in_file} : Specs.summary =
let standard_streams_initialized_in_tu =
let includes_iostream tu =
let magic_iostream_marker =
(* always [Some _] because we create a global variable with [mk_global] *)
Option.value_exn
( Pvar.mk_global
(Mangled.from_string
(* infer's C++ headers define this global variable in <iostream> *)
"__infer_translation_unit_init_streams")
(TUFile tu)
|> Pvar.get_initializer_pname )
in
get_procs_in_file (Procdesc.get_proc_name proc_desc)
|> List.exists ~f:(Typ.Procname.equal magic_iostream_marker)
in
Option.value_map ~default:false ~f:includes_iostream
(Procdesc.get_attributes proc_desc).ProcAttributes.translation_unit
in
let proc_data = ProcData.make_default proc_desc tenv in
let initial =
( Bottom
, if standard_streams_initialized_in_tu then SiofDomain.VarNames.of_list standard_streams
else SiofDomain.VarNames.empty )
in
let updated_summary =
match Analyzer.compute_post proc_data ~initial with
| Some post ->
Summary.update_summary post summary
| None ->
summary
in
( match Typ.Procname.get_global_name_of_initializer (Procdesc.get_proc_name proc_desc) with
| Some gname ->
siof_check proc_desc gname updated_summary
| None ->
() ) ;
updated_summary