[siof] generic model of std::ios_base::Init that prevents SIOF

Summary:
Enrich the domain of SIOF to contain, as well as the globals needed by a
procedure, the globals that the procedure initializes. Also add the possibility
to model some procedures as initializing some variables. Use that mechanism to
teach the checker about `std::ios_base::Init`.

Reviewed By: mbouaziz

Differential Revision: D4588284

fbshipit-source-id: d72fc87
master
Jules Villard 8 years ago committed by Facebook Github Bot
parent f1698f3816
commit a79096efa8

@ -20,6 +20,31 @@ let is_whitelisted (pname : Procname.t) =
Procname.get_qualifiers pname
|> QualifiedCppName.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 models = List.map ~f:parse_siof_model [
("std::ios_base::Init::Init", [
"std::cerr"; "std::wcerr";
"std::cin"; "std::wcin";
"std::clog"; "std::wclog";
"std::cout"; "std::wcout";
]);
]
let is_modelled =
let models_matcher =
List.map models ~f:(fun {qual_name} -> qual_name)
|> QualifiedCppName.quals_matcher_of_fuzzy_qual_names in
fun pname ->
Procname.get_qualifiers pname
|> QualifiedCppName.match_qualifiers models_matcher
module Summary = Summary.Make (struct
type summary = SiofDomain.astate
@ -38,7 +63,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let is_compile_time_constructed pdesc pv =
let init_pname = Pvar.get_initializer_pname pv in
match Option.bind init_pname (Summary.read_summary pdesc) with
| Some Domain.Bottom ->
| Some (Domain.BottomSiofTrace.Bottom, _)->
(* we analyzed the initializer for this global and found that it doesn't require any runtime
initialization so cannot participate in SIOF *)
true
@ -57,16 +82,30 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|> IList.map (fun v -> (v, loc)) in
GlobalsAccesses.of_list globals_accesses
let filter_global_accesses initialized globals =
let initialized_matcher =
Domain.VarNames.elements initialized
|> QualifiedCppName.quals_matcher_of_fuzzy_qual_names in
(* gvar \notin initialized, up to some fuzzing *)
let f (gvar, _) =
QualifiedCppName.qualifiers_of_qual_name (Pvar.to_string gvar)
|> Fn.non (QualifiedCppName.match_qualifiers initialized_matcher) in
GlobalsAccesses.filter f globals
let add_globals astate outer_loc globals =
if GlobalsAccesses.is_empty globals then
astate
else
let trace = match astate with
| Domain.Bottom -> SiofTrace.empty
| Domain.NonBottom t -> t in
let trace = match fst astate with
| Domain.BottomSiofTrace.Bottom -> SiofTrace.empty
| Domain.BottomSiofTrace.NonBottom t -> t in
(* filter out variables that are known to be already initialized *)
let non_init_globals =
let initialized = snd astate in
filter_global_accesses initialized globals in
let globals_trace =
SiofTrace.add_sink (SiofTrace.make_access globals outer_loc) trace in
Domain.NonBottom globals_trace
SiofTrace.add_sink (SiofTrace.make_access non_init_globals outer_loc) trace in
(Domain.BottomSiofTrace.NonBottom globals_trace, snd astate)
let add_params_globals astate pdesc call_loc params =
IList.map (fun (e, _) -> get_globals pdesc call_loc e) params
@ -74,7 +113,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
|> add_globals astate (Procdesc.get_loc pdesc)
let at_least_nonbottom =
Domain.join (Domain.NonBottom SiofTrace.empty)
Domain.join ((Domain.BottomSiofTrace.NonBottom SiofTrace.empty), Domain.VarNames.empty)
let exec_instr astate { ProcData.pdesc; } _ (instr : Sil.instr) =
match instr with
@ -85,20 +124,40 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
get_globals pdesc loc exp |> add_globals astate proc_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.quals_matcher_of_fuzzy_qual_names [qual_name]
|> Fn.flip QualifiedCppName.match_qualifiers
(Procname.get_qualifiers callee_pname) then
Some initialized_globals
else
None) in
Domain.join astate (Domain.BottomSiofTrace.NonBottom SiofTrace.empty,
Domain.VarNames.of_list init)
| Call (_, Const (Cfun callee_pname), _::params_without_self, loc, _)
when Procname.is_c_method callee_pname && Procname.is_constructor callee_pname
&& Procname.is_constexpr callee_pname ->
add_params_globals astate pdesc loc params_without_self
| Call (_, Const (Cfun callee_pname), params, loc, _) ->
let callsite = CallSite.make callee_pname loc in
let callee_globals =
match Summary.read_summary pdesc callee_pname with
| Some (Domain.NonBottom trace) ->
Domain.NonBottom (SiofTrace.with_callsite trace callsite)
| None | Some Domain.Bottom ->
Domain.Bottom in
let callee_astate = match Summary.read_summary pdesc callee_pname with
| Some (Domain.BottomSiofTrace.NonBottom trace, initialized_globals) ->
let trace_without_initialized_globals =
let sinks_with_non_init_globals =
SiofTrace.Sinks.filter (fun sink ->
filter_global_accesses (snd astate) (SiofTrace.Sink.kind sink)
|> Fn.non GlobalsAccesses.is_empty) (SiofTrace.sinks trace) in
SiofTrace.update_sinks trace sinks_with_non_init_globals in
(Domain.BottomSiofTrace.NonBottom
(SiofTrace.with_callsite trace_without_initialized_globals callsite),
initialized_globals)
| Some ((Domain.BottomSiofTrace.Bottom, _) as astate) ->
astate
| None ->
(Domain.BottomSiofTrace.Bottom, Domain.VarNames.empty) in
add_params_globals astate pdesc loc params
|> Domain.join callee_globals
|> Domain.join callee_astate
|>
(* make sure it's not Bottom: we made a function call so this needs initialization *)
at_least_nonbottom
@ -130,7 +189,7 @@ let report_siof trace pdesc gname loc =
attrs.ProcAttributes.translation_unit in
let trace_of_pname pname =
match Summary.read_summary pdesc pname with
| Some (SiofDomain.NonBottom summary) -> summary
| Some (SiofDomain.BottomSiofTrace.NonBottom summary, _) -> summary
| _ -> SiofTrace.empty in
let report_one_path (passthroughs, path) =
@ -166,7 +225,7 @@ let report_siof trace pdesc gname loc =
|> IList.iter report_one_path
let siof_check pdesc gname = function
| Some (SiofDomain.NonBottom post) ->
| Some ((SiofDomain.BottomSiofTrace.NonBottom post, _)) ->
let attrs = Procdesc.get_attributes pdesc in
let all_globals = SiofTrace.Sinks.fold
(fun sink -> GlobalsAccesses.union (SiofTrace.Sink.kind sink))
@ -176,11 +235,13 @@ let siof_check pdesc gname = function
attrs.ProcAttributes.translation_unit in
if GlobalsAccesses.exists (is_foreign tu_opt) all_globals then
report_siof post pdesc gname attrs.ProcAttributes.loc;
| Some SiofDomain.Bottom | None ->
| Some (SiofDomain.BottomSiofTrace.Bottom, _) | None ->
()
let compute_post proc_data =
Analyzer.compute_post proc_data ~initial:SiofDomain.Bottom |> Option.map ~f:SiofDomain.normalize
Analyzer.compute_post proc_data
~initial:(SiofDomain.BottomSiofTrace.Bottom, SiofDomain.VarNames.empty)
|> Option.map ~f:SiofDomain.normalize
let checker ({ Callbacks.proc_desc; } as callback) =
let post =

@ -9,22 +9,18 @@
open! IStd
(* The domain for the analysis is sets of global variables if an initialization is needed at
runtime, or Bottom if no initialization is needed. For instance, `int x = 32; int y = x * 52;`
gives a summary of Bottom for both initializers corresponding to these globals, but `int x =
foo();` gives a summary of at least "NonBottom {}" for x's initializer since x will need runtime
initialization.
module VarNames = PrettyPrintable.MakePPSet(String)
The encoding in terms of a BottomLifted domain is an efficiency hack to represent two pieces of
information: whether a global variable (via its initializer function) requires runtime
initialization, and which globals requiring initialization a given function (transitively)
accesses. *)
include AbstractDomain.BottomLifted(SiofTrace)
module BottomSiofTrace = AbstractDomain.BottomLifted(SiofTrace)
include AbstractDomain.Pair
(BottomSiofTrace)
(AbstractDomain.FiniteSet(VarNames))
(** group together procedure-local accesses *)
let normalize astate = match astate with
| Bottom -> astate
| NonBottom trace ->
let normalize ((trace, initialized) as astate) = match trace with
| BottomSiofTrace.Bottom -> astate
| BottomSiofTrace.NonBottom trace ->
let elems = SiofTrace.Sinks.elements (SiofTrace.sinks trace) in
let (direct, indirect) = IList.partition SiofTrace.is_intraprocedural_access elems in
match direct with
@ -40,4 +36,4 @@ let normalize astate = match astate with
SiofTrace.make_access kind loc::indirect
|> SiofTrace.Sinks.of_list
|> SiofTrace.update_sinks trace in
NonBottom trace'
(BottomSiofTrace.NonBottom trace', initialized)

@ -7,17 +7,28 @@
* of patent rights can be found in the PATENTS file in the same directory.
*)
(* The domain for the analysis is sets of global variables if an initialization is needed at
runtime, or Bottom if no initialization is needed. For instance, `int x = 32; int y = x * 52;`
gives a summary of Bottom for both initializers corresponding to these globals, but `int x =
foo();` gives a summary of at least "NonBottom {}" for x's initializer since x will need runtime
initialization.
module VarNames : PrettyPrintable.PPSet with type elt = string
module BottomSiofTrace : module type of AbstractDomain.BottomLifted(SiofTrace)
(* The domain for the analysis is:
- On the one hand, sets of global variables if an initialization is needed at runtime, or Bottom
if no initialization is needed. For instance, `int x = 32; int y = x * 52;` gives a summary of
Bottom for both initializers corresponding to these globals, but `int x = foo();` gives a summary
of at least "NonBottom {}" for x's initializer since x will need runtime initialization.
The encoding in terms of a BottomLifted domain is an efficiency hack to represent two pieces of
information: whether a global variable (via its initializer function) requires runtime
initialization, and which globals requiring initialization a given function (transitively)
accesses. *)
include module type of (AbstractDomain.BottomLifted(SiofTrace))
accesses.
- On the other hand, the set of variables that are guaranteed to be initialized when the function
terminates (even before main() has started). For instance, this is the case for
std::ios_base::Init::Init(). *)
include module type of AbstractDomain.Pair
(AbstractDomain.BottomLifted(SiofTrace))
(AbstractDomain.FiniteSet(VarNames))
(** group together procedure-local accesses *)
val normalize : astate -> astate

@ -22,7 +22,10 @@ SOURCES = \
siof/siof.cpp \
siof/siof_templated.cpp \
siof/siof_different_tu.cpp \
siof/std_ios_base_init.cpp \
HEADERS = siof/siof_types.h
include $(TESTS_DIR)/clang.make
infer-out/report.json: $(MAKEFILE_LIST)

@ -7,3 +7,4 @@ codetoanalyze/cpp/checkers/siof/siof.cpp, __infer_globals_initializer_pod_access
codetoanalyze/cpp/checkers/siof/siof_templated.cpp, __infer_globals_initializer_another_templated_global_object, 0, STATIC_INITIALIZATION_ORDER_FIASCO, [initialization of another_templated_global_object,call to SomeOtherTemplatedNonPODObject<_Bool>_SomeOtherTemplatedNonPODObject,access to extern_global_object]
codetoanalyze/cpp/checkers/siof/siof_templated.cpp, __infer_globals_initializer_another_templated_global_object2, 0, STATIC_INITIALIZATION_ORDER_FIASCO, [initialization of another_templated_global_object2,call to access_to_non_pod,access to global_object2,access to some_other_global_object2]
codetoanalyze/cpp/checkers/siof/siof_templated.cpp, __infer_globals_initializer_another_templated_global_object3, 0, STATIC_INITIALIZATION_ORDER_FIASCO, [initialization of another_templated_global_object3,call to access_to_templated_non_pod,access to global_object3]
codetoanalyze/cpp/checkers/siof/std_ios_base_init.cpp, __infer_globals_initializer_global_bad_std_cerr_access, 0, STATIC_INITIALIZATION_ORDER_FIASCO, [initialization of global_bad_std_cerr_access,call to return_4_SIOF,call to printing_SIOF,access to std::__1::cerr]

@ -0,0 +1,44 @@
/*
* 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.
*/
#include <iostream>
void printing_no_SIOF() {
std::ios_base::Init ioInit;
std::cerr << "I haz warning!";
}
void printing_SIOF() {
std::cerr << "I forgot to initialize std::cerr so I might not see this error "
"message!";
}
int return_4_SIOF() {
printing_SIOF();
return 4;
}
void print_from_function_call() { std::cout << "I can even print here"; }
int return_4_no_SIOF() {
printing_no_SIOF();
std::clog << "I can even print here with clog";
std::wclog << "I can even print here with wclog";
std::cout << "I can even print here with cout";
std::wcout << "I can even print here with wcout";
std::cerr << "I can even print here with cerr";
std::wcerr << "I can even print here with wcerr";
int x;
std::cin >> x;
std::wcin >> x;
print_from_function_call();
return 4;
}
int global_bad_std_cerr_access = return_4_SIOF();
int global_good_std_cerr_access = return_4_no_SIOF();
Loading…
Cancel
Save