Allow tracking of single-valued objects to be dynamic.

PiperOrigin-RevId: 502401836
diff --git a/lifetime_analysis/lifetime_analysis.cc b/lifetime_analysis/lifetime_analysis.cc
index 35af397..43d5dae 100644
--- a/lifetime_analysis/lifetime_analysis.cc
+++ b/lifetime_analysis/lifetime_analysis.cc
@@ -52,13 +52,15 @@
  public:
   TransferStmtVisitor(
       ObjectRepository& object_repository, PointsToMap& points_to_map,
-      LifetimeConstraints& constraints, const clang::FunctionDecl* func,
+      LifetimeConstraints& constraints, ObjectSet& single_valued_objects,
+      const clang::FunctionDecl* func,
       const llvm::DenseMap<const clang::FunctionDecl*,
                            FunctionLifetimesOrError>& callee_lifetimes,
       const DiagnosticReporter& diag_reporter)
       : object_repository_(object_repository),
         points_to_map_(points_to_map),
         constraints_(constraints),
+        single_valued_objects_(single_valued_objects),
         func_(func),
         callee_lifetimes_(callee_lifetimes),
         diag_reporter_(diag_reporter) {}
@@ -94,6 +96,7 @@
   ObjectRepository& object_repository_;
   PointsToMap& points_to_map_;
   LifetimeConstraints& constraints_;
+  ObjectSet& single_valued_objects_;
   const clang::FunctionDecl* func_;
   const llvm::DenseMap<const clang::FunctionDecl*, FunctionLifetimesOrError>&
       callee_lifetimes_;
@@ -289,7 +292,7 @@
 void SetPointerPointsToSetRespectingTypes(
     const Object* pointer, const ObjectSet& points_to,
     PointsToMap& points_to_map, clang::ASTContext& ast_context,
-    LifetimeConstraints& constraints,
+    LifetimeConstraints& constraints, const ObjectSet& single_valued_objects,
     const ObjectRepository& object_repository) {
   assert(pointer->Type()->isPointerType() ||
          pointer->Type()->isReferenceType());
@@ -307,8 +310,7 @@
       // TODO(veluca): explicitly splitting between "object values before an
       // expression" and "object values after an expression" will likely make
       // this whole kind of reasoning clearer and less error-prone.
-      if (object_repository.GetObjectValueType(pointer) !=
-          ObjectRepository::ObjectValueType::kSingleValued) {
+      if (!single_valued_objects.Contains(pointer)) {
         // Descending in callees is handled at a higher level.
         GenerateConstraintsForAssignmentNonRecursive(
             original_points_to_set, {object},
@@ -324,12 +326,12 @@
 void SetAllPointersPointsToSetRespectingTypes(
     const ObjectSet& pointers, const ObjectSet& points_to,
     PointsToMap& points_to_map, clang::ASTContext& ast_context,
-    LifetimeConstraints& constraints,
+    LifetimeConstraints& constraints, const ObjectSet& single_valued_objects,
     const ObjectRepository& object_repository) {
   for (auto pointer : pointers) {
-    SetPointerPointsToSetRespectingTypes(pointer, points_to, points_to_map,
-                                         ast_context, constraints,
-                                         object_repository);
+    SetPointerPointsToSetRespectingTypes(
+        pointer, points_to, points_to_map, ast_context, constraints,
+        single_valued_objects, object_repository);
   }
 }
 
@@ -383,6 +385,7 @@
     const Object* arg_object, clang::QualType type,
     const ValueLifetimes& value_lifetimes, PointsToMap& points_to_map,
     ObjectRepository& object_repository, LifetimeConstraints& constraints,
+    ObjectSet& single_valued_objects,
     const llvm::DenseMap<Lifetime, ObjectSet>& lifetime_to_object_set,
     clang::ASTContext& ast_context) {
   class Visitor : public LifetimeVisitor {
@@ -390,10 +393,11 @@
     Visitor(ObjectRepository& object_repository, PointsToMap& points_to_map,
             LifetimeConstraints& constraints,
             const llvm::DenseMap<Lifetime, ObjectSet>& lifetime_to_object_set,
-            clang::ASTContext& ast_context)
+            ObjectSet& single_valued_objects, clang::ASTContext& ast_context)
         : object_repository_(object_repository),
           points_to_map_(points_to_map),
           constraints_(constraints),
+          single_valued_objects_(single_valued_objects),
           lifetime_to_object_set_(lifetime_to_object_set),
           ast_context_(ast_context) {}
 
@@ -429,7 +433,7 @@
         }
         SetAllPointersPointsToSetRespectingTypes(
             objects, points_to, points_to_map_, ast_context_, constraints_,
-            object_repository_);
+            single_valued_objects_, object_repository_);
         if (!kUseConstraintBasedAnalysis) {
           // This assertion may fail to be true when visiting the return value
           // object, if its objects were created recursively.
@@ -453,11 +457,12 @@
     ObjectRepository& object_repository_;
     PointsToMap& points_to_map_;
     LifetimeConstraints& constraints_;
+    ObjectSet& single_valued_objects_;
     const llvm::DenseMap<Lifetime, ObjectSet>& lifetime_to_object_set_;
     clang::ASTContext& ast_context_;
   };
   Visitor visitor(object_repository, points_to_map, constraints,
-                  lifetime_to_object_set, ast_context);
+                  lifetime_to_object_set, single_valued_objects, ast_context);
   VisitLifetimes({arg_object}, type,
                  ObjectLifetimes(arg_object->GetLifetime(), value_lifetimes),
                  visitor);
@@ -473,7 +478,7 @@
     const clang::Expr* call, const std::vector<FunctionParameter>& fn_params,
     const ValueLifetimes& return_lifetimes, ObjectRepository& object_repository,
     PointsToMap& points_to_map, LifetimeConstraints& constraints,
-    clang::ASTContext& ast_context) {
+    ObjectSet& single_valued_objects, clang::ASTContext& ast_context) {
   // TODO(mboehme): The following description says what we _want_ to do, but
   // this isn't what we actually do right now. Modify the code so that it
   // corresponds to the description, then remove this TODO.
@@ -595,7 +600,8 @@
   for (auto [type, param_lifetimes, arg_object] : fn_params) {
     PropagateLifetimesToPointees(arg_object, type, param_lifetimes,
                                  points_to_map, object_repository, constraints,
-                                 lifetime_to_object_set, ast_context);
+                                 single_valued_objects, lifetime_to_object_set,
+                                 ast_context);
   }
 
   // Step 3: Determine points-to set for the return value.
@@ -604,7 +610,8 @@
       const Object* init_object = object_repository.GetInitializedObject(call);
       PropagateLifetimesToPointees(
           init_object, call->getType(), return_lifetimes, points_to_map,
-          object_repository, constraints, lifetime_to_object_set, ast_context);
+          object_repository, constraints, single_valued_objects,
+          lifetime_to_object_set, ast_context);
     } else {
       ObjectSet rval_points_to;
 
@@ -627,7 +634,8 @@
 }
 
 LifetimeLattice LifetimeAnalysis::initialElement() {
-  return LifetimeLattice(object_repository_.InitialPointsToMap());
+  return LifetimeLattice(object_repository_.InitialPointsToMap(),
+                         object_repository_.InitialSingleValuedObjects());
 }
 
 std::string LifetimeAnalysis::ToString(const LifetimeLattice& state) {
@@ -649,8 +657,8 @@
   auto stmt = cfg_stmt->getStmt();
 
   TransferStmtVisitor visitor(object_repository_, state.PointsTo(),
-                              state.Constraints(), func_, callee_lifetimes_,
-                              diag_reporter_);
+                              state.Constraints(), state.SingleValuedObjects(),
+                              func_, callee_lifetimes_, diag_reporter_);
   if (std::optional<std::string> err =
           visitor.Visit(const_cast<clang::Stmt*>(stmt))) {
     state = LifetimeLattice(*err);
@@ -922,11 +930,11 @@
       // only in very specific circumstances:
       // - We need to know unambiguously what the LHS refers to, so that we
       //   know we're definitely writing to a particular object, and
-      // - That destination object needs to be "single-valued" (it can't be
-      //   an array, for example).
+      // - That destination object needs to be "single-valued" (see docstring of
+      //   LifetimeLattice::SingleValuedObjects for the definition of this
+      //   term).
       if (lhs_points_to.size() == 1 &&
-          object_repository_.GetObjectValueType(*lhs_points_to.begin()) ==
-              ObjectRepository::ObjectValueType::kSingleValued) {
+          single_valued_objects_.Contains(*lhs_points_to.begin())) {
         // Replacing the points-to-set entirely does not generate any
         // constraints.
         points_to_map_.SetPointerPointsToSet(lhs_points_to, rhs_points_to);
@@ -1240,7 +1248,7 @@
     std::optional<ObjectSet> single_call_points_to = TransferLifetimesForCall(
         call, fn_params, callee_lifetimes.GetReturnLifetimes(),
         object_repository_, points_to_map_, constraints_,
-        callee->getASTContext());
+        single_valued_objects_, callee->getASTContext());
     if (single_call_points_to) {
       if (call_points_to) {
         call_points_to.value().Add(std::move(single_call_points_to).value());
@@ -1307,7 +1315,7 @@
   TransferLifetimesForCall(
       construct_expr, fn_params,
       ValueLifetimes::ForLifetimeLessType(constructor->getReturnType()),
-      object_repository_, points_to_map_, constraints_,
+      object_repository_, points_to_map_, constraints_, single_valued_objects_,
       constructor->getASTContext());
   return std::nullopt;
 }