diff --git a/Makefile b/Makefile index 4dad4b521..b8e81107f 100644 --- a/Makefile +++ b/Makefile @@ -60,15 +60,15 @@ DIRECT_TESTS += \ cpp_conflicts \ cpp_errors \ cpp_frontend \ - cpp_linters \ + cpp_linters \ cpp_linters-for-test-only \ cpp_liveness \ cpp_nullable \ - cpp_ownership cpp_pulse \ + cpp_pulse \ cpp_quandary cpp_quandaryBO \ cpp_racerd \ cpp_siof \ - cpp_starvation \ + cpp_starvation \ cpp_uninit \ diff --git a/infer/man/man1/infer-analyze.txt b/infer/man/man1/infer-analyze.txt index e56b402b7..da207f89c 100644 --- a/infer/man/man1/infer-analyze.txt +++ b/infer/man/man1/infer-analyze.txt @@ -98,9 +98,8 @@ OPTIONS --no-default-checkers Deactivates: Default checkers: --biabduction, - --fragment-retains-view, --linters, --liveness, --ownership, - --racerd, --siof, --starvation, --uninit (Conversely: - --default-checkers) + --fragment-retains-view, --linters, --liveness, --racerd, --siof, + --starvation, --uninit (Conversely: --default-checkers) --eradicate Activates: the eradicate @Nullable checker for Java annotations @@ -197,14 +196,6 @@ OPTIONS Activates: Enable --nullsafe and disable all other checkers (Conversely: --no-nullsafe-only) - --no-ownership - Deactivates: the detection of C++ lifetime bugs (Conversely: - --ownership) - - --ownership-only - Activates: Enable --ownership and disable all other checkers - (Conversely: --no-ownership-only) - --perf-profiler-data-file file Specify the file containing perf profiler data to read diff --git a/infer/man/man1/infer-full.txt b/infer/man/man1/infer-full.txt index 1554d63b3..577f7d3bc 100644 --- a/infer/man/man1/infer-full.txt +++ b/infer/man/man1/infer-full.txt @@ -287,9 +287,9 @@ OPTIONS --no-default-checkers Deactivates: Default checkers: --biabduction, - --fragment-retains-view, --linters, --liveness, --ownership, - --racerd, --siof, --starvation, --uninit (Conversely: - --default-checkers) See also infer-analyze(1). + --fragment-retains-view, --linters, --liveness, --racerd, --siof, + --starvation, --uninit (Conversely: --default-checkers) + See also infer-analyze(1). --no-default-linters Deactivates: Use the default linters for the analysis. @@ -781,14 +781,6 @@ OPTIONS Activates: Show the list of reports and exit (Conversely: --no-only-show) See also infer-explore(1). - --no-ownership - Deactivates: the detection of C++ lifetime bugs (Conversely: - --ownership) See also infer-analyze(1). - - --ownership-only - Activates: Enable --ownership and disable all other checkers - (Conversely: --no-ownership-only) See also infer-analyze(1). - --perf-profiler-data-file file Specify the file containing perf profiler data to read See also infer-analyze(1). diff --git a/infer/man/man1/infer.txt b/infer/man/man1/infer.txt index d0a8e75a2..ac0a765c1 100644 --- a/infer/man/man1/infer.txt +++ b/infer/man/man1/infer.txt @@ -287,9 +287,9 @@ OPTIONS --no-default-checkers Deactivates: Default checkers: --biabduction, - --fragment-retains-view, --linters, --liveness, --ownership, - --racerd, --siof, --starvation, --uninit (Conversely: - --default-checkers) See also infer-analyze(1). + --fragment-retains-view, --linters, --liveness, --racerd, --siof, + --starvation, --uninit (Conversely: --default-checkers) + See also infer-analyze(1). --no-default-linters Deactivates: Use the default linters for the analysis. @@ -781,14 +781,6 @@ OPTIONS Activates: Show the list of reports and exit (Conversely: --no-only-show) See also infer-explore(1). - --no-ownership - Deactivates: the detection of C++ lifetime bugs (Conversely: - --ownership) See also infer-analyze(1). - - --ownership-only - Activates: Enable --ownership and disable all other checkers - (Conversely: --no-ownership-only) See also infer-analyze(1). - --perf-profiler-data-file file Specify the file containing perf profiler data to read See also infer-analyze(1). diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 2a6d004b2..e58c95eec 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -614,7 +614,6 @@ and ( annotation_reachability , liveness , loop_hoisting , nullsafe - , ownership , printf_args , pulse , purity @@ -667,13 +666,13 @@ and ( annotation_reachability mk_checker ~long:"nullsafe" ~deprecated:["-check-nullable"; "-suggest-nullable"] "[EXPERIMENTAL] Nullable type checker (incomplete: use --eradicate for now)" - and ownership = mk_checker ~long:"ownership" ~default:true "the detection of C++ lifetime bugs" and printf_args = mk_checker ~long:"printf-args" ~default:false "the detection of mismatch between the Java printf format strings and the argument types \ For, example, this checker will warn about the type error in `printf(\"Hello %d\", \ \"world\")`" - and pulse = mk_checker ~long:"pulse" "[EXPERIMENTAL] C++ lifetime analysis" + and pulse = + mk_checker ~long:"pulse" ~deprecated:["-ownership"] "[EXPERIMENTAL] C++ lifetime analysis" and purity = mk_checker ~long:"purity" ~default:false "[EXPERIMENTAL] Purity analysis" and quandary = mk_checker ~long:"quandary" ~default:false "the quandary taint analysis" and quandaryBO = @@ -737,7 +736,6 @@ and ( annotation_reachability , liveness , loop_hoisting , nullsafe - , ownership , printf_args , pulse , purity @@ -2926,8 +2924,6 @@ and only_footprint = !only_footprint and only_show = !only_show -and ownership = !ownership - and passthroughs = !passthroughs and patterns_modeled_expensive = match patterns_modeled_expensive with k, r -> (k, !r) diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index dc3c4a7f0..63f03e9e8 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -516,8 +516,6 @@ val only_footprint : bool val only_show : bool -val ownership : bool - val perf_profiler_data_file : string option val pmd_xml : bool diff --git a/infer/src/checkers/Ownership.ml b/infer/src/checkers/Ownership.ml deleted file mode 100644 index 770b875f0..000000000 --- a/infer/src/checkers/Ownership.ml +++ /dev/null @@ -1,418 +0,0 @@ -(* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - *) - -open! IStd -module F = Format -module L = Logging - -module Base = struct - type t = AccessPath.base - - let compare = AccessPath.compare_base - - let pp = AccessPath.pp_base -end - -module VarSet = AbstractDomain.FiniteSet (Base) - -module CapabilityDomain = struct - type t = - | InvalidatedAt of Location.t - (** neither owned nor borrowed; we can't safely do anything with this value. tagged with the - location where invalidation occurred. *) - | BorrowedFrom of VarSet.t - (** not owned, but borrowed from existing reference(s). for now, permits both reads and writes *) - | Owned - (** owned. permits reads, writes, and ownership transfer (e.g. call a destructor, delete, or free) *) - - let make_borrowed_vars vars = - assert (not (VarSet.is_empty vars)) ; - BorrowedFrom vars - - - (** Owned <= BorrowedFrom <= InvalidatedAt *) - let ( <= ) ~lhs ~rhs = - if phys_equal lhs rhs then true - else - match (lhs, rhs) with - | InvalidatedAt loc1, InvalidatedAt loc2 -> - Location.compare loc1 loc2 <= 0 - | _, InvalidatedAt _ -> - true - | InvalidatedAt _, _ -> - false - | BorrowedFrom s1, BorrowedFrom s2 -> - VarSet.( <= ) ~lhs:s1 ~rhs:s2 - | Owned, _ -> - true - | _, Owned -> - false - - - let join astate1 astate2 = - if phys_equal astate1 astate2 then astate1 - else - match (astate1, astate2) with - | BorrowedFrom s1, BorrowedFrom s2 -> - BorrowedFrom (VarSet.union s1 s2) - | Owned, astate | astate, Owned -> - astate - | InvalidatedAt loc1, InvalidatedAt loc2 -> - (* pick the "higher" program point that occurs syntactically later *) - if Location.compare loc1 loc2 >= 0 then astate1 else astate2 - | (InvalidatedAt _ as invalid), _ | _, (InvalidatedAt _ as invalid) -> - invalid - - - let widen ~prev ~next ~num_iters:_ = join prev next - - let pp fmt = function - | InvalidatedAt loc -> - F.fprintf fmt "InvalidatedAt(%a)" Location.pp loc - | BorrowedFrom vars -> - F.fprintf fmt "BorrowedFrom(%a)" VarSet.pp vars - | Owned -> - F.pp_print_string fmt "Owned" -end - -let rec is_function_typ = function - | {Typ.desc= Tptr (t, _)} -> - is_function_typ t - | {Typ.desc= Tstruct typename} -> - String.is_prefix ~prefix:"std::function" (Typ.Name.name typename) - | _ -> - false - - -(** map from program variable to its capability *) -module Domain = struct - include AbstractDomain.Map (Base) (CapabilityDomain) - - let report message loc ltr summary = - Reporting.log_error summary ~loc ~ltr IssueType.use_after_lifetime message - - - let report_return_stack_var (var, _) loc summary = - let message = - F.asprintf "Reference to stack variable %a is returned at %a" Var.pp var Location.pp loc - in - let ltr = - [ Errlog.make_trace_element 0 loc "Return of stack variable" [] - ; Errlog.make_trace_element 0 loc "End of procedure" [] ] - in - report message loc ltr summary - - - let report_use_after_lifetime (var, _) ~use_loc ~invalidated_loc summary = - if Var.appears_in_source_code var then - let message = - F.asprintf "Variable %a is used at line %a after its lifetime ended at %a" Var.pp var - Location.pp use_loc Location.pp invalidated_loc - in - let ltr = - [ Errlog.make_trace_element 0 invalidated_loc "End of variable lifetime" [] - ; Errlog.make_trace_element 0 use_loc "Use of invalid variable" [] ] - in - report message use_loc ltr summary - - - (* complain if we do not have the right capability to access [var] *) - let check_var_lifetime base_var use_loc summary astate = - let open CapabilityDomain in - match find base_var astate with - | InvalidatedAt invalidated_loc -> - report_use_after_lifetime base_var ~use_loc ~invalidated_loc summary - | BorrowedFrom borrowed_vars -> - (* warn if any of the borrowed vars are Invalid *) - let report_invalidated v = - match find v astate with - | InvalidatedAt invalidated_loc -> - report_use_after_lifetime base_var ~use_loc ~invalidated_loc summary - | BorrowedFrom _ - (* TODO: can do deeper checking here, but have to worry about borrow cycles *) - | Owned -> - () - | exception Caml.Not_found -> - () - in - VarSet.iter report_invalidated borrowed_vars - | Owned -> - () - | exception Caml.Not_found -> - () - - - let base_add_read ((_, typ) as base_var) loc summary astate = - (* don't warn if it's a read of a std::function type. we model closures as borrowing their - captured vars, but simply reading a closure doesn't actually use these vars. this means that - we'll miss bugs involving invalid uses of std::function's, but that seems ok *) - if not (is_function_typ typ) then check_var_lifetime base_var loc summary astate ; - astate - - - let access_path_add_read (base, _) loc summary astate = base_add_read base loc summary astate - - let exp_add_reads exp loc summary astate = - List.fold - ~f:(fun acc access_expr -> - access_path_add_read (HilExp.AccessExpression.to_access_path access_expr) loc summary acc - ) - (HilExp.get_access_exprs exp) ~init:astate - - - let actuals_add_reads actuals loc summary astate = - (* TODO: handle reads in actuals + return values properly. This is nontrivial because the - frontend sometimes chooses to translate return as pass-by-ref on a dummy actual *) - List.fold actuals - ~f:(fun acc actual_exp -> exp_add_reads actual_exp loc summary acc) - ~init:astate - - - let borrow_vars lhs_base rhs_vars astate = - (* borrow all of the vars read on the rhs *) - if VarSet.is_empty rhs_vars then remove lhs_base astate - else add lhs_base (CapabilityDomain.make_borrowed_vars rhs_vars) astate - - - (* copy capability if it exists, consider borrowing if it doesn't *) - let copy_or_borrow_var lhs_base rhs_base astate = - try - let rhs_capability = find rhs_base astate in - add lhs_base rhs_capability astate - with Caml.Not_found -> - if Var.is_cpp_temporary (fst rhs_base) then - borrow_vars lhs_base (VarSet.singleton rhs_base) astate - else add lhs_base CapabilityDomain.Owned astate - - - (* handle assigning directly to a base var *) - let handle_var_assign ?(is_operator_equal = false) lhs_base rhs_exp loc summary astate = - match (rhs_exp : HilExp.t) with - | Constant _ when not (Var.is_cpp_temporary (fst lhs_base)) -> - add lhs_base CapabilityDomain.Owned astate - | AccessExpression (Base rhs_base | AddressOf (Base rhs_base)) - when not (Var.appears_in_source_code (fst rhs_base)) -> - copy_or_borrow_var lhs_base rhs_base astate - | Closure (_, captured_vars) -> - (* TODO: can be folded into the case above once we have proper AccessExpressions *) - let vars_captured_by_ref = - List.fold captured_vars - ~f:(fun acc (((_, typ) as base), _) -> - match typ.Typ.desc with Typ.Tptr _ -> VarSet.add base acc | _ -> acc ) - ~init:VarSet.empty - in - borrow_vars lhs_base vars_captured_by_ref astate - | AccessExpression (Base rhs_base) - when (not is_operator_equal) && Typ.is_reference (snd rhs_base) -> - copy_or_borrow_var lhs_base rhs_base astate - | AccessExpression (AddressOf (Base rhs_base)) when not is_operator_equal -> - borrow_vars lhs_base (VarSet.singleton rhs_base) astate - | _ -> - (* TODO: support capability transfer between source vars, other assignment modes *) - exp_add_reads rhs_exp loc summary astate |> remove lhs_base - - - let release_ownership base loc summary astate = - base_add_read base loc summary astate |> add base (CapabilityDomain.InvalidatedAt loc) -end - -module TransferFunctions (CFG : ProcCfg.S) = struct - module CFG = CFG - module Domain = Domain - - type extras = Summary.t - - let is_aggregate (_, typ) = - match typ.Typ.desc with - | Tstruct _ -> - (* TODO: we could compute this precisely by recursively checking all of the fields of the - type, but that's going to be expensive. will add it to the frontend instead *) - true - | _ -> - false - - - let get_assigned_base (access_expression : HilExp.AccessExpression.t) = - match access_expression with - | Base base -> - Some base - | _ -> - let base = HilExp.AccessExpression.get_base access_expression in - (* assume assigning to any field of an aggregate struct re-initalizes the struct *) - Option.some_if (is_aggregate base) base - - - let returns_struct pname = - (* Assumption: we add an extra return param for structs *) - Ondemand.get_proc_desc pname - |> Option.value_map ~default:false ~f:Procdesc.has_added_return_param - - - let acquire_ownership call_exp return_base actuals loc summary astate = - let aquire_ownership_of_first_param actuals = - match actuals with - | HilExp.AccessExpression (AddressOf access_expression) :: other_actuals -> ( - match get_assigned_base access_expression with - | Some constructed_base -> - let astate' = Domain.actuals_add_reads other_actuals loc summary astate in - Some (Domain.add constructed_base CapabilityDomain.Owned astate') - | None -> - Some astate ) - | _ -> - Some astate - in - match call_exp with - | HilInstr.Direct pname -> - (* TODO: support new[], malloc, others? *) - if Typ.Procname.equal pname BuiltinDecl.__new then - let astate' = Domain.actuals_add_reads actuals loc summary astate in - Some (Domain.add return_base CapabilityDomain.Owned astate') - else if Typ.Procname.equal pname BuiltinDecl.__placement_new then - match List.rev actuals with - | HilExp.AccessExpression (Base placement_base) :: other_actuals -> - (* TODO: placement new creates an alias between return var and placement var, should - eventually model as return borrowing from placement (see - FN_placement_new_aliasing2_bad test) *) - Domain.actuals_add_reads other_actuals loc summary astate - |> Domain.add placement_base CapabilityDomain.Owned - |> Domain.add return_base CapabilityDomain.Owned - |> Option.some - | _ :: other_actuals -> - Domain.actuals_add_reads other_actuals loc summary astate - |> Domain.add return_base CapabilityDomain.Owned - |> Option.some - | _ -> - L.die InternalError "Placement new without placement in %a %a" Typ.Procname.pp pname - Location.pp loc - else if Typ.Procname.is_constructor pname then aquire_ownership_of_first_param actuals - else if returns_struct pname then aquire_ownership_of_first_param (List.rev actuals) - else None - | HilInstr.Indirect _ -> - None - - - let is_destructor = function - | Typ.Procname.ObjC_Cpp clang_pname -> - Typ.Procname.ObjC_Cpp.is_destructor clang_pname - && not - (* Our frontend generates synthetic inner destructors to model invocation of base class - destructors correctly; see D5834555/D7189239 *) - (Typ.Procname.ObjC_Cpp.is_inner_destructor clang_pname) - | _ -> - false - - - let is_early_return call_exp = - match call_exp with - | HilInstr.Direct pname -> - Typ.Procname.equal pname BuiltinDecl.exit - || Typ.Procname.equal pname BuiltinDecl.objc_cpp_throw - || Typ.Procname.equal pname BuiltinDecl.abort - | _ -> - false - - - let exec_instr (astate : Domain.t) (proc_data : extras ProcData.t) _ (instr : HilInstr.t) = - let summary = proc_data.extras in - match instr with - | Assign (lhs_access_exp, rhs_exp, loc) -> ( - match get_assigned_base lhs_access_exp with - | Some lhs_base -> - Domain.handle_var_assign lhs_base rhs_exp loc summary astate - | None -> - (* assign to field, array, indirectly with &/*, or a combination *) - Domain.exp_add_reads rhs_exp loc summary astate - |> Domain.access_path_add_read - (HilExp.AccessExpression.to_access_path lhs_access_exp) - loc summary ) - | Call (_, Direct callee_pname, [AccessExpression (Base lhs_base)], _, loc) - when Typ.Procname.equal callee_pname BuiltinDecl.__delete -> - (* TODO: support delete[], free, and (in some cases) std::move *) - Domain.release_ownership lhs_base loc summary astate - | Call (_, Direct callee_pname, [AccessExpression (AddressOf (Base lhs_base))], _, loc) - when is_destructor callee_pname -> - Domain.release_ownership lhs_base loc summary astate - | Call - ( _ - , Direct (Typ.Procname.ObjC_Cpp callee_pname) - , [AccessExpression (AddressOf (Base lhs_base)); rhs_exp] - , _ - , loc ) - when Typ.Procname.ObjC_Cpp.is_operator_equal callee_pname -> - (* TODO: once we go interprocedural, this case should only apply for operator='s with an - empty summary *) - Domain.handle_var_assign ~is_operator_equal:true lhs_base rhs_exp loc summary astate - | Call - ( _ - , Direct (Typ.Procname.ObjC_Cpp callee_pname) - , AccessExpression (AddressOf (Base lhs_base)) :: _ - , _ - , loc ) - when Typ.Procname.ObjC_Cpp.is_cpp_lambda callee_pname -> - (* invoking a lambda; check that its captured vars are valid *) - Domain.check_var_lifetime lhs_base loc summary astate ; - astate - | Call (ret_id_typ, call_exp, actuals, _, loc) -> ( - match acquire_ownership call_exp ret_id_typ actuals loc summary astate with - | Some astate' -> - astate' - | None -> - if is_early_return call_exp then - (* thrown exception, abort(), or exit; return _|_ *) - Domain.empty - else - let astate' = Domain.actuals_add_reads actuals loc summary astate in - Domain.remove ret_id_typ astate' ) - | Assume (assume_exp, _, _, loc) -> - Domain.exp_add_reads assume_exp loc summary astate - | Metadata _ -> - astate - - - let pp_session_name _node fmt = F.pp_print_string fmt "ownership" -end - -module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (ProcCfg.Exceptional)) - -let report_invalid_return post end_loc summary = - let proc_name = Summary.get_proc_name summary in - let is_local_to_procedure var = - (* In case of lambdas, only report on variables local to the lambda that are not captured from - the enclosing procedure to avoid false positives in case the lambda is called within the - same procedure (which is frequent, at the cost of false negatives in case this lambda is - returned to the caller). *) - Var.is_local_to_procedure proc_name var - && not (Procdesc.is_captured_var summary.Summary.proc_desc var) - in - (* look for return values that are borrowed from (now-invalid) local variables *) - let report_invalid_return base (capability : CapabilityDomain.t) = - if Var.is_return (fst base) then - match capability with - | BorrowedFrom vars -> - VarSet.iter - (fun ((var, _) as borrowed_base) -> - if is_local_to_procedure var then - Domain.report_return_stack_var borrowed_base end_loc summary ) - vars - | InvalidatedAt invalidated_loc -> - Domain.report_use_after_lifetime base ~use_loc:end_loc ~invalidated_loc summary - | Owned -> - () - in - Domain.iter report_invalid_return post - - -let checker {Callbacks.proc_desc; tenv; summary} = - let proc_data = ProcData.make proc_desc tenv summary in - let initial = Domain.empty in - ( match Analyzer.compute_post proc_data ~initial with - | Some post -> - let end_loc = Procdesc.Node.get_loc (Procdesc.get_exit_node proc_desc) in - report_invalid_return post end_loc summary - | None -> - () ) ; - summary diff --git a/infer/src/checkers/Ownership.mli b/infer/src/checkers/Ownership.mli deleted file mode 100644 index 489676eb8..000000000 --- a/infer/src/checkers/Ownership.mli +++ /dev/null @@ -1,10 +0,0 @@ -(* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - *) - -open! IStd - -val checker : Callbacks.proc_callback_t diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml index 043fc50b4..865148ca4 100644 --- a/infer/src/checkers/registerCheckers.ml +++ b/infer/src/checkers/registerCheckers.ml @@ -81,9 +81,6 @@ let all_checkers = ; callbacks= [ (Procedure NullabilitySuggest.checker, Language.Java) ; (Procedure NullabilitySuggest.checker, Language.Clang) ] } - ; { name= "ownership" - ; active= Config.ownership - ; callbacks= [(Procedure Ownership.checker, Language.Clang)] } ; {name= "pulse"; active= Config.pulse; callbacks= [(Procedure Pulse.checker, Language.Clang)]} ; { name= "quandary" ; active= Config.quandary || Config.quandaryBO diff --git a/infer/tests/codetoanalyze/cpp/ownership/Makefile b/infer/tests/codetoanalyze/cpp/ownership/Makefile deleted file mode 100644 index 0a1c672f3..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -TESTS_DIR = ../../.. - -# see explanations in cpp/errors/Makefile for the custom isystem -CLANG_OPTIONS = -x c++ -std=c++14 -Wc++1z-extensions -nostdinc++ -isystem$(MODELS_DIR)/cpp/include -isystem$(CLANG_INCLUDES)/c++/v1/ -c -INFER_OPTIONS = --ownership-only --ml-buckets cpp --debug-exceptions --project-root $(TESTS_DIR) -INFERPRINT_OPTIONS = --issues-tests - -SOURCES = $(wildcard *.cpp) - -include $(TESTS_DIR)/clang.make - -infer-out/report.json: $(MAKEFILE_LIST) diff --git a/infer/tests/codetoanalyze/cpp/ownership/basics.cpp b/infer/tests/codetoanalyze/cpp/ownership/basics.cpp deleted file mode 100644 index 9ec9b48f4..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/basics.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include - -struct Aggregate { - int i; - - ~Aggregate() {} -}; - -void aggregate_reassign_ok() { - const int len = 5; - Aggregate arr[len]; - for (int i = 0; i < len; i++) { - Aggregate s = {1}; - // assign with curly bracket syntax doesn't call constructor; need to - // recognize that this is a reassignment anyway - arr[0] = s; // shouldn't be flagged as a use-after-lifetime - } -} - -struct AggregateWithConstructedField { - std::string str; -}; - -void aggregate_reassign2_ok() { - AggregateWithConstructedField arr[10]; - for (int i = 0; i < 10; i++) { - // this is translated as string(&(a.str), "hi"). need to make sure this is - // treated the same as initializing a - AggregateWithConstructedField a{"hi"}; - arr[i] = a; - } -} - -struct NestedAggregate { - AggregateWithConstructedField a; -}; - -void aggregate_reassign3_ok() { - NestedAggregate arr[10]; - for (int i = 0; i < 10; i++) { - // this is translated as std::basic_string(&(a.str), "hi"). need to make - // sure this is treated the same as initializing a - NestedAggregate a{{"hi"}}; - arr[i] = a; - } -} - -int multiple_invalidations_branch_bad(int n, int* ptr) { - if (n == 7) { - delete ptr; - } else { - delete ptr; - } - return *ptr; -} - -int multiple_invalidations_loop_bad(int n, int* ptr) { - for (int i = 0; i < n; i++) { - if (i == 7) { - delete ptr; - } else { - delete ptr; - } - } - return *ptr; -} - -Aggregate* pointer_arithmetic_ok(Aggregate* a) { - a->~Aggregate(); - a++; - return a; -} - -void iterator_pointer_arithmetic_ok(std::vector v) { - for (auto it = v.begin(); it != v.end(); ++it) { - it->~Aggregate(); - } -} - -struct A { - ~A(); - int f(int i); -}; - -A getA(); - -int struct_inside_loop_ok(std::vector numbers) { - int sum; - for (auto number : numbers) { - A a = getA(); - sum += a.f(number); - } - return sum; -} diff --git a/infer/tests/codetoanalyze/cpp/ownership/closures.cpp b/infer/tests/codetoanalyze/cpp/ownership/closures.cpp deleted file mode 100644 index a9a34b985..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/closures.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -#include - -struct S { - int f; - - S() { f = 1; } -}; - -int ref_capture_destroy_invoke_bad() { - std::function f; - { - S s; - f = [&s] { return s.f; }; - } // destructor for s called here - return f(); // s used here -} - -int implicit_ref_capture_destroy_invoke_bad() { - std::function f; - { - auto s = S(); - f = [&] { return s.f; }; - } - return f(); -} - -int FN_reassign_lambda_capture_destroy_invoke_bad() { - std::function f; - { - auto s = S(); - auto tmp = [&] { return s.f; }; - f = tmp; - } - return f(); -} - -// frontend doesn't understand difference between capture-by-value and -// capture-by-ref, need to fix -int value_capture_destroy_invoke_ok() { - std::function f; - { - S s; - f = [s] { return s.f; }; - } - return f(); -} - -// same thing here -int implicit_value_capture_destroy_invoke_ok() { - std::function f; - { - S s; - f = [=] { return s.f; }; - } - return f(); -} - -int ref_capture_invoke_ok() { - std::function f; - int ret; - { - S s; - f = [&s] { return s.f; }; - ret = f(); - } - return ret; -} - -void invoke_twice_ok() { - std::function f; - int ret; - { - S s; - f = [&s] { return s.f; }; - f(); - f(); - } -} - -std::function ref_capture_read_lambda_ok() { - std::function f; - int ret; - { - S s; - f = [&s] { return s.f; }; - } - auto tmp = - f; // reading (but not invoking) the lambda doesn't use its captured vars -} - -// we'll miss this because we count invoking a lambda object as a use of its -// captured vars, not the lambda object itself. -void FN_delete_lambda_then_call_bad() { - auto lambda = [] { return 1; }; - ~lambda(); - return lambda(); -} - -// need to treat escaping as a use in order to catch this -std::function FN_ref_capture_return_lambda_bad() { - std::function f; - int ret; - { - S s; - f = [&s] { return s.f; }; - } - return f; // if the caller invokes the lambda, it will try to read the invalid - // stack address -} - -S& lambda_return_local_bad() { - S x; - auto f = [&x](void) -> S& { - S y; - return y; - }; - return f(); -} - -int ref_capture_return_enclosing_local_lambda_ok() { - S x; - auto f = [&x](void) -> S& { - // do not report this because there is a good chance that this function will - // only be used in the local scope - return x; - }; - return f().f; -} - -S& FN_ref_capture_return_enclosing_local_lambda_bad() { - S x; - auto f = [&x](void) -> S& { - // no way to know if ok here - return x; - }; - // woops, this returns a ref to a local! - return f(); -} diff --git a/infer/tests/codetoanalyze/cpp/ownership/issues.exp b/infer/tests/codetoanalyze/cpp/ownership/issues.exp deleted file mode 100644 index c6676af1d..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/issues.exp +++ /dev/null @@ -1,27 +0,0 @@ -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_branch_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_loop_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_loop_bad, 5, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_loop_bad, 8, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/closures.cpp, implicit_ref_capture_destroy_invoke_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/closures.cpp, implicit_value_capture_destroy_invoke_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/closures.cpp, lambda_return_local_bad::lambda_closures.cpp:119:12::operator(), 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [Return of stack variable,End of procedure] -codetoanalyze/cpp/ownership/closures.cpp, ref_capture_destroy_invoke_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/closures.cpp, value_capture_destroy_invoke_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_deleted_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_literal_stack_reference_bad, 0, USE_AFTER_LIFETIME, no_bucket, ERROR, [Return of stack variable,End of procedure] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_stack_pointer_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [Return of stack variable,End of procedure] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_variable_stack_reference1_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [Return of stack variable,End of procedure] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_variable_stack_reference2_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [Return of stack variable,End of procedure] -codetoanalyze/cpp/ownership/use_after_delete.cpp, delete_in_branch_bad, 5, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, delete_in_loop_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, deref_deleted_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, double_delete_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, reassign_field_of_deleted_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, return_deleted_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, use_in_branch_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_delete.cpp, use_in_loop_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, FP_destructor_order_empty_destructor_ok, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, destructor_order_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, double_destructor_bad, 5, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, use_after_destructor_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, use_after_scope4_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [End of variable lifetime,Use of invalid variable] diff --git a/infer/tests/codetoanalyze/cpp/ownership/reference_wrapper.cpp b/infer/tests/codetoanalyze/cpp/ownership/reference_wrapper.cpp deleted file mode 100644 index 92823f312..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/reference_wrapper.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -struct B { - B(int v) : f(v){}; - int f; -}; - -struct WrapsB { - WrapsB(int f) { b = new B(f); } - B* b; - B* getb() { return b; }; - ~WrapsB() { delete b; } -}; - -struct ReferenceWrapperHeap { - ReferenceWrapperHeap(WrapsB& a) : b(a.getb()){}; - B* b; -}; - -ReferenceWrapperHeap getwrapperHeap() { - WrapsB a(1); - return a; // We store a.b in ReferenceWrapper, but we delete a.b in the - // destructor of WrapsB -} - -int FN_reference_wrapper_heap_bad() { - ReferenceWrapperHeap rw = getwrapperHeap(); - return rw.b->f; // we want to report use after lifetime bug here -} - -struct ReferenceWrapperStack { - ReferenceWrapperStack(B& bref) : b(&bref){}; - B* b; -}; - -ReferenceWrapperStack getwrapperStack() { - B b(1); - return b; -} - -int FN_reference_wrapper_stack_bad() { - ReferenceWrapperStack rw = getwrapperStack(); - return rw.b->f; // we want to report use after lifetime bug here -} diff --git a/infer/tests/codetoanalyze/cpp/ownership/returns.cpp b/infer/tests/codetoanalyze/cpp/ownership/returns.cpp deleted file mode 100644 index 522285feb..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/returns.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include - -namespace returns { - -struct S { - int f_; - - S(int f) : f_(f) {} - - ~S() {} -}; - -const int& return_literal_stack_reference_bad() { return 1; } - -const int& return_variable_stack_reference1_bad() { - const int& x = 2; - return x; -} - -const int& return_variable_stack_reference2_bad() { - const int& x = 2; - const int& y = x; - return y; -} - -const int return_read_of_stack_reference_ok() { - const int& x = 2; - return x; -} - -const int& return_formal_reference_ok(int& formal) { return formal; } - -const int& return_reference_to_formal_pointer_ok(const int* formal) { - const int& local = *formal; - return local; -} - -extern const int& callee(); - -const int& return_reference_from_callee_ok() { - const int& local = callee(); - return local; -} - -const int return_int_ok() { return 1; } - -const bool return_comparison_temp_ok() { return 1 != 2; } - -const bool compare_local_refs_ok() { - const int& local1 = 1; - const int& local2 = 1; - return local1 != local2; -} - -extern int& global; - -const int& return_global_reference_ok() { return global; } - -struct MemberReference { - int& member1; - - int& return_member_reference_ok() { return member1; } - - int* member2; - int* return_member_reference_indirect_ok() { - int* local = member2; - return local; - } -}; - -extern const char* const kOptions; - -const char* return_field_addr_ternary_ok() { - const char* const* const t = &kOptions; - return t ? *t : ""; -} - -int* return_stack_pointer_bad() { - int x = 3; - return &x; -} - -S* return_static_local_ok() { - S* local; - static S s{1}; - local = &s; - return local; -} - -S* return_static_local_inner_scope_ok(bool b) { - S* local = nullptr; - if (b) { - static S s{1}; - local = &s; - // destructor for s gets called here, but it shouldn't be - } - return local; -} - -int* return_formal_pointer_ok(int* formal) { return formal; } - -int* return_deleted_bad() { - int* x = new int; - *x = 2; - delete x; - return x; -} - -// this *could* be ok depending on what the destructor does, but there's -// probably no good reason to do it -S* FN_return_destructed_pointer_bad() { - S* s = new S(1); - s->~S(); - return s; -} - -const char* return_nullptr1_ok() { return nullptr; } - -const char* return_nullptr2_ok() { - const char* local = nullptr; - return local; -} - -struct A { - ~A(); -}; - -int try_catch_return_ok() { - A a; - try { - return 1; - } catch (...) { - return 2; - } -} - -} // namespace returns diff --git a/infer/tests/codetoanalyze/cpp/ownership/use_after_delete.cpp b/infer/tests/codetoanalyze/cpp/ownership/use_after_delete.cpp deleted file mode 100644 index 357e239a8..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/use_after_delete.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include - -struct Simple { - int f; -}; - -void deref_deleted_bad() { - auto s = new Simple{1}; - delete s; - Simple tmp = *s; -} - -Simple* return_deleted_bad() { - auto s = new Simple{1}; - delete s; - return s; -} - -Simple* reassign_deleted_ok() { - auto s = new Simple{1}; - delete s; - s = new Simple{2}; - return s; -} - -void reassign_field_of_deleted_bad() { - auto s = new Simple{1}; - delete s; - s->f = 7; -} - -void reassign_field_of_reinitialized_ok(Simple* tmp) { - auto s = new Simple{1}; - delete s; - s = tmp; - s->f = 7; -} - -void double_delete_bad() { - auto s = new Simple{1}; - delete s; - delete s; -} - -Simple* delete_in_branch_bad(bool b) { - auto s = new Simple{1}; - if (b) { - delete s; - } - return s; -} - -void delete_in_branch_ok(bool b) { - auto s = new Simple{1}; - if (b) { - delete s; - } else { - delete s; - } -} - -void use_in_branch_bad(bool b) { - auto s = new Simple{1}; - delete s; - if (b) { - auto tmp = *s; - } -} - -void delete_in_loop_bad() { - auto s = new Simple{1}; - for (int i = 0; i < 10; i++) { - delete s; - } -} - -void delete_in_loop_ok() { - for (int i = 0; i < 10; i++) { - auto s = new Simple{1}; - delete s; - } -} - -void delete_ref_in_loop_ok(int j, std::vector v) { - int i = 0; - for (int i = 0; i < 10; i++) { - auto s = &v[i]; - delete s; - } -} - -void use_in_loop_bad() { - auto s = new Simple{1}; - delete s; - for (int i = 0; i < 10; i++) { - s->f = i; - } -} - -Simple* gated_delete_abort_ok(bool b) { - auto s = new Simple{1}; - if (b) { - delete s; - std::abort(); - } - return s; -} - -Simple* gated_exit_abort_ok(bool b) { - auto s = new Simple{1}; - if (b) { - delete s; - exit(1); - } - return s; -} - -Simple* gated_delete_throw_ok(bool b) { - auto s = new Simple{1}; - if (b) { - delete s; - throw 5; - } - return s; -} diff --git a/infer/tests/codetoanalyze/cpp/ownership/use_after_destructor.cpp b/infer/tests/codetoanalyze/cpp/ownership/use_after_destructor.cpp deleted file mode 100644 index 001fcea98..000000000 --- a/infer/tests/codetoanalyze/cpp/ownership/use_after_destructor.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include - -struct S { - int* f; - S(int i) { - f = new int; - *f = i; - } - - // missing: operator= to copy the pointer. double delete can happen if - // operator= is called - - ~S() { delete f; } -}; - -// destructor called at end of function, no issues -void normal_scope_destructor_ok() { S s(1); } - -void nested_scope_destructor_ok() { - { S s(1); } -} - -int reinit_after_explicit_destructor_ok() { - S s(1); - s.~S(); - s = S(2); - return *s.f; -} - -void placement_new_explicit_destructor_ok() { - char buf[sizeof(S)]; - { - S* s = new (buf) S(1); - s->~S(); - } - { - // this use of [buf] shouldn't be flagged - S* s = new (buf) S(2); - s->~S(); - } -} - -void double_destructor_bad() { - S s(1); - s.~S(); - // destructor will be called again after S goes out of scope, which is - // undefined behavior -} - -int use_after_destructor_bad() { - S s(1); - s.~S(); - int ret = *s.f; - s = S{2}; - return ret; -} - -// can't get this yet because we assume operator= copies resources correctly -// (but this isn't true for S) -void FN_use_after_scope1_bad() { - S s(1); - { - S tmp(2); - s = tmp; // translated as operator=(s, tmp) - } // destructor for tmp runs here - // destructor for s here; second time the value held by s has been destructed -} - -void FN_use_after_scope2_bad() { - S s(1); - { - s = S(1); - } // destructor runs here, but our frontend currently doesn't insert it -} - -struct POD { - int f; -}; - -// this code is ok since double-destructing POD struct is ok -void destruct_twice_ok() { - POD p{1}; - { - POD tmp{2}; - p = tmp; - } // destructor for tmp -} // destructor for p runs here, but it's harmless - -class Subclass : virtual POD { - int* f; - Subclass() { f = new int; } - - /** frontend code for this destructor will be: - * ~Subclass: - * __infer_inner_destructor_~Subclass(this) - * __infer_inner_destructor_~POD(this) - * - * __infer_inner_destructor_~Subclass: - * delete f; - * - * We need to be careful not to warn that this has been double-destructed - */ - ~Subclass() { delete f; } -}; - -void basic_placement_new_ok() { - S* ptr = new S(1); - S* tptr = new (ptr) S(1); - tptr->~S(); - delete[] ptr; -} - -S* destruct_pointer_contents_then_placement_new1_ok(S* s) { - s->~S(); - new (s) S(1); - return s; -} - -// need better heap abstraction to catch this example and the next -S* FN_placement_new_aliasing1_bad() { - S* s = new S(1); - s->~S(); - auto alias = new (s) S(2); - delete alias; // this deletes s too - return s; // bad, returning freed memory -} - -S* FN_placement_new_aliasing2_bad() { - S* s = new S(1); - s->~S(); - auto alias = new (s) S(2); - delete s; // this deletes alias too - return alias; // bad, returning freed memory -} - -void placement_new_non_var_ok() { - struct M { - S* s; - } m; - m.s = new S(1); - m.s->~S(); - new (m.s) S(2); - delete m.s; -} - -void return_placement_new_ok() { - auto mem = new S(1); - return new (mem) S(2); -} - -void destructor_in_loop_ok() { - for (int i = 0; i < 10; i++) { - S s(1); - } -} - -int FN_use_after_scope3_bad() { - int* p; - { - int value = 3; - p = &value; - } // we do not know in the plugin that value is out of scope - return *p; -} - -struct C { - C(int v) : f(v){}; - ~C(); - int f; -}; - -int use_after_scope4_bad() { - C* pc; - { - C c(3); - pc = &c; - } - return pc->f; -} - -struct B { - ~B(); -}; - -struct A { - ~A() { (void)*f; } - const B* f; -}; - -void destructor_order_bad() { - A a; - B b; - a.f = &b; -} - -struct A2 { - ~A2() {} - const B* f; -}; - -// need interprocedural analysis to fix this -void FP_destructor_order_empty_destructor_ok() { - A2 a; - B b; - a.f = &b; -}