Open-source lifetime inference/verification code.

PiperOrigin-RevId: 450954978
diff --git a/lifetime_analysis/test/basic.cc b/lifetime_analysis/test/basic.cc
new file mode 100644
index 0000000..d442510
--- /dev/null
+++ b/lifetime_analysis/test/basic.cc
@@ -0,0 +1,462 @@
+// 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", "-> static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StringLiteral) {
+  EXPECT_THAT(GetLifetimes(R"(
+    const char* target() {
+      return "this is a string literal";
+    }
+  )"),
+              LifetimesAre({{"target", "-> static"}}));
+}
+
+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, UndefinedFunction_NoLifetimeElision) {
+  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, UndefinedFunction_LifetimeElision) {
+  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"}}));
+}
+
+}  // namespace
+}  // namespace lifetimes
+}  // namespace tidy
+}  // namespace clang