| // 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 for basic functionality. | |
| #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, CompilationError) { | |
| // Check that we don't analyze code that doesn't compile. | |
| // This is a regression test -- we actually used to produce the lifetimes | |
| // "a -> a" for this test. | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* a) { | |
| undefined(&a); | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"", "Compilation error -- see log for details"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, CompilationErrorFallback) { | |
| // Allow analysis of broken code to check that our fallback for detecting | |
| // expressions containing errors works. | |
| AnalyzeBrokenCode(); | |
| EXPECT_THAT( | |
| GetLifetimes(R"( | |
| int* target(int* a) { | |
| undefined(&a); | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre( | |
| {{"target", "ERROR: encountered an expression containing errors"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, CompilationErrorFromWerrorDoesNotPreventAnalysis) { | |
| // Warnings upgraded through -Werror should not prevent analysis. | |
| EXPECT_THAT(GetLifetimes(R"( | |
| #pragma clang diagnostic push | |
| #pragma clang diagnostic error "-Wunused-variable" | |
| int* target(int* a) { | |
| int i = 0; | |
| return a; | |
| } | |
| #pragma clang diagnostic pop | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, NoLifetimes) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void target() { | |
| } | |
| )"), | |
| LifetimesAre({{"target", ""}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, NoLifetimesArithmetic) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int target(int a, int b) { | |
| return (a + b) - (-b) * a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "(), ()"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, PointerToMemberDoesNotGetLifetime) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| struct S {}; | |
| void target(S* s, int S::*ptr_to_member) {} | |
| )"), | |
| LifetimesAre({{"target", "a, ()"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, UnconstrainedParameter) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void target(int* a) { | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentPtr) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* a) { | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentPtrInitList) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* a) { | |
| return { a }; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnArgumentRef) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target(int& a) { | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnFirstArgumentPtr) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* a, int* b) { | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a, b -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnFirstArgumentRef) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target(int& a, int& b) { | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a, b -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnRefFromPtr) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target(int* a) { | |
| return *a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnPtrFromRef) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int& a) { | |
| return &a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnDereferencedArgument) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int** a) { | |
| return *a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "(a, b) -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalViaPtr) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target() { | |
| int a = 42; | |
| return &a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalViaRef) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target() { | |
| int a = 42; | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnStaticViaPtr) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target() { | |
| static int a = 42; | |
| return &a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "-> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, StringLiteral) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| const char* target() { | |
| return "this is a string literal"; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "-> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, OutParameter) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void target(int& a) { | |
| a = 42; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, AssigningToPtrParamDoesNotChangeLifetime) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void target(int* p) { | |
| int a = 42; | |
| p = &a; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, PtrInitializationTransfersLifetimes) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* p) { | |
| int* p2 = p; | |
| return p2; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, PtrAssignmentTransfersLifetimes) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* p) { | |
| int* p2; | |
| p2 = p; | |
| return p2; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, RefInitializationTransfersLifetimes) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target(int& r) { | |
| int& r2 = r; | |
| return r2; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, RefAssignmentDoesNotTransferLifetimes) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int& target(int& r) { | |
| int a = 42; | |
| int& r2 = a; | |
| r2 = r; | |
| return r2; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalSneaky_Initialization) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* arg1) { | |
| // Initialization should be aware that outer pointer is invariant in its | |
| // type. | |
| int** pp = &arg1; | |
| int local = 42; | |
| *pp = &local; | |
| return arg1; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalSneaky_Assignment) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* arg1) { | |
| // Assignment should be aware that outer pointer is invariant in its type. | |
| int** pp; | |
| pp = &arg1; | |
| int local = 42; | |
| *pp = &local; | |
| return arg1; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalSneaky2_Initialization) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* arg1) { | |
| // Initialization should be aware that outer pointer is invariant in its | |
| // type. | |
| int** pp = &arg1; | |
| int local = 42; | |
| arg1 = &local; | |
| return *pp; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalSneaky2_Assignment) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* arg1) { | |
| // Assignment should be aware that outer pointer is invariant in its type. | |
| int** pp; | |
| pp = &arg1; | |
| int local = 42; | |
| arg1 = &local; | |
| return *pp; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ReturnLocalSneaky3_Initialization) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* arg1) { | |
| int*& pp = arg1; | |
| int local = 42; | |
| arg1 = &local; | |
| return pp; | |
| } | |
| )"), | |
| LifetimesAre({{"target", | |
| "ERROR: function returns reference to a local"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, SwapPointers) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void swap_ptr(int** pp1, int** pp2) { | |
| int* tmp = *pp2; | |
| *pp2 = *pp1; | |
| *pp1 = tmp; | |
| } | |
| )"), | |
| LifetimesAre({{"swap_ptr", "(a, b), (a, c)"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, DuplicatePointer) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| void duplicate_ptr(int* from, int** to1, int** to2) { | |
| *to1 = from; | |
| *to2 = from; | |
| } | |
| )"), | |
| LifetimesAre({{"duplicate_ptr", "a, (a, b), (a, c)"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, Aliasing) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int** a, int** b, int* c) { | |
| *a = c; | |
| return *b; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "(a, b), (c, d), a -> c"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, IncompleteType) { | |
| // Test that we can handle pointers to incomplete types. | |
| EXPECT_THAT(GetLifetimes(R"( | |
| struct S; | |
| S* target(S* s) { | |
| return s; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, DISABLED_IncompleteTypeTemplate) { | |
| // TODO(mboehme): Disabled because it returns the wrong lifetimes. | |
| // S<int*> is never instantiated because we only deal with pointers to it, | |
| // so it's an incomplete type. | |
| // | |
| // We can handle incomplete types in principle, but in this case, because | |
| // we don't create any pointees for the fields of `S<int*>`, we will produce | |
| // these incorrect lifetimes: | |
| // (a, b) -> (c, b) | |
| // Even more strangely, the lifetimes we infer change (to the correct ones) | |
| // once we happen to instantiate S<int*> somewhere else in the same | |
| // translation unit. | |
| // | |
| // I'm not sure how best to solve this. We could simply force instantiation | |
| // of all uninstantiated templates we see, but I believe this might change the | |
| // semantics of the program in subtle ways. | |
| // | |
| // The better alternative seems to be: If we're unifying lifetimes of an | |
| // object that is of an instantiated class template type, unify the lifetimes | |
| // of its template arguments too. This can be overly restrictive -- think of a | |
| // class template that doesn't actually use its template arguments in any of | |
| // its fields, e.g. `template <class T> struct S {};`. However, it seems to be | |
| // the only option that produces consistent results without requiring us to | |
| // instantiate class templates that could otherwise be used as incomplete | |
| // types. | |
| EXPECT_THAT(GetLifetimes(R"( | |
| template <class T> | |
| struct S { | |
| T t; | |
| }; | |
| S<int*>* target(S<int*>* s) { | |
| return s; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "(a, b) -> (a, b)"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, UndefinedFunctionNoLifetimeElision) { | |
| EXPECT_THAT( | |
| GetLifetimes(R"( | |
| int* f(int* a); | |
| int* target(int* a) { | |
| return f(a); | |
| } | |
| )"), | |
| LifetimesAre({{"f", "ERROR: Lifetime elision not enabled for 'f'"}, | |
| {"target", | |
| "ERROR: No lifetimes for callee 'f': Lifetime elision not " | |
| "enabled for 'f'"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, UndefinedFunctionLifetimeElision) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| #pragma clang lifetime_elision | |
| int* f(int* a); | |
| int* target(int* a) { | |
| return f(a); | |
| } | |
| )"), | |
| LifetimesAre({{"f", "a -> a"}, {"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, ForwardDeclaration) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* f(int* a); | |
| int* target(int* a) { | |
| return f(a); | |
| } | |
| int* f(int* a) { | |
| return a; | |
| } | |
| )"), | |
| LifetimesAre({{"f", "a -> a"}, {"target", "a -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, OverwriteSingleDestination) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| int* target(int* a, int* b) { | |
| int** pp = &b; | |
| // There is only one thing that `pp` can be pointing at, so the analysis | |
| // should conclude that `b` is being overwritten with `a`. | |
| *pp = a; | |
| return b; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a, b -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, DISABLED_OverwriteSingleDestinationVariant) { | |
| // TODO(veluca): analysis currently concludes something "overly restrictive" | |
| // on this function. Figure out when we run on real world code whether this is | |
| // an actual problem we might want to do something about. | |
| EXPECT_THAT(GetLifetimes(R"( | |
| // Similar to above, but potentially leave `pp` uninitialized. | |
| int* target(int* a, int* b) { | |
| int** pp; | |
| if (*a > 0) { | |
| pp = &b; | |
| } | |
| // If `pp` is uninitialized, the following is UB, so the analysis can | |
| // assume that `pp` was initialized to point to `b`. | |
| // This particular test function is pretty terrible style, but it seems | |
| // plausible that similar situations can come up in more reasonable code. | |
| *pp = a; | |
| return b; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a, b -> a"}})); | |
| } | |
| TEST_F(LifetimeAnalysisTest, OverwriteMultipleDestinations) { | |
| EXPECT_THAT(GetLifetimes(R"( | |
| // This is a regression test. The analysis used to conclude falsely that `b` | |
| // was unconditionally being overwritten with `a` in the assignment and was | |
| // therefore producing the wrong lifetimes "a, b -> a". | |
| int* target(int* a, int* b) { | |
| int** pp = *a > 0? &a : &b; | |
| // The analysis should understand that the following assignment _might_ | |
| // overwrite `b` with `a` but does not necessarily do so. | |
| *pp = a; | |
| return b; | |
| } | |
| )"), | |
| LifetimesAre({{"target", "a, a -> a"}})); | |
| } | |
| } // namespace | |
| } // namespace lifetimes | |
| } // namespace tidy | |
| } // namespace clang |