From 22aca7494b15d2bba7d016c665086145a158d6ab Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Tue, 3 Oct 2017 01:52:04 -0700 Subject: [PATCH] [siof] understand that initialises streams Summary: Inject a marker using a global variable in , and whitelist it so that the frontend translates it. Use the marker in the SIOF checker to tell whether a file includes . If so, start the analysis of its methods assuming that the standard streams are initialised. Reviewed By: sblackshear Differential Revision: D5941343 fbshipit-source-id: 3388d55 --- .../models/cpp/include/infer_model/iostream.h | 15 ++++++ infer/models/cpp/include/iostream | 7 +++ infer/src/checkers/Siof.ml | 47 ++++++++++++++----- infer/src/clang/cFrontend_decl.ml | 39 +++++++++------ infer/tests/codetoanalyze/cpp/siof/Makefile | 1 + .../cpp/siof/siof/include_iostream.cpp | 13 +++++ .../codetoanalyze/cpp/siof/siof/siof.cpp | 2 + .../cpp/siof/siof/std_ios_base_init.cpp | 16 ++++++- 8 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 infer/models/cpp/include/infer_model/iostream.h create mode 100644 infer/models/cpp/include/iostream create mode 100644 infer/tests/codetoanalyze/cpp/siof/siof/include_iostream.cpp diff --git a/infer/models/cpp/include/infer_model/iostream.h b/infer/models/cpp/include/infer_model/iostream.h new file mode 100644 index 000000000..d48d8f051 --- /dev/null +++ b/infer/models/cpp/include/infer_model/iostream.h @@ -0,0 +1,15 @@ +/* + * 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. + */ +#pragma once + +#include_next + +// this is a marker so that infer can tell whether iostream has been included by +// a give source file +std::ios_base::Init __infer_translation_unit_init_streams; diff --git a/infer/models/cpp/include/iostream b/infer/models/cpp/include/iostream new file mode 100644 index 000000000..013157c59 --- /dev/null +++ b/infer/models/cpp/include/iostream @@ -0,0 +1,7 @@ +#include + +#ifdef INFER_CPP11_ON +# include +#else +# include_next +#endif diff --git a/infer/src/checkers/Siof.ml b/infer/src/checkers/Siof.ml index d8d2b4e3a..54e5c92b7 100644 --- a/infer/src/checkers/Siof.ml +++ b/infer/src/checkers/Siof.ml @@ -28,17 +28,17 @@ type siof_model = 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 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 = @@ -223,9 +223,30 @@ let siof_check pdesc gname (summary: Specs.summary) = | Some (Bottom, _) | None -> () -let checker {Callbacks.proc_desc; tenv; summary} : Specs.summary = +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 *) + "__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, SiofDomain.VarNames.empty) 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 diff --git a/infer/src/clang/cFrontend_decl.ml b/infer/src/clang/cFrontend_decl.ml index d07a344e7..f0f86bc15 100644 --- a/infer/src/clang/cFrontend_decl.ml +++ b/infer/src/clang/cFrontend_decl.ml @@ -197,33 +197,41 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron | None -> false - let should_translate_decl trans_unit_ctx dec decl_trans_context = + let should_translate_decl trans_unit_ctx (dec: Clang_ast_t.decl) decl_trans_context = let info = Clang_ast_proj.get_decl_tuple dec in let source_range = info.Clang_ast_t.di_source_range in let translate_when_used = match dec with - | Clang_ast_t.FunctionDecl (_, name_info, _, _) - | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXConstructorDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXConversionDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXDestructorDecl (_, name_info, _, _, _) + | FunctionDecl (_, name_info, _, _) + | CXXMethodDecl (_, name_info, _, _, _) + | CXXConstructorDecl (_, name_info, _, _, _) + | CXXConversionDecl (_, name_info, _, _, _) + | CXXDestructorDecl (_, name_info, _, _, _) -> is_whitelisted_cpp_method (CAst_utils.get_qualified_name name_info) | _ -> false in + let always_translate = + match dec with + | VarDecl (_, {ni_name}, _, _) + -> String.is_prefix ni_name ~prefix:"__infer_" + | _ + -> false + in let translate_location = - CLocation.should_translate_lib trans_unit_ctx source_range decl_trans_context - ~translate_when_used + always_translate + || CLocation.should_translate_lib trans_unit_ctx source_range decl_trans_context + ~translate_when_used in let never_translate_decl = match dec with - | Clang_ast_t.FunctionDecl (_, name_info, _, _) - | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXConstructorDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXConversionDecl (_, name_info, _, _, _) - | Clang_ast_t.CXXDestructorDecl (_, name_info, _, _, _) + | FunctionDecl (_, name_info, _, _) + | CXXMethodDecl (_, name_info, _, _, _) + | CXXConstructorDecl (_, name_info, _, _, _) + | CXXConversionDecl (_, name_info, _, _, _) + | CXXDestructorDecl (_, name_info, _, _, _) -> let fun_name = name_info.Clang_ast_t.ni_name in - Str.string_match (Str.regexp "__infer_skip__") fun_name 0 + String.is_prefix ~prefix:"__infer_skip__" fun_name | _ -> false in @@ -282,7 +290,8 @@ module CFrontend_decl_funct (T : CModule_type.CTranslation) : CModule_type.CFron | None -> () ) | VarDecl (decl_info, named_decl_info, qt, ({vdi_is_global; vdi_init_expr} as vdi)) - when vdi_is_global && Option.is_some vdi_init_expr + when String.is_prefix ~prefix:"__infer_" named_decl_info.ni_name + || vdi_is_global && Option.is_some vdi_init_expr -> (* create a fake procedure that initializes the global variable so that the variable initializer can be analyzed by the backend (eg, the SIOF checker) *) let procname = diff --git a/infer/tests/codetoanalyze/cpp/siof/Makefile b/infer/tests/codetoanalyze/cpp/siof/Makefile index 35730ae6c..5e3c4464a 100644 --- a/infer/tests/codetoanalyze/cpp/siof/Makefile +++ b/infer/tests/codetoanalyze/cpp/siof/Makefile @@ -20,6 +20,7 @@ SOURCES = \ siof/pod_across_translation_units-1.cpp \ siof/pod_across_translation_units-2.cpp \ siof/pod_same_translation_unit.cpp \ + siof/include_iostream.cpp \ siof/siof.cpp \ siof/siof_templated.cpp \ siof/siof_different_tu.cpp \ diff --git a/infer/tests/codetoanalyze/cpp/siof/siof/include_iostream.cpp b/infer/tests/codetoanalyze/cpp/siof/siof/include_iostream.cpp new file mode 100644 index 000000000..7e19ecca1 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/siof/siof/include_iostream.cpp @@ -0,0 +1,13 @@ +/* + * 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 + +void safe_streams() { + std::cout << "yay I'm safe thanks to having included "; +} diff --git a/infer/tests/codetoanalyze/cpp/siof/siof/siof.cpp b/infer/tests/codetoanalyze/cpp/siof/siof/siof.cpp index 7a0860ab0..5bca4a3b3 100644 --- a/infer/tests/codetoanalyze/cpp/siof/siof/siof.cpp +++ b/infer/tests/codetoanalyze/cpp/siof/siof/siof.cpp @@ -12,6 +12,7 @@ extern SomeNonPODObject extern_global_object; SomeNonPODObject global_object; extern int access_to_non_pod(); +void safe_streams(); struct SomeOtherNonPODObject { SomeOtherNonPODObject() { @@ -21,6 +22,7 @@ struct SomeOtherNonPODObject { SomeOtherNonPODObject(int i) { global_object.some_method(); // OK, same translation unit + safe_streams(); // OK, that function is SIOF safe }; }; diff --git a/infer/tests/codetoanalyze/cpp/siof/siof/std_ios_base_init.cpp b/infer/tests/codetoanalyze/cpp/siof/siof/std_ios_base_init.cpp index e00fc80bc..94cbad644 100644 --- a/infer/tests/codetoanalyze/cpp/siof/siof/std_ios_base_init.cpp +++ b/infer/tests/codetoanalyze/cpp/siof/siof/std_ios_base_init.cpp @@ -6,11 +6,23 @@ * 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 +#include +#include + +namespace std { +extern std::istream cin; +extern std::wistream wcin; +extern std::ostream cerr; +extern std::wostream wcerr; +extern std::ostream clog; +extern std::wostream wclog; +extern std::ostream cout; +extern std::wostream wcout; +}; // namespace std void printing_no_SIOF() { std::ios_base::Init ioInit; - std::cerr << "I haz warning!"; + std::cerr << "it's ok to print here"; } void printing_SIOF() {