Open-source lifetime inference/verification code.
PiperOrigin-RevId: 450954978
diff --git a/lifetime_analysis/test/BUILD b/lifetime_analysis/test/BUILD
new file mode 100644
index 0000000..c0c0cab
--- /dev/null
+++ b/lifetime_analysis/test/BUILD
@@ -0,0 +1,188 @@
+# Test utilities and tests for lifetime_analysis.
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "lifetime_analysis_test",
+ testonly = 1,
+ srcs = ["lifetime_analysis_test.cc"],
+ hdrs = ["lifetime_analysis_test.h"],
+ deps = [
+ "//lifetime_analysis:analyze",
+ "//lifetime_annotations/test:named_func_lifetimes",
+ "//lifetime_annotations/test:run_on_code",
+ "@absl//absl/container:flat_hash_map",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "builtin",
+ srcs = ["builtin.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "lifetime_params",
+ srcs = ["lifetime_params.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "virtual_functions",
+ srcs = ["virtual_functions.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "casts",
+ srcs = ["casts.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "initializers",
+ srcs = ["initializers.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "recursion",
+ srcs = ["recursion.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "function_templates",
+ srcs = ["function_templates.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "function_calls",
+ srcs = ["function_calls.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "execution_order",
+ srcs = ["execution_order.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "control_flow",
+ srcs = ["control_flow.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "basic",
+ srcs = ["basic.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "static_lifetime",
+ srcs = ["static_lifetime.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "arrays",
+ srcs = ["arrays.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "records",
+ srcs = ["records.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "inheritance",
+ srcs = ["inheritance.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "class_templates",
+ srcs = ["class_templates.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "initialization",
+ srcs = ["initialization.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "expr",
+ srcs = ["expr.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "defaulted_functions",
+ srcs = ["defaulted_functions.cc"],
+ deps = [
+ ":lifetime_analysis_test",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/lifetime_analysis/test/arrays.cc b/lifetime_analysis/test/arrays.cc
new file mode 100644
index 0000000..dae88a9
--- /dev/null
+++ b/lifetime_analysis/test/arrays.cc
@@ -0,0 +1,112 @@
+// 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 involving arrays.
+
+#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, ArrayOfInts) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void target() {
+ int x[] = {0};
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ArrayMergesLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void target(int** array, int* p, int* q) {
+ array[0] = p;
+ array[1] = q;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ArrayOfStructsMergesLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* i;
+ };
+ void target(S** array, S* p, S* q) {
+ array[0] = p;
+ array[1] = q;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b, c), (a, b), (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleArray) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, unsigned x) {
+ int* v[2];
+ v[0] = a;
+ v[1] = b;
+ return v[x & 1];
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleArrayInit) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, unsigned x) {
+ int* v[2] = {a, b};
+ return v[x & 1];
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleArrayInitConstExprSubscriptIndex) {
+ // There is a potential to track the lifetime of each array element
+ // separately, when the array's size and subscript indices are known
+ // statically. But is hard-to-impossible to do for all arrays. We treat an
+ // array as a single object as a result, and merge the points-to sets of all
+ // its elements.
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b) {
+ int* v[2] = {a, b};
+ return v[0];
+ }
+ )"),
+ LifetimesAre({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleArrayPtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, unsigned x) {
+ int* v[2];
+ *v = a;
+ *(v + 1) = b;
+ return *(v + (x & 1));
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleArrayFnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, int** c, unsigned x) {
+ *c = a;
+ *(c + 1) = b;
+ return *(c + (x & 1));
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, (a, b), () -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
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
diff --git a/lifetime_analysis/test/builtin.cc b/lifetime_analysis/test/builtin.cc
new file mode 100644
index 0000000..945aedd
--- /dev/null
+++ b/lifetime_analysis/test/builtin.cc
@@ -0,0 +1,122 @@
+// 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 involving builtins.
+
+#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, ReturnPtrFromRefAddressOf) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int& a) {
+ return __builtin_addressof(a);
+ }
+ )"),
+ LifetimesContain({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnDoublePtrFromRefAddressOf) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int** target(int*& a) {
+ return __builtin_addressof(a);
+ }
+ )"),
+ LifetimesContain({{"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, BuiltinNoLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int target(int a) {
+ return __builtin_labs(a);
+ }
+ )"),
+ LifetimesContain({{"target", "()"}}));
+}
+
+// TODO(veluca): add tests for the strto* functions.
+
+TEST_F(LifetimeAnalysisTest, BuiltinMemStrChr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void* memchr(void* a, int val, int num) {
+ return __builtin_memchr(a, val, num);
+ }
+ const char* strchr(const char* a, int val) {
+ return __builtin_strchr(a, val);
+ }
+ const char* strrchr(const char* a, int val) {
+ return __builtin_strrchr(a, val);
+ }
+ )"),
+ LifetimesContain({
+ {"memchr", "a, (), () -> a"},
+ {"strchr", "a, () -> a"},
+ {"strrchr", "a, () -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, BuiltinStrProcessing) {
+ EXPECT_THAT(GetLifetimes(R"(
+ const char* strstr(const char* a, const char* b) {
+ return __builtin_strstr(a, b);
+ }
+ const char* strpbrk(const char* a, const char* b) {
+ return __builtin_strpbrk(a, b);
+ }
+ )"),
+ LifetimesContain({
+ {"strstr", "a, b -> a"},
+ {"strpbrk", "a, b -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, BuiltinForward) {
+ EXPECT_THAT(GetLifetimes(R"(
+ namespace std {
+ // This is simplified from the actual definition of forward(), but it's
+ // all we need for this test.
+ template<class T>
+ T&& forward(T& t) noexcept {
+ return static_cast<T&&>(t);
+ }
+ }
+ int* target(int* a) {
+ return std::forward(a);
+ }
+ )"),
+ LifetimesContain({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, BuiltinMove) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ namespace std {
+ // This is simplified from the actual definition of move(), but it's all
+ // we need for this test.
+ template<class T>
+ T&& move(T&& t) noexcept {
+ return static_cast<T&&>(t);
+ }
+ }
+ int* move_int_ptr(int* a) {
+ return std::move(a);
+ }
+ template <class T, class U> struct S { T t; U u; };
+ S<int**, int*> move_template(S<int**, int*> s) {
+ return std::move(s);
+ }
+ )"),
+ LifetimesContain({{"move_int_ptr", "a -> a"},
+ {"move_template", "(<a, b, c>) -> (<a, b, c>)"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/casts.cc b/lifetime_analysis/test/casts.cc
new file mode 100644
index 0000000..bfc1c20
--- /dev/null
+++ b/lifetime_analysis/test/casts.cc
@@ -0,0 +1,211 @@
+// 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 involving casts.
+
+#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, DISABLED_StaticCast) {
+ // TODO(veluca): the `Object` we create for the base struct does not know
+ // about the derived struct, so this test will fail when trying to access the
+ // base on the object of the derived class. See also DynamicCastAccessField.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct Base {
+ virtual bool is_derived() { return false; }
+ };
+ struct Derived : public Base {
+ bool is_derived() override {
+ return true;
+ }
+ };
+ Derived* test_static_cast_ptr(Base* base, Derived* derived) {
+ if (base->is_derived()) {
+ return static_cast<Derived*>(base);
+ }
+ return derived;
+ }
+ Derived& test_static_cast_ref(Base& base, Derived& derived) {
+ if (base.is_derived()) {
+ return static_cast<Derived&>(base);
+ }
+ return derived;
+ }
+ )"),
+ LifetimesContain({{"test_static_cast_ptr", "a, a -> a"},
+ {"test_static_cast_ref", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DISABLED_DynamicCastWithFnCall) {
+ // TODO(veluca): the `Object` we create for the base struct does not know
+ // about the derived struct, so this test will fail when trying to access the
+ // base on the object of the derived class. See also DynamicCastAccessField.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct Base {
+ virtual bool is_derived() { return false; }
+ };
+ struct Derived : public Base {
+ bool is_derived() override {
+ return true;
+ }
+ };
+ Derived* test_dynamic_cast_ptr(Base* base, Derived* derived) {
+ if (Derived* derived_from_base = dynamic_cast<Derived*>(base)) {
+ return derived_from_base;
+ }
+ return derived;
+ }
+ Derived& test_dynamic_cast_ref(Base& base, Derived& derived) {
+ // We don't have support for exceptions enabled, so we can't use
+ // dynamic_cast unconditionally and check whether it succeeded or failed
+ // by catching std::bad_cast. Instead, we call is_derived() like we do in
+ // StaticCast test above. This makes the dynamic_cast somewhat pointless,
+ // but at least we can test that we do propagate the points-to set through
+ // it correctly.
+ if (base.is_derived()) {
+ return dynamic_cast<Derived&>(base);
+ }
+ return derived;
+ }
+ // Also test that we handle function calls to test_dynamic_cast_...()
+ // correctly. Our logic for function calls should realize that
+ // test_dynamic_cast_...() may return not just `derived` but also `base` and
+ // that therefore all three lifetimes should be the same.
+ Derived* call_dynamic_cast_ptr(Base* base, Derived* derived) {
+ return test_dynamic_cast_ptr(base, derived);
+ }
+ Derived& call_dynamic_cast_ref(Base& base, Derived& derived) {
+ return test_dynamic_cast_ref(base, derived);
+ }
+ )"),
+ LifetimesContain({{"test_dynamic_cast_ptr", "a, a -> a"},
+ {"test_dynamic_cast_ref", "a, a -> a"},
+ {"call_dynamic_cast_ptr", "a, a -> a"},
+ {"call_dynamic_cast_ref", "a, a -> a"}}));
+}
+
+// TODO(mboehme): This test currently fails when trying to access `derived->a`
+// because it can't find the field. This is because we set up the object as a
+// `Base` and only gave it the fields that are present on `Base`.
+// There are several ways we could resolve this:
+// a) When setting up the object initially, proactively give it the fields of
+// all transitive derived classes. This can, however, be very costly if the
+// object type is the base class of a large object hierarchy.
+// b) When the object becomes accessible through a pointer to the derived class,
+// add all of the fields of that derived class if they aren't present yet.
+// c) When we perform a field access, add the field if it isn't present yet.
+// Of these, c) may be the easiest to implement, and it also avoids
+// speculatively adding fields to the object that may never be accessed.
+TEST_F(LifetimeAnalysisTest, DISABLED_DynamicCastAccessField) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct Base {
+ virtual ~Base() {}
+ };
+ struct Derived : public Base {
+ int* p;
+ };
+ Derived* DerivedFromBase(Base* base) {
+ return dynamic_cast<Derived*>(base);
+ }
+ int* target(Base* base) {
+ if (auto* derived = DerivedFromBase(base)) {
+ return derived->p;
+ }
+ return nullptr;
+ }
+ )"),
+ LifetimesContain({{"DerivedFromBase", "a -> a"}, {"target", "a -> a"}}));
+}
+
+// TODO(mboehme): This example demonstrates an issue related to field access on
+// derived classes that may be hard to overcome in a principled way. In a
+// multi-TU setting, neither the definition of these functions nor that of the
+// class Derived need be visible within the TU that contains target().
+// Currently, this example fails because SetFieldIfPresent() and
+// GetFieldIfPresent() cannot access Derived::p. But even when this is resolved,
+// we face these issues:
+// - The call to SetFieldIfPresent() is a no-op with respect to the points-to
+// map. Even though `base` and `p` share the same lifetime, the logic for
+// performing function calls doesn't see any object of type `int*` that could
+// be modified by the callee.
+// - There is no existing object for GetFieldIfPresent() to return.
+// We could fix the second issue by creating a new object and giving it the same
+// lifetime as `base` (which we know to do because of the signature of
+// GetFieldIfPresent()). However, we would still infer incorrect lifetimes of
+// "a, b -> b" for target() because we would not understand that `p` potentially
+// gets propagated to the return value of target().
+// The alternative function call algorithm that veluca@ is working on might
+// resolve this issue and infer correct lifetimes in this case.
+TEST_F(LifetimeAnalysisTest, DISABLED_DynamicCastFieldAccessBehindFnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct Base {
+ virtual ~Base() {}
+ };
+ struct Derived : public Base {
+ int* p;
+ };
+ void SetFieldIfPresent(Base* base, int* p) {
+ if (auto* derived = dynamic_cast<Derived*>(base)) {
+ derived->p = p;
+ }
+ }
+ int* GetFieldIfPresent(Base* base) {
+ if (auto* derived = dynamic_cast<Derived*>(base)) {
+ return derived->p;
+ }
+ return nullptr;
+ }
+ int* target(Base* base, int* p) {
+ SetFieldIfPresent(base, p);
+ return GetFieldIfPresent(base);
+ }
+ )"),
+ LifetimesContain({{"SetFieldIfPresent", "a, a"},
+ {"GetFieldIfPresent", "a -> a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReinterpretCastPtr) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ double* target(int* p) {
+ return reinterpret_cast<double*>(p);
+ }
+ )"),
+ LifetimesAre({{"target", "ERROR: type-unsafe cast prevents analysis"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReinterpretCastRef) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ double& target(int& p) {
+ return reinterpret_cast<double&>(p);
+ }
+ )"),
+ LifetimesAre({{"target", "ERROR: type-unsafe cast prevents analysis"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, IntegralToPointerCast) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ // We want to avoid including <cstdint>, so just assume `long long` is big
+ // enough to hold a pointer.
+ int* target(long long i) {
+ return reinterpret_cast<int*>(i);
+ }
+ )"),
+ LifetimesAre({{"target", "ERROR: type-unsafe cast prevents analysis"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/class_templates.cc b/lifetime_analysis/test/class_templates.cc
new file mode 100644
index 0000000..4927f33
--- /dev/null
+++ b/lifetime_analysis/test/class_templates.cc
@@ -0,0 +1,631 @@
+// 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 involving class templates.
+
+#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, StructTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ int* target(S<int*> s) {
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplatePtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ int* target(S<int*>* s) {
+ return s->a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateInnerDoubleUsage) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ T b;
+ };
+ int* target(S<int**>* s) {
+ int l = 0;
+ *s->b = &l;
+ return *s->a;
+ }
+ )"),
+ LifetimesAre({{"target",
+ "ERROR: function returns reference to a local "
+ "through parameter 's'"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTwoTemplateArguments) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T, typename U>
+ struct S {
+ T t;
+ U u;
+ };
+
+ int* return_t(S<int*, int*>& v) {
+ return v.t;
+ }
+
+ int* return_u(S<int*, int*>& v) {
+ return v.u;
+ }
+ )"),
+ LifetimesAre({{"return_t", "(<a, b>, c) -> a"},
+ {"return_u", "(<a, b>, c) -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsNestedClasses) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct Outer {
+ template <typename U>
+ struct Inner {
+ T t;
+ U u;
+ };
+ };
+
+ int* return_t(Outer<int*>::Inner<int*>& inner) {
+ return inner.t;
+ }
+
+ int* return_u(Outer<int*>::Inner<int*>& inner) {
+ return inner.u;
+ }
+ )"),
+ LifetimesAre({{"return_t", "(<a>::<b>, c) -> a"},
+ {"return_u", "(<a>::<b>, c) -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsConstructInner) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct Inner {
+ Inner (T a): a(a) {}
+ T a;
+ };
+ template <typename T, typename U>
+ struct Outer {
+ Outer(T a, U& b): a(a), b(b) {}
+ T a;
+ U b;
+ };
+ int* target(int* a, int* b) {
+ Inner<int*> is(b);
+ Outer<int*, Inner<int*>> s(a, is);
+ return s.b.a;
+ }
+ )"),
+ LifetimesContain({{"target", "a, b -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsTernary) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T, typename U>
+ struct S {
+ T t;
+ U u;
+ };
+
+ int* f(S<int*, int*>& v) {
+ return *v.t < *v.u ? v.t : v.u;
+ }
+ )"),
+ LifetimesAre({{"f", "(<a, a>, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateLocalVariable) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ const int* target(S<int*> s) {
+ S<const int*> t;
+ t.a = s.a;
+ return t.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplatePointerToMember) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T, typename U>
+ struct S {
+ T a;
+ U b;
+ };
+ int** target(S<int*, int*>& s) {
+ return &s.b;
+ }
+ )"),
+ LifetimesAre({{"target", "(<a, b>, c) -> (b, c)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateWithPointer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ T* a;
+ };
+ int** target(S<int*>& s) {
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"target", "(<a> [b], c) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateWithTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ int* target(S<S<int*>> s) {
+ return s.a.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateInnerTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct U {
+ T a;
+ };
+ template <typename T>
+ struct S {
+ U<T> a;
+ };
+ int* target(S<int*>* s) {
+ return s->a.a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DISABLED_StructTemplateInnerTemplatePtr) {
+ // TODO(veluca): we don't correctly propagate lifetime arguments when creating
+ // template arguments for fields that use the template argument indirectly,
+ // such as behind a pointer or as template arguments to a struct passed as a
+ // template argument to the member.
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct U {
+ T a;
+ };
+ template <typename T>
+ struct S {
+ U<T*> a;
+ };
+ int* target(S<int*>* s) {
+ return *s->a.a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateSwapArguments) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T, typename U>
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ T a;
+ U b;
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ S<U, T>* next;
+ };
+ int* target(S<int*, int*>* s) {
+ return s->next->a;
+ }
+ int* target_swtwice(S<int*, int*>* s) {
+ return s->next->next->a;
+ }
+ )"),
+ LifetimesAre({{"target", "(<a, b> [c], d) -> b"},
+ {"target_swtwice", "(<a, b> [c], d) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateMemberCall) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ // TODO(mboehme): The real `vector` doesn't have lifetime parameters, but
+ // we use these here as we don't have the ability to do `lifetime_cast`s
+ // yet.
+ struct [[clang::annotate("lifetime_params", "a")]] vector {
+ T& operator[](int i) { return a[i]; }
+ [[clang::annotate("member_lifetimes", "a")]]
+ T* a;
+ };
+
+ int* get(vector<int*>& v, int i) {
+ return v[i];
+ }
+ )"),
+ LifetimesAre({{"vector<int *>::operator[]", "(<a> [b], c): () -> (a, b)"},
+ {"get", "(<a> [b], c), () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTwoTemplateArgumentsCall) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T, typename U>
+ struct S {
+ T t;
+ U u;
+ };
+
+ int* f(S<int*, int*>& v) {
+ return *v.t < *v.u ? v.t : v.u;
+ }
+
+ int* g(S<int*, int*>& v) {
+ return f(v);
+ }
+ )"),
+ LifetimesAre({{"f", "(<a, a>, b) -> a"}, {"g", "(<a, a>, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructNoTemplateInnerTemplate) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct X {
+ T field;
+ };
+
+ struct Y {
+ X<int*> field;
+ };
+
+ int* target_byref(Y& s) {
+ return s.field.field;
+ }
+
+ int* target_byvalue(Y s) {
+ return s.field.field;
+ }
+ )"),
+ LifetimesContain({{"target_byref", "a -> a"},
+ {"target_byvalue",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturn) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(S<int*>& s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnXvalue) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(S<int*> s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> take_by_ref(S<int*>& s) {
+ return s;
+ }
+ S<int*> take_by_value(S<int*> s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"take_by_ref", "(a, b) -> a"},
+ {"take_by_value", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnLocal) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(int* a) {
+ int i = 42;
+ return { &i };
+ }
+ )"),
+ LifetimesAre({{"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T a) : a(a) {}
+ T a;
+ };
+ S<int*> ConstructorCastSyntax(int* a) {
+ return S(a);
+ }
+ S<int*> ConstructTemporarySyntax(int* a) {
+ return S{a};
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"},
+ {"ConstructorCastSyntax", "a -> a"},
+ {"ConstructTemporarySyntax", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryInitializerList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> InitListExpr(int* a) {
+ return {a};
+ }
+ S<int*> CastWithInitListExpr(int* a) {
+ return S<int*>{a};
+ }
+ )"),
+ LifetimesAre({{"InitListExpr", "a -> a"},
+ {"CastWithInitListExpr", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnUnionTemporaryInitializerList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ union S {
+ T a;
+ };
+ S<int*> target(int* a) {
+ return {a};
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DISABLED_StructTemplateReturnPassByValue) {
+ // TODO(veluca): disabled because calling a function with a pass-by-value
+ // struct is not yet supported -- see TODO in TransferLifetimesForCall.
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> t(S<int*> s) {
+ return s;
+ }
+ S<int*> target(S<int*> s) {
+ return t(s);
+ }
+ )"),
+ LifetimesAre({{"t", "a -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithTemplateArgs) {
+ EXPECT_THAT(GetLifetimes(R"(
+template <typename T, typename U>
+struct S {
+ T t;
+ U u;
+};
+
+int* target(S<int*, int*>* s, int* t, int* u) {
+ s->t = t;
+ s->u = u;
+ return s->t;
+}
+ )"),
+ // With template arguments, now the struct and its fields can
+ // have different lifetimes.
+ LifetimesAre({{"target", "(<a, b>, c), a, b -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ExampleFromRFC) {
+ // This is an example from the lifetimes RFC.
+ EXPECT_THAT(GetLifetimes(R"(
+template <typename T>
+struct R {
+ R(T t) : t(t) {}
+ T t;
+};
+
+bool some_condition();
+
+template <typename T>
+struct S {
+ S(T a, T b) : r(some_condition() ? R(a) : R(b)) {}
+ R<T> r;
+};
+
+int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.t;
+}
+ )"),
+ LifetimesContain({{"R<int *>::R", "(a, b): a"},
+ {"S<int *>::S", "(a, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VariadicTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <int idx, typename... Args> struct S {};
+ template <int idx, typename T, typename... Args>
+ struct S<idx, T, Args...> {
+ T t;
+ S<idx+1, Args...> nested;
+ };
+
+ template <typename... Args>
+ struct tuple: public S<0, Args...> {};
+
+ int* target(tuple<int*, int*>& s) {
+ return s.nested.t;
+ }
+ )"),
+ LifetimesAre({{"target", "(<a, b>, c) -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DISABLED_VariadicTemplateConstructTrivial) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <int idx, typename... Args> struct S {};
+ template <int idx, typename T, typename... Args>
+ struct S<idx, T, Args...> {
+ T t;
+ S<idx+1, Args...> nested;
+ };
+
+ template <typename... Args>
+ struct tuple: public S<0, Args...> {};
+
+ void target(int* a, int* b) {
+ tuple<int*, int*> s = {a, b};
+ }
+ )"),
+ LifetimesAre({{"target", "a, b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VariadicTemplateConstruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename... Args> struct S { S() {} };
+ template <typename T, typename... Args>
+ struct S<T, Args...> {
+ T t;
+ S<Args...> nested;
+ S(T t, Args... args): t(t), nested(args...) {}
+ };
+
+ void target(int* a, int* b) {
+ S<int*, int*> s = {a, b};
+ }
+ )"),
+ LifetimesContain({{"target", "a, b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, NoexceptTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S() noexcept(isnoexcept<T>()) {}
+ template <typename U>
+ static constexpr bool isnoexcept() { return true; }
+ };
+
+ void f() {
+ S<int> s;
+ }
+ )"),
+ LifetimesContain({{"f", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, TypeTemplateArgAfterNonType) {
+ // Minimized repro for a crash from b/228325046.
+ EXPECT_THAT(GetLifetimes(R"(
+ template<int _Idx, typename _Head>
+ struct _Head_base
+ {
+ constexpr _Head_base(_Head&& __h)
+ : _M_head_impl(__h) { }
+
+ _Head _M_head_impl;
+ };
+
+ void f() {
+ _Head_base<0, void*> head_base(nullptr);
+ }
+ )"),
+ LifetimesContain({{"f", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ TemplateContainingTypedefInstantiatedAnotherTemplate) {
+ // Minimized repro for a crash from b/228325046.
+ // The scenario that triggered the crash is:
+ // - We have a template (in this case `remove_reference`) containing a typedef
+ // - That typedef depends on a template parameter
+ // - We instantiate the template with an argument that is another template
+ // The bug was that we weren't desugaring the typedef and hence coming up with
+ // a different value for the depth of the template argument than
+ // TemplateTypeParmType::getDepth() uses.
+ EXPECT_THAT(GetLifetimes(R"(
+ namespace std {
+ template <typename T1, typename T2> struct pair {
+ T1 t1;
+ T2 t2;
+ };
+
+ template<typename _Tp>
+ struct remove_reference
+ { typedef _Tp type; };
+
+ template<typename _Tp>
+ constexpr _Tp&&
+ forward(typename std::remove_reference<_Tp>::type& __t) noexcept
+ { return static_cast<_Tp&&>(__t); }
+ }
+
+ void f() {
+ std::pair<int, int> p;
+ std::forward<decltype(p)>(p);
+ }
+ )"),
+ LifetimesContain({{"f", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DISABLED_ReturnPointerToTemplate) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <class T> struct S { T t; };
+ S<int*>* target(S<int*>* s) {
+ return s;
+ }
+ )"),
+ // TODO(b/230456778): This currently erroneously returns (a, b) -> (c, b)
+ LifetimesAre({{"f", "(a, b) -> (a, b)"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/control_flow.cc b/lifetime_analysis/test/control_flow.cc
new file mode 100644
index 0000000..0b4f354
--- /dev/null
+++ b/lifetime_analysis/test/control_flow.cc
@@ -0,0 +1,232 @@
+// 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
diff --git a/lifetime_analysis/test/defaulted_functions.cc b/lifetime_analysis/test/defaulted_functions.cc
new file mode 100644
index 0000000..c3b4046
--- /dev/null
+++ b/lifetime_analysis/test/defaulted_functions.cc
@@ -0,0 +1,88 @@
+// 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 defaulted functions are analyzed correctly.
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "lifetime_analysis/test/lifetime_analysis_test.h"
+
+namespace clang {
+namespace tidy {
+namespace lifetimes {
+namespace {
+
+class DefaultedFunctions : public LifetimeAnalysisTest {};
+
+TEST_F(DefaultedFunctions, DefaultConstrutor_NoRecordTypeFieldsNoBases) {
+ GetLifetimesOptions options;
+ options.include_implicit_methods = true;
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int i;
+ };
+ void target() {
+ S();
+ }
+
+ )",
+ options),
+ // Test is successful if we can call the default constructor.
+ LifetimesAre({{"S::S", "a:"}, {"target", ""}}));
+}
+
+TEST_F(DefaultedFunctions, DefaultConstrutor_LifetimeParam) {
+ GetLifetimesOptions options;
+ options.include_implicit_methods = true;
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* p;
+ };
+ void target() {
+ S();
+ }
+
+ )",
+ options),
+ LifetimesAre({{"S::S", "(a, b):"}, {"target", ""}}));
+}
+
+TEST_F(DefaultedFunctions, DefaultConstrutor_RecordTypeFields) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct S {};
+ struct T {
+ S s;
+ };
+ void f() {
+ T();
+ }
+ )"),
+ // TODO(b/230693710): This documents that defaulted default
+ // constructors on classes with record-type fields are currently
+ // not supported.
+ LifetimesAre({{"T::T", "ERROR: unsupported type of defaulted function"},
+ {"f", "ERROR: No lifetimes for constructor T"}}));
+}
+
+TEST_F(DefaultedFunctions, DefaultConstrutor_BaseClass) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct S {};
+ struct T : public S {};
+ void f() {
+ T();
+ }
+ )"),
+ // TODO(b/230693710): This documents that defaulted default
+ // constructors on derived classes are currently not supported.
+ LifetimesAre({{"T::T", "ERROR: unsupported type of defaulted function"},
+ {"f", "ERROR: No lifetimes for constructor T"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/execution_order.cc b/lifetime_analysis/test/execution_order.cc
new file mode 100644
index 0000000..a69a1a0
--- /dev/null
+++ b/lifetime_analysis/test/execution_order.cc
@@ -0,0 +1,107 @@
+// 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 execution order 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, OrderOfOperations_OrderOfExecution) {
+ // This is a regression test for a wrong result that was generated by the
+ // constraint-based approach.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* p) {
+ int* p2 = p;
+ int local = 42;
+ p = &local;
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, OrderOfOperations_OrderOfExecution2) {
+ // This is a regression test for a wrong result that was generated by the
+ // constraint-based approach.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* p) {
+ int **pp = &p;
+ int local = 42;
+ int* p2;
+ pp = &p2;
+ *pp = &local;
+ return p;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, OrderOfOperations_Assignment) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* p) {
+ int* p2 = p;
+ int local = 42;
+ p2 = &local;
+ p2 = p;
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, TakeAReferenceLater) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* p) {
+ int* p2 = p;
+ int local = 42;
+ p = &local;
+ int** pp = &p;
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ChainedAssignment) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, int* c) {
+ a = b = c;
+ return a;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b, c -> c"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ParenExpr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, int* c) {
+ a = (b = c);
+ return a;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b, c -> c"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ParenExpr2) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* a, int* b, int* c) {
+ (a = b) = c;
+ return a;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b, c -> c"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/expr.cc b/lifetime_analysis/test/expr.cc
new file mode 100644
index 0000000..de0b316
--- /dev/null
+++ b/lifetime_analysis/test/expr.cc
@@ -0,0 +1,40 @@
+// 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 various types of expressions.
+
+#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, IncrementAndDecrement) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* prefix_inc(int* p) {
+ return ++p;
+ }
+ int* prefix_dec(int* p) {
+ return --p;
+ }
+ int* postfix_inc(int* p) {
+ return p++;
+ }
+ int* postfix_dec(int* p) {
+ return p--;
+ }
+ )"),
+ LifetimesAre({{"prefix_inc", "a -> a"},
+ {"prefix_dec", "a -> a"},
+ {"postfix_inc", "a -> a"},
+ {"postfix_dec", "a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/function_calls.cc b/lifetime_analysis/test/function_calls.cc
new file mode 100644
index 0000000..93f56a7
--- /dev/null
+++ b/lifetime_analysis/test/function_calls.cc
@@ -0,0 +1,494 @@
+// 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 propagating pointees through function calls.
+//
+// Not every test that contains a function call should go here -- just those
+// that test some specific aspect of the logic that propagates pointees through
+// function calls.
+
+#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, SimpleFnIdentity) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int* a) {
+ return a;
+ }
+ int* target(int* a) {
+ return f(a);
+ }
+ )"),
+ LifetimesAre({{"f", "a -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleFnStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f() {
+ static int i = 42;
+ return &i;
+ }
+ int* target() {
+ return f();
+ }
+ )"),
+ LifetimesAre({{"f", "-> static"}, {"target", "-> static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleFnStaticOutParam) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int** p) {
+ static int i = 42;
+ *p = &i;
+ }
+ int* target() {
+ int* p;
+ f(&p);
+ return p;
+ }
+ )"),
+ LifetimesAre({{"f", "(static, a)"}, {"target", "-> static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleFnIdentityArg1) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int* a, int* b) {
+ return a;
+ }
+ int* target(int* a, int* b) {
+ return f(b, a);
+ }
+ )"),
+ LifetimesAre({{"f", "a, b -> a"}, {"target", "a, b -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleFnCall) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ int* get_lesser_of(int* a, int* b) {
+ if (*a < *b) {
+ return a;
+ }
+ return b;
+ }
+ int* target(int* a, int* b) {
+ return get_lesser_of(a, b);
+ }
+ )"),
+ LifetimesAre({{"get_lesser_of", "a, a -> a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, RefFnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* get_as_ptr(int& a) {
+ return &a;
+ }
+ int* target(int& a) {
+ return get_as_ptr(a);
+ }
+ )"),
+ LifetimesAre({{"get_as_ptr", "a -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_NonPointerParameter) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int id(int i) {
+ return i;
+ }
+ int target() {
+ return id(42);
+ }
+ )"),
+ LifetimesAre({{"id", "()"}, {"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_DoublePointer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int** f(int** pp) {
+ return pp;
+ }
+ int** target(int** pp) {
+ return f(pp);
+ }
+ )"),
+ LifetimesAre(
+ {{"f", "(a, b) -> (a, b)"}, {"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_DoublePointerDeref) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int** pp) {
+ return *pp;
+ }
+ int* target(int** pp) {
+ return f(pp);
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b) -> a"}, {"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_MultipleDoublePointer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int** f(int** pp1, int** pp2) {
+ return pp1;
+ }
+ int* target(int* p1, int* p2) {
+ return *f(&p1, &p2);
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), (c, d) -> (a, b)"},
+ {"target", "a, b -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_MultipleDoublePointerWithControlFlow) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int** f(int** pp1, int** pp2) {
+ if (**pp1 < **pp2) {
+ *pp1 = *pp2;
+ }
+ return pp1;
+ }
+ int* target(int* p1, int* p2) {
+ return *f(&p1, &p2);
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), (a, c) -> (a, b)"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_MultipleDoublePointerWithOuterConst) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int** pp1, int** const pp2) {
+ *pp1 = *pp2;
+ }
+ int* target1(int* p1, int* p2) {
+ // Making this call can cause p1 to be overwritten with p2...
+ f(&p1, &p2);
+ return p1;
+ }
+ int* target2(int* p1, int* p2) {
+ // ...and it can also cause p2 to be overwritten with p1.
+ //
+ // The `const` only causes `pp2` itself to be const, but `*pp2` and
+ // `**pp2` are both non-const. In other words, from the lifetimes of `f()`
+ // alone, it would be entirely possible for it to do `*pp2 = *pp1`.
+ f(&p1, &p2);
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), (a, c)"},
+ {"target1", "a, a -> a"},
+ {"target2", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_MultipleDoublePointerWithMiddleConst) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int** pp1, int* const * pp2) {
+ *pp1 = *pp2;
+ }
+ int* target1(int* p1, int* p2) {
+ // Making this call can cause p1 to be overwritten with p2...
+ f(&p1, &p2);
+ return p1;
+ }
+ int* target2(int* p1, int* p2) {
+ // ...but it can't cause p2 to be overwritten with p1.
+ f(&p1, &p2);
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), (a, c)"},
+ {"target1", "a, a -> a"},
+ {"target2", "a, b -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_MultipleDoublePointerWithInnerConst) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(const int** pp1, int** pp2) {
+ *pp1 = *pp2;
+ }
+ const int* target1(const int* p1, int* p2) {
+ // Making this call can cause p1 to be overwritten with p2.
+ f(&p1, &p2);
+ return p1;
+ }
+ const int* target2(const int* p1, int* p2) {
+ // The analysis concludes that p2 could also be overwritten by p1,
+ // despite the fact that a const int* cannot be converted to an int*.
+ // This is because, when determining what objects the callee might copy,
+ // the analysis looks only at lifetimes in the function signature but not
+ // at whether the objects that these lifetimes refer to can be converted
+ // into one another.
+ // As a result, the lifetimes we infer for target2() are stricter than
+ // they would need to be.
+ f(&p1, &p2);
+ return p2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), (a, c)"},
+ {"target1", "a, a -> a"},
+ {"target2", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_TriplePointerWithConst_1) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int*** ppp1, int** const * ppp2) {
+ *ppp1 = *ppp2;
+ }
+ int** target(int* p1, int** pp2) {
+ // - `pp2` cannot be overwritten because of the `const` in the signature
+ // of `f()`. (Without this, we would infer a local lifetime for the
+ // return value.)
+ // - `*pp2` can be overwritten.
+ int** pp1 = &p1;
+ f(&pp1, &pp2);
+ return pp2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b, c), (a, b, d)"},
+ {"target", "a, (a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_TriplePointerWithConst_2) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int*** ppp1, int* const ** ppp2) {
+ **ppp1 = **ppp2;
+ }
+ int* const * target(int* p1, int* const * pp2) {
+ // - `pp2` cannot be overwritten because of the lifetimes in the signature
+ // of `f()`.
+ // - `*pp2` cannot be overwritten because of the `const` in the signature
+ // of `f()`.
+ int** pp1 = &p1;
+ f(&pp1, &pp2);
+ return pp2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b, c), (a, d, e)"},
+ {"target", "a, (b, c) -> (b, c)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_TriplePointerWithConst_3) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int* const ** ppp1, int* const ** ppp2) {
+ *ppp1 = *ppp2;
+ }
+ int* const * target(int* const p1, int* const * pp2) {
+ // - `pp2` can be overwritten (hence the return value has local lifetime)
+ // - `*pp2` can be overwritten (hence both `p1` and `pp2` have lifetime a)
+ int* const * pp1 = &p1;
+ f(&pp1, &pp2);
+ return pp2;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b, c), (a, b, d)"},
+ {"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_OutputParam) {
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int* in, int** out) {
+ *out = in;
+ }
+ int* target(int* p) {
+ int* result;
+ f(p, &result);
+ return result;
+ }
+ )"),
+ LifetimesAre({{"f", "a, (a, b)"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_Operator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {};
+ bool operator<(const S& s1, const S& s2) {
+ return false;
+ }
+ bool target(const S& s) {
+ return s < s;
+ }
+ )"),
+ LifetimesAre({{"operator<", "a, b"}, {"target", "a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FnCall_PassLambda) {
+ // This test doesn't do anything interesting from a lifetimes point of view.
+ // It's just intended to test that we can instantiate a capture-less lambda
+ // and convert it to a function pointer.
+ EXPECT_THAT(GetLifetimes(R"(
+ void call_callback(void(*callback)()) {
+ // TODO(mboehme): Can't actually call the callback yet because we don't
+ // have support for indirect callees.
+ // callback();
+ }
+
+ void target() {
+ call_callback([] {});
+ }
+ )"),
+ LifetimesContain({{"call_callback", "a"}, {"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleIndirectFnCall) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ int* get_lesser_of(int* a, int* b) {
+ if (*a < *b) {
+ return a;
+ }
+ return b;
+ }
+ int* target(int* a, int* b) {
+ auto fp = get_lesser_of;
+ return fp(a, b);
+ }
+ )"),
+ LifetimesAre({{"get_lesser_of", "a, a -> a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleIndirectFnCallFwdDecl) {
+ // Tests that the analysis correctly identifies dependencies due to non-call
+ // uses of a function.
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ int* get_lesser_of(int* a, int* b);
+ int* target(int* a, int* b) {
+ auto fp = get_lesser_of;
+ return fp(a, b);
+ }
+ int* get_lesser_of(int* a, int* b) {
+ if (*a < *b) {
+ return a;
+ }
+ return b;
+ }
+ )"),
+ LifetimesAre({{"get_lesser_of", "a, a -> a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalIndirectFnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* get_first(int* a, int* b) {
+ return a;
+ }
+ int* get_second(int* a, int* b) {
+ return b;
+ }
+ int* target(int* a, int* b) {
+ auto fp = *a < *b ? get_first : get_second;
+ return fp(a, b);
+ }
+ )"),
+ LifetimesAre({{"get_first", "a, b -> a"},
+ {"get_second", "a, b -> b"},
+ {"target", "a, a -> a"}}));
+}
+
+// TODO(mboehme): Add a test where we're calling a function with lifetime
+// signature `static -> a`. The analysis should realize that f could return
+// its input pointee. Creating such a test is currently difficult because we
+// don't have lifetime annotations and the inferred lifetime for the return
+// value of f will always be static in this case.
+
+TEST_F(LifetimeAnalysisTest, ComplexFnCallGraph) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int* a, int* b) {
+ if (*a < *b) {
+ return a;
+ }
+ return b;
+ }
+ int* g(int* a, int* b) {
+ return f(a, b);
+ }
+ int* h(int* a, int* b) {
+ return f(a, b);
+ }
+ int* target(int* a, int* b, int* c, int* d) {
+ return f(g(a, b), h(c, d));
+ }
+ )"),
+ LifetimesAre({{"f", "a, a -> a"},
+ {"g", "a, a -> a"},
+ {"h", "a, a -> a"},
+ {"target", "a, a, a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ComplexFnCallGraphUnusedArgs) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int* a, int* b) {
+ if (*a < *b) {
+ return a;
+ }
+ return b;
+ }
+ int* g(int* a, int* b) {
+ return f(a, b);
+ }
+ int* h(int* a, int* b) {
+ return f(a, b);
+ }
+ int* target(int* a, int* b, int* c, int* d) {
+ return f(g(a, b), h(a, b));
+ }
+ )"),
+ LifetimesAre({{"f", "a, a -> a"},
+ {"g", "a, a -> a"},
+ {"h", "a, a -> a"},
+ {"target", "a, a, b, c -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCall) {
+ // Tests that lifetimes of structs are properly propagated (in both
+ // directions) through function calls.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void f(S* s, int* a) {
+ s->a = a;
+ }
+ int* target(S* s, int* a) {
+ f(s, a);
+ return s->a;
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), a"}, {"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructDoubleCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void f(S* s, int* a) {
+ s->a = a;
+ }
+ int* g(S* s) {
+ return s->a;
+ }
+ int* target(S* s, int* a) {
+ f(s, a);
+ return g(s);
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), a"},
+ {"g", "(a, b) -> a"},
+ {"target", "(a, b), a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/function_templates.cc b/lifetime_analysis/test/function_templates.cc
new file mode 100644
index 0000000..dd90beb
--- /dev/null
+++ b/lifetime_analysis/test/function_templates.cc
@@ -0,0 +1,154 @@
+// 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 involving function templates.
+
+#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, FunctionTemplatePtr) {
+ EXPECT_THAT(GetLifetimesWithPlaceholder(R"(
+ template <typename T>
+ T* target(T* t) {
+ return t;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplatePtrWithTwoArgs) {
+ EXPECT_THAT(GetLifetimesWithPlaceholder(R"(
+ template <typename T, typename U>
+ T* target(T* t, U* u1, U& u2) {
+ u1 = &u2;
+ return t;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b, c -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplatePtrWithTemplatedStruct) {
+ EXPECT_THAT(GetLifetimesWithPlaceholder(R"(
+ template <typename T>
+ struct S {
+ T t;
+ };
+
+ template <typename T>
+ T* target(S<T*>* s) {
+ return s->t;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplatePtrWithMultipleFunctions) {
+ // The code has both template and non-template functions/code.
+ EXPECT_THAT(GetLifetimesWithPlaceholder(R"(
+ static int x = 3;
+ template <typename T>
+ struct A {
+ T x;
+ T y;
+ };
+ template <typename T>
+ T* target(T* t) {
+ return t;
+ }
+ template <typename U>
+ U* target2(U* u) {
+ return u;
+ }
+ int foo(A<int>* a) {
+ return a->x + a->y + x;
+ }
+ )"),
+ LifetimesAre(
+ {{"target", "a -> a"}, {"target2", "a -> a"}, {"foo", "a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplateCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ T* t(T* a, T* b) {
+ if (*a > *b) {
+ return a;
+ }
+ return b;
+ }
+ int* target(int* a, int* b) {
+ return t(a, b);
+ }
+ )"),
+ LifetimesContain({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplateCallIgnoreArg) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ T* t(T* a, T* b) {
+ return a;
+ }
+ int* target(int* a, int* b) {
+ return t(a, b);
+ }
+ )"),
+ LifetimesContain({{"target", "a, b -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplateCallPtrInstantiation) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ T* t(T* a, T* b) {
+ if (*a > *b) {
+ return a;
+ }
+ return b;
+ }
+ int** target(int** a, int** b) {
+ return t(a, b);
+ }
+ )"),
+ LifetimesContain({{"target", "(a, b), (a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplateCallIgnoreArgPtrInstantiation) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ T* t(T* a, T* b) {
+ return a;
+ }
+ int** target(int** a, int** b) {
+ return t(a, b);
+ }
+ )"),
+ LifetimesContain({{"target", "(a, b), (c, d) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionTemplateInsideClassTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ template <typename U>
+ U f(T t, U u) {
+ return u;
+ }
+ };
+ int* target(S<int *>& s, int* p1, int* p2) {
+ return s.f(p1, p2);
+ }
+ )"),
+ LifetimesContain({{"target", "(a, b), c, d -> d"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/inheritance.cc b/lifetime_analysis/test/inheritance.cc
new file mode 100644
index 0000000..cf4a2a4
--- /dev/null
+++ b/lifetime_analysis/test/inheritance.cc
@@ -0,0 +1,281 @@
+// 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 involving inheritance.
+
+#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, StructSimpleInheritance) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct [[clang::annotate("lifetime_params", "a")]] B {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+};
+struct S : public B {
+};
+int* target(S* s, int* a) {
+ s->a = a;
+ return s->a;
+}
+ )"),
+ LifetimesAre({{"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ DISABLED_StructInheritanceCallTrivialDefaultConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct T {};
+ struct S: public T {
+ S(): T() {}
+ int* a;
+ };
+ void target() {
+ S s;
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInheritanceCallBaseConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] T {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ T(int* b): b(b) {}
+ };
+ struct S: public T {
+ S(int* a, int* b): a(a), T(b) {}
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ int* target(int* a, int* b) {
+ S s(a, b);
+ return s.b;
+ }
+ )"),
+ LifetimesContain({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInheritanceCallBaseConstructorTypedef) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] T {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ T(int* b): b(b) {}
+ };
+ using U = T;
+ struct S: public U {
+ S(int* a, int* b): a(a), T(b) {}
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ int* target(int* a, int* b) {
+ S s(a, b);
+ return s.b;
+ }
+ )"),
+ LifetimesContain({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ StructInheritanceCallBaseConstructorTypedefBaseInit) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] T {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ T(int* b): b(b) {}
+ };
+ using U = T;
+ struct S: public T {
+ S(int* a, int* b): a(a), U(b) {}
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ int* target(int* a, int* b) {
+ S s(a, b);
+ return s.b;
+ }
+ )"),
+ LifetimesContain({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructSimpleInheritanceWithMethod) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+struct [[clang::annotate("lifetime_params", "a")]] B {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return a; }
+};
+struct S : public B {
+};
+int* target(S* s, int* a) {
+ s->a = a;
+ return s->f();
+}
+ )"),
+ LifetimesAre({{"B::f", "(a, b): -> a"}, {"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructSimpleInheritanceWithMethodInDerived) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+struct [[clang::annotate("lifetime_params", "a")]] B {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+};
+struct S : public B {
+ int* f() { return a; }
+};
+int* target(S* s, int* a) {
+ s->a = a;
+ return s->f();
+}
+ )"),
+ LifetimesAre({{"S::f", "(a, b): -> a"}, {"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructSimpleInheritanceChained) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+struct [[clang::annotate("lifetime_params", "a")]] A {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+};
+struct B : public A {
+ int* f() { return a; }
+};
+struct S : public B {
+};
+int* target(S* s, int* a) {
+ s->a = a;
+ return s->f();
+}
+ )"),
+ LifetimesAre({{"B::f", "(a, b): -> a"}, {"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructSimpleInheritanceWithSwappedTemplateArgs) {
+ // Base and Derived have template arguments where the order is swapped, so
+ // if the code reuse the same vector representation for the lifetimes
+ // Derived (T, U) for the base class where Base has (U, T) this code fails.
+ EXPECT_THAT(GetLifetimes(R"(
+template <typename U, typename T>
+struct Base {
+ T base_t;
+ U base_u;
+};
+
+template <typename T, typename U>
+struct Derived : public Base<U, T> {
+ T derived_t;
+ U derived_u;
+};
+
+int* target(Derived<int*, float*>* d, int* t1, int* t2) {
+ d->derived_t = t1;
+ d->base_t = t2;
+ return d->derived_t;
+}
+ )"),
+ // The lifetime for Derived::derived_t should also be
+ // Base::base_t. See discussions at cl/411724984.
+ LifetimesAre({{"target", "(<a, b>, c), a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructSimpleInheritanceWithDoubledTemplateArgs) {
+ // Base and Derived have different number of template arguments.
+ // Similar test case as StructSimpleInheritanceWithSwappedTemplateArgs.
+ EXPECT_THAT(GetLifetimes(R"(
+template <typename T, typename U>
+struct Base {
+ T base_t;
+ U base_u;
+};
+
+template <typename T>
+struct Derived : public Base<T, T> {
+ T derived_t;
+};
+
+int* target(Derived<int*>* d, int* t1, int* t2, int* t3) {
+ d->derived_t = t1;
+ d->base_t = t2;
+ d->base_u = t3;
+ return d->derived_t;
+}
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ StructSimpleInheritanceWithTemplateSubstitutedAndArgs) {
+ // Base is a template type and has different number of template arguments from
+ // Derived. Similar test case as
+ // StructSimpleInheritanceWithSwappedTemplateArgs.
+ EXPECT_THAT(GetLifetimes(R"(
+template <typename T>
+struct Base {
+ T base_t;
+};
+
+template <typename B, typename T>
+struct Derived : public B {
+ T derived_t;
+};
+
+int* target(Derived<Base<int*>, int*>* d, int* t1, int* t2) {
+ d->derived_t = t1;
+ d->base_t = t2;
+ return d->derived_t;
+}
+ )"),
+ LifetimesAre({{"target", "(<a, b>, c), b, a -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, PassDerivedByValue) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct [[clang::annotate("lifetime_params", "a")]] B {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return a; }
+};
+struct S : public B {
+};
+int* target(S s) {
+ return s.f();
+}
+ )"),
+ LifetimesAre({{"B::f", "(a, b): -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, PassDerivedByValue_BaseIsTemplate) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+template <class T>
+struct B {
+ T a;
+ T f() { return a; }
+};
+template <class T>
+struct S : public B<T> {
+};
+int* target(S<int *> s) {
+ return s.f();
+}
+ )"),
+ LifetimesAre({{"B<int *>::f", "(a, b): -> a"}, {"target", "a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/initialization.cc b/lifetime_analysis/test/initialization.cc
new file mode 100644
index 0000000..5998a6a
--- /dev/null
+++ b/lifetime_analysis/test/initialization.cc
@@ -0,0 +1,121 @@
+// 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 initialization.
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "lifetime_analysis/test/lifetime_analysis_test.h"
+
+namespace clang {
+namespace tidy {
+namespace lifetimes {
+namespace {
+
+// TODO(danakj): Crashes trying to find the initializer expression under
+// MaterializeTemporaryExpr. Should be improved by cl/414032764.
+TEST_F(LifetimeAnalysisTest, DISABLED_VarDeclReferenceToRecordTemporary) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ int* target(int* a) {
+ const S<int*>& s = S<int*>{a};
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VarDeclReferenceToRecordTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*>* target(S<int*>* a) {
+ S<int*>& b = *a;
+ return &b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VarDeclReferenceToRecordNoTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ S* target(S* a) {
+ S& b = *a;
+ return &b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorInitReferenceToRecord) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ template <class Ref>
+ struct R {
+ R(S& s): s(s) {}
+ Ref s;
+ };
+ int* target(S* a) {
+ R<S&> r(*a);
+ return r.s.a;
+ }
+ )"),
+ LifetimesAre({{"R<S &>::R", "(a, b, c): (a, b)"},
+ {"target", "(a, b) -> a"}}));
+}
+
+// TODO(danakj): Fails because a nested TransferMemberExpr() ends up looking for
+// the field from the outer expr on the object of the inner expr.
+//
+// The code:
+// ObjectSet struct_points_to =
+// points_to_map.GetExprObjectSet(member->getBase());
+//
+// The AST:
+// MemberExpr 0x4027d3f2628 'int *':'int *' lvalue .p 0x4027d3f7338
+// `-MemberExpr 0x4027d3f25f8 'S<int *>':'struct S<int *>' lvalue .s
+// 0x4027d3f74c0
+// `-DeclRefExpr 0x4027d3f25d8 'R<int *>':'struct R<int *>' lvalue Var
+// 0x4027d3f6cd0 'r' 'R<int *>':'struct R<int *>'
+//
+// The p field is on struct S, but the code tries to find it on an object
+// of type R<int *>.
+TEST_F(LifetimeAnalysisTest, MemberInitReferenceToRecord) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename P>
+ struct S {
+ P p;
+ };
+ template<typename P>
+ struct [[clang::annotate("lifetime_params", "a")]] R {
+ R(P p): ss{p} {}
+ S<P> ss;
+ [[clang::annotate("member_lifetimes", "a")]]
+ S<P>& s{ss};
+ };
+ int* target(int* a) {
+ R<int*> r(a);
+ return r.s.p;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(<a> [b], b): a"}, {"target", "a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/initializers.cc b/lifetime_analysis/test/initializers.cc
new file mode 100644
index 0000000..5eafe80
--- /dev/null
+++ b/lifetime_analysis/test/initializers.cc
@@ -0,0 +1,502 @@
+// 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 involving initializers.
+
+#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,
+ ReturnStructFieldFromMultipleInitializersConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T i) : i(i) {}
+ T i;
+ };
+ int* ConstructorSyntax(int* a, int* b, bool cond) {
+ return (cond ? S<int*>{a} : S<int*>{b}).i;
+ }
+ int* CastSyntax(int* a, int* b, bool cond) {
+ return (cond ? S<int*>(a) : S<int*>(b)).i;
+ }
+ )"),
+ LifetimesAre({
+ {"S<int *>::S", "(a, b): a"},
+ {"ConstructorSyntax", "a, a, () -> a"},
+ {"CastSyntax", "a, a, () -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ReturnStructFieldFromMultipleInitializersInitList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T i;
+ };
+ int* target(int* a, int* b, bool cond) {
+ return (cond ? S<int*>{a} : S<int*>{b}).i;
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ReturnStructFromMultipleInitializersConstructSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T i) : i(i) {}
+ T i;
+ };
+ S<int*> target(int* a, int* b) {
+ return true ? S<int*>{a} : S<int*>{b};
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructFromMultipleInitializersCastSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T i) : i(i) {}
+ T i;
+ };
+ S<int*> target(int* a, int* b) {
+ return true ? S<int*>(a) : S<int*>(b);
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ReturnStructFromMultipleInitializersInitListSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T i;
+ };
+ S<int*> target(int* a, int* b) {
+ return true ? S<int*>{a} : S<int*>{b};
+ }
+ )"),
+ LifetimesAre({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithMultipleInitializersConstructorSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T i) : i(i) {}
+ T i;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s = true ? S{a} : S{b};
+ return s.i;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithMultipleInitializersCastSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T i) : i(i) {}
+ T i;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s = true ? S(a) : S(b);
+ return s.i;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithMultipleInitializersInitListSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T i;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s = true ? S<int*>{a} : S<int*>{b};
+ return s.i;
+ }
+ )"),
+ LifetimesAre({{"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ConstructorInitWithMultipleInitializersConstructorSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : r(true ? R{a} : R{b}) {}
+ R<T> r;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *>::S", "(a, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ConstructorInitWithMultipleInitializersCastSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : r(true ? R(a) : R(b)) {}
+ R<T> r;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *>::S", "(a, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ConstructorInitWithMultipleInitializersInitListSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : r(true ? R<T>{a} : R<T>{b}) {}
+ R<T> r;
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a, a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ MemberInitWithMultipleInitializersConstructorSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : a(a), b(b) {}
+ T a;
+ T b;
+ R<T> r{true ? R{a} : R{b}};
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *>::S", "(a, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MemberInitWithMultipleInitializersCastSyntax) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : a(a), b(b) {}
+ T a;
+ T b;
+ R<T> r{true ? R(a) : R(b)};
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *>::S", "(a, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorInitWithMultiplePointers) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T, typename U, typename V>
+ struct S {
+ S(T a, U b) : r(true ? a : b) {}
+ R<V> r;
+ };
+ int* target(int* a, int* b) {
+ S<int*, int*, int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *, int *, int *>::S", "(<b, c, a>, d): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ConstructorInitWithMultiplePointersAndStoresFields) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T, typename U, typename V>
+ struct S {
+ S(T a, U b) : a_(a), b_(b), r(true ? a : b) {}
+ T a_;
+ U b_;
+ R<V> r;
+ };
+ int* target(int* a, int* b) {
+ S<int*, int*, int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *, int *, int *>::S", "(<a, a, a>, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MemberInitWithMultiplePointers) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ R(T i) : i(i) {}
+ T i;
+ };
+ template <typename T, typename U, typename V>
+ struct S {
+ S(T a, U b) : a(a), b(b) {}
+ T a;
+ U b;
+ R<V> r{true ? a : b};
+ };
+ int* target(int* a, int* b) {
+ S<int*, int*, int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(a, b): a"},
+ {"S<int *, int *, int *>::S", "(<a, a, a>, b): a, a"},
+ {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MemberInitWithMultipleInitializersInitListSyntax) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ T i;
+ };
+ template <typename T>
+ struct S {
+ S(T a, T b) : a(a), b(b) {}
+ T a;
+ T b;
+ R<T> r{true ? R<T>{a} : R<T>{b}};
+ };
+ int* target(int* a, int* b) {
+ S<int*> s(a, b);
+ return s.r.i;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a, a"}, {"target", "a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DeclStructInitializerWithConversionOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ T a;
+ };
+ template <typename T>
+ struct S {
+ T a;
+ operator R<T>() { return {a}; }
+ };
+ int* target(int* a) {
+ R<int*> r = S<int*>{a};
+ return r.a;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::operator R", "(a, b): -> a"},
+ {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DeclStructInitializerFromCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ T a;
+ };
+ template <typename T>
+ struct R<T> f(T a) {
+ return R<T>{a};
+ }
+ int* target(int* a) {
+ R<int*> r = f<int*>(a);
+ return r.a;
+ }
+ )"),
+ LifetimesAre({{"f", "a -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructInitializerWithConversionOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct R {
+ T a;
+ };
+ template <typename T>
+ struct S {
+ T a;
+ operator R<T>() { return {a}; }
+ };
+ R<int*> target(int* a) {
+ return S<int*>{a};
+ }
+ )"),
+ LifetimesAre({{"S<int *>::operator R", "(a, b): -> a"},
+ {"target", "a -> a"}}));
+}
+
+// TODO(danakj): Crashes due to operator() not being a CXXConstructExpr, but
+// SetExprObjectSetRespectingType only handles CXXConstructExpr for record
+// types.
+TEST_F(LifetimeAnalysisTest,
+ DISABLED_ConstructorInitializerWithConversionOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ template <typename T>
+ struct R {
+ T a;
+ operator S<T>() { return {a}; }
+ };
+
+ // This initializes the `s` field from a constructor initializer.
+ template <typename T>
+ struct QConstructor {
+ QQConstructor(T a) : s(R<T>{a}) {}
+ S<T> s;
+ };
+ int* constructor(int* a) {
+ return QQConstructor<int*>{a}.s.a;
+ }
+
+ // This initializes the `s` field from a transparent InitListExpr on a
+ // member initializer.
+ template <typename T>
+ struct QMember {
+ QMember(T a) : a(a) {}
+ T a;
+ S<T> s{S<T>(R<T>{a})};
+ };
+ int* member(int* a) {
+ return QMember<int*>{a}.s.a;
+ }
+)"),
+ LifetimesAre({{"QConstructor<int *>::QConstructor", "(a, b): a"},
+ {"QMember<int *>::QMember", "(a, b): a"},
+ {"constructor", "a -> a"},
+ {"member", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInitializerWithCtorCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T a) : a(a) {}
+ T a;
+ };
+ int* TransparentInitListExpr(int* a) {
+ S<int*> s{S<int*>(a)};
+ return s.a;
+ }
+ int* CastSyntax(int* a) {
+ S<int*> s((S<int*>(a)));
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"},
+ {"TransparentInitListExpr", "a -> a"},
+ {"CastSyntax", "a -> a"}}));
+}
+
+// TODO(danakj): Crashes because the initializer expression is a
+// CXXStaticCastExpr, and operator() is not a CXXConstructExpr, but
+// SetExprObjectSetRespectingType only handles CXXConstructExpr for record
+// types.
+TEST_F(LifetimeAnalysisTest, DISABLED_StaticCastInitializer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ template <typename T>
+ struct R {
+ T a;
+ operator S<T>() { return {a}; }
+ };
+ int* target(int* a) {
+ return static_cast<S<int*>>(R<int*>{a}).a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/lifetime_analysis_test.cc b/lifetime_analysis/test/lifetime_analysis_test.cc
new file mode 100644
index 0000000..b608eb6
--- /dev/null
+++ b/lifetime_analysis/test/lifetime_analysis_test.cc
@@ -0,0 +1,177 @@
+// 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
+
+#include "lifetime_analysis/test/lifetime_analysis_test.h"
+
+#include <fstream>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <variant>
+
+#include "lifetime_annotations/test/run_on_code.h"
+
+namespace clang {
+namespace tidy {
+namespace lifetimes {
+namespace {
+
+void SaveDotFile(absl::string_view dot, absl::string_view filename_base,
+ absl::string_view test_name, absl::string_view description) {
+ std::string base_path =
+ absl::StrCat(testing::TempDir(), "/", test_name, ".", filename_base);
+ std::ofstream out(absl::StrCat(base_path, ".dot"));
+ if (!out) {
+ llvm::errs() << "Error opening dot file: " << strerror(errno) << "\n";
+ return;
+ }
+ out << dot;
+ if (!out) {
+ llvm::errs() << "Error writing dot file: " << strerror(errno) << "\n";
+ return;
+ }
+ out.close();
+ if (system(
+ absl::StrCat("dot ", base_path, ".dot -T svg -o ", base_path, ".svg")
+ .c_str()) != 0) {
+ llvm::errs() << "Error invoking graphviz. dot file can be found at: "
+ << base_path << ".dot\n";
+ return;
+ }
+}
+
+} // namespace
+
+void LifetimeAnalysisTest::TearDown() {
+ if (HasFailure()) {
+ for (const auto& [func, debug_info] : debug_info_map_) {
+ std::cerr << debug_info.ast << "\n";
+
+ std::cerr << debug_info.object_repository << "\n";
+
+ const char* test_name =
+ testing::UnitTest::GetInstance()->current_test_info()->name();
+
+ SaveDotFile(debug_info.points_to_map_dot,
+ absl::StrCat(func, "_points_to"), test_name,
+ "Points-to map of exit block");
+ SaveDotFile(debug_info.cfg_dot, absl::StrCat(func, "_cfg"), test_name,
+ "Control-flow graph");
+ }
+ std::cerr << "Debug graphs can be found in " << testing::TempDir()
+ << std::endl;
+ }
+}
+
+std::string LifetimeAnalysisTest::QualifiedName(
+ const clang::FunctionDecl* func) {
+ // TODO(veluca): figure out how to name overloaded functions.
+ std::string str;
+ llvm::raw_string_ostream ostream(str);
+ func->printQualifiedName(ostream);
+ ostream.flush();
+ return str;
+}
+
+NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimes(
+ llvm::StringRef source_code, const GetLifetimesOptions& options) {
+ NamedFuncLifetimes tu_lifetimes;
+
+ auto test = [&tu_lifetimes, &options, this](
+ clang::ASTContext& ast_context,
+ const LifetimeAnnotationContext& lifetime_context) {
+ // This will get called even if the code contains compilation errors.
+ // So we need to check to avoid performing an analysis on code that
+ // doesn't compile.
+ if (ast_context.getDiagnostics().hasUncompilableErrorOccurred() &&
+ !analyze_broken_code_) {
+ tu_lifetimes.Add("", "Compilation error -- see log for details");
+ return;
+ }
+
+ auto result_callback = [&tu_lifetimes, &options](
+ const clang::FunctionDecl* func,
+ const FunctionLifetimesOrError&
+ lifetimes_or_error) {
+ if (std::holds_alternative<FunctionAnalysisError>(lifetimes_or_error)) {
+ tu_lifetimes.Add(
+ QualifiedName(func),
+ absl::StrCat(
+ "ERROR: ",
+ std::get<FunctionAnalysisError>(lifetimes_or_error).message));
+ return;
+ }
+ const auto& func_lifetimes =
+ std::get<FunctionLifetimes>(lifetimes_or_error);
+
+ // Do not insert in the result set implicitly-defined constructors or
+ // assignment operators.
+ if (auto* constructor =
+ clang::dyn_cast<clang::CXXConstructorDecl>(func)) {
+ if (constructor->isImplicit() && !options.include_implicit_methods) {
+ return;
+ }
+ }
+ if (auto* method = clang::dyn_cast<clang::CXXMethodDecl>(func)) {
+ if (method->isImplicit() && !options.include_implicit_methods) {
+ return;
+ }
+ }
+
+ tu_lifetimes.Add(QualifiedName(func), NameLifetimes(func_lifetimes));
+ };
+
+ FunctionDebugInfoMap func_ptr_debug_info_map;
+ llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError>
+ analysis_result;
+ if (options.with_template_placeholder) {
+ AnalyzeTranslationUnitWithTemplatePlaceholder(
+ ast_context.getTranslationUnitDecl(), lifetime_context,
+ result_callback,
+ /*diag_reporter=*/{}, &func_ptr_debug_info_map);
+ } else {
+ analysis_result = AnalyzeTranslationUnit(
+ ast_context.getTranslationUnitDecl(), lifetime_context,
+ /*diag_reporter=*/{}, &func_ptr_debug_info_map);
+
+ for (const auto& [func, lifetimes_or_error] : analysis_result) {
+ result_callback(func, lifetimes_or_error);
+ }
+ }
+
+ for (auto& [func, debug_info] : func_ptr_debug_info_map) {
+ debug_info_map_.try_emplace(func->getDeclName().getAsString(),
+ std::move(debug_info));
+ }
+ };
+
+ if (!runOnCodeWithLifetimeHandlers(source_code, test,
+ {"-fsyntax-only", "-std=c++17"})) {
+ // We need to disambiguate between two cases:
+ // - We were unable to run the analysis at all (because there was some
+ // internal error)
+ // In this case, `tu_lifetimes` will be empty, so add a corresponding
+ // note here.
+ // - The analysis emitted an error diagnostic, which will also cause us to
+ // end up here.
+ // In this case, `tu_lifetimes` already contains an error empty, so we
+ // don't need to do anything.
+ if (tu_lifetimes.Entries().empty()) {
+ tu_lifetimes.Add("", "Error running dataflow analysis");
+ }
+ }
+
+ return tu_lifetimes;
+}
+
+NamedFuncLifetimes LifetimeAnalysisTest::GetLifetimesWithPlaceholder(
+ llvm::StringRef source_code) {
+ GetLifetimesOptions options;
+ options.with_template_placeholder = true;
+ return GetLifetimes(source_code, options);
+}
+
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/lifetime_analysis_test.h b/lifetime_analysis/test/lifetime_analysis_test.h
new file mode 100644
index 0000000..fea24d5
--- /dev/null
+++ b/lifetime_analysis/test/lifetime_analysis_test.h
@@ -0,0 +1,49 @@
+// 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
+
+#ifndef DEVTOOLS_RUST_CC_INTEROP_LIFETIME_ANALYSIS_TEST_LIFETIME_ANALYSIS_TEST_H_
+#define DEVTOOLS_RUST_CC_INTEROP_LIFETIME_ANALYSIS_TEST_LIFETIME_ANALYSIS_TEST_H_
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "lifetime_analysis/analyze.h"
+#include "lifetime_annotations/test/named_func_lifetimes.h"
+
+namespace clang {
+namespace tidy {
+namespace lifetimes {
+
+class LifetimeAnalysisTest : public testing::Test {
+ protected:
+ void TearDown() override;
+
+ static std::string QualifiedName(const clang::FunctionDecl* func);
+
+ struct GetLifetimesOptions {
+ GetLifetimesOptions()
+ : with_template_placeholder(false), include_implicit_methods(false) {}
+ bool with_template_placeholder;
+ bool include_implicit_methods;
+ };
+
+ NamedFuncLifetimes GetLifetimes(
+ llvm::StringRef source_code,
+ const GetLifetimesOptions& options = GetLifetimesOptions());
+
+ NamedFuncLifetimes GetLifetimesWithPlaceholder(llvm::StringRef source_code);
+
+ void AnalyzeBrokenCode() { analyze_broken_code_ = true; }
+
+ private:
+ absl::flat_hash_map<std::string, FunctionDebugInfo> debug_info_map_;
+ bool analyze_broken_code_ = false;
+};
+
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
+
+#endif // DEVTOOLS_RUST_CC_INTEROP_LIFETIME_ANALYSIS_TEST_LIFETIME_ANALYSIS_TEST_H_
diff --git a/lifetime_analysis/test/lifetime_params.cc b/lifetime_analysis/test/lifetime_params.cc
new file mode 100644
index 0000000..39288c3
--- /dev/null
+++ b/lifetime_analysis/test/lifetime_params.cc
@@ -0,0 +1,87 @@
+// 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 involving lifetime parameters.
+
+#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, SimpleLifetimeParams) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* x;
+ };
+
+ S target(S s) {
+ return s;
+ }
+ )"),
+ LifetimesContain({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeParamsMultiplePointers) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a", "b")]] S {
+ [[clang::annotate("member_lifetimes", "a", "b")]]
+ int** x;
+ };
+
+ S target(S s) {
+ return s;
+ }
+ )"),
+ LifetimesContain({{"target", "([a, b]) -> ([a, b])"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeParamsMultiplePointersMultipleMembers) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a", "b")]] S {
+ [[clang::annotate("member_lifetimes", "a", "b")]]
+ int** x;
+ [[clang::annotate("member_lifetimes", "b", "a")]]
+ int** y;
+ };
+
+ int** ret_x(S s) {
+ return s.x;
+ }
+
+ int** ret_y(S s) {
+ return s.y;
+ }
+ )"),
+ LifetimesAre({{"ret_y", "([a, b]) -> (b, a)"},
+ {"ret_x", "([a, b]) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, LifetimeParamsNested) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a", "b")]] T {
+ [[clang::annotate("member_lifetimes", "a", "b")]]
+ int** x;
+ };
+
+ struct [[clang::annotate("lifetime_params", "a", "b")]] S {
+ [[clang::annotate("member_lifetimes", "b", "a")]]
+ T t;
+ };
+
+ int** target(S s) {
+ return s.t.x;
+ }
+ )"),
+ LifetimesContain({{"target", "([a, b]) -> (b, a)"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/records.cc b/lifetime_analysis/test/records.cc
new file mode 100644
index 0000000..2533e15
--- /dev/null
+++ b/lifetime_analysis/test/records.cc
@@ -0,0 +1,1179 @@
+// 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 involving (non-template) records (structs, classes, unions).
+
+#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, MembersWithSameAnnotationMergeLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* i;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* j;
+ };
+ void target(S* s, int* p, int* q) {
+ s->i = p;
+ s->j = q;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructsWithTemplateFieldsDoesNotMergeLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename A, typename B>
+ struct S { A i; B j; };
+ void target(S<int*, int*>* s, int* p, int* q) {
+ s->i = p;
+ s->j = q;
+ }
+ )"),
+ LifetimesAre({{"target", "(<a, b>, c), a, b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithArrayMergesLifetimes) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename A>
+ struct S { A array; };
+ void target(S<int**>* s, int* p, int* q) {
+ s->array[0] = p;
+ s->array[1] = q;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b, c), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, DeclRecordWithConditionalOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename P>
+ struct S { P p; };
+ int* target(int* a, int* b, bool cond) {
+ S<int*> s = cond ? S<int*>{a} : S<int*>{b};
+ return s.p;
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnRecordWithConditionalOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename P>
+ struct S { P p; };
+ S<int*> target(int* a, int* b, bool cond) {
+ return cond ? S<int*>{a} : S<int*>{b};
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MaterializeRecordWithConditionalOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename P>
+ struct S { P p; };
+ int* target(int* a, int* b, bool cond) {
+ return (cond ? S<int*>{a} : S<int*>{b}).p;
+ }
+ )"),
+ LifetimesAre({{"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorInitRecordWithConditionalOperator) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename P>
+ struct S { P p; };
+ template <typename P>
+ struct T {
+ T(int* a, int* b, bool cond) : s(cond ? S<int*>{a} : S<int*>{b}) {}
+ S<P> s;
+ };
+ int* target(int* a, int* b, bool cond) {
+ T<int*> t(a, b, cond);
+ return t.s.p;
+ }
+ )"),
+ LifetimesAre({{"T<int *>::T", "(a, b): a, a, ()"},
+ {"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MemberInitRecordWithConditionalOperator) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename P>
+ struct S { P p; };
+ template <typename A, typename B, typename P>
+ struct T {
+ T(int* a, int* b, bool cond) : a(a), b(b), cond(cond) {}
+ A a;
+ B b;
+ bool cond;
+ S<P> s{cond ? S<int*>{a} : S<int*>{b}};
+ };
+ int* target(int* a, int* b, bool cond) {
+ T<int*, int*, int*> t(a, b, cond);
+ return t.s.p;
+ }
+ )"),
+ LifetimesAre({{"T<int *, int *, int *>::T", "(<a, a, a>, b): a, a, ()"},
+ {"target", "a, a, () -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleStruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ };
+ void target(S* s, int* a, int* b) {
+ s->a = a;
+ s->b = b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, SimpleUnion) {
+ EXPECT_THAT(GetLifetimes(R"(
+ union [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ };
+ void target(S* s, int* a, int* b) {
+ s->a = a;
+ s->b = b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructReference) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ };
+ void target(S& s, int* a, int* b) {
+ s.a = a;
+ s.b = b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), a, a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructValue) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ int* target(S s) {
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMultiplePtrsSameLifetime) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a", "a")]]
+ int*** a;
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** b;
+ };
+ void f(S& s) {
+ **s.a = *s.b;
+ }
+ void target(int** a, int* b) {
+ S s{&a, &b};
+ f(s);
+ }
+ )"),
+ LifetimesContain({{"target", "(a, b), a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructNonLocalPtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ int* target(S* s) {
+ return s->a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberStructInitializedWithInitializerList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] T {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S(int* a): t{a} { }
+ [[clang::annotate("member_lifetimes", "a")]]
+ T t;
+ };
+ int* target(int* a) {
+ return S{a}.t.a;
+ }
+ )"),
+ LifetimesAre({{"S::S", "(a, b): a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberPtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int a;
+ };
+ int* target(S* s) {
+ return &s->a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructReferenceMember) {
+ // This is a regression test for a bug where we were not treating accesses to
+ // member variables of reference type correctly.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S1 {
+ int a;
+ };
+ struct [[clang::annotate("lifetime_params", "a")]] S2 {
+ [[clang::annotate("member_lifetimes", "a")]]
+ S1 &s1;
+ };
+ int& target(S2* s2) {
+ // Make sure we can find the field S1::a. This is to ensure that our
+ // member access for s2->s1 is in fact returning an object of type S1
+ // (not S1&).
+ s2->s1.a = 5;
+ return s2->s1.a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructStaticMemberFunction) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ static int* f(int* x) { return x; }
+ };
+ int* target(int* a) {
+ return S::f(a);
+ }
+ )"),
+ LifetimesAre({{"S::f", "a -> a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFunction) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return a; }
+ };
+ )"),
+ LifetimesAre({{"S::f", "(a, b): -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFunctionExplicitThis) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return this->a; }
+ };
+ )"),
+ LifetimesAre({{"S::f", "(a, b): -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFunctionCall) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return a; }
+ };
+ int* target(S* s) {
+ return s->f();
+ }
+ )"),
+ LifetimesAre({{"S::f", "(a, b): -> a"}, {"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFunctionCallDot) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ int* f() { return a; }
+ };
+ int* target(S* s) {
+ return (*s).f();
+ }
+ )"),
+ LifetimesAre({{"S::f", "(a, b): -> a"}, {"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFunctionComplexCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ void set(int* x) { a = x; }
+ int* f() { return a; }
+ };
+ int* target(S* s, int* b) {
+ s->set(b);
+ return (*s).f();
+ }
+ )"),
+ LifetimesAre({{"S::set", "(a, b): a"},
+ {"S::f", "(a, b): -> a"},
+ {"target", "(a, b), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructReturnAddressOfMemberFunction) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ static void f();
+ };
+ typedef void (*funtype)();
+ funtype target() {
+ S s;
+ return s.f;
+ }
+ )"),
+ LifetimesContain({{"target", "-> static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructDefaultConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target() {
+ S s;
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructDefaultConstructor_ExplicitCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target() {
+ S();
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S(int* a) { this->a = a; }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(int* a) {
+ static S s{a};
+ }
+ )"),
+ LifetimesAre({{"S::S", "(a, b): a"}, {"target", "static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCopyConstructorStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(S* x) {
+ static S s = *x;
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorOutputsFieldPointer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S(int** field_out) {
+ *field_out = &i;
+ }
+ int i;
+ };
+ int* target() {
+ int* i_out;
+ S s(&i_out);
+ return i_out;
+ }
+ )"),
+ LifetimesAre({{"S::S", "a: (a, b)"},
+ {"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorOutputsThisPointer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S(S** this_out) {
+ *this_out = this;
+ }
+ };
+ S* target() {
+ S* s_out;
+ S s(&s_out);
+ return s_out;
+ }
+ )"),
+ LifetimesAre({{"S::S", "a: (a, b)"},
+ {"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ StructConstructorOutputsFieldPointerConstructorInitializer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S(int** field_out) {
+ *field_out = &i;
+ }
+ int i;
+ };
+ struct T {
+ T(int** int_out): s(int_out) {}
+ S s;
+ };
+ )"),
+ LifetimesAre({{"S::S", "a: (a, b)"}, {"T::T", "a: (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ StructConstructorOutputsThisPointerConstructorInitializer) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S(S** this_out) {
+ *this_out = this;
+ }
+ };
+ struct T {
+ T(S** this_out): s(this_out) {}
+ S s;
+ };
+ )"),
+ LifetimesAre({{"S::S", "a: (a, b)"}, {"T::T", "a: (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorOutputsThisPointerInitMember) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S(S** this_out) {
+ *this_out = this;
+ }
+ };
+ static S* static_s_ptr;
+ struct T {
+ T() {}
+ S s{&static_s_ptr};
+ };
+ )"),
+ LifetimesAre({{"S::S", "a: (a, b)"}, {"T::T", "static:"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorInitializers) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S(int* a): a(a) { }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a = nullptr;
+ // The following members don't affect lifetimes, but we keep them
+ // around to make sure that the related code is exercised.
+ int b = 0;
+ // This member points into the struct itself, forcing the lifetime
+ // parameter in the constructor to be the same as the lifetime of the
+ // object itself.
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* c = &b;
+ int d;
+ };
+ )"),
+ LifetimesAre({{"S::S", "(a, a): a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorStaticPtr) {
+ // TODO(veluca): this is overly restrictive in the same way as
+ // StaticPointerOutParam.
+ EXPECT_THAT(GetLifetimes(R"(
+ static int x;
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S() { a = &x; }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a = nullptr;
+ };
+ )"),
+ LifetimesAre({{"S::S", "(static, a):"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorStaticPtrInitializer) {
+ // TODO(veluca): this is overly restrictive in the same way as
+ // StaticPointerOutParam.
+ EXPECT_THAT(GetLifetimes(R"(
+ static int x;
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S(): a(&x) { }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a = nullptr;
+ };
+ )"),
+ LifetimesAre({{"S::S", "(static, a):"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructConstructorStaticPtrMemberInitializer) {
+ // TODO(veluca): this is overly restrictive in the same way as
+ // StaticPointerOutParam.
+ EXPECT_THAT(GetLifetimes(R"(
+ static int x;
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S() { }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a = &x;
+ };
+ )"),
+ LifetimesAre({{"S::S", "(static, a):"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructMemberFreeFunction) {
+ // Check that calling a method behaves in the same way as a free function.
+ EXPECT_THAT(GetLifetimes(R"(
+ static int x;
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ void f() { a = &x; }
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void f(S& a) {
+ a.a = &x;
+ }
+ )"),
+ LifetimesAre({{"S::f", "(static, a):"}, {"f", "(static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnFieldFromTemporaryStructConstructor) {
+ // S(i) with a single argument produces a clang::CXXFunctionalCastExpr around
+ // a clang::CXXConstructExpr.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ S(int* i) : f(i) {}
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* f;
+ };
+ int* ConstructorSyntax(int* i) {
+ return S{i}.f;
+ }
+ int* CastSyntax(int* i) {
+ return S(i).f;
+ }
+ )"),
+ LifetimesAre({{"S::S", "(a, b): a"},
+ {"ConstructorSyntax", "a -> a"},
+ {"CastSyntax", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ReturnFieldFromTemporaryStructConstructorInitList) {
+ // S has no constructors so S{i} produces a clang::InitListExpr.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* f;
+ };
+ int* target(int* i) {
+ return S{i}.f;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnFieldFromTemporaryUnion) {
+ // S has no constructors so S{i} produces a clang::InitListExpr.
+ EXPECT_THAT(GetLifetimes(R"(
+ union [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* f;
+ };
+ int* target(int* i) {
+ return S{i}.f;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInitList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(int* a) {
+ S s{a};
+ }
+ )"),
+ LifetimesAre({{"target", "a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, UnionInitList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ union [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(int* a) {
+ S s{a};
+ }
+ )"),
+ LifetimesAre({{"target", "a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCopy) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(S* a, S* b) {
+ *a = *b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), (a, c)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCopyStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(S* x) {
+ static S s;
+ s = *x;
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a)"}}));
+}
+
+// We fail to initialize the temporary object in a CXXOperatorCallExpr argument,
+// which causes us to assert when we visit the MaterializeTemporaryExpr later.
+TEST_F(LifetimeAnalysisTest, DISABLED_CallExprWithRecordInitializedArguments) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* i;
+ };
+ S callee(const S& s1, const S& s2) {
+ return S{s2.i};
+ }
+ S target(int* a, int* b) {
+ return callee(S{a}, S{b});
+ }
+ )"),
+ LifetimesAre({{"target", "a, b -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructAssignMemberStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ void target(int* x) {
+ static S s;
+ s.a = x;
+ }
+ )"),
+ LifetimesAre({{"target", "static"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCopyExplicit) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ S& operator=(const S& other) {
+ a = other.a;
+ return *this;
+ }
+ };
+ void target(S* a, S* b) {
+ *a = *b;
+ }
+ )"),
+ LifetimesAre({{"S::operator=", "(a, c): (a, b) -> (a, c)"},
+ {"target", "(a, b), (a, c)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructCopyExplicitNoop) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ S& operator=(const S& other) {
+ return *this;
+ }
+ };
+ void target(S* a, S* b) {
+ *a = *b;
+ }
+ )"),
+ LifetimesAre({{"S::operator=", "(c, d): (a, b) -> (c, d)"},
+ {"target", "(a, b), (c, d)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructWithStruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] T {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ T t;
+ };
+ int* target(S* s) {
+ return s->t.a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, NonReferenceLikeStruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int a;
+ };
+ void target(S* a, S* b) {
+ a->a = b->a;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructNonReferenceLikeField) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ int a;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* b;
+ };
+ void target(S* a, S* b) {
+ a->a = b->a;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), (c, d)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructAssignToReference) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ int a;
+ [[clang::annotate("member_lifetimes", "a")]]
+ int& b;
+ };
+ void target(S* a, S* b) {
+ a->a = b->a;
+ a->b = b->b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b), (c, d)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, NonReferenceLikeStructCopyAssignment) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int a;
+ };
+ void target(S* a, S* b) {
+ *a = *b;
+ }
+ )"),
+ LifetimesAre({{"target", "a, b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnReferenceLikeStruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* p;
+ };
+ S target() {
+ int i = 42;
+ S s = { &i };
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnNonReferenceLikeStruct) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int a;
+ };
+ S target() {
+ int i = 42;
+ S s = { i };
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnNonReferenceLikeStructCopy) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int a;
+ };
+ S target(S& s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", "a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnNonReferenceLikeStructFromTemporary) {
+ // This is a repro for a crash observed on b/228325046.
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {};
+ S target() {
+ return S();
+ }
+ )"),
+ LifetimesAre({{"target", ""}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInnerDoublePtrInitList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** x;
+ };
+
+ void f(int** b) {
+ S s{b};
+ int i = 0;
+ *s.x = &i;
+ }
+ )"),
+ LifetimesAre({{"f",
+ "ERROR: function returns reference to a local "
+ "through parameter 'b'"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInnerDoublePtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** x;
+ };
+
+ void g(S* s, int* a) {
+ *s->x = a;
+ }
+
+ void f(int* a, int** b) {
+ S s{b};
+ g(&s, a);
+ }
+ )"),
+ LifetimesAre({{"f", "a, (a, b)"}, {"g", "(a, b), a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInnerDoublePtrAssign) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** x;
+ };
+
+ void g(S* s, int* a) {
+ *s->x = a;
+ }
+
+ int* f(int* a, int** b) {
+ S s{b};
+ g(&s, a);
+ return *b;
+ }
+ )"),
+ LifetimesAre({{"f", "a, (a, b) -> a"}, {"g", "(a, b), a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructInnerDoublePtrParam) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** x;
+ };
+
+ void g(S* s, int* a) {
+ *s->x = a;
+ }
+
+ void f(S& s, int* a, int** b) {
+ s.x = b;
+ g(&s, a);
+ }
+ )"),
+ LifetimesAre({{"f", "(a, b), a, (a, a)"}, {"g", "(a, b), a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, List) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] List {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ List* next;
+ void Append(List& oth) {
+ next = &oth;
+ }
+ int* Get() const {
+ return a;
+ }
+ };
+ int* target(List* l, int* a) {
+ if (l->next) {
+ l->next->a = a;
+ }
+ return l->Get();
+ }
+ )"),
+ LifetimesAre({{"List::Append", "(a, b): (a, a)"},
+ {"List::Get", "(a, b): -> a"},
+ {"target", "(a, b), a -> a"}}));
+}
+
+// TODO(danakj): Crashes trying to find the initializer expression under
+// MaterializeTemporaryExpr. Should be improved by cl/414032764.
+TEST_F(LifetimeAnalysisTest, DISABLED_VarDeclReferenceToRecordTemporary) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ int* target(int* a) {
+ const S<int*>& s = S<int*>{a};
+ return s.a;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VarDeclReferenceToRecordTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*>* target(S<int*>* a) {
+ S<int*>& b = *a;
+ return &b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, VarDeclReferenceToRecordNoTemplate) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ S* target(S* a) {
+ S& b = *a;
+ return &b;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> (a, b)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorInitReferenceToRecord) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* a;
+ };
+ template <class Ref>
+ struct R {
+ R(S& s): s(s) {}
+ Ref s;
+ };
+ int* target(S* a) {
+ R<S&> r(*a);
+ return r.s.a;
+ }
+ )"),
+ LifetimesAre({{"R<S &>::R", "(a, b, c): (a, b)"},
+ {"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MemberInitReferenceToRecord) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ template <typename P>
+ struct S {
+ P p;
+ };
+ template<typename P>
+ struct [[clang::annotate("lifetime_params", "a")]] R {
+ R(P p): ss{p} {}
+ S<P> ss;
+ [[clang::annotate("member_lifetimes", "a")]]
+ S<P>& s{ss};
+ };
+ int* target(int* a) {
+ R<int*> r(a);
+ return r.s.p;
+ }
+ )"),
+ LifetimesAre({{"R<int *>::R", "(<a> [b], b): a"}, {"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturn) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(S<int*>& s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", "(a, b) -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnXvalue) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(S<int*> s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> take_by_ref(S<int*>& s) {
+ return s;
+ }
+ S<int*> take_by_value(S<int*> s) {
+ return s;
+ }
+ )"),
+ LifetimesAre({{"take_by_ref", "(a, b) -> a"},
+ {"take_by_value", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StructTemplateReturnLocal) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> target(int* a) {
+ int i = 42;
+ return { &i };
+ }
+ )"),
+ LifetimesAre({{"target",
+ "ERROR: function returns reference to a local"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryConstructor) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ S(T a) : a(a) {}
+ T a;
+ };
+ S<int*> ConstructorCastSyntax(int* a) {
+ return S(a);
+ }
+ S<int*> ConstructTemporarySyntax(int* a) {
+ return S{a};
+ }
+ )"),
+ LifetimesAre({{"S<int *>::S", "(a, b): a"},
+ {"ConstructorCastSyntax", "a -> a"},
+ {"ConstructTemporarySyntax", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStructTemporaryInitializerList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ struct S {
+ T a;
+ };
+ S<int*> InitListExpr(int* a) {
+ return {a};
+ }
+ S<int*> CastWithInitListExpr(int* a) {
+ return S<int*>{a};
+ }
+ )"),
+ LifetimesAre({{"InitListExpr", "a -> a"},
+ {"CastWithInitListExpr", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnUnionTemporaryInitializerList) {
+ EXPECT_THAT(GetLifetimes(R"(
+ template <typename T>
+ union S {
+ T a;
+ };
+ S<int*> target(int* a) {
+ return {a};
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/recursion.cc b/lifetime_analysis/test/recursion.cc
new file mode 100644
index 0000000..c8ebe2e
--- /dev/null
+++ b/lifetime_analysis/test/recursion.cc
@@ -0,0 +1,78 @@
+// 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 involving recursion.
+
+#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, InfiniteDirectRecursion) {
+ // TODO(danakj): Infinite recursion is UB, so we would like to avoid that we
+ // call an opaque function that is able to break the recursion (by exiting the
+ // program, theoretically).
+ EXPECT_THAT(GetLifetimes(R"(
+ void opaque();
+ int* f(int* a) {
+ // TODO(danakj): opaque();
+ return f(a);
+ }
+ )"),
+ LifetimesAre({{"f", "a -> b"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FiniteDirectRecursion_1Pointee) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int n, int* a) {
+ if (n <= 0) return a;
+ return f(n - 1, a);
+ }
+ )"),
+ LifetimesAre({{"f", "(), a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FiniteDirectRecursion_2Pointees) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int n, int* a, int* b) {
+ if (n <= 0) return a;
+ return f(n - 1, b, a);
+ }
+ )"),
+ LifetimesAre({{"f", "(), a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FiniteDirectRecursion_3Pointees) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* f(int n, int* a, int* b, int *c) {
+ if (n <= 0) return a;
+ return f(n - 1, b, c, a);
+ }
+ )"),
+ LifetimesAre({{"f", "(), a, a, a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MutualFiniteRecursion) {
+ EXPECT_THAT(GetLifetimes(R"(
+ int* g(int n, int* a);
+ int* f(int n, int* a) {
+ if (n == 0) return a;
+ return g(n - 1, a);
+ }
+ int* g(int n, int* a) {
+ if (n == 0) return a;
+ return f(n - 1, a);
+ }
+ )"),
+ LifetimesAre({{"f", "(), a -> a"}, {"g", "(), a -> a"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/static_lifetime.cc b/lifetime_analysis/test/static_lifetime.cc
new file mode 100644
index 0000000..8d18583
--- /dev/null
+++ b/lifetime_analysis/test/static_lifetime.cc
@@ -0,0 +1,237 @@
+// 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 involving static lifetimes.
+
+#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, MaybeReturnStatic) {
+ // Check that we don't infer 'static for the parameter or the return value,
+ // which would be overly restrictive.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ int* target(int* i_non_static) {
+ if (*i_non_static > 0) {
+ return i_non_static;
+ } else {
+ static int i_static;
+ return &i_static;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MaybeReturnStaticConst) {
+ // Same as above, but return a pointer-to-const. This should have no
+ // influence on the outcome.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ const int* target(int* i_non_static) {
+ if (*i_non_static > 0) {
+ return i_non_static;
+ } else {
+ static int i_static;
+ return &i_static;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, StaticPointerOutParam) {
+ // TODO(mboehme): The lifetimes inferred here are overly restrictive. The
+ // function doesn't require the input that is passed in to have static
+ // lifetime, so it shouldn't enforce this condition on the caller. The
+ // lifetimes should be (a, b), and this would still allow the caller to
+ // substitute `static for a if desired.
+ // The root of the issue is that when we see a static lifetime in a points-to
+ // set, we don't know whether that means that
+ // - The pointer happens to point to something with static lifetime, but
+ // nothing is depending on that, or
+ // - The pointer is required to point to something with static lifetime.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ void f(int** p) {
+ static int i = 42;
+ *p = &i;
+ }
+ )"),
+ LifetimesAre({{"f", "(static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStruct) {
+ // We infer a `static` lifetime parameter for `s_static` because any pointers
+ // contained in it need to outlive the struct itself. This implies that the
+ // lifetime parameter for the return value also needs to be `static`, and
+ // hence the lifetime parameter on `*s_input` needs to be `static` too.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** pp;
+ };
+ S* target(S* s_input) {
+ if (**s_input->pp > 0) {
+ return s_input;
+ } else {
+ static S s_static;
+ return &s_static;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a) -> (static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStructConst) {
+ // Same as above, but return a pointer-to-const. This shouldn't affect the
+ // result, as it's still possible to modify `*s.pp` even if for a `const S s`.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a", "a")]]
+ int** pp;
+ };
+ const S* target(S* s_input) {
+ if (**s_input->pp > 0) {
+ return s_input;
+ } else {
+ static S s_static;
+ return &s_static;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a) -> (static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, MaybeReturnStaticStructConstWithoutPointer) {
+ // Same as above, but with a struct that doesn't actually contain any
+ // pointers. This changes the result, as a 'static struct without any pointer
+ // can be used in place of a struct of the same type of any lifetime.
+
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ int i;
+ };
+ const S* target(S* s_input) {
+ if (s_input->i > 0) {
+ return s_input;
+ } else {
+ static S s_static;
+ return &s_static;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStaticDoublePointerWithConditional) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* p;
+ };
+ int** target(int** pp1, int** pp2) {
+ // Force *pp1 to have static lifetime.
+ static S s;
+ s.p = *pp1;
+
+ if (**pp1 > 0) {
+ return pp1;
+ } else {
+ return pp2;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a), (static, a) -> (static, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ReturnStaticConstDoublePointerWithConditional) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct [[clang::annotate("lifetime_params", "a")]] S {
+ [[clang::annotate("member_lifetimes", "a")]]
+ int* p;
+ };
+ int* const * target(int** pp1, int** pp2) {
+ // Force *pp1 to have static lifetime.
+ static S s;
+ s.p = *pp1;
+
+ if (**pp1 > 0) {
+ return pp1;
+ } else {
+ return pp2;
+ }
+ }
+ )"),
+ LifetimesAre({{"target", "(static, a), (b, a) -> (b, a)"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorStoresThisPointerInStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S() {
+ static S* last_constructed = this;
+ }
+ };
+ )"),
+ // Because S() stores the `this` pointer in a static variable, the
+ // lifetime of the `this` pointer needs to be static. This means
+ // that any instances of `S` that are constructed need to have
+ // static lifetime.
+ LifetimesAre({{"S::S", "static:"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, ConstructorStoresThisPointerInStatic_WithField) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S() {
+ static S* last_constructed = this;
+ }
+ };
+ struct T {
+ // Ensure that T() isn't defaulted because we don't want to trigger the
+ // special logic for defaulted functions.
+ T() {}
+ S s;
+ };
+ )"),
+ // TODO(b/230725905): The lifetimes for T::T should be "static:"
+ // because T contains a member variable of type S, and all
+ // instances of S need to be static.
+ LifetimesAre({{"S::S", "static:"}, {"T::T", "a:"}}));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ ConstructorStoresThisPointerInStatic_WithDerivedClass) {
+ EXPECT_THAT(GetLifetimes(R"(
+ struct S {
+ S() {
+ static S* last_constructed = this;
+ }
+ };
+ struct T : public S {
+ // Ensure that T() isn't defaulted because we don't want to trigger the
+ // special logic for defaulted functions.
+ T() {}
+ };
+ )"),
+ // TODO(b/230725905): The lifetimes for T::T should be "static:"
+ // because T derives from S and all instances of S need to be
+ // static.
+ LifetimesAre({{"S::S", "static:"}, {"T::T", "a:"}}));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang
diff --git a/lifetime_analysis/test/virtual_functions.cc b/lifetime_analysis/test/virtual_functions.cc
new file mode 100644
index 0000000..78eb244
--- /dev/null
+++ b/lifetime_analysis/test/virtual_functions.cc
@@ -0,0 +1,265 @@
+// 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 involving lifetime propagation between virtual functions.
+
+#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, WithPureVirtual) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a) = 0;
+};
+
+struct Derived : public Base {
+ int* f(int* a) override { return a; }
+};
+ )"),
+ LifetimesContain(
+ {{"Base::f", "b: a -> a"}, {"Derived::f", "b: a -> a"}}));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a) = 0;
+};
+
+struct Derived1 : public Base {
+ int* f(int* a) override { return a; }
+};
+
+struct Derived2 : public Base {
+ int* f(int* a) override {
+ static int i = 42;
+ return &i;
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a -> a"},
+ {"Derived1::f", "b: a -> a"},
+ {"Derived2::f", "b: a -> static"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithTwoDeriveds) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a, int* b) = 0;
+};
+
+struct Derived1 : public Base {
+ int* f(int* a, int* b) override { return a; }
+};
+
+struct Derived2 : public Base {
+ int* f(int* a, int* b) override { return b; }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a, a -> a"},
+ {"Derived1::f", "c: a, b -> a"},
+ {"Derived2::f", "c: a, b -> b"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithBaseReturnStatic) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a) { return a; }
+};
+
+struct Derived : public Base {
+ int* f(int* a) override {
+ static int i = 42;
+ return &i;
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a -> a"},
+ {"Derived::f", "b: a -> static"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceChained) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a) = 0;
+};
+
+struct Derived1 : public Base {
+ int* f(int* a) override {
+ static int i = 42;
+ return &i;
+ }
+};
+
+struct Derived2 : public Derived1 {
+ int* f(int* a) override { return a; }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a -> a"},
+ {"Derived1::f", "b: a -> a"},
+ {"Derived2::f", "b: a -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithControlFlow) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a, int* b) { return a; }
+};
+
+struct Derived : public Base {
+ int* f(int* a, int* b) override {
+ if (*a < *b)
+ return a;
+ return b;
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a, a -> a"},
+ {"Derived::f", "b: a, a -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithLocal) {
+ EXPECT_THAT(
+ GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a) { return a; }
+};
+
+struct Derived : public Base {
+ int* f(int* a) override {
+ int i = 42;
+ return &i;
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a -> a"},
+ {"Derived::f", "ERROR: function returns reference to a local"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithStaticPtr) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual void f(int** a) {}
+};
+
+struct Derived : public Base {
+ void f(int** a) override {
+ static int i = 42;
+ *a = &i;
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: (static, a)"},
+ {"Derived::f", "b: (static, a)"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithRecursion) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a, int* b) { return a; }
+};
+
+struct Derived : public Base {
+ int* f(int* a, int* b) override {
+ if (*a > *b)
+ return b;
+ *a -= 1;
+ return f(a, b);
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a, a -> a"},
+ {"Derived::f", "c: a, b -> b"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest, FunctionVirtualInheritanceWithExplicitBaseCall) {
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a, int* b) { return a; }
+};
+
+struct Derived : public Base {
+ int* f(int* a, int* b) override {
+ if (*a > *b)
+ return b;
+ return Base::f(a, b);
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a, a -> a"},
+ {"Derived::f", "b: a, a -> a"},
+ }));
+}
+
+TEST_F(LifetimeAnalysisTest,
+ DISABLED_FunctionVirtualInheritanceWithComplexRecursion) {
+ // TODO(kinuko): Fix this. Currently this doesn't work because in
+ // AnalyzeFunctionRecursive() the recursion cycle check
+ // (FindAndMarkCycleWithFunc) happens before the code expands the possible
+ // overrides, and let it return early when it finds f() in Base::f() even if
+ // it has overrides. Later in AnalyzeRecursiveFunctions Base::f() is analyzed
+ // but it doesn't expand the overrides there. See the TODO in
+ // AnalyzeFunctionRecursive.
+ EXPECT_THAT(GetLifetimes(R"(
+struct Base {
+ virtual ~Base() {}
+ virtual int* f(int* a, int* b) {
+ if (*a > *b)
+ return b;
+ *a -= 1;
+ return f(a, b);
+ }
+};
+
+struct Derived : public Base {
+ int* f(int* a, int* b) override {
+ if (*a == *b)
+ return a;
+ return Base::f(a, b);
+ }
+};
+ )"),
+ LifetimesContain({
+ {"Base::f", "b: a, a -> a"},
+ {"Derived::f", "b: a, a -> a"},
+ }));
+}
+
+} // namespace
+} // namespace lifetimes
+} // namespace tidy
+} // namespace clang