Add specialized templates to help tracing stl containers.

We need to be able to trace zero-initialized memory, it would seem.
Failure to pass these tests means that a platform would have to
initialize a heap with StackSupport::kNoConservativeStackScan.

Change-Id: Ifd68f0ac3f361078f8868ee58a48c4276dc233b6
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/73290
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fxjs/BUILD.gn b/fxjs/BUILD.gn
index 53eb78b..3de1ad2 100644
--- a/fxjs/BUILD.gn
+++ b/fxjs/BUILD.gn
@@ -114,6 +114,7 @@
 
     if (pdf_enable_xfa) {
       sources += [
+        "gc/container_trace.h",
         "gc/gced_tree_node.h",
         "gc/gced_tree_node_mixin.h",
         "gc/heap.cpp",
@@ -232,6 +233,7 @@
     pdfium_root_dir = "../"
     if (pdf_enable_xfa) {
       sources += [
+        "gc/container_trace_unittest.cpp",
         "gc/gced_tree_node_mixin_unittest.cpp",
         "gc/gced_tree_node_unittest.cpp",
         "gc/heap_unittest.cpp",
diff --git a/fxjs/gc/container_trace.h b/fxjs/gc/container_trace.h
new file mode 100644
index 0000000..c615a2a
--- /dev/null
+++ b/fxjs/gc/container_trace.h
@@ -0,0 +1,60 @@
+// Copyright 2020 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FXJS_GC_CONTAINER_TRACE_H_
+#define FXJS_GC_CONTAINER_TRACE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "v8/include/cppgc/member.h"
+#include "v8/include/cppgc/visitor.h"
+
+namespace fxgc {
+
+template <typename T, typename V = cppgc::Visitor>
+void ContainerTrace(V* visitor, const std::list<cppgc::Member<T>>& container) {
+  // Defend against CPPGC invoking Trace() on zero-initialized memory.
+  if (container.empty())
+    return;
+
+  for (const auto& item : container)
+    visitor->Trace(item);
+}
+
+template <typename T, typename U, typename V = cppgc::Visitor>
+void ContainerTrace(V* visitor,
+                    const std::map<U, cppgc::Member<T>>& container) {
+  // Defend against CPPGC invoking Trace() on zero-initialized memory.
+  if (container.empty())
+    return;
+
+  for (const auto& item : container)
+    visitor->Trace(item.second);
+}
+
+template <typename T, typename V = cppgc::Visitor>
+void ContainerTrace(V* visitor, const std::set<cppgc::Member<T>>& container) {
+  // Defend against CPPGC invoking Trace() on zero-initialized memory.
+  if (container.empty())
+    return;
+
+  for (const auto& item : container)
+    visitor->Trace(item);
+}
+
+template <typename T, typename V = cppgc::Visitor>
+void ContainerTrace(V* visitor,
+                    const std::vector<cppgc::Member<T>>& container) {
+  for (const auto& item : container)
+    visitor->Trace(item);
+}
+
+}  // namespace fxgc
+
+using fxgc::ContainerTrace;
+
+#endif  // FXJS_GC_CONTAINER_TRACE_H_
diff --git a/fxjs/gc/container_trace_unittest.cpp b/fxjs/gc/container_trace_unittest.cpp
new file mode 100644
index 0000000..6133a0c
--- /dev/null
+++ b/fxjs/gc/container_trace_unittest.cpp
@@ -0,0 +1,172 @@
+// Copyright 2020 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fxjs/gc/container_trace.h"
+
+#include <stdint.h>
+
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "v8/include/cppgc/member.h"
+
+namespace {
+
+class Thing : public cppgc::GarbageCollected<Thing> {
+ public:
+  void Trace(cppgc::Visitor* visitor) const {}
+};
+
+class CountingVisitor {
+ public:
+  CountingVisitor() = default;
+
+  void Trace(const void* that) { ++call_count_; }
+  int call_count() const { return call_count_; }
+
+ private:
+  int call_count_ = 0;
+};
+
+}  // namespace
+
+TEST(ContainerTrace, ZeroedListQuirk) {
+  using Type = std::list<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  // Can't trust iterators.
+  // EXPECT_NE(thing->begin(), thing->end());
+
+  // But we can trust empty() and guard with it.
+  EXPECT_TRUE(thing->empty());
+}
+
+TEST(ContainerTrace, ZeroedMapQuirk) {
+  using Type = std::map<int, cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  // Can't trust iterators.
+  // EXPECT_NE(thing->begin(), thing->end());
+
+  // But we can trust empty() and guard with it.
+  EXPECT_TRUE(thing->empty());
+}
+
+TEST(ContainerTrace, ZeroedSetQuirk) {
+  using Type = std::set<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  // Can't trust iterators.
+  // EXPECT_NE(thing->begin(), thing->end());
+
+  // But we can trust empty() and guard with it.
+  EXPECT_TRUE(thing->empty());
+}
+
+TEST(ContainerTrace, ZeroedVectorQuirk) {
+  using Type = std::vector<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  // Can trust iterators, no guard required.
+  EXPECT_EQ(thing->begin(), thing->end());
+}
+
+TEST(ContainerTrace, ZeroedListTrace) {
+  using Type = std::list<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, *thing);
+  EXPECT_EQ(0, cv.call_count());
+}
+
+TEST(ContainerTrace, ZeroedMapTrace) {
+  using Type = std::map<int, cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, *thing);
+  EXPECT_EQ(0, cv.call_count());
+}
+
+TEST(ContainerTrace, ZeroedSetTrace) {
+  using Type = std::set<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, *thing);
+  EXPECT_EQ(0, cv.call_count());
+}
+
+TEST(ContainerTrace, ZeroedVectorTrace) {
+  using Type = std::vector<cppgc::Member<Thing>>;
+  uint8_t buffer[sizeof(Type)];
+  memset(&buffer, 0, sizeof(buffer));
+
+  auto* thing = reinterpret_cast<Type*>(buffer);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, *thing);
+  EXPECT_EQ(0, cv.call_count());
+}
+
+TEST(ContainerTrace, ActualListTrace) {
+  std::list<cppgc::Member<Thing>> thing;
+  thing.emplace_back(nullptr);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, thing);
+  EXPECT_EQ(1, cv.call_count());
+}
+
+TEST(ContainerTrace, ActualMapTrace) {
+  std::map<int, cppgc::Member<Thing>> thing;
+  thing[42] = nullptr;
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, thing);
+  EXPECT_EQ(1, cv.call_count());
+}
+
+TEST(ContainerTrace, ActualSetTrace) {
+  std::set<cppgc::Member<Thing>> thing;
+  thing.insert(nullptr);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, thing);
+  EXPECT_EQ(1, cv.call_count());
+}
+
+TEST(ContainerTrace, ActualVectorTrace) {
+  std::vector<cppgc::Member<Thing>> thing;
+  thing.emplace_back(nullptr);
+
+  CountingVisitor cv;
+  ContainerTrace(&cv, thing);
+  EXPECT_EQ(1, cv.call_count());
+}