Fix double-free in absl::StatusOr<T>::emplace when value constructor throws

When `emplace()` is called on an `ok()` `StatusOr<T>`, it calls
`Clear()` to destroy the existing object. If the new value's
constructor throws an exception, `status_` was previously left as
`OkStatus`. This caused `~StatusOrData()` to erroneously destroy the
uninitialized memory again during stack unwinding.

This CL fixes the issue by temporarily setting `status_` to an
internal non-ok state (using the zero-allocation inline
representation) before calling `MakeValue()`, restoring it to
`OkStatus` only if construction succeeds.

PiperOrigin-RevId: 945290296
Change-Id: I17111c161f9d3d9f44ae014c49ab76960c7efc7b
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h
index d2892c5..59ce36a 100644
--- a/absl/status/internal/status_internal.h
+++ b/absl/status/internal/status_internal.h
@@ -63,6 +63,10 @@
 enum class StatusCode : int;
 enum class StatusToStringMode : int;
 
+// Forward declaration of StatusOr for Status friendship.
+template <typename T>
+class StatusOr;
+
 namespace status_internal {
 #ifndef SWIG
 class StatusPrivateAccessor;
diff --git a/absl/status/status.h b/absl/status/status.h
index faed05e..0b39b9b 100644
--- a/absl/status/status.h
+++ b/absl/status/status.h
@@ -708,6 +708,8 @@
 
   friend class absl::status_internal::StatusPrivateAccessor;
   friend class absl::status_internal::StatusPrivateAccessorForStatusBuilder;
+  template <typename T>
+  friend class absl::StatusOr;
 #endif  // !SWIG
 
   // Creates a status in the canonical error space with the specified
diff --git a/absl/status/statusor.h b/absl/status/statusor.h
index ed34bb8..cabfd66 100644
--- a/absl/status/statusor.h
+++ b/absl/status/statusor.h
@@ -614,11 +614,14 @@
   T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
     if (ok()) {
       this->Clear();
-      this->MakeValue(std::forward<Args>(args)...);
-    } else {
-      this->MakeValue(std::forward<Args>(args)...);
-      this->status_ = absl::OkStatus();
+      // Temporarily transition to a non-ok status (using the zero-allocation
+      // inlined representation) so that if MakeValue() throws an exception,
+      // ok() returns false during stack unwinding and ~StatusOrData() does not
+      // attempt to destroy uninitialized memory.
+      this->status_ = absl::Status(absl::StatusCode::kInternal);
     }
+    this->MakeValue(std::forward<Args>(args)...);
+    this->status_ = absl::OkStatus();
     return this->data_;
   }
 
@@ -630,11 +633,14 @@
              Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
     if (ok()) {
       this->Clear();
-      this->MakeValue(ilist, std::forward<Args>(args)...);
-    } else {
-      this->MakeValue(ilist, std::forward<Args>(args)...);
-      this->status_ = absl::OkStatus();
+      // Temporarily transition to a non-ok status (using the zero-allocation
+      // inlined representation) so that if MakeValue() throws an exception,
+      // ok() returns false during stack unwinding and ~StatusOrData() does not
+      // attempt to destroy uninitialized memory.
+      this->status_ = absl::Status(absl::StatusCode::kInternal);
     }
+    this->MakeValue(ilist, std::forward<Args>(args)...);
+    this->status_ = absl::OkStatus();
     return this->data_;
   }
 
diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc
index 2205acf..d03e8be 100644
--- a/absl/status/statusor_test.cc
+++ b/absl/status/statusor_test.cc
@@ -406,6 +406,53 @@
                                  Field(&InPlaceHelper::y, Pointee(4)))));
 }
 
+#ifdef ABSL_HAVE_EXCEPTIONS
+class ThrowOnEmplace {
+ public:
+  explicit ThrowOnEmplace(int* counter, int val) : destructor_calls_(counter) {
+    if (val < 0) {
+      throw std::runtime_error("expected");
+    }
+    // While destructor_calls tracks the logic, ptr_ ensures that a double
+    // destruction actually results in a reliable crash. Performing a real heap
+    // allocation and deallocation (new/delete) guarantees that AddressSanitizer
+    // (ASAN) or the heap allocator will instantly catch the double-free if the
+    // bug regresses, rather than relying solely on the integer check.
+    ptr_ = new int(val);
+  }
+
+  ThrowOnEmplace(const ThrowOnEmplace&) = delete;
+  ThrowOnEmplace& operator=(const ThrowOnEmplace&) = delete;
+
+  ~ThrowOnEmplace() {
+    if (destructor_calls_) {
+      ++(*destructor_calls_);
+    }
+    delete ptr_;
+  }
+
+ private:
+  int* destructor_calls_ = nullptr;
+  int* ptr_ = nullptr;
+};
+
+TEST(StatusOr, EmplaceThrowsExceptionSafety) {
+  int destructor_calls = 0;
+  {
+    absl::StatusOr<ThrowOnEmplace> status_or(std::in_place, &destructor_calls,
+                                             1);
+    EXPECT_TRUE(status_or.ok());
+    EXPECT_THROW(status_or.emplace(&destructor_calls, -1), std::runtime_error);
+    EXPECT_FALSE(status_or.ok());
+    EXPECT_EQ(status_or.status().code(), absl::StatusCode::kInternal);
+  }
+  // Verifies that the initial object is properly destroyed by Clear() (count is
+  // 1), and that the exception thrown during replacement does not cause a
+  // second destruction (double-free) during stack unwinding.
+  EXPECT_EQ(destructor_calls, 1);
+}
+#endif  // ABSL_HAVE_EXCEPTIONS
+
 TEST(StatusOr, TestCopyCtorStatusOk) {
   const int kI = 4;
   const absl::StatusOr<int> original(kI);