Fix FPDFPageObj_SetIsActive() crash

When FPDFPageObj_SetIsActive() deactivates the sole page object,
FPDFPage_GenerateContent() may delete the page object's content stream.
When the page object gets reactivated, its content stream will have
unexpectedly disappeared by the time the next FPDFPage_GenerateContent()
call gets made. This leads to a CHECK() failure crash in
CPDF_PageContentManager::UpdateStream().

Fix this by adjusting CPDF_PageContentGenerator to avoid the crash.
By adding CPDF_PageContentManager::HasStreamAtIndex(),
CPDF_PageContentGenerator can now call it to see if the content stream
is gone. If it gone, call AddStream() instead of UpdateStream().

With the crash fixes, resolve a TODO and add the rest of the test case
that exercises FPDFPageObj_SetIsActive().

Bug: 378120423
Change-Id: I7523a8dac6be881ee520747f81b0730f815d3144
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/125690
Reviewed-by: dan sinclair <dsinclair@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 1639b5e..ac86ce2 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -282,7 +282,11 @@
       continue;
     }
 
-    page_content_manager.UpdateStream(stream_index, buf);
+    if (page_content_manager.HasStreamAtIndex(stream_index)) {
+      page_content_manager.UpdateStream(stream_index, buf);
+    } else {
+      page_content_manager.AddStream(buf);
+    }
   }
 }
 
diff --git a/core/fpdfapi/edit/cpdf_pagecontentmanager.cpp b/core/fpdfapi/edit/cpdf_pagecontentmanager.cpp
index 676469e..c26decf 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentmanager.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentmanager.cpp
@@ -70,6 +70,10 @@
   ExecuteScheduledRemovals();
 }
 
+bool CPDF_PageContentManager::HasStreamAtIndex(size_t stream_index) {
+  return !!GetStreamByIndex(stream_index);
+}
+
 RetainPtr<CPDF_Stream> CPDF_PageContentManager::GetStreamByIndex(
     size_t stream_index) {
   RetainPtr<CPDF_Stream> contents_stream = GetContentsStream();
diff --git a/core/fpdfapi/edit/cpdf_pagecontentmanager.h b/core/fpdfapi/edit/cpdf_pagecontentmanager.h
index 5785fc3..fcb4162 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentmanager.h
+++ b/core/fpdfapi/edit/cpdf_pagecontentmanager.h
@@ -33,6 +33,8 @@
   // is empty, then schedule the removal of the stream instead.
   void UpdateStream(size_t stream_index, fxcrt::ostringstream* buf);
 
+  bool HasStreamAtIndex(size_t stream_index);
+
  private:
   // Gets the Content stream at a given index. If Contents is a single stream
   // rather than an array, it is retrievable at index 0.
diff --git a/fpdfsdk/fpdf_editpage_embeddertest.cpp b/fpdfsdk/fpdf_editpage_embeddertest.cpp
index 25c9932..066c888 100644
--- a/fpdfsdk/fpdf_editpage_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpage_embeddertest.cpp
@@ -611,7 +611,25 @@
 
   // Reactivate `page_obj` and render.
   ASSERT_TRUE(FPDFPageObj_SetIsActive(page_obj, true));
-  // TODO(crbug.com/378120423): The test should be able to call
-  // FPDFPage_GenerateContent() without crashing. Then check the in-memory
-  // representation and the saved outputs.
+  EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page.get());
+    CompareBitmap(bitmap.get(), page_width, page_height, kChecksum);
+    EXPECT_EQ(1, FPDFPage_CountObjects(page.get()));
+  }
+
+  {
+    // Save a copy, open the copy, and render it.
+    EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+    ASSERT_TRUE(OpenSavedDocument());
+    FPDF_PAGE saved_page = LoadSavedPage(0);
+    ASSERT_TRUE(saved_page);
+
+    ScopedFPDFBitmap bitmap = RenderSavedPage(saved_page);
+    CompareBitmap(bitmap.get(), page_width, page_height, kChecksum);
+    EXPECT_EQ(1, FPDFPage_CountObjects(saved_page));
+
+    CloseSavedPage(saved_page);
+    CloseSavedDocument();
+  }
 }