delete ownership checker

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: bb2446a19
master
Jules Villard 5 years ago committed by Facebook Github Bot
parent bc208ee9c8
commit c89a8d3e63

@ -60,15 +60,15 @@ DIRECT_TESTS += \
cpp_conflicts \ cpp_conflicts \
cpp_errors \ cpp_errors \
cpp_frontend \ cpp_frontend \
cpp_linters \ cpp_linters \
cpp_linters-for-test-only \ cpp_linters-for-test-only \
cpp_liveness \ cpp_liveness \
cpp_nullable \ cpp_nullable \
cpp_ownership cpp_pulse \ cpp_pulse \
cpp_quandary cpp_quandaryBO \ cpp_quandary cpp_quandaryBO \
cpp_racerd \ cpp_racerd \
cpp_siof \ cpp_siof \
cpp_starvation \ cpp_starvation \
cpp_uninit \ cpp_uninit \

@ -98,9 +98,8 @@ OPTIONS
--no-default-checkers --no-default-checkers
Deactivates: Default checkers: --biabduction, Deactivates: Default checkers: --biabduction,
--fragment-retains-view, --linters, --liveness, --ownership, --fragment-retains-view, --linters, --liveness, --racerd, --siof,
--racerd, --siof, --starvation, --uninit (Conversely: --starvation, --uninit (Conversely: --default-checkers)
--default-checkers)
--eradicate --eradicate
Activates: the eradicate @Nullable checker for Java annotations Activates: the eradicate @Nullable checker for Java annotations
@ -197,14 +196,6 @@ OPTIONS
Activates: Enable --nullsafe and disable all other checkers Activates: Enable --nullsafe and disable all other checkers
(Conversely: --no-nullsafe-only) (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 --perf-profiler-data-file file
Specify the file containing perf profiler data to read Specify the file containing perf profiler data to read

@ -287,9 +287,9 @@ OPTIONS
--no-default-checkers --no-default-checkers
Deactivates: Default checkers: --biabduction, Deactivates: Default checkers: --biabduction,
--fragment-retains-view, --linters, --liveness, --ownership, --fragment-retains-view, --linters, --liveness, --racerd, --siof,
--racerd, --siof, --starvation, --uninit (Conversely: --starvation, --uninit (Conversely: --default-checkers)
--default-checkers) See also infer-analyze(1). See also infer-analyze(1).
--no-default-linters --no-default-linters
Deactivates: Use the default linters for the analysis. Deactivates: Use the default linters for the analysis.
@ -781,14 +781,6 @@ OPTIONS
Activates: Show the list of reports and exit (Conversely: Activates: Show the list of reports and exit (Conversely:
--no-only-show) See also infer-explore(1). --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 --perf-profiler-data-file file
Specify the file containing perf profiler data to read Specify the file containing perf profiler data to read
See also infer-analyze(1). See also infer-analyze(1).

@ -287,9 +287,9 @@ OPTIONS
--no-default-checkers --no-default-checkers
Deactivates: Default checkers: --biabduction, Deactivates: Default checkers: --biabduction,
--fragment-retains-view, --linters, --liveness, --ownership, --fragment-retains-view, --linters, --liveness, --racerd, --siof,
--racerd, --siof, --starvation, --uninit (Conversely: --starvation, --uninit (Conversely: --default-checkers)
--default-checkers) See also infer-analyze(1). See also infer-analyze(1).
--no-default-linters --no-default-linters
Deactivates: Use the default linters for the analysis. Deactivates: Use the default linters for the analysis.
@ -781,14 +781,6 @@ OPTIONS
Activates: Show the list of reports and exit (Conversely: Activates: Show the list of reports and exit (Conversely:
--no-only-show) See also infer-explore(1). --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 --perf-profiler-data-file file
Specify the file containing perf profiler data to read Specify the file containing perf profiler data to read
See also infer-analyze(1). See also infer-analyze(1).

@ -614,7 +614,6 @@ and ( annotation_reachability
, liveness , liveness
, loop_hoisting , loop_hoisting
, nullsafe , nullsafe
, ownership
, printf_args , printf_args
, pulse , pulse
, purity , purity
@ -667,13 +666,13 @@ and ( annotation_reachability
mk_checker ~long:"nullsafe" mk_checker ~long:"nullsafe"
~deprecated:["-check-nullable"; "-suggest-nullable"] ~deprecated:["-check-nullable"; "-suggest-nullable"]
"[EXPERIMENTAL] Nullable type checker (incomplete: use --eradicate for now)" "[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 = and printf_args =
mk_checker ~long:"printf-args" ~default:false mk_checker ~long:"printf-args" ~default:false
"the detection of mismatch between the Java printf format strings and the argument types \ "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\", \ For, example, this checker will warn about the type error in `printf(\"Hello %d\", \
\"world\")`" \"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 purity = mk_checker ~long:"purity" ~default:false "[EXPERIMENTAL] Purity analysis"
and quandary = mk_checker ~long:"quandary" ~default:false "the quandary taint analysis" and quandary = mk_checker ~long:"quandary" ~default:false "the quandary taint analysis"
and quandaryBO = and quandaryBO =
@ -737,7 +736,6 @@ and ( annotation_reachability
, liveness , liveness
, loop_hoisting , loop_hoisting
, nullsafe , nullsafe
, ownership
, printf_args , printf_args
, pulse , pulse
, purity , purity
@ -2926,8 +2924,6 @@ and only_footprint = !only_footprint
and only_show = !only_show and only_show = !only_show
and ownership = !ownership
and passthroughs = !passthroughs and passthroughs = !passthroughs
and patterns_modeled_expensive = match patterns_modeled_expensive with k, r -> (k, !r) and patterns_modeled_expensive = match patterns_modeled_expensive with k, r -> (k, !r)

@ -516,8 +516,6 @@ val only_footprint : bool
val only_show : bool val only_show : bool
val ownership : bool
val perf_profiler_data_file : string option val perf_profiler_data_file : string option
val pmd_xml : bool val pmd_xml : bool

@ -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

@ -81,9 +81,6 @@ let all_checkers =
; callbacks= ; callbacks=
[ (Procedure NullabilitySuggest.checker, Language.Java) [ (Procedure NullabilitySuggest.checker, Language.Java)
; (Procedure NullabilitySuggest.checker, Language.Clang) ] } ; (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= "pulse"; active= Config.pulse; callbacks= [(Procedure Pulse.checker, Language.Clang)]}
; { name= "quandary" ; { name= "quandary"
; active= Config.quandary || Config.quandaryBO ; active= Config.quandary || Config.quandaryBO

@ -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…
Cancel
Save