Implement pdf_nametree_fuzzer.

Improve coverage for cpdf_nametree.cpp code that implements
functionality used by APIs in public/fpdf_attachment.h. Fix an assertion
failure that the fuzzer easily found.

Change-Id: Icc6521fe630aaf2fca7af5a2ba492205f455d135
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/61470
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
diff --git a/core/fpdfdoc/cpdf_nametree.cpp b/core/fpdfdoc/cpdf_nametree.cpp
index ff0fbbe..fd3f592 100644
--- a/core/fpdfdoc/cpdf_nametree.cpp
+++ b/core/fpdfdoc/cpdf_nametree.cpp
@@ -352,7 +352,9 @@
     SearchNameNodeByIndex(m_pRoot.Get(), 0, 0, &nCurIndex, &csName, &pFind,
                           nullptr);
   }
-  ASSERT(pFind);
+  // Give up if that fails too.
+  if (!pFind)
+    return false;
 
   // Insert the name and the object into the leaf array found. Note that the
   // insertion position is right after the key-value pair returned by |index|.
diff --git a/testing/fuzzers/BUILD.gn b/testing/fuzzers/BUILD.gn
index f82c395..dccf6b6 100644
--- a/testing/fuzzers/BUILD.gn
+++ b/testing/fuzzers/BUILD.gn
@@ -66,6 +66,10 @@
     }
   }
 }
+if (is_clang) {
+  # Fuzzers that use FuzzedDataProvider can only be built with Clang.
+  fuzzer_list += [ "pdf_nametree_fuzzer" ]
+}
 
 group("fuzzers") {
   testonly = true
@@ -414,6 +418,20 @@
   }
 }
 
+if (is_clang) {
+  pdfium_fuzzer("pdf_nametree_fuzzer") {
+    sources = [
+      "pdf_nametree_fuzzer.cc",
+    ]
+    deps = [
+      "../../core/fpdfapi/page",
+      "../../core/fpdfapi/parser",
+      "../../core/fpdfdoc",
+      "../../third_party:pdfium_base",
+    ]
+  }
+}
+
 pdfium_fuzzer("pdf_cmap_fuzzer") {
   sources = [
     "pdf_cmap_fuzzer.cc",
diff --git a/testing/fuzzers/pdf_nametree_fuzzer.cc b/testing/fuzzers/pdf_nametree_fuzzer.cc
new file mode 100644
index 0000000..9a37024
--- /dev/null
+++ b/testing/fuzzers/pdf_nametree_fuzzer.cc
@@ -0,0 +1,75 @@
+// Copyright 2019 The 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "core/fpdfapi/page/cpdf_streamparser.h"
+#include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_object.h"
+#include "core/fpdfdoc/cpdf_nametree.h"
+#include "third_party/base/span.h"
+
+struct Params {
+  bool delete_backwards;
+  uint8_t count;
+  std::vector<WideString> names;
+};
+
+std::vector<WideString> GetNames(uint8_t count,
+                                 FuzzedDataProvider* data_provider) {
+  std::vector<WideString> names;
+  names.reserve(count);
+  for (size_t i = 0; i < count; ++i) {
+    // The name is not that interesting here. Keep it short.
+    constexpr size_t kMaxNameLen = 10;
+    std::string str = data_provider->ConsumeRandomLengthString(kMaxNameLen);
+    names.push_back(WideString::FromUTF16LE(
+        reinterpret_cast<const unsigned short*>(str.data()),
+        str.size() / sizeof(unsigned short)));
+  }
+  return names;
+}
+
+Params GetParams(FuzzedDataProvider* data_provider) {
+  Params params;
+  params.delete_backwards = data_provider->ConsumeBool();
+  params.count = data_provider->ConsumeIntegralInRange(1, 255);
+  params.names = GetNames(params.count, data_provider);
+  return params;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider data_provider(data, size);
+  Params params = GetParams(&data_provider);
+
+  // |remaining| needs to outlive |parser|.
+  std::vector<uint8_t> remaining =
+      data_provider.ConsumeRemainingBytes<uint8_t>();
+  if (remaining.empty())
+    return 0;
+
+  CPDF_StreamParser parser(remaining);
+  auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  CPDF_NameTree name_tree(dict.Get());
+  for (const auto& name : params.names) {
+    RetainPtr<CPDF_Object> obj = parser.ReadNextObject(
+        /*bAllowNestedArray*/ true, /*bInArray=*/false, /*dwRecursionLevel=*/0);
+    if (!obj)
+      break;
+
+    name_tree.AddValueAndName(std::move(obj), name);
+  }
+
+  if (params.delete_backwards) {
+    for (size_t i = params.count; i > 0; --i)
+      name_tree.DeleteValueAndName(i);
+  } else {
+    for (size_t i = 0; i < params.count; ++i)
+      name_tree.DeleteValueAndName(0);
+  }
+  return 0;
+}