Summary: Replaced by pulse. `--ownership` is now a deprecated form of `--pulse`. The ownership checker is starting to give wrong answers due to changes in the clang frontend, so it's better to remove it in favour of pulse. there_goes_my_hero Reviewed By: ngorogiannis Differential Revision: D16107650 fbshipit-source-id: bb2446a19master
parent
bc208ee9c8
commit
c89a8d3e63
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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 <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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<Aggregate> 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<int> numbers) {
|
|
||||||
int sum;
|
|
||||||
for (auto number : numbers) {
|
|
||||||
A a = getA();
|
|
||||||
sum += a.f(number);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
@ -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 <functional>
|
|
||||||
|
|
||||||
struct S {
|
|
||||||
int f;
|
|
||||||
|
|
||||||
S() { f = 1; }
|
|
||||||
};
|
|
||||||
|
|
||||||
int ref_capture_destroy_invoke_bad() {
|
|
||||||
std::function<int()> 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<int()> f;
|
|
||||||
{
|
|
||||||
auto s = S();
|
|
||||||
f = [&] { return s.f; };
|
|
||||||
}
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
|
|
||||||
int FN_reassign_lambda_capture_destroy_invoke_bad() {
|
|
||||||
std::function<int()> 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<int()> f;
|
|
||||||
{
|
|
||||||
S s;
|
|
||||||
f = [s] { return s.f; };
|
|
||||||
}
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
|
|
||||||
// same thing here
|
|
||||||
int implicit_value_capture_destroy_invoke_ok() {
|
|
||||||
std::function<int()> f;
|
|
||||||
{
|
|
||||||
S s;
|
|
||||||
f = [=] { return s.f; };
|
|
||||||
}
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ref_capture_invoke_ok() {
|
|
||||||
std::function<int()> f;
|
|
||||||
int ret;
|
|
||||||
{
|
|
||||||
S s;
|
|
||||||
f = [&s] { return s.f; };
|
|
||||||
ret = f();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void invoke_twice_ok() {
|
|
||||||
std::function<int()> f;
|
|
||||||
int ret;
|
|
||||||
{
|
|
||||||
S s;
|
|
||||||
f = [&s] { return s.f; };
|
|
||||||
f();
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::function<int()> ref_capture_read_lambda_ok() {
|
|
||||||
std::function<int()> 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<int()> FN_ref_capture_return_lambda_bad() {
|
|
||||||
std::function<int()> 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();
|
|
||||||
}
|
|
@ -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]
|
|
@ -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
|
|
||||||
}
|
|
@ -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 <string>
|
|
||||||
|
|
||||||
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
|
|
@ -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 <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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<std::string> 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;
|
|
||||||
}
|
|
@ -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 <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
Loading…
Reference in new issue