Add embedder test to help demonstrate FPDFPageObj_SetIsActive() crash

The newly added FPDFPageObj_SetIsActive() API can cause a crash in some
cases. Add a small test file that has the properties to trigger the
crash. Then add a test that exercises such a scenario and stops just
short of actually crashing.

Bug: 378120423
Change-Id: I211d11fbeda95e96035ad6aa6603db7fca32a31d
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/125670
Reviewed-by: dan sinclair <dsinclair@google.com>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_editpage_embeddertest.cpp b/fpdfsdk/fpdf_editpage_embeddertest.cpp
index 21ee8aa..25c9932 100644
--- a/fpdfsdk/fpdf_editpage_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpage_embeddertest.cpp
@@ -496,7 +496,7 @@
   CloseSavedDocument();
 }
 
-TEST_F(FPDFEditPageEmbedderTest, PageObjectIsActive) {
+TEST_F(FPDFEditPageEmbedderTest, PageObjectSetIsActive) {
   const char* one_rectangle_inactive_checksum = []() {
     if (CFX_DefaultRenderDevice::UseSkiaRenderer()) {
       return "cf5bb4e61609162c03f4c8a6d9791230";
@@ -564,3 +564,54 @@
     CloseSavedDocument();
   }
 }
+
+TEST_F(FPDFEditPageEmbedderTest, Bug378120423) {
+  const char kChecksum[] = "b53fb03e2bc41ef18d4ba61f0f681365";
+  const char kBlankChecksum[] = "eee4600ac08b458ac7ac2320e225674c";
+
+  ASSERT_TRUE(OpenDocument("bug_378120423.pdf"));
+  ScopedEmbedderTestPage page = LoadScopedPage(0);
+  ASSERT_TRUE(page);
+  const int page_width = static_cast<int>(FPDF_GetPageWidth(page.get()));
+  const int page_height = static_cast<int>(FPDF_GetPageHeight(page.get()));
+
+  {
+    // Render the page as is.
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page.get());
+    CompareBitmap(bitmap.get(), page_width, page_height, kChecksum);
+    EXPECT_EQ(1, FPDFPage_CountObjects(page.get()));
+  }
+
+  // Deactivate `page_obj` and render.
+  FPDF_PAGEOBJECT page_obj = FPDFPage_GetObject(page.get(), 0);
+  ASSERT_TRUE(FPDFPageObj_SetIsActive(page_obj, false));
+  EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
+  {
+    ScopedFPDFBitmap bitmap = RenderLoadedPage(page.get());
+    CompareBitmap(bitmap.get(), page_width, page_height, kBlankChecksum);
+    // `page_obj` can still be found. It is just deactivated.
+    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, kBlankChecksum);
+    // `page_obj` did not get written out to the saved PDF.
+    EXPECT_EQ(0, FPDFPage_CountObjects(saved_page));
+
+    CloseSavedPage(saved_page);
+    CloseSavedDocument();
+  }
+
+  // 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.
+}
diff --git a/testing/resources/bug_378120423.in b/testing/resources/bug_378120423.in
new file mode 100644
index 0000000..a267f7b
--- /dev/null
+++ b/testing/resources/bug_378120423.in
@@ -0,0 +1,36 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+{{streamlen}}
+>>
+stream
+q
+10 20 m
+10 30 l
+20 30 l
+h f
+Q
+endstream
+endobj
+
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_378120423.pdf b/testing/resources/bug_378120423.pdf
new file mode 100644
index 0000000..a58b56d
--- /dev/null
+++ b/testing/resources/bug_378120423.pdf
@@ -0,0 +1,47 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+  /MediaBox [0 0 200 200]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+/Length 32
+>>
+stream
+q
+10 20 m
+10 30 l
+20 30 l
+h f
+Q
+endstream
+endobj
+
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+308
+%%EOF