| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| // Tests that control flow is taken into account correctly. |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "lifetime_analysis/test/lifetime_analysis_test.h" |
| |
| namespace clang { |
| namespace tidy { |
| namespace lifetimes { |
| namespace { |
| |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentWithControlFlow) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* get_lesser_of(int* a, int* b) { |
| if (*a < *b) { |
| return a; |
| } |
| return b; |
| } |
| )"), |
| LifetimesAre({{"get_lesser_of", "a, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReturnPtrArgumentWithConditionalOperator) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* get_lesser_of(int* a, int* b) { |
| return *a < *b? a : b; |
| } |
| )"), |
| LifetimesAre({{"get_lesser_of", "a, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReturnRefArgumentWithConditionalOperator) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int& get_lesser_of(int& a, int& b) { |
| return a < b? a : b; |
| } |
| )"), |
| LifetimesAre({{"get_lesser_of", "a, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ControlFlowExceptionsWorkSometimes) { |
| // This test documents that we do understand the control flow resulting from |
| // exceptions in some limited circumstances. However, this is not true in the |
| // general case -- see the test ControlFlowExceptionsNotSupportedInGeneral -- |
| // and it's a non-goal to add support for this. |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* a, int* b) { |
| try { |
| throw 42; |
| return a; |
| } catch(...) { |
| return b; |
| } |
| } |
| )"), |
| LifetimesAre({{"target", "a, b -> b"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ControlFlowExceptionsNotSupportedInGeneral) { |
| // This test documents that we do not in general treat the control flow |
| // resulting from exceptions correctly; changing this is a non-goal. |
| EXPECT_THAT(GetLifetimes(R"( |
| void may_throw() { |
| throw 42; |
| } |
| int* target(int* a, int* b) { |
| try { |
| may_throw(); |
| return a; |
| } catch(...) { |
| return b; |
| } |
| } |
| )"), |
| LifetimesContain({{"target", "a, b -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, DoublePointerWithConditionalAssignment) { |
| // This is a regression test for a bug where we were not taking all |
| // substitutions into account in the return value lifetimes. |
| |
| EXPECT_THAT(GetLifetimes(R"( |
| int** target(int** pp1, int** pp2) { |
| if (**pp1 > **pp2) { |
| *pp1 = *pp2; |
| } |
| return pp2; |
| } |
| )"), |
| LifetimesAre({{"target", "(a, b), (a, c) -> (a, c)"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentWithControlFlowAndJoin) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* get_lesser_of(int* a, int* b) { |
| int* p = a; |
| if (*a < *b) { |
| p = b; |
| } |
| return p; |
| } |
| )"), |
| LifetimesAre({{"get_lesser_of", "a, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentWithUnnecessaryAssignment) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a, int* b) { |
| for (int i=0; i<*a; i++) { |
| p = a; |
| p = b; |
| } |
| return p; |
| } |
| )"), |
| LifetimesAre({{"target", "a, b, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceInControlFlow) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a) { |
| int local = 42; |
| int** pp = &a; |
| if (*a < *p) { |
| pp = &p; |
| } |
| p = &local; |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", |
| "ERROR: function returns reference to a local"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceEndOfBlock) { |
| // Make sure that the analysis handles statement ordering correctly. |
| |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* a) { |
| int local = 42; |
| int** pp = &a; |
| int* b = a; |
| int* p = a; |
| if (*p < *a) { |
| p = b; |
| pp = &p; |
| b = &local; |
| } |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", "a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceSneaky) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a) { |
| int local = 42; |
| int** pp = &a; |
| int* b = a; |
| for (int i=0; i<*a; i++) { |
| p = b; |
| pp = &p; |
| b = &local; |
| } |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", |
| "ERROR: function returns reference to a local"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceSneakyParam) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a, int* c) { |
| int** pp = &a; |
| int* b = a; |
| for (int i=0; i<*a; i++) { |
| p = b; |
| pp = &p; |
| b = c; |
| } |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", "a, a, a -> a"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceAndOverwrite) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a, int* b) { |
| int** pp = &a; |
| if (*a < *p) { |
| pp = &p; |
| } |
| p = b; |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", "a, b, b -> b"}})); |
| } |
| |
| TEST_F(LifetimeAnalysisTest, TakeAReferenceTooStrict) { |
| EXPECT_THAT(GetLifetimes(R"( |
| int* target(int* p, int* a) { |
| int** pp = &a; |
| if (*p < *a) { |
| p = a; |
| pp = &p; |
| } |
| return *pp; |
| } |
| )"), |
| LifetimesAre({{"target", "a, a -> a"}})); |
| // TODO(mboehme): This result is too strict. This is because at the |
| // return statement, the analysis concludes that |
| // - pp may be pointing at either p or a, and |
| // - p may either still have its original value or it may be pointing at a |
| // The analysis doesn't "know" that the combination "p has original value and |
| // pp points at p" can never occur. It may be possible to solve this with path |
| // conditions -- IIUC, this is exactly what they are for. |
| } |
| |
| } // namespace |
| } // namespace lifetimes |
| } // namespace tidy |
| } // namespace clang |