Add transfer function for `MaterializeTemporaryExpr` in nullability verification

This allows us to handle expressions where `prvalue` temporaries are written to memory so they can be bound to references. For example, passing a member call to a function that takes a const reference argument.

PiperOrigin-RevId: 502610175
diff --git a/nullability_verification/pointer_nullability_analysis.cc b/nullability_verification/pointer_nullability_analysis.cc
index fd73e18..a750066 100644
--- a/nullability_verification/pointer_nullability_analysis.cc
+++ b/nullability_verification/pointer_nullability_analysis.cc
@@ -486,6 +486,26 @@
   });
 }
 
+void transferNonFlowSensitiveMaterializeTemporaryExpr(
+    const MaterializeTemporaryExpr* MTE, const MatchFinder::MatchResult& MR,
+    TransferState<PointerNullabilityLattice>& State) {
+  State.Lattice.insertExprNullabilityIfAbsent(MTE, [&]() {
+    auto BaseNullability = State.Lattice.getExprNullability(MTE->getSubExpr());
+    if (BaseNullability.has_value()) {
+      return BaseNullability->vec();
+    } else {
+      // Since we process child nodes before parents, we should already have
+      // computed the base (child) nullability. However, this is not true in all
+      // test cases. So, we return unspecified nullability annotations.
+      // TODO: Fix this issue, add a CHECK(BaseNullability.has_value()) and
+      // remove the else branch.
+      llvm::dbgs() << "Nullability of child node not found\n";
+      return std::vector<NullabilityKind>(countPointersInType(MTE->getType()),
+                                          NullabilityKind::Unspecified);
+    }
+  });
+}
+
 auto buildNonFlowSensitiveTransferer() {
   return CFGMatchSwitchBuilder<TransferState<PointerNullabilityLattice>>()
       .CaseOfCFGStmt<DeclRefExpr>(ast_matchers::declRefExpr(),
@@ -496,6 +516,9 @@
                                         transferNonFlowSensitiveMemberCallExpr)
       .CaseOfCFGStmt<CastExpr>(ast_matchers::castExpr(),
                                transferNonFlowSensitiveCastExpr)
+      .CaseOfCFGStmt<MaterializeTemporaryExpr>(
+          ast_matchers::materializeTemporaryExpr(),
+          transferNonFlowSensitiveMaterializeTemporaryExpr)
       .Build();
 }
 
diff --git a/nullability_verification/pointer_nullability_verification_test.cc b/nullability_verification/pointer_nullability_verification_test.cc
index 92815d9..0dd5942 100644
--- a/nullability_verification/pointer_nullability_verification_test.cc
+++ b/nullability_verification/pointer_nullability_verification_test.cc
@@ -3022,12 +3022,6 @@
     }
   )cc"));
 
-  // Member variables and member calls on a struct with nested template
-  // arguments.
-  // TODO: Add non-flow-sensitive transfer function for the dereference
-  // operator.
-  // TODO: Handle member call arguments (includes writing
-  // transferMaterializeTemporaryExpr).
   EXPECT_TRUE(checkDiagnostics(Declarations + R"cc(
     template <typename T0, typename T1>
     struct Struct2Arg {
@@ -3063,26 +3057,22 @@
           <NK_unspecified, NK_nullable, NK_nonnull, NK_nullable, NK_nullable>(
               p.arg1);
 
-      // TODO: Add support for member call arguments.
-      // __assert_nullability<NK_unspecified, NK_nullable>(p->getT0());
-      // __assert_nullability  // TODO: fix false negative.
-      //     <NK_unspecified, NK_nullable, NK_unspecified>(p->getT0());
-      // __assert_nullability  // TODO: fix false negative.
-      //     <NK_unspecified>(p->getT0());
-      // __assert_nullability  // TODO: fix false negative.
-      //     <NK_unspecified, NK_nullable, NK_nullable>(p->arg0);
-      //  __assert_nullability  // TODO: fix false negative.
-      //     <NK_nullable>(p->arg0);
+      __assert_nullability<NK_unspecified, NK_nullable>(p.getT0());
+      __assert_nullability<NK_nonnull>(p.getT1().getT0().getT1());
 
-      // __assert_nullability<NK_nonnull>(p->getT1().getT0().getT1());
-      // __assert_nullability<NK_nonnull>(p->getT1().arg0.getT1());
-      // __assert_nullability<NK_nonnull>(p->arg1.getT0().arg1);
-      // __assert_nullability<NK_nonnull>(p->arg1.arg0.arg1);
+      __assert_nullability  // [[unsafe]]
+          <NK_unspecified, NK_nullable, NK_unspecified>(p.getT0());
+      __assert_nullability  // [[unsafe]]
+          <NK_unspecified>(p.getT0());
 
-      // __assert_nullability  // TODO: fix false negative.
-      //     <>(p->getT1().getT0().getT1());
-      // __assert_nullability  // TODO: fix false negative.
-      //     <NK_nonnull, NK_nonnull>(p->arg1.getT0().arg1);
+      __assert_nullability<NK_nonnull>(p.getT1().arg0.getT1());
+      __assert_nullability<NK_nonnull>(p.arg1.getT0().arg1);
+      __assert_nullability<NK_nonnull>(p.arg1.arg0.arg1);
+
+      __assert_nullability  // [[unsafe]]
+          <>(p.getT1().getT0().getT1());
+      __assert_nullability  // [[unsafe]]
+          <NK_nonnull, NK_nonnull>(p.arg1.getT0().arg1);
     }
   )cc"));
 }
@@ -3156,6 +3146,71 @@
   )cc"));
 }
 
+// TODO: Handle non-flow-sensitive nullability of free functions to make the
+// following test work:
+TEST(PointerNullabilityTest, NonFlowSensitiveMaterializeTemporaryExpr) {
+  EXPECT_TRUE(checkDiagnostics(R"cc(
+    int *_Nonnull makeNonnull();
+    int *_Nullable makeNullable();
+    int *makeUnannotated();
+
+    template <typename T>
+    T identity(const T &);
+
+    void target() {
+      {
+        *identity<int *_Nonnull>(makeNonnull());
+        int *const &p = makeNonnull();
+        *p;
+      }
+      {
+        *identity<int *_Nullable>(makeNullable());  // TODO: Fix false negative.
+        int *const &p = makeNullable();
+        *p;  // [[unsafe]]
+      }
+      {
+        *identity<int *>(makeUnannotated());
+        int *const &p = makeUnannotated();
+        *p;
+      }
+    }
+  )cc"));
+
+  EXPECT_TRUE(checkDiagnostics(R"cc(
+    template <typename T0, typename T1>
+    struct Struct2Arg {
+      T0 getT0();
+      T1 getT1();
+    };
+
+    template <typename T>
+    T make();
+
+    template <typename T>
+    T identity(const T &);
+
+    void target(Struct2Arg<int *, int *_Nullable> &p) {
+      *identity<Struct2Arg<int *, int *_Nullable>>(p).getT0();
+      *identity<Struct2Arg<int *, int *_Nullable>>(
+           make<Struct2Arg<int *, int *_Nullable>>())
+           .getT0();
+      *identity<Struct2Arg<int *, int *_Nullable>>(
+           Struct2Arg<int *, int *_Nullable>(p))
+           .getT0();
+      *identity<int *>(p.getT0());
+      *identity<Struct2Arg<int *, int *_Nullable>>(p).getT1();  // TODO: Fix
+      // false negative.
+      *identity<Struct2Arg<int *, int *_Nullable>>(
+           make<Struct2Arg<int *, int *_Nullable>>())
+           .getT1();
+      *identity<Struct2Arg<int *, int *_Nullable>>(
+           Struct2Arg<int *, int *_Nullable>(p))
+           .getT1();                         // TODO: Fix false negative.
+      *identity<int *_Nullable>(p.getT1());  // TODO: Fix false negative.
+    }
+  )cc"));
+}
+
 }  // namespace
 }  // namespace nullability
 }  // namespace tidy