blob: 0932bbc93681a4a3e8b91ef5ce7815c9fe281878 [file]
// Copyright 2023 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fpdfapi/parser/object_tree_traversal_util.h"
#include <stdint.h>
#include <map>
#include <queue>
#include <set>
#include <utility>
#include <vector>
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/tree_traversal_util.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/containers/contains.h"
#include "core/fxcrt/unowned_ptr.h"
namespace {
// Traverses the PDF object hierarchy in a document to extract an `AdjacencyMap`
// and root node IDs.
class PdfReferenceGraphBuilder {
public:
explicit PdfReferenceGraphBuilder(const CPDF_Document* document)
: document_(document) {
const CPDF_Parser* parser = document_->GetParser();
const CPDF_Dictionary* trailer = parser ? parser->GetTrailer() : nullptr;
if (trailer) {
EnqueueRoot(pdfium::WrapRetain(trailer),
parser->GetTrailerObjectNumber());
}
}
PdfReferenceGraph Build() {
DrainObjectQueue();
// Also traverse the catalog if it was not reached from the trailer
// (e.g. if the trailer's `/Root` was missing, null, or diverged).
const CPDF_Dictionary* catalog = document_->GetRoot();
if (catalog && !pdfium::Contains(seen_objects_, catalog)) {
EnqueueRoot(pdfium::WrapRetain(catalog), catalog->GetObjNum());
DrainObjectQueue();
}
return {std::move(roots_), std::move(adj_)};
}
private:
void EnqueueRoot(RetainPtr<const CPDF_Dictionary> root,
uint32_t root_object_number) {
if (!root || !seen_objects_.insert(root.Get()).second) {
return;
}
if (root_object_number) {
roots_.push_back(root_object_number);
object_number_map_[root.Get()] = root_object_number;
}
object_queue_.push(std::move(root));
}
void DrainObjectQueue() {
while (!object_queue_.empty()) {
RetainPtr<const CPDF_Object> current_object = object_queue_.front();
object_queue_.pop();
switch (current_object->GetType()) {
case CPDF_Object::kArray: {
CPDF_ArrayLocker locker(current_object->AsArray());
for (const auto& it : locker) {
PushNewObject(current_object, it);
}
break;
}
case CPDF_Object::kDictionary: {
CPDF_DictionaryLocker locker(current_object->AsDictionary());
for (const auto& it : locker) {
PushNewObject(current_object, it.second);
}
break;
}
case CPDF_Object::kReference: {
const CPDF_Reference* ref_object = current_object->AsReference();
const uint32_t ref_object_number = GetObjectNumber(ref_object);
const uint32_t referenced_object_number = ref_object->GetRefObjNum();
RetainPtr<const CPDF_Object> referenced_object;
if (ref_object->HasIndirectObjectHolder()) {
// Calling GetIndirectObject() does not work for normal references.
referenced_object = ref_object->GetDirect();
} else {
// Calling GetDirect() does not work for references from trailers.
referenced_object =
document_->GetIndirectObject(referenced_object_number);
}
// Unlike the other object types, CPDF_Reference can point at nullptr.
if (referenced_object) {
CHECK(referenced_object_number);
if (ref_object_number) {
if (referenced_object_number != ref_object_number) {
adj_[ref_object_number].push_back(referenced_object_number);
}
} else {
roots_.push_back(referenced_object_number);
}
PushNewObject(ref_object, referenced_object);
}
break;
}
case CPDF_Object::kStream: {
RetainPtr<const CPDF_Dictionary> dict =
current_object->AsStream()->GetDict();
CHECK(dict->IsInline()); // i.e. No object number.
CPDF_DictionaryLocker locker(dict);
for (const auto& it : locker) {
PushNewObject(current_object, it.second);
}
break;
}
default: {
break;
}
}
}
}
void PushNewObject(const CPDF_Object* parent_object,
RetainPtr<const CPDF_Object> child_object) {
CHECK(parent_object);
CHECK(child_object);
const bool inserted = seen_objects_.insert(child_object).second;
if (!inserted) {
return;
}
const uint32_t child_object_number = child_object->GetObjNum();
if (child_object_number) {
object_number_map_[child_object] = child_object_number;
} else {
// This search can fail for inlined trailers.
auto it = object_number_map_.find(parent_object);
if (it != object_number_map_.end()) {
object_number_map_[child_object] = it->second;
}
}
object_queue_.push(std::move(child_object));
}
// Returns 0 if not found.
uint32_t GetObjectNumber(const CPDF_Object* object) const {
auto it = object_number_map_.find(object);
return it != object_number_map_.end() ? it->second : 0;
}
UnownedPtr<const CPDF_Document> const document_;
std::vector<pdfium::NodeId> roots_;
pdfium::AdjacencyMap adj_;
// Queue of objects to traverse.
// - Pointers in the queue are non-null.
// - The same pointer never enters the queue twice.
std::queue<RetainPtr<const CPDF_Object>> object_queue_;
// Map of objects to "top-level" object numbers. For inline objects, this is
// the ancestor object with an object number. The keys are non-null and the
// values are never 0.
// This is used to prevent self-references, as a single PDF object, with
// inlined objects, is represented by multiple CPDF_Objects.
std::map<const CPDF_Object*, uint32_t> object_number_map_;
// Tracks traversed objects to prevent duplicates from getting into
// `object_queue_` and `object_number_map_`.
std::set<const CPDF_Object*> seen_objects_;
};
} // namespace
PdfReferenceGraph BuildPdfReferenceGraphForTesting(
const CPDF_Document* document) {
return PdfReferenceGraphBuilder(document).Build();
}
std::set<uint32_t> GetObjectsWithReferences(const CPDF_Document* document) {
PdfReferenceGraph graph = PdfReferenceGraphBuilder(document).Build();
std::map<pdfium::NodeId, int> counts =
pdfium::CalculateReferenceCounts(graph.roots, graph.adj);
std::set<uint32_t> results;
for (const auto& it : counts) {
results.insert(it.first);
}
return results;
}
std::set<uint32_t> GetObjectsWithMultipleReferences(
const CPDF_Document* document) {
PdfReferenceGraph graph = PdfReferenceGraphBuilder(document).Build();
std::map<pdfium::NodeId, int> counts =
pdfium::CalculateReferenceCounts(graph.roots, graph.adj);
std::set<uint32_t> results;
for (const auto& it : counts) {
if (it.second > 1) {
results.insert(it.first);
}
}
return results;
}