diff --git a/infer/tests/codetoanalyze/cpp/pulse/Makefile b/infer/tests/codetoanalyze/cpp/pulse/Makefile index 3e1d1d1e7..248da497e 100644 --- a/infer/tests/codetoanalyze/cpp/pulse/Makefile +++ b/infer/tests/codetoanalyze/cpp/pulse/Makefile @@ -10,7 +10,7 @@ CLANG_OPTIONS = -x c++ -std=c++14 -Wc++1z-extensions -nostdinc++ -isystem$(MODEL INFER_OPTIONS = --pulse-only --debug-exceptions --project-root $(TESTS_DIR) INFERPRINT_OPTIONS = --issues-tests -SOURCES = $(wildcard ../ownership/*.cpp) $(wildcard *.cpp) +SOURCES = $(wildcard *.cpp) include $(TESTS_DIR)/clang.make diff --git a/infer/tests/codetoanalyze/cpp/pulse/basics.cpp b/infer/tests/codetoanalyze/cpp/pulse/basics.cpp new file mode 100644 index 000000000..6c06cd2e6 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/basics.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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 FP_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 FP_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 FP_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* FP_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 FP_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/pulse/closures.cpp b/infer/tests/codetoanalyze/cpp/pulse/closures.cpp new file mode 100644 index 000000000..ca6dfeee7 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/closures.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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 FN_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 FN_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 +} + +int ref_capture_return_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_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/pulse/issues.exp b/infer/tests/codetoanalyze/cpp/pulse/issues.exp index e79872112..a213dd015 100644 --- a/infer/tests/codetoanalyze/cpp/pulse/issues.exp +++ b/infer/tests/codetoanalyze/cpp/pulse/issues.exp @@ -1,33 +1,33 @@ -codetoanalyze/cpp/ownership/basics.cpp, aggregate_reassign2_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `AggregateWithConstructedField_~AggregateWithConstructedField(&(a))` at line 39, column 3 here,accessed `&(a)` here] -codetoanalyze/cpp/ownership/basics.cpp, aggregate_reassign3_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `NestedAggregate_~NestedAggregate(&(a))` at line 53, column 3 here,accessed `&(a)` here] -codetoanalyze/cpp/ownership/basics.cpp, aggregate_reassign_ok, 7, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `Aggregate_~Aggregate(&(s))` at line 25, column 3 here,accessed `&(s)` here] -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_branch_bad, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 60, column 5 here,accessed `*(ptr)` here] -codetoanalyze/cpp/ownership/basics.cpp, multiple_invalidations_loop_bad, 8, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 70, column 7 here,accessed `*(ptr)` here] -codetoanalyze/cpp/ownership/basics.cpp, pointer_arithmetic_ok, 2, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `Aggregate_~Aggregate(a)` at line 77, column 3 here,accessed `a` here] -codetoanalyze/cpp/ownership/basics.cpp, struct_inside_loop_ok, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `A_~A(&(a))` at line 100, column 3 here,accessed `&(a)` here] -codetoanalyze/cpp/ownership/returns.cpp, returns::FN_return_destructed_pointer_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `returns::S_~S(s)` at line 120, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/returns.cpp, returns::return_deleted_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete x` at line 112, column 3 here,accessed `x` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, delete_in_branch_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 57, column 5 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, delete_in_loop_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 82, column 5 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, delete_ref_in_loop_ok, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 97, column 5 here,accessed `*(v.__internal_array)[_]` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, deref_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 18, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, double_delete_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 50, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, gated_delete_abort_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 112, column 5 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, gated_delete_throw_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 130, column 5 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, gated_exit_abort_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 121, column 5 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, reassign_field_of_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 37, column 3 here,accessed `s->f` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, return_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 24, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, use_in_branch_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 73, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_delete.cpp, use_in_loop_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 103, column 3 here,accessed `s->f` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, FN_placement_new_aliasing1_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete alias` at line 133, column 3 here,accessed `s` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, FN_placement_new_aliasing2_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 141, column 3 here,accessed `alias` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, basic_placement_new_ok, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(tptr)` at line 118, column 3 here,accessed `ptr` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, destructor_in_loop_ok, 2, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 163, column 3 here,accessed `&(s)` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, double_destructor_bad, 5, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 54, column 3 here,accessed `&(s)` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, reinit_after_explicit_destructor_ok, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 34, column 3 here,accessed `&(s)` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, use_after_destructor_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 61, column 3 here,accessed `&(s)` here] -codetoanalyze/cpp/ownership/use_after_destructor.cpp, use_after_scope4_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `C_~C(&(c))` at line 186, column 3 here,accessed `pc->f` here] +codetoanalyze/cpp/pulse/basics.cpp, FP_aggregate_reassign2_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `AggregateWithConstructedField_~AggregateWithConstructedField(&(a))` at line 39, column 3 here,accessed `&(a)` here] +codetoanalyze/cpp/pulse/basics.cpp, FP_aggregate_reassign3_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `NestedAggregate_~NestedAggregate(&(a))` at line 53, column 3 here,accessed `&(a)` here] +codetoanalyze/cpp/pulse/basics.cpp, FP_aggregate_reassign_ok, 7, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `Aggregate_~Aggregate(&(s))` at line 25, column 3 here,accessed `&(s)` here] +codetoanalyze/cpp/pulse/basics.cpp, FP_pointer_arithmetic_ok, 2, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `Aggregate_~Aggregate(a)` at line 77, column 3 here,accessed `a` here] +codetoanalyze/cpp/pulse/basics.cpp, FP_struct_inside_loop_ok, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `A_~A(&(a))` at line 100, column 3 here,accessed `&(a)` here] +codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_branch_bad, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 60, column 5 here,accessed `*(ptr)` here] +codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_loop_bad, 8, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 70, column 7 here,accessed `*(ptr)` here] codetoanalyze/cpp/pulse/join.cpp, visit_list, 11, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete result` at line 21, column 5 here,accessed `*(result)` here] +codetoanalyze/cpp/pulse/returns.cpp, returns::return_deleted_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete x` at line 112, column 3 here,accessed `x` here] +codetoanalyze/cpp/pulse/returns.cpp, returns::return_destructed_pointer_bad, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `returns::S_~S(s)` at line 120, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, FP_delete_ref_in_loop_ok, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 97, column 5 here,accessed `*(v.__internal_array)[_]` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, FP_gated_delete_abort_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 112, column 5 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, FP_gated_delete_throw_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 130, column 5 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, FP_gated_exit_abort_ok, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 121, column 5 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, delete_in_branch_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 57, column 5 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, delete_in_loop_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 82, column 5 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, deref_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 18, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, double_delete_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 50, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, reassign_field_of_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 37, column 3 here,accessed `s->f` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, return_deleted_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 24, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, use_in_branch_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 73, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_delete.cpp, use_in_loop_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 103, column 3 here,accessed `s->f` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, FP_basic_placement_new_ok, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(tptr)` at line 118, column 3 here,accessed `ptr` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, FP_destructor_in_loop_ok, 2, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 162, column 3 here,accessed `&(s)` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, FP_reinit_after_explicit_destructor_ok, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 34, column 3 here,accessed `&(s)` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, double_destructor_bad, 5, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 54, column 3 here,accessed `&(s)` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, placement_new_aliasing1_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete alias` at line 132, column 3 here,accessed `s` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, placement_new_aliasing2_bad, 5, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete s` at line 140, column 3 here,accessed `alias` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, use_after_destructor_bad, 4, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `S_~S(&(s))` at line 61, column 3 here,accessed `&(s)` here] +codetoanalyze/cpp/pulse/use_after_destructor.cpp, use_after_scope4_bad, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidated by destructor call `C_~C(&(c))` at line 185, column 3 here,accessed `pc->f` here] codetoanalyze/cpp/pulse/use_after_free.cpp, use_after_free_simple_bad, 2, USE_AFTER_FREE, no_bucket, ERROR, [invalidated by call to `free(x)` at line 10, column 3 here,accessed `*(x)` here] codetoanalyze/cpp/pulse/vector.cpp, FP_init_fill_then_push_back_loop_ok, 6, USE_AFTER_LIFETIME, no_bucket, ERROR, [potentially invalidated by call to `std::vector::push_back(&(vec), ..)` at line 56, column 5 here,accessed `*(elt)` here] codetoanalyze/cpp/pulse/vector.cpp, FP_push_back_in_loop_ok, 3, USE_AFTER_LIFETIME, no_bucket, ERROR, [potentially invalidated by call to `std::vector::push_back(vec, ..)` at line 31, column 5 here,accessed `*(vec.__internal_array)[_]` here] diff --git a/infer/tests/codetoanalyze/cpp/pulse/reference_wrapper.cpp b/infer/tests/codetoanalyze/cpp/pulse/reference_wrapper.cpp new file mode 100644 index 000000000..9b1ff26f3 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/reference_wrapper.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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/pulse/returns.cpp b/infer/tests/codetoanalyze/cpp/pulse/returns.cpp new file mode 100644 index 000000000..d9bcb4c5c --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/returns.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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& FN_return_literal_stack_reference_bad() { return 1; } + +const int& FN_return_variable_stack_reference1_bad() { + const int& x = 2; + return x; +} + +const int& FN_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* FN_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* 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/pulse/use_after_delete.cpp b/infer/tests/codetoanalyze/cpp/pulse/use_after_delete.cpp new file mode 100644 index 000000000..011b6cc9a --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/use_after_delete.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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 FP_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* FP_gated_delete_abort_ok(bool b) { + auto s = new Simple{1}; + if (b) { + delete s; + std::abort(); + } + return s; +} + +Simple* FP_gated_exit_abort_ok(bool b) { + auto s = new Simple{1}; + if (b) { + delete s; + exit(1); + } + return s; +} + +Simple* FP_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/pulse/use_after_destructor.cpp b/infer/tests/codetoanalyze/cpp/pulse/use_after_destructor.cpp new file mode 100644 index 000000000..6ebc0f75f --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/pulse/use_after_destructor.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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 FP_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 FP_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; +} + +S* 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* 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 FP_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 FN_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; +}