diff --git a/Makefile b/Makefile index 1accc0440..3f5e66e71 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ BUILD_SYSTEMS_TESTS += \ DIRECT_TESTS += \ c_biabduction c_bufferoverrun c_errors c_frontend \ - cpp_bufferoverrun cpp_errors cpp_frontend cpp_liveness cpp_quandary cpp_siof cpp_threadsafety cpp_nullable \ + cpp_bufferoverrun cpp_errors cpp_frontend cpp_liveness cpp_quandary cpp_siof cpp_threadsafety cpp_uninit cpp_nullable \ ifneq ($(BUCK),no) BUILD_SYSTEMS_TESTS += buck-clang-db buck_flavors buck_flavors_run buck_flavors_deterministic diff --git a/infer/src/backend/specs.ml b/infer/src/backend/specs.ml index e77cc1104..cd0c43e93 100644 --- a/infer/src/backend/specs.ml +++ b/infer/src/backend/specs.ml @@ -337,7 +337,8 @@ type payload = ; resources: ResourceLeakDomain.summary option ; siof: SiofDomain.astate option ; threadsafety: ThreadSafetyDomain.summary option - ; buffer_overrun: BufferOverrunDomain.Summary.t option } + ; buffer_overrun: BufferOverrunDomain.Summary.t option + ; uninit: UninitDomain.summary option } type summary = { nodes: Procdesc.Node.id list (** ids of cfg nodes of the procedure *) @@ -466,14 +467,15 @@ let pp_payload pe fmt ; siof ; threadsafety ; buffer_overrun - ; annot_map } = + ; annot_map + ; uninit } = let pp_opt prefix pp fmt = function | Some x -> F.fprintf fmt "%s: %a@\n" prefix pp x | None -> () in - F.fprintf fmt "%a%a%a%a%a%a%a%a@\n" + F.fprintf fmt "%a%a%a%a%a%a%a%a%a@\n" (pp_opt "PrePosts" (pp_specs pe)) (Option.map ~f:NormSpec.tospecs preposts) (pp_opt "TypeState" (TypeState.pp TypeState.unit_ext)) @@ -482,6 +484,7 @@ let pp_payload pe fmt (pp_opt "ThreadSafety" ThreadSafetyDomain.pp_summary) threadsafety (pp_opt "BufferOverrun" BufferOverrunDomain.Summary.pp) buffer_overrun (pp_opt "AnnotationReachability" AnnotReachabilityDomain.pp) annot_map + (pp_opt "Uninitialised" UninitDomain.pp_summary) uninit let pp_summary_text fmt summary = let err_log = summary.attributes.ProcAttributes.err_log in @@ -693,7 +696,8 @@ let empty_payload = ; resources= None ; siof= None ; threadsafety= None - ; buffer_overrun= None } + ; buffer_overrun= None + ; uninit= None } (** [init_summary (depend_list, nodes, proc_flags, calls, in_out_calls_opt, proc_attributes)] diff --git a/infer/src/backend/specs.mli b/infer/src/backend/specs.mli index 061ec8b9d..3e98010d7 100644 --- a/infer/src/backend/specs.mli +++ b/infer/src/backend/specs.mli @@ -137,7 +137,8 @@ type payload = ; resources: ResourceLeakDomain.summary option ; siof: SiofDomain.astate option ; threadsafety: ThreadSafetyDomain.summary option - ; buffer_overrun: BufferOverrunDomain.Summary.t option } + ; buffer_overrun: BufferOverrunDomain.Summary.t option + ; uninit: UninitDomain.summary option } (** Procedure summary *) type summary = diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 82dc16123..1113cd163 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -660,7 +660,8 @@ and ( annotation_reachability , resource_leak , siof , threadsafety - , suggest_nullable ) = + , suggest_nullable + , uninit ) = let all_checkers = ref [] in let default_checkers = ref [] in let mk_checker ?(default= false) ~long doc = @@ -705,7 +706,7 @@ and ( annotation_reachability and suggest_nullable = mk_checker ~long:"suggest-nullable" ~default:false "Nullable annotation sugesstions analysis (experimental)" - in + and uninit = mk_checker ~long:"uninit" ~default:true "checker for use of uninitialized values" in let mk_only (var, long) = let _ : bool ref = CLOpt.mk_bool_group ~long:(long ^ "-only") @@ -745,7 +746,8 @@ and ( annotation_reachability , resource_leak , siof , threadsafety - , suggest_nullable ) + , suggest_nullable + , uninit ) and annotation_reachability_custom_pairs = CLOpt.mk_json ~long:"annotation-reachability-custom-pairs" @@ -2338,6 +2340,8 @@ and show_progress_bar = !progress_bar and siof = !siof +and uninit = !uninit + and siof_safe_methods = !siof_safe_methods and skip_analysis_in_path = !skip_analysis_in_path diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index cf8815fcd..79a9c951f 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -625,6 +625,8 @@ val show_progress_bar : bool val siof : bool +val uninit : bool + val siof_safe_methods : string list val skip_analysis_in_path : string list diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml index 9bf81db7d..09da6614c 100644 --- a/infer/src/checkers/registerCheckers.ml +++ b/infer/src/checkers/registerCheckers.ml @@ -58,6 +58,7 @@ let checkers = , [(Procedure RepeatedCallsChecker.callback_check_repeated_calls, Config.Java)] ) ; ("resource leak", Config.resource_leak, [(Procedure ResourceLeaks.checker, Config.Java)]) ; ("SIOF", Config.siof, [(Procedure Siof.checker, Config.Clang)]) + ; ("uninitialized variables", Config.uninit, [(Procedure Uninit.checker, Config.Clang)]) ; ( "thread safety" , Config.threadsafety , [ (Procedure ThreadSafety.analyze_procedure, Config.Clang) diff --git a/infer/src/checkers/uninit.ml b/infer/src/checkers/uninit.ml new file mode 100644 index 000000000..f498b4814 --- /dev/null +++ b/infer/src/checkers/uninit.ml @@ -0,0 +1,259 @@ +(* + * 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 (Var) +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 + +(* Check if an expression is in set of variables *) +let exp_in_set exp vset = + let _, pvars = Exp.get_vars exp in + List.exists ~f:(fun pv -> D.mem (Var.of_pvar pv) vset) pvars + +let zip_actual_formal_params callee_pname actual_params = + match Ondemand.get_proc_desc callee_pname with + | Some pdesc + -> let formals, _ = List.unzip (Procdesc.get_formals pdesc) in + let actual, _ = List.unzip actual_params in + (List.zip actual formals, actual) + | _ + -> (None, []) + +let deref_actual_params callee_pname actual_params deref_formal_params = + match zip_actual_formal_params callee_pname actual_params with + | None, _ + -> [] + | Some assoc_actual_formal, _ + -> List.fold + ~f:(fun acc (a, f) -> + let fe = Exp.Lvar (Pvar.mk f callee_pname) in + if exp_in_set fe deref_formal_params then a :: acc else acc) + ~init:[] assoc_actual_formal + +module TransferFunctions (CFG : ProcCfg.S) = struct + module CFG = CFG + (* a state is a pair: ({set of uninitialized locals}, {set of written formal params}) *) + (* module Domain = AbstractDomain.Pair (UninitVars) (PrePost)*) + module Domain = RecordDomain + + type extras = ProcData.no_extras + + let apply_fun_exp_set f exp vset = + let _, pvars = Exp.get_vars exp in + List.fold ~f:(fun acc_set pvar -> f (Var.of_pvar pvar) acc_set) ~init:vset pvars + + let exp_add_set exp vset = apply_fun_exp_set D.add exp vset + + let exp_remove_set exp vset = apply_fun_exp_set D.remove exp vset + + let set_difference set1 set2 = + List.fold ~f:(fun acc_set e -> exp_remove_set e acc_set) ~init:set1 set2 + + (* check if its a basic type *) + let rec is_basic_typ t = + match t.Typ.desc with + | Typ.Tint _ | Typ.Tfloat _ | Typ.Tvoid + -> true + | Typ.Tptr (t', _) + -> is_basic_typ t' + | _ + -> false + + (* returns the formal parameter of a procedure that have a pointer type *) + let ptr_formals pdesc = + let proc_name = Procdesc.get_proc_name pdesc in + let is_ptr (_, t) = match t.Typ.desc with Typ.Tptr _ -> true | _ -> false in + let pfs = List.filter ~f:is_ptr (Procdesc.get_formals pdesc) in + List.map ~f:(fun (name, _) -> Exp.Lvar (Pvar.mk name proc_name)) pfs + + (* Update the set of written formals when it finds a sequence + n$0=*&i; + .... + *n$0=..; +*) + let update_state_formal node pdesc formal_set = + let ptr_formals = ptr_formals pdesc in + let ids_assigned_to_formals, dereferenced_ids = + List.fold + ~f:(fun (acc1, acc2) (i, _) -> + match i with + | Sil.Load (lhs_id, rhs_exp, _, _) when List.mem ptr_formals rhs_exp ~equal:Exp.equal + -> ((lhs_id, rhs_exp) :: acc1, acc2) + | Sil.Store (Exp.Var lhs_id, _, _, _) + -> (acc1, lhs_id :: acc2) + | _ + -> (acc1, acc2)) + (CFG.instr_ids node) ~init:([], []) + in + List.fold + ~f:(fun acc_set (id, formal_param) -> + if List.mem dereferenced_ids id ~equal:Ident.equal then exp_add_set formal_param acc_set + else acc_set) + ids_assigned_to_formals ~init:formal_set + + let exec_instr + ( {Domain.uninit_vars= uninit; Domain.aliased_vars= _; Domain.prepost= pre, wformals} as + astate ) {ProcData.pdesc} node = function + | Sil.Load (_, rhs_exp, _, _) + -> let post = update_state_formal node pdesc wformals in + let set_ptr_formals = ptr_formals pdesc in + let pre' = + if List.mem set_ptr_formals ~equal:Exp.equal rhs_exp && not (exp_in_set rhs_exp post) + then exp_add_set rhs_exp pre + else pre + in + {astate with prepost= (pre', post)} + | Sil.Store (lhs_exp, _, rhs_exp, _) + -> if exp_in_set rhs_exp uninit then astate + else {astate with Domain.uninit_vars= exp_remove_set lhs_exp uninit} + | Sil.Call (ret_id, Exp.Const Const.Cfun callee_pname, actual_params, _, _) + when not intraprocedural_only + -> ( + let astate' = + Option.value_map + ~f:(fun (ret_id, _) -> + {astate with Domain.uninit_vars= exp_remove_set (Exp.Var ret_id) uninit}) + ~default:astate ret_id + in + match + ( zip_actual_formal_params callee_pname actual_params + , Summary.read_summary pdesc callee_pname ) + with + | (Some assoc_actual_formal, actual), Some {pre= _; post= wformals_callee} + -> let initialized_actual_params = + List.filter + ~f:(fun a -> + match List.Assoc.find assoc_actual_formal ~equal:Exp.equal a with + | Some fp + -> D.mem (Var.of_pvar (Pvar.mk fp callee_pname)) wformals_callee + | None + -> false) + actual + in + let uninit' = set_difference uninit initialized_actual_params in + {astate with Domain.uninit_vars= uninit'; Domain.prepost= astate'.prepost} + | _, _ + -> L.(debug Linters Medium) + "Warning: unable to deal with callee '%s' summary@\n" + (Typ.Procname.to_string callee_pname) ; + astate' ) + | Sil.Call (_, _, actual_params, _, _) + -> (* in case of intraprocedural only analysis we assume that parameters + passed by reference to a function will be initialized inside that function *) + let actual_passed_by_ref = + List.fold + ~f:(fun acc (e, t) -> match t.Typ.desc with Typ.Tptr _ -> e :: acc | _ -> acc) + ~init:[] actual_params + in + let uninit' = set_difference uninit actual_passed_by_ref in + {astate with Domain.uninit_vars= uninit'} + | Sil.Declare_locals (pvar_list, _) + -> List.fold + ~f:(fun astate_acc (pvar, t) -> + if is_basic_typ t then + { astate_acc with + Domain.uninit_vars= D.add (Var.of_pvar pvar) astate_acc.Domain.uninit_vars } + else astate_acc) + ~init:astate pvar_list + | Sil.Remove_temps _ | Sil.Abstract _ | Sil.Nullify _ | Sil.Prune _ + -> astate +end + +module CFG = ProcCfg.OneInstrPerNode (ProcCfg.Normal) +module Analyzer = AbstractInterpreter.Make (CFG) (TransferFunctions) + +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 init = + { RecordDomain.uninit_vars= UninitVars.empty + ; RecordDomain.aliased_vars= AliasedVars.empty + ; RecordDomain.prepost= (D.empty, D.empty) } + in + let invariant_map = + Analyzer.exec_cfg cfg (ProcData.make_default proc_desc tenv) ~initial:init ~debug:true + in + let report_uninit_value uninit_vars instr = + let report message loc = + 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 + in + match instr with + | Sil.Load (_, Exp.Lvar pv, _, loc) + | Sil.Store (_, _, Exp.Lvar pv, loc) + when exp_in_set (Exp.Lvar pv) uninit_vars + -> let message = + F.asprintf "The value read from %a was never initialized" Exp.pp (Exp.Lvar pv) + in + report message loc + | Sil.Call (_, Exp.Const Const.Cfun callee_pname, actual_params, loc, _) + when not intraprocedural_only -> ( + match Summary.read_summary proc_desc callee_pname with + | Some {pre= deref_formal_params; post= _} + -> let deref_actual_params = + deref_actual_params callee_pname actual_params deref_formal_params + in + List.iter + ~f:(fun (e, _) -> + if exp_in_set e uninit_vars && List.mem ~equal:Exp.equal deref_actual_params e then + let message = + F.asprintf "The value of %a is read in %a but was never initialized" Exp.pp e + Typ.Procname.pp callee_pname + in + report message loc) + actual_params + | _ + -> () ) + | _ + -> () + in + let report_on_node node = + List.iter (CFG.instr_ids node) ~f:(fun (instr, node_id_opt) -> + match node_id_opt with + | Some node_id -> ( + match Analyzer.extract_pre node_id invariant_map with + | Some + { RecordDomain.uninit_vars= uninitialized_vars + ; RecordDomain.aliased_vars= _ + ; RecordDomain.prepost= _ } + -> report_uninit_value uninitialized_vars instr + | None + -> () ) + | None + -> () ) + in + List.iter (CFG.nodes cfg) ~f:report_on_node ; + 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 + -> L.(die InternalError) + "Analyzer failed to compute post for %a" Typ.Procname.pp (Procdesc.get_proc_name proc_desc) diff --git a/infer/src/checkers/uninitDomain.ml b/infer/src/checkers/uninitDomain.ml new file mode 100644 index 000000000..746a6978d --- /dev/null +++ b/infer/src/checkers/uninitDomain.ml @@ -0,0 +1,66 @@ +(* + * 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 Domain = AbstractDomain.InvertedSet (Var) + +type summary = {pre: Domain.t; post: Domain.t} + +module VarPair = struct + type t = Var.t * Var.t [@@deriving compare] + + let pp fmt pair = F.fprintf fmt " (%a, %a)" Var.pp (fst pair) Var.pp (snd pair) +end + +let pp_summary fmt {pre; post} = + F.fprintf fmt "@\n Pre: %a @\nPost: %a @\n" Domain.pp pre Domain.pp post + +module Record + (Domain1 : AbstractDomain.S) + (Domain2 : AbstractDomain.S) + (Domain3 : AbstractDomain.S) = +struct + type astate = + { uninit_vars: Domain1.astate + ; aliased_vars: Domain2.astate + ; prepost: Domain3.astate * Domain3.astate } + + let ( <= ) ~lhs:({uninit_vars= lhs_uv; aliased_vars= lhs_av; prepost= lhs_pp} as lhs) + ~rhs:({uninit_vars= rhs_uv; aliased_vars= rhs_av; prepost= rhs_pp} as rhs) = + if phys_equal lhs rhs then true + else Domain1.( <= ) ~lhs:lhs_uv ~rhs:rhs_uv && Domain2.( <= ) ~lhs:lhs_av ~rhs:rhs_av + && Domain3.( <= ) ~lhs:(fst lhs_pp) ~rhs:(fst rhs_pp) + && Domain3.( <= ) ~lhs:(snd lhs_pp) ~rhs:(snd rhs_pp) + + let join ({uninit_vars= uv1; aliased_vars= av1; prepost= pp1} as astate1) + ({uninit_vars= uv2; aliased_vars= av2; prepost= pp2} as astate2) = + if phys_equal astate1 astate2 then astate1 + else + { uninit_vars= Domain1.join uv1 uv2 + ; aliased_vars= Domain2.join av1 av2 + ; prepost= (Domain3.join (fst pp1) (fst pp2), Domain3.join (snd pp1) (snd pp2)) } + + let widen ~prev:({uninit_vars= prev_uv; aliased_vars= prev_av; prepost= prev_pp} as prev) + ~next:({uninit_vars= next_uv; aliased_vars= next_av; prepost= next_pp} as next) ~num_iters = + if phys_equal prev next then prev + else + { uninit_vars= Domain1.widen ~prev:prev_uv ~next:next_uv ~num_iters + ; aliased_vars= Domain2.widen ~prev:prev_av ~next:next_av ~num_iters + ; prepost= + ( Domain3.widen ~prev:(fst prev_pp) ~next:(fst next_pp) ~num_iters + , Domain3.widen ~prev:(snd prev_pp) ~next:(snd next_pp) ~num_iters ) } + + let pp fmt {uninit_vars= uv; aliased_vars= av; prepost= pp} = + F.fprintf fmt "@\n uninit_vars: %a @\n aliased_vars: %a @\n prepost: (%a, %a)" Domain1.pp uv + Domain2.pp av Domain3.pp (fst pp) Domain3.pp (snd pp) +end diff --git a/infer/tests/codetoanalyze/cpp/uninit/Makefile b/infer/tests/codetoanalyze/cpp/uninit/Makefile new file mode 100644 index 000000000..5439b92be --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/uninit/Makefile @@ -0,0 +1,20 @@ +# 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. + +TESTS_DIR = ../../.. + +ANALYZER = checkers +# see explanations in cpp/errors/Makefile for the custom isystem +CLANG_OPTIONS = -x c++ -std=c++11 -nostdinc++ -isystem$(MODELS_DIR)/cpp/include -isystem$(CLANG_INCLUDES)/c++/v1/ -c +INFER_OPTIONS = --uninit --ml-buckets cpp --no-filtering --debug-exceptions --project-root $(TESTS_DIR) +INFERPRINT_OPTIONS = --issues-tests + +SOURCES = uninit.mm + +include $(TESTS_DIR)/clang.make + +infer-out/report.json: $(MAKEFILE_LIST) diff --git a/infer/tests/codetoanalyze/cpp/uninit/inter_proc_uninit.cpp b/infer/tests/codetoanalyze/cpp/uninit/inter_proc_uninit.cpp new file mode 100644 index 000000000..241c4f43e --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/uninit/inter_proc_uninit.cpp @@ -0,0 +1,199 @@ +/* + * 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. + */ + +void init(int* i) { *i = 10; } + +void init_bool(bool* i) { *i = false; } + +void no_init(int* i) { i = 0; } + +void no_init_bool(bool* i) { i = 0; } + +int inc(int x) { return x++; } +// error is detected before call as we copy x +// so no need to put it in the summary + +int no_init_return_bad() { + int x; + return x; // error +} + +void bad1() { + int a; + int b = a; // Error + int c = b; // Error but we do not report as it depends from line 20 +} + +int bad2() { + int a; + int b = 0; + int c = 0; + + no_init(&a); + + b = a; // error +} + +int bad3() { + int a; + int b = 0; + int c = 0; + + no_init(&a); + + c = inc(a); // error +} + +int ok1() { + int a; + int b = 0; + int c = 0; + + init(&a); + c = a; // OK +} + +int ok2() { + int a; + int b = 0; + int c = 0; + + init(&a); + + c = inc(a); // ok +} + +int bad4() { + int a; + int b = 0; + int c = 0; + + no_init(&a); + + b = a; // report here error + + c = inc(b); // do not report as it depends from line 31 + + return 0; +} + +// this function shows that we correctly reportat +// line 88 but not report the error at line 90 +int bad5() { + int a; + int b = 0; + int c = 0; + + no_init(&a); + + b = a; // error + + return b; // should not report as it depends from line 31 +} + +// this shows that in case a function return an uninit value, it gets the blame +// rather than the caller. +int blame_on_callee() { + int a; + int c = no_init_return_bad(); + + a = c; // we don't flag the error here as it is flagged in no_init_return + // definition + + return 0; +} + +void maybe_init(int y, int* formal) { + + if (y == 0) { + *formal = 5; + }; +} + +void must_init(int y, int* formal) { + + if (y == 0) { + *formal = 5; + } else { + *formal = 17; + }; +} + +int call_maybe_init_bad(int y) { + int x; + maybe_init(y, &x); + return x; +} + +int call_must_init_ok(int y) { + int x; + must_init(y, &x); + return x; +} + +void square_init(int x, int& res) { res = x * x; } + +int square_no_init(int x, int& res) { return res * res; } + +void use_square_OK() { + + int i; + square_init(2, i); +} + +void use_square_bad() { + + int i; + i = square_no_init(2, i); // Error +} + +int no_deref(int* x) { + int* y = 0; + x = y; + return *x; // this is not actually a deref of a formal +} + +void init_x(int* x) { + int* y; + y = x; + *y = 25; // this is writing x +} + +int use_init_x_OK() { + + int a; + init_x(&a); + + return a; +} + +void bool1_bad() { + bool a; + bool b = a; + bool c = b; +} + +int bool2_bad() { + bool a; + bool b = 0; + bool c = 0; + + no_init_bool(&a); + + b = a; // error +} + +int bool1_ok() { + bool a; + bool b = 0; + bool c = 0; + + init_bool(&a); + c = a; // OK +} diff --git a/infer/tests/codetoanalyze/cpp/uninit/issues.exp b/infer/tests/codetoanalyze/cpp/uninit/issues.exp new file mode 100644 index 000000000..c9462208e --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/uninit/issues.exp @@ -0,0 +1,4 @@ +codetoanalyze/cpp/uninit/uninit.mm, bad1, 2, UNINITIALIZED_VALUE, [] +codetoanalyze/cpp/uninit/uninit.mm, branch1_FP, 11, UNINITIALIZED_VALUE, [] +codetoanalyze/cpp/uninit/uninit.mm, loop1_FP, 10, UNINITIALIZED_VALUE, [] +codetoanalyze/cpp/uninit/uninit.mm, no_init_return_bad, 2, UNINITIALIZED_VALUE, [] diff --git a/infer/tests/codetoanalyze/cpp/uninit/uninit.cpp b/infer/tests/codetoanalyze/cpp/uninit/uninit.cpp new file mode 100644 index 000000000..753d8f0e3 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/uninit/uninit.cpp @@ -0,0 +1,139 @@ +/* + * 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. + */ + +void init(int* i) { *i = 10; } + +void init_bool(bool* i) { *i = false; } + +void no_init(int* i) {} + +void no_init_bool(bool* i) {} + +int inc(int x) { return x + 1; } +// error is detected before call as we copy x +// so no need to put it in the summary + +int no_init_return_bad() { + int x; + return x; // error +} + +int bad1() { + int a; + int b = a; // Error + int c = b; // Error but we do not report as it depends from line 20 + return c; +} + +int ok1() { + int a; + int b; + no_init(&a); + + b = a; // OK (only intraprocedural) + return b; +} + +int ok2() { + int a; + int c; + no_init(&a); + + c = inc(a); // OK (only intraprocedural) + return c; +} + +int ok3() { + int a; + int c; + + init(&a); + c = a; // OK + + return c; +} + +int ok4() { + int a; + int c; + + init(&a); + + c = inc(a); // ok + return c; +} + +int ok5() { + int a; + int b; + int c; + + no_init(&a); + + b = a; // ok (only intraprocedural) + + c = inc(b); // do not report as it depends from line 31 + + return c; +} + +void square_init(int x, int& res) { res = x * x; } + +int square_no_init(int x, int& res) { return res * res; } + +void use_square_ok1() { + + int i; + square_init(2, i); +} + +int use_square_ok2() { + + int i; + i = square_no_init(2, i); // OK only intraprocedural + return i; +} + +bool getOK(void); + +int branch1_FP() { + + int size; + + bool ok = getOK(); + + if (ok) { + size = 1; + } + + if (ok) { + return size; + } + + return 0; +} + +int loop1_FP() { + + int size; + + for (;;) { + size = 1; + if (getOK()) + break; + } + + return size; +} + +int ok6() { + int x; + x = 7; + return x; +}