Commit a85635b839755765713eb27d767f42b357056b2e

Authored by Jay Berkenbilt
Committed by GitHub
2 parents fd17c8e3 50bc82b4

Merge pull request #929 from m-holger/ogguard

Add new convenience class QPDFObjGen::Guard
include/qpdf/QPDF.hh
... ... @@ -1017,7 +1017,7 @@ class QPDF
1017 1017 public:
1018 1018 std::map<QPDFObjGen, QPDFObjectHandle> object_map;
1019 1019 std::vector<QPDFObjectHandle> to_copy;
1020   - std::set<QPDFObjGen> visiting;
  1020 + QPDFObjGen::set visiting;
1021 1021 };
1022 1022  
1023 1023 class EncryptionParameters
... ... @@ -1252,8 +1252,8 @@ class QPDF
1252 1252  
1253 1253 void getAllPagesInternal(
1254 1254 QPDFObjectHandle cur_pages,
1255   - std::set<QPDFObjGen>& visited,
1256   - std::set<QPDFObjGen>& seen);
  1255 + QPDFObjGen::set& visited,
  1256 + QPDFObjGen::set& seen);
1257 1257 void insertPage(QPDFObjectHandle newpage, int pos);
1258 1258 void flattenPagesTree();
1259 1259 void insertPageobjToPage(
... ... @@ -1645,7 +1645,7 @@ class QPDF
1645 1645 ObjUser const& ou,
1646 1646 QPDFObjectHandle oh,
1647 1647 std::function<int(QPDFObjectHandle&)> skip_stream_parameters,
1648   - std::set<QPDFObjGen>& visited,
  1648 + QPDFObjGen::set& visited,
1649 1649 bool top);
1650 1650 void filterCompressedObjects(std::map<int, int> const& object_stream_data);
1651 1651  
... ...
include/qpdf/QPDFAcroFormDocumentHelper.hh
... ... @@ -254,7 +254,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
254 254 QPDFObjectHandle field,
255 255 QPDFObjectHandle parent,
256 256 int depth,
257   - std::set<QPDFObjGen>& visited);
  257 + QPDFObjGen::set& visited);
258 258 QPDFObjectHandle getOrCreateAcroForm();
259 259 void adjustInheritedFields(
260 260 QPDFObjectHandle obj,
... ...
include/qpdf/QPDFJob.hh
... ... @@ -571,7 +571,7 @@ class QPDFJob
571 571  
572 572 // JSON
573 573 void doJSON(QPDF& pdf, Pipeline*);
574   - std::set<QPDFObjGen> getWantedJSONObjects();
  574 + QPDFObjGen::set getWantedJSONObjects();
575 575 void doJSONObject(
576 576 Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
577 577 void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf);
... ...
include/qpdf/QPDFObjGen.hh
... ... @@ -24,6 +24,10 @@
24 24  
25 25 #include <qpdf/DLL.h>
26 26 #include <iostream>
  27 +#include <set>
  28 +
  29 +class QPDFObjectHandle;
  30 +class QPDFObjectHelper;
27 31  
28 32 // This class represents an object ID and generation pair. It is
29 33 // suitable to use as a key in a map or set.
... ... @@ -31,6 +35,7 @@
31 35 class QPDFObjGen
32 36 {
33 37 public:
  38 + // ABI: change to default.
34 39 QPDF_DLL
35 40 QPDFObjGen() :
36 41 obj(0),
... ... @@ -84,12 +89,72 @@ class QPDFObjGen
84 89 QPDF_DLL
85 90 friend std::ostream& operator<<(std::ostream& os, const QPDFObjGen& og);
86 91  
  92 + // Convenience class for loop detection when processing objects.
  93 + //
  94 + // The class adds 'add' methods to a std::set<QPDFObjGen> which allows
  95 + // to test whether an QPDFObjGen is present in the set and to insert it in
  96 + // a single operation. The 'add' method is overloaded to take a QPDFObjGen,
  97 + // QPDFObjectHandle or an QPDFObjectHelper as parameter.
  98 + //
  99 + // The erase method is modified to ignore requests to erase
  100 + // QPDFObjGen(0, 0).
  101 + //
  102 + // Usage example:
  103 + //
  104 + // void process_object(QPDFObjectHandle oh, QPDFObjGen::Tracker& seen)
  105 + // {
  106 + // if (seen.add(oh)) {
  107 + // // handle first encounter of oh
  108 + // } else {
  109 + // // handle loop / subsequent encounter of oh
  110 + // }
  111 + // }
  112 + class QPDF_DLL_CLASS set: public std::set<QPDFObjGen>
  113 + {
  114 + public:
  115 + // Add 'og' to the set. Return false if 'og' is already present in
  116 + // the set. Attempts to insert QPDFObjGen(0, 0) are ignored.
  117 + QPDF_DLL
  118 + bool
  119 + add(QPDFObjGen og)
  120 + {
  121 + if (og.isIndirect()) {
  122 + if (count(og) > 0) {
  123 + return false;
  124 + }
  125 + emplace(og);
  126 + }
  127 + return true;
  128 + }
  129 +
  130 + QPDF_DLL
  131 + bool add(QPDFObjectHandle const& oh);
  132 +
  133 + QPDF_DLL
  134 + bool add(QPDFObjectHelper const& oh);
  135 +
  136 + QPDF_DLL
  137 + void
  138 + erase(QPDFObjGen og)
  139 + {
  140 + if (og.isIndirect()) {
  141 + std::set<QPDFObjGen>::erase(og);
  142 + }
  143 + }
  144 +
  145 + QPDF_DLL
  146 + void erase(QPDFObjectHandle const& oh);
  147 +
  148 + QPDF_DLL
  149 + void erase(QPDFObjectHelper const& oh);
  150 + };
  151 +
87 152 private:
88 153 // This class does not use the Members pattern to avoid a memory
89 154 // allocation for every one of these. A lot of these get created
90 155 // and destroyed.
91   - int obj;
92   - int gen;
  156 + int obj{0};
  157 + int gen{0};
93 158 };
94 159  
95 160 #endif // QPDFOBJGEN_HH
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1611,7 +1611,7 @@ class QPDFObjectHandle
1611 1611 void objectWarning(std::string const& warning);
1612 1612 void assertType(char const* type_name, bool istype);
1613 1613 inline bool dereference();
1614   - void makeDirect(std::set<QPDFObjGen>& visited, bool stop_at_streams);
  1614 + void makeDirect(QPDFObjGen::set& visited, bool stop_at_streams);
1615 1615 void disconnect();
1616 1616 void setParsedOffset(qpdf_offset_t offset);
1617 1617 void parseContentStream_internal(
... ...
include/qpdf/QPDFOutlineDocumentHelper.hh
... ... @@ -22,13 +22,13 @@
22 22 #ifndef QPDFOUTLINEDOCUMENTHELPER_HH
23 23 #define QPDFOUTLINEDOCUMENTHELPER_HH
24 24  
  25 +#include <qpdf/QPDF.hh>
25 26 #include <qpdf/QPDFDocumentHelper.hh>
26 27 #include <qpdf/QPDFNameTreeObjectHelper.hh>
  28 +#include <qpdf/QPDFObjGen.hh>
27 29 #include <qpdf/QPDFOutlineObjectHelper.hh>
28 30  
29   -#include <qpdf/QPDF.hh>
30 31 #include <map>
31   -#include <set>
32 32 #include <vector>
33 33  
34 34 #include <qpdf/DLL.h>
... ... @@ -69,16 +69,16 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
69 69 {
70 70 friend class QPDFOutlineObjectHelper;
71 71  
  72 + // ABI: remove QPDF_DLL and pass og by value.
72 73 QPDF_DLL
73 74 static bool
74 75 checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen const& og)
75 76 {
76   - return dh.checkSeen(og);
  77 + return !dh.m->seen.add(og);
77 78 }
78 79 };
79 80  
80 81 private:
81   - bool checkSeen(QPDFObjGen const& og);
82 82 void initializeByPage();
83 83  
84 84 class Members
... ... @@ -94,7 +94,7 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
94 94 Members(Members const&) = delete;
95 95  
96 96 std::vector<QPDFOutlineObjectHelper> outlines;
97   - std::set<QPDFObjGen> seen;
  97 + QPDFObjGen::set seen;
98 98 QPDFObjectHandle dest_dict;
99 99 std::shared_ptr<QPDFNameTreeObjectHelper> names_dest;
100 100 std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page;
... ...
libqpdf/NNTree.cc
... ... @@ -638,26 +638,21 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty)
638 638 auto opath = this->path;
639 639 bool failed = false;
640 640  
641   - std::set<QPDFObjGen> seen;
  641 + QPDFObjGen::set seen;
642 642 for (auto i: this->path) {
643   - if (i.node.isIndirect()) {
644   - seen.insert(i.node.getObjGen());
645   - }
  643 + seen.add(i.node);
646 644 }
647 645 while (!failed) {
648   - if (node.isIndirect()) {
649   - auto og = node.getObjGen();
650   - if (seen.count(og)) {
651   - QTC::TC("qpdf", "NNTree deepen: loop");
652   - warn(
653   - impl.qpdf,
654   - node,
655   - "loop detected while traversing name/number tree");
656   - failed = true;
657   - break;
658   - }
659   - seen.insert(og);
  646 + if (!seen.add(node)) {
  647 + QTC::TC("qpdf", "NNTree deepen: loop");
  648 + warn(
  649 + impl.qpdf,
  650 + node,
  651 + "loop detected while traversing name/number tree");
  652 + failed = true;
  653 + break;
660 654 }
  655 +
661 656 if (!node.isDictionary()) {
662 657 QTC::TC("qpdf", "NNTree node is not a dictionary");
663 658 warn(
... ... @@ -928,17 +923,15 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
928 923 }
929 924 }
930 925  
931   - std::set<QPDFObjGen> seen;
  926 + QPDFObjGen::set seen;
932 927 auto node = this->oh;
933 928 iterator result(*this);
934 929  
935 930 while (true) {
936   - auto og = node.getObjGen();
937   - if (seen.count(og)) {
  931 + if (!seen.add(node)) {
938 932 QTC::TC("qpdf", "NNTree loop in find");
939 933 error(qpdf, node, "loop detected in find");
940 934 }
941   - seen.insert(og);
942 935  
943 936 auto kids = node.getKey("/Kids");
944 937 int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
... ...
libqpdf/QPDF.cc
... ... @@ -2176,7 +2176,8 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign)
2176 2176 void
2177 2177 QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
2178 2178 {
2179   - if (foreign.isReserved()) {
  2179 + auto foreign_tc = foreign.getTypeCode();
  2180 + if (foreign_tc == ::ot_reserved) {
2180 2181 throw std::logic_error(
2181 2182 "QPDF: attempting to copy a foreign reserved object");
2182 2183 }
... ... @@ -2193,70 +2194,62 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier&amp; obj_copier, bool top)
2193 2194  
2194 2195 if (foreign.isIndirect()) {
2195 2196 QPDFObjGen foreign_og(foreign.getObjGen());
2196   - if (obj_copier.visiting.find(foreign_og) != obj_copier.visiting.end()) {
2197   - QTC::TC("qpdf", "QPDF loop reserving objects");
  2197 + if (obj_copier.object_map.count(foreign_og) > 0) {
  2198 + QTC::TC("qpdf", "QPDF already reserved object");
  2199 + if (obj_copier.visiting.count(foreign_og)) {
  2200 + QTC::TC("qpdf", "QPDF loop reserving objects");
  2201 + }
2198 2202 return;
2199 2203 }
2200   - if (obj_copier.object_map.find(foreign_og) !=
2201   - obj_copier.object_map.end()) {
2202   - QTC::TC("qpdf", "QPDF already reserved object");
  2204 + if (!obj_copier.visiting.add(foreign_og)) {
2203 2205 return;
2204 2206 }
2205 2207 QTC::TC("qpdf", "QPDF copy indirect");
2206   - obj_copier.visiting.insert(foreign_og);
2207   - auto mapping = obj_copier.object_map.find(foreign_og);
2208   - if (mapping == obj_copier.object_map.end()) {
  2208 + if (obj_copier.object_map.count(foreign_og) == 0) {
2209 2209 obj_copier.to_copy.push_back(foreign);
2210   - QPDFObjectHandle reservation;
2211   - if (foreign.isStream()) {
2212   - reservation = newStream();
2213   - } else {
2214   - reservation = QPDFObjectHandle::newReserved(this);
2215   - }
2216   - obj_copier.object_map[foreign_og] = reservation;
  2210 + obj_copier.object_map[foreign_og] = foreign.isStream()
  2211 + ? newStream()
  2212 + : QPDFObjectHandle::newReserved(this);
2217 2213 }
2218 2214 }
2219 2215  
2220   - if (foreign.isArray()) {
  2216 + if (foreign_tc == ::ot_array) {
2221 2217 QTC::TC("qpdf", "QPDF reserve array");
2222 2218 int n = foreign.getArrayNItems();
2223 2219 for (int i = 0; i < n; ++i) {
2224 2220 reserveObjects(foreign.getArrayItem(i), obj_copier, false);
2225 2221 }
2226   - } else if (foreign.isDictionary()) {
  2222 + } else if (foreign_tc == ::ot_dictionary) {
2227 2223 QTC::TC("qpdf", "QPDF reserve dictionary");
2228 2224 for (auto const& key: foreign.getKeys()) {
2229 2225 reserveObjects(foreign.getKey(key), obj_copier, false);
2230 2226 }
2231   - } else if (foreign.isStream()) {
  2227 + } else if (foreign_tc == ::ot_stream) {
2232 2228 QTC::TC("qpdf", "QPDF reserve stream");
2233 2229 reserveObjects(foreign.getDict(), obj_copier, false);
2234 2230 }
2235 2231  
2236   - if (foreign.isIndirect()) {
2237   - QPDFObjGen foreign_og(foreign.getObjGen());
2238   - obj_copier.visiting.erase(foreign_og);
2239   - }
  2232 + obj_copier.visiting.erase(foreign);
2240 2233 }
2241 2234  
2242 2235 QPDFObjectHandle
2243 2236 QPDF::replaceForeignIndirectObjects(
2244 2237 QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
2245 2238 {
  2239 + auto foreign_tc = foreign.getTypeCode();
2246 2240 QPDFObjectHandle result;
2247 2241 if ((!top) && foreign.isIndirect()) {
2248 2242 QTC::TC("qpdf", "QPDF replace indirect");
2249   - QPDFObjGen foreign_og(foreign.getObjGen());
2250   - auto mapping = obj_copier.object_map.find(foreign_og);
  2243 + auto mapping = obj_copier.object_map.find(foreign.getObjGen());
2251 2244 if (mapping == obj_copier.object_map.end()) {
2252 2245 // This case would occur if this is a reference to a Page
2253 2246 // or Pages object that we didn't traverse into.
2254 2247 QTC::TC("qpdf", "QPDF replace foreign indirect with null");
2255 2248 result = QPDFObjectHandle::newNull();
2256 2249 } else {
2257   - result = obj_copier.object_map[foreign_og];
  2250 + result = mapping->second;
2258 2251 }
2259   - } else if (foreign.isArray()) {
  2252 + } else if (foreign_tc == ::ot_array) {
2260 2253 QTC::TC("qpdf", "QPDF replace array");
2261 2254 result = QPDFObjectHandle::newArray();
2262 2255 int n = foreign.getArrayNItems();
... ... @@ -2266,7 +2259,7 @@ QPDF::replaceForeignIndirectObjects(
2266 2259 replaceForeignIndirectObjects(
2267 2260 foreign.getArrayItem(i), obj_copier, false));
2268 2261 }
2269   - } else if (foreign.isDictionary()) {
  2262 + } else if (foreign_tc == ::ot_dictionary) {
2270 2263 QTC::TC("qpdf", "QPDF replace dictionary");
2271 2264 result = QPDFObjectHandle::newDictionary();
2272 2265 std::set<std::string> keys = foreign.getKeys();
... ... @@ -2276,10 +2269,9 @@ QPDF::replaceForeignIndirectObjects(
2276 2269 replaceForeignIndirectObjects(
2277 2270 foreign.getKey(iter), obj_copier, false));
2278 2271 }
2279   - } else if (foreign.isStream()) {
  2272 + } else if (foreign_tc == ::ot_stream) {
2280 2273 QTC::TC("qpdf", "QPDF replace stream");
2281   - QPDFObjGen foreign_og(foreign.getObjGen());
2282   - result = obj_copier.object_map[foreign_og];
  2274 + result = obj_copier.object_map[foreign.getObjGen()];
2283 2275 result.assertStream();
2284 2276 QPDFObjectHandle dict = result.getDict();
2285 2277 QPDFObjectHandle old_dict = foreign.getDict();
... ... @@ -2511,7 +2503,7 @@ QPDF::getCompressibleObjGens()
2511 2503 QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt");
2512 2504 QPDFObjGen encryption_dict_og = encryption_dict.getObjGen();
2513 2505  
2514   - std::set<QPDFObjGen> visited;
  2506 + QPDFObjGen::set visited;
2515 2507 std::list<QPDFObjectHandle> queue;
2516 2508 queue.push_front(this->m->trailer);
2517 2509 std::vector<QPDFObjGen> result;
... ... @@ -2520,7 +2512,7 @@ QPDF::getCompressibleObjGens()
2520 2512 queue.pop_front();
2521 2513 if (obj.isIndirect()) {
2522 2514 QPDFObjGen og = obj.getObjGen();
2523   - if (visited.count(og)) {
  2515 + if (!visited.add(og)) {
2524 2516 QTC::TC("qpdf", "QPDF loop detected traversing objects");
2525 2517 continue;
2526 2518 }
... ... @@ -2532,7 +2524,6 @@ QPDF::getCompressibleObjGens()
2532 2524 obj.hasKey("/Contents")))) {
2533 2525 result.push_back(og);
2534 2526 }
2535   - visited.insert(og);
2536 2527 }
2537 2528 if (obj.isStream()) {
2538 2529 QPDFObjectHandle dict = obj.getDict();
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -57,7 +57,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
57 57 "/Fields", QPDFObjectHandle::newArray());
58 58 }
59 59 fields.appendItem(ff.getObjectHandle());
60   - std::set<QPDFObjGen> visited;
  60 + QPDFObjGen::set visited;
61 61 traverseField(
62 62 ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited);
63 63 }
... ... @@ -68,53 +68,48 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields(
68 68 {
69 69 analyze();
70 70 std::map<std::string, std::string> renames;
71   - std::list<QPDFObjectHandle> queue;
72   - queue.insert(queue.begin(), fields.begin(), fields.end());
73   - std::set<QPDFObjGen> seen;
74   - while (!queue.empty()) {
75   - QPDFObjectHandle obj = queue.front();
76   - queue.pop_front();
77   - auto og = obj.getObjGen();
78   - if (seen.count(og)) {
79   - // loop
80   - continue;
81   - }
82   - seen.insert(og);
83   - auto kids = obj.getKey("/Kids");
84   - if (kids.isArray()) {
85   - for (auto kid: kids.aitems()) {
86   - queue.push_back(kid);
  71 + QPDFObjGen::set seen;
  72 + for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()};
  73 + !queue.empty();
  74 + queue.pop_front()) {
  75 + auto& obj = queue.front();
  76 + if (seen.add(obj)) {
  77 + auto kids = obj.getKey("/Kids");
  78 + if (kids.isArray()) {
  79 + for (auto kid: kids.aitems()) {
  80 + queue.push_back(kid);
  81 + }
87 82 }
88   - }
89 83  
90   - if (obj.hasKey("/T")) {
91   - // Find something we can append to the partial name that
92   - // makes the fully qualified name unique. When we find
93   - // something, reuse the same suffix for all fields in this
94   - // group with the same name. We can only change the name
95   - // of fields that have /T, and this field's /T is always
96   - // at the end of the fully qualified name, appending to /T
97   - // has the effect of appending the same thing to the fully
98   - // qualified name.
99   - std::string old_name =
100   - QPDFFormFieldObjectHelper(obj).getFullyQualifiedName();
101   - if (renames.count(old_name) == 0) {
102   - std::string new_name = old_name;
103   - int suffix = 0;
104   - std::string append;
105   - while (!getFieldsWithQualifiedName(new_name).empty()) {
106   - ++suffix;
107   - append = "+" + std::to_string(suffix);
108   - new_name = old_name + append;
  84 + if (obj.hasKey("/T")) {
  85 + // Find something we can append to the partial name that
  86 + // makes the fully qualified name unique. When we find
  87 + // something, reuse the same suffix for all fields in this
  88 + // group with the same name. We can only change the name
  89 + // of fields that have /T, and this field's /T is always
  90 + // at the end of the fully qualified name, appending to /T
  91 + // has the effect of appending the same thing to the fully
  92 + // qualified name.
  93 + std::string old_name =
  94 + QPDFFormFieldObjectHelper(obj).getFullyQualifiedName();
  95 + if (renames.count(old_name) == 0) {
  96 + std::string new_name = old_name;
  97 + int suffix = 0;
  98 + std::string append;
  99 + while (!getFieldsWithQualifiedName(new_name).empty()) {
  100 + ++suffix;
  101 + append = "+" + std::to_string(suffix);
  102 + new_name = old_name + append;
  103 + }
  104 + renames[old_name] = append;
  105 + }
  106 + std::string append = renames[old_name];
  107 + if (!append.empty()) {
  108 + obj.replaceKey(
  109 + "/T",
  110 + QPDFObjectHandle::newUnicodeString(
  111 + obj.getKey("/T").getUTF8Value() + append));
109 112 }
110   - renames[old_name] = append;
111   - }
112   - std::string append = renames[old_name];
113   - if (!append.empty()) {
114   - obj.replaceKey(
115   - "/T",
116   - QPDFObjectHandle::newUnicodeString(
117   - obj.getKey("/T").getUTF8Value() + append));
118 113 }
119 114 }
120 115 }
... ... @@ -172,7 +167,7 @@ QPDFAcroFormDocumentHelper::setFormFieldName(
172 167 QPDFFormFieldObjectHelper ff, std::string const& name)
173 168 {
174 169 ff.setFieldAttribute("/T", name);
175   - std::set<QPDFObjGen> visited;
  170 + QPDFObjGen::set visited;
176 171 auto ff_oh = ff.getObjectHandle();
177 172 traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited);
178 173 }
... ... @@ -193,12 +188,11 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const&amp; name)
193 188 {
194 189 analyze();
195 190 // Keep from creating an empty entry
196   - std::set<QPDFObjGen> result;
197 191 auto iter = this->m->name_to_fields.find(name);
198 192 if (iter != this->m->name_to_fields.end()) {
199   - result = iter->second;
  193 + return iter->second;
200 194 }
201   - return result;
  195 + return {};
202 196 }
203 197  
204 198 std::vector<QPDFAnnotationObjectHelper>
... ... @@ -223,18 +217,12 @@ std::vector&lt;QPDFFormFieldObjectHelper&gt;
223 217 QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph)
224 218 {
225 219 analyze();
226   - std::set<QPDFObjGen> added;
  220 + QPDFObjGen::set todo;
227 221 std::vector<QPDFFormFieldObjectHelper> result;
228   - auto widget_annotations = getWidgetAnnotationsForPage(ph);
229   - for (auto annot: widget_annotations) {
230   - auto field = getFieldForAnnotation(annot);
231   - field = field.getTopLevelField();
232   - auto og = field.getObjectHandle().getObjGen();
233   - if (!added.count(og)) {
234   - added.insert(og);
235   - if (field.getObjectHandle().isDictionary()) {
236   - result.push_back(field);
237   - }
  222 + for (auto& annot: getWidgetAnnotationsForPage(ph)) {
  223 + auto field = getFieldForAnnotation(annot).getTopLevelField();
  224 + if (todo.add(field) && field.getObjectHandle().isDictionary()) {
  225 + result.push_back(field);
238 226 }
239 227 }
240 228 return result;
... ... @@ -278,7 +266,7 @@ QPDFAcroFormDocumentHelper::analyze()
278 266 // Traverse /AcroForm to find annotations and map them
279 267 // bidirectionally to fields.
280 268  
281   - std::set<QPDFObjGen> visited;
  269 + QPDFObjGen::set visited;
282 270 int nfields = fields.getArrayNItems();
283 271 QPDFObjectHandle null(QPDFObjectHandle::newNull());
284 272 for (int i = 0; i < nfields; ++i) {
... ... @@ -324,7 +312,7 @@ QPDFAcroFormDocumentHelper::traverseField(
324 312 QPDFObjectHandle field,
325 313 QPDFObjectHandle parent,
326 314 int depth,
327   - std::set<QPDFObjGen>& visited)
  315 + QPDFObjGen::set& visited)
328 316 {
329 317 if (depth > 100) {
330 318 // Arbitrarily cut off recursion at a fixed depth to avoid
... ... @@ -346,12 +334,11 @@ QPDFAcroFormDocumentHelper::traverseField(
346 334 return;
347 335 }
348 336 QPDFObjGen og(field.getObjGen());
349   - if (visited.count(og) != 0) {
  337 + if (!visited.add(og)) {
350 338 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop");
351 339 field.warnIfPossible("loop detected while traversing /AcroForm");
352 340 return;
353 341 }
354   - visited.insert(og);
355 342  
356 343 // A dictionary encountered while traversing the /AcroForm field
357 344 // may be a form field, an annotation, or the merger of the two. A
... ... @@ -888,7 +875,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
888 875  
889 876 // Now do the actual copies.
890 877  
891   - std::set<QPDFObjGen> added_new_fields;
  878 + QPDFObjGen::set added_new_fields;
892 879 for (auto annot: old_annots.aitems()) {
893 880 if (annot.isStream()) {
894 881 annot.warnIfPossible("ignoring annotation that's a stream");
... ... @@ -970,73 +957,68 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
970 957 // Traverse the field, copying kids, and preserving
971 958 // integrity.
972 959 std::list<QPDFObjectHandle> queue;
  960 + QPDFObjGen::set seen;
973 961 if (maybe_copy_object(top_field)) {
974 962 queue.push_back(top_field);
975 963 }
976   - std::set<QPDFObjGen> seen;
977   - while (!queue.empty()) {
978   - QPDFObjectHandle obj = queue.front();
979   - queue.pop_front();
980   - auto orig_og = obj.getObjGen();
981   - if (seen.count(orig_og)) {
982   - // loop
983   - break;
984   - }
985   - seen.insert(orig_og);
986   - auto parent = obj.getKey("/Parent");
987   - if (parent.isIndirect()) {
988   - auto parent_og = parent.getObjGen();
989   - if (orig_to_copy.count(parent_og)) {
990   - obj.replaceKey("/Parent", orig_to_copy[parent_og]);
991   - } else {
992   - parent.warnIfPossible(
993   - "while traversing field " +
994   - obj.getObjGen().unparse(',') + ", found parent (" +
995   - parent_og.unparse(',') +
996   - ") that had not been seen, indicating likely"
997   - " invalid field structure");
  964 + for (; !queue.empty(); queue.pop_front()) {
  965 + auto& obj = queue.front();
  966 + if (seen.add(obj)) {
  967 + auto parent = obj.getKey("/Parent");
  968 + if (parent.isIndirect()) {
  969 + auto parent_og = parent.getObjGen();
  970 + if (orig_to_copy.count(parent_og)) {
  971 + obj.replaceKey("/Parent", orig_to_copy[parent_og]);
  972 + } else {
  973 + parent.warnIfPossible(
  974 + "while traversing field " +
  975 + obj.getObjGen().unparse(',') +
  976 + ", found parent (" + parent_og.unparse(',') +
  977 + ") that had not been seen, indicating likely"
  978 + " invalid field structure");
  979 + }
998 980 }
999   - }
1000   - auto kids = obj.getKey("/Kids");
1001   - if (kids.isArray()) {
1002   - for (int i = 0; i < kids.getArrayNItems(); ++i) {
1003   - auto kid = kids.getArrayItem(i);
1004   - if (maybe_copy_object(kid)) {
1005   - kids.setArrayItem(i, kid);
1006   - queue.push_back(kid);
  981 + auto kids = obj.getKey("/Kids");
  982 + if (kids.isArray()) {
  983 + for (int i = 0; i < kids.getArrayNItems(); ++i) {
  984 + auto kid = kids.getArrayItem(i);
  985 + if (maybe_copy_object(kid)) {
  986 + kids.setArrayItem(i, kid);
  987 + queue.push_back(kid);
  988 + }
1007 989 }
1008 990 }
1009   - }
1010 991  
1011   - if (override_da || override_q) {
1012   - adjustInheritedFields(
1013   - obj,
1014   - override_da,
1015   - from_default_da,
1016   - override_q,
1017   - from_default_q);
1018   - }
1019   - if (foreign) {
1020   - // Lazily initialize our /DR and the conflict map.
1021   - init_dr_map();
1022   - // The spec doesn't say anything about /DR on the
1023   - // field, but lots of writers put one there, and
1024   - // it is frequently the same as the document-level
1025   - // /DR. To avoid having the field's /DR point to
1026   - // information that we are not maintaining, just
1027   - // reset it to that if it exists. Empirical
1028   - // evidence suggests that many readers, including
1029   - // Acrobat, Adobe Acrobat Reader, chrome, firefox,
1030   - // the mac Preview application, and several of the
1031   - // free readers on Linux all ignore /DR at the
1032   - // field level.
1033   - if (obj.hasKey("/DR")) {
1034   - obj.replaceKey("/DR", dr);
  992 + if (override_da || override_q) {
  993 + adjustInheritedFields(
  994 + obj,
  995 + override_da,
  996 + from_default_da,
  997 + override_q,
  998 + from_default_q);
  999 + }
  1000 + if (foreign) {
  1001 + // Lazily initialize our /DR and the conflict map.
  1002 + init_dr_map();
  1003 + // The spec doesn't say anything about /DR on the
  1004 + // field, but lots of writers put one there, and
  1005 + // it is frequently the same as the document-level
  1006 + // /DR. To avoid having the field's /DR point to
  1007 + // information that we are not maintaining, just
  1008 + // reset it to that if it exists. Empirical
  1009 + // evidence suggests that many readers, including
  1010 + // Acrobat, Adobe Acrobat Reader, chrome, firefox,
  1011 + // the mac Preview application, and several of the
  1012 + // free readers on Linux all ignore /DR at the
  1013 + // field level.
  1014 + if (obj.hasKey("/DR")) {
  1015 + obj.replaceKey("/DR", dr);
  1016 + }
  1017 + }
  1018 + if (foreign && obj.getKey("/DA").isString() &&
  1019 + (!dr_map.empty())) {
  1020 + adjustDefaultAppearances(obj, dr_map);
1035 1021 }
1036   - }
1037   - if (foreign && obj.getKey("/DA").isString() &&
1038   - (!dr_map.empty())) {
1039   - adjustDefaultAppearances(obj, dr_map);
1040 1022 }
1041 1023 }
1042 1024  
... ... @@ -1064,9 +1046,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations(
1064 1046 maybe_copy_object(annot);
1065 1047  
1066 1048 // Now we have copies, so we can safely mutate.
1067   - if (have_field && !added_new_fields.count(top_field.getObjGen())) {
  1049 + if (have_field && added_new_fields.add(top_field)) {
1068 1050 new_fields.push_back(top_field);
1069   - added_new_fields.insert(top_field.getObjGen());
1070 1051 }
1071 1052 new_annots.push_back(annot);
1072 1053  
... ...
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -36,20 +36,14 @@ QPDFFormFieldObjectHelper
36 36 QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different)
37 37 {
38 38 auto top_field = this->oh;
39   - std::set<QPDFObjGen> seen;
40   - while (top_field.isDictionary() &&
41   - (!top_field.getKey("/Parent").isNull())) {
  39 + QPDFObjGen::set seen;
  40 + while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").isNull()) {
42 41 top_field = top_field.getKey("/Parent");
43 42 if (is_different) {
44 43 *is_different = true;
45 44 }
46   - auto og = top_field.getObjGen();
47   - if (seen.count(og)) {
48   - break;
49   - }
50   - seen.insert(og);
51 45 }
52   - return QPDFFormFieldObjectHelper(top_field);
  46 + return {top_field};
53 47 }
54 48  
55 49 QPDFObjectHandle
... ... @@ -76,17 +70,17 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const&amp; name)
76 70 return QPDFObjectHandle::newNull();
77 71 }
78 72 QPDFObjectHandle result(node.getKey(name));
79   - std::set<QPDFObjGen> seen;
80   - while (result.isNull() && node.hasKey("/Parent")) {
81   - seen.insert(node.getObjGen());
82   - node = node.getKey("/Parent");
83   - if (seen.count(node.getObjGen())) {
84   - break;
85   - }
86   - result = node.getKey(name);
87   - if (!result.isNull()) {
88   - QTC::TC(
89   - "qpdf", "QPDFFormFieldObjectHelper non-trivial inheritance");
  73 + if (result.isNull()) {
  74 + QPDFObjGen::set seen;
  75 + while (seen.add(node) && node.hasKey("/Parent")) {
  76 + node = node.getKey("/Parent");
  77 + result = node.getKey(name);
  78 + if (!result.isNull()) {
  79 + QTC::TC(
  80 + "qpdf",
  81 + "QPDFFormFieldObjectHelper non-trivial inheritance");
  82 + return result;
  83 + }
90 84 }
91 85 }
92 86 return result;
... ... @@ -127,8 +121,8 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName()
127 121 {
128 122 std::string result;
129 123 QPDFObjectHandle node = this->oh;
130   - std::set<QPDFObjGen> seen;
131   - while ((!node.isNull()) && (seen.count(node.getObjGen()) == 0)) {
  124 + QPDFObjGen::set seen;
  125 + while (!node.isNull() && seen.add(node)) {
132 126 if (node.getKey("/T").isString()) {
133 127 if (!result.empty()) {
134 128 QTC::TC(
... ... @@ -138,7 +132,6 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName()
138 132 }
139 133 result = node.getKey("/T").getUTF8Value() + result;
140 134 }
141   - seen.insert(node.getObjGen());
142 135 node = node.getKey("/Parent");
143 136 }
144 137 return result;
... ...
libqpdf/QPDFJob.cc
... ... @@ -1001,18 +1001,16 @@ QPDFJob::parse_object_id(
1001 1001 }
1002 1002 }
1003 1003  
1004   -std::set<QPDFObjGen>
  1004 +QPDFObjGen::set
1005 1005 QPDFJob::getWantedJSONObjects()
1006 1006 {
1007   - std::set<QPDFObjGen> wanted_og;
  1007 + QPDFObjGen::set wanted_og;
1008 1008 for (auto const& iter: m->json_objects) {
1009 1009 bool trailer;
1010 1010 int obj = 0;
1011 1011 int gen = 0;
1012 1012 parse_object_id(iter, trailer, obj, gen);
1013   - if (obj) {
1014   - wanted_og.insert(QPDFObjGen(obj, gen));
1015   - }
  1013 + wanted_og.add(QPDFObjGen(obj, gen));
1016 1014 }
1017 1015 return wanted_og;
1018 1016 }
... ... @@ -1045,7 +1043,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1045 1043 bool first_object = true;
1046 1044 JSON::writeDictionaryOpen(p, first_object, 1);
1047 1045 bool all_objects = m->json_objects.empty();
1048   - std::set<QPDFObjGen> wanted_og = getWantedJSONObjects();
  1046 + auto wanted_og = getWantedJSONObjects();
1049 1047 for (auto& obj: pdf.getAllObjects()) {
1050 1048 std::string key = obj.unparse();
1051 1049 if (this->m->json_version > 1) {
... ... @@ -1065,11 +1063,8 @@ QPDFJob::doJSONObjects(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1065 1063 if (this->m->json_objects.count("trailer")) {
1066 1064 json_objects.insert("trailer");
1067 1065 }
1068   - auto wanted = getWantedJSONObjects();
1069   - for (auto const& og: wanted) {
1070   - std::ostringstream s;
1071   - s << "obj:" << og.unparse(' ') << " R";
1072   - json_objects.insert(s.str());
  1066 + for (auto og: getWantedJSONObjects()) {
  1067 + json_objects.emplace("obj:" + og.unparse(' ') + " R");
1073 1068 }
1074 1069 pdf.writeJSON(
1075 1070 this->m->json_version,
... ... @@ -1090,7 +1085,7 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1090 1085 bool first_object = true;
1091 1086 JSON::writeDictionaryOpen(p, first_object, 1);
1092 1087 bool all_objects = m->json_objects.empty();
1093   - std::set<QPDFObjGen> wanted_og = getWantedJSONObjects();
  1088 + auto wanted_og = getWantedJSONObjects();
1094 1089 for (auto& obj: pdf.getAllObjects()) {
1095 1090 if (all_objects || wanted_og.count(obj.getObjGen())) {
1096 1091 auto j_details = JSON::makeDictionary();
... ... @@ -2451,8 +2446,8 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF&amp; pdf)
2451 2446  
2452 2447 // Return true as soon as we find any shared resources.
2453 2448  
2454   - std::set<QPDFObjGen> resources_seen; // shared resources detection
2455   - std::set<QPDFObjGen> nodes_seen; // loop detection
  2449 + QPDFObjGen::set resources_seen; // shared resources detection
  2450 + QPDFObjGen::set nodes_seen; // loop detection
2456 2451  
2457 2452 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2458 2453 v << prefix << ": " << pdf.getFilename()
... ... @@ -2465,10 +2460,9 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF&amp; pdf)
2465 2460 QPDFObjectHandle node = *queue.begin();
2466 2461 queue.pop_front();
2467 2462 QPDFObjGen og = node.getObjGen();
2468   - if (nodes_seen.count(og)) {
  2463 + if (!nodes_seen.add(og)) {
2469 2464 continue;
2470 2465 }
2471   - nodes_seen.insert(og);
2472 2466 QPDFObjectHandle dict = node.isStream() ? node.getDict() : node;
2473 2467 QPDFObjectHandle kids = dict.getKey("/Kids");
2474 2468 if (kids.isArray()) {
... ... @@ -2489,33 +2483,29 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF&amp; pdf)
2489 2483 // This is a leaf node or a form XObject.
2490 2484 QPDFObjectHandle resources = dict.getKey("/Resources");
2491 2485 if (resources.isIndirect()) {
2492   - QPDFObjGen resources_og = resources.getObjGen();
2493   - if (resources_seen.count(resources_og)) {
  2486 + if (!resources_seen.add(resources)) {
2494 2487 QTC::TC("qpdf", "QPDFJob found shared resources in leaf");
2495 2488 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2496 2489 v << " found shared resources in leaf node "
2497 2490 << og.unparse(' ') << ": "
2498   - << resources_og.unparse(' ') << "\n";
  2491 + << resources.getObjGen().unparse(' ') << "\n";
2499 2492 });
2500 2493 return true;
2501 2494 }
2502   - resources_seen.insert(resources_og);
2503 2495 }
2504 2496 QPDFObjectHandle xobject =
2505 2497 (resources.isDictionary() ? resources.getKey("/XObject")
2506 2498 : QPDFObjectHandle::newNull());
2507 2499 if (xobject.isIndirect()) {
2508   - QPDFObjGen xobject_og = xobject.getObjGen();
2509   - if (resources_seen.count(xobject_og)) {
  2500 + if (!resources_seen.add(xobject)) {
2510 2501 QTC::TC("qpdf", "QPDFJob found shared xobject in leaf");
2511 2502 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
2512 2503 v << " found shared xobject in leaf node "
2513   - << og.unparse(' ') << ": " << xobject_og.unparse(' ')
2514   - << "\n";
  2504 + << og.unparse(' ') << ": "
  2505 + << xobject.getObjGen().unparse(' ') << "\n";
2515 2506 });
2516 2507 return true;
2517 2508 }
2518   - resources_seen.insert(xobject_og);
2519 2509 }
2520 2510 if (xobject.isDictionary()) {
2521 2511 for (auto const& k: xobject.getKeys()) {
... ...
libqpdf/QPDFObjGen.cc
1 1 #include <qpdf/QPDFObjGen.hh>
2 2  
3   -#include <qpdf/QUtil.hh>
  3 +#include <qpdf/QPDFObjectHandle.hh>
  4 +#include <qpdf/QPDFObjectHelper.hh>
  5 +#include <qpdf/QPDFObject_private.hh>
4 6  
  7 +#include <stdexcept>
  8 +
  9 +// ABI: inline and pass og by value
5 10 std::ostream&
6 11 operator<<(std::ostream& os, const QPDFObjGen& og)
7 12 {
... ... @@ -9,8 +14,55 @@ operator&lt;&lt;(std::ostream&amp; os, const QPDFObjGen&amp; og)
9 14 return os;
10 15 }
11 16  
  17 +// ABI: inline
12 18 std::string
13 19 QPDFObjGen::unparse(char separator) const
14 20 {
15   - return std::to_string(this->obj) + separator + std::to_string(this->gen);
  21 + return std::to_string(obj) + separator + std::to_string(gen);
  22 +}
  23 +
  24 +bool
  25 +QPDFObjGen::set::add(QPDFObjectHandle const& oh)
  26 +{
  27 + if (auto* ptr = oh.getObjectPtr()) {
  28 + return add(ptr->getObjGen());
  29 + } else {
  30 + throw std::logic_error("attempt to retrieve QPDFObjGen from "
  31 + "uninitialized QPDFObjectHandle");
  32 + return false;
  33 + }
  34 +}
  35 +
  36 +bool
  37 +QPDFObjGen::set::add(QPDFObjectHelper const& helper)
  38 +{
  39 + if (auto* ptr = helper.getObjectHandle().getObjectPtr()) {
  40 + return add(ptr->getObjGen());
  41 + } else {
  42 + throw std::logic_error("attempt to retrieve QPDFObjGen from "
  43 + "uninitialized QPDFObjectHandle");
  44 + return false;
  45 + }
  46 +}
  47 +
  48 +void
  49 +QPDFObjGen::set::erase(QPDFObjectHandle const& oh)
  50 +{
  51 + if (auto* ptr = oh.getObjectPtr()) {
  52 + erase(ptr->getObjGen());
  53 + } else {
  54 + throw std::logic_error("attempt to retrieve QPDFObjGen from "
  55 + "uninitialized QPDFObjectHandle");
  56 + }
  57 +}
  58 +
  59 +void
  60 +QPDFObjGen::set::erase(QPDFObjectHelper const& helper)
  61 +{
  62 + if (auto* ptr = helper.getObjectHandle().getObjectPtr()) {
  63 + erase(ptr->getObjGen());
  64 + } else {
  65 + throw std::logic_error("attempt to retrieve QPDFObjGen from "
  66 + "uninitialized QPDFObjectHandle");
  67 + }
16 68 }
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -1588,22 +1588,12 @@ QPDFObjectHandle::rotatePage(int angle, bool relative)
1588 1588 int new_angle = angle;
1589 1589 if (relative) {
1590 1590 int old_angle = 0;
1591   - bool found_rotate = false;
1592 1591 QPDFObjectHandle cur_obj = *this;
1593   - bool searched_parent = false;
1594   - std::set<QPDFObjGen> visited;
1595   - while (!found_rotate) {
1596   - if (visited.count(cur_obj.getObjGen())) {
1597   - // Don't get stuck in an infinite loop
  1592 + QPDFObjGen::set visited;
  1593 + while (visited.add(cur_obj)) {
  1594 + // Don't get stuck in an infinite loop
  1595 + if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1598 1596 break;
1599   - }
1600   - if (!visited.empty()) {
1601   - searched_parent = true;
1602   - }
1603   - visited.insert(cur_obj.getObjGen());
1604   - if (cur_obj.getKey("/Rotate").isInteger()) {
1605   - found_rotate = true;
1606   - old_angle = cur_obj.getKey("/Rotate").getIntValueAsInt();
1607 1597 } else if (cur_obj.getKey("/Parent").isDictionary()) {
1608 1598 cur_obj = cur_obj.getKey("/Parent");
1609 1599 } else {
... ... @@ -1613,7 +1603,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative)
1613 1603 QTC::TC(
1614 1604 "qpdf",
1615 1605 "QPDFObjectHandle found old angle",
1616   - searched_parent ? 0 : 1);
  1606 + visited.size() > 1 ? 0 : 1);
1617 1607 if ((old_angle % 90) != 0) {
1618 1608 old_angle = 0;
1619 1609 }
... ... @@ -2181,20 +2171,15 @@ QPDFObjectHandle::unsafeShallowCopy()
2181 2171 }
2182 2172  
2183 2173 void
2184   -QPDFObjectHandle::makeDirect(
2185   - std::set<QPDFObjGen>& visited, bool stop_at_streams)
  2174 +QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams)
2186 2175 {
2187 2176 assertInitialized();
2188 2177  
2189 2178 auto cur_og = getObjGen();
2190   - if (cur_og.getObj() != 0) {
2191   - if (visited.count(cur_og)) {
2192   - QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
2193   - throw std::runtime_error(
2194   - "loop detected while converting object from "
2195   - "indirect to direct");
2196   - }
2197   - visited.insert(cur_og);
  2179 + if (!visited.add(cur_og)) {
  2180 + QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
  2181 + throw std::runtime_error("loop detected while converting object from "
  2182 + "indirect to direct");
2198 2183 }
2199 2184  
2200 2185 if (isBool() || isInteger() || isName() || isNull() || isReal() ||
... ... @@ -2232,9 +2217,7 @@ QPDFObjectHandle::makeDirect(
2232 2217 "unknown object type");
2233 2218 }
2234 2219  
2235   - if (cur_og.getObj()) {
2236   - visited.erase(cur_og);
2237   - }
  2220 + visited.erase(cur_og);
2238 2221 }
2239 2222  
2240 2223 QPDFObjectHandle
... ... @@ -2258,7 +2241,7 @@ QPDFObjectHandle::copyStream()
2258 2241 void
2259 2242 QPDFObjectHandle::makeDirect(bool allow_streams)
2260 2243 {
2261   - std::set<QPDFObjGen> visited;
  2244 + QPDFObjGen::set visited;
2262 2245 makeDirect(visited, allow_streams);
2263 2246 }
2264 2247  
... ...
libqpdf/QPDFOutlineDocumentHelper.cc
... ... @@ -15,13 +15,8 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF&amp; qpdf) :
15 15 return;
16 16 }
17 17 QPDFObjectHandle cur = outlines.getKey("/First");
18   - std::set<QPDFObjGen> seen;
19   - while (!cur.isNull()) {
20   - auto og = cur.getObjGen();
21   - if (seen.count(og)) {
22   - break;
23   - }
24   - seen.insert(og);
  18 + QPDFObjGen::set seen;
  19 + while (!cur.isNull() && seen.add(cur)) {
25 20 this->m->outlines.push_back(
26 21 QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
27 22 cur = cur.getKey("/Next");
... ... @@ -104,13 +99,3 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
104 99 }
105 100 return result;
106 101 }
107   -
108   -bool
109   -QPDFOutlineDocumentHelper::checkSeen(QPDFObjGen const& og)
110   -{
111   - if (this->m->seen.count(og) > 0) {
112   - return true;
113   - }
114   - this->m->seen.insert(og);
115   - return false;
116   -}
... ...
libqpdf/QPDFPageObjectHelper.cc
... ... @@ -246,32 +246,23 @@ QPDFPageObjectHelper::getAttribute(
246 246 std::function<QPDFObjectHandle()> get_fallback,
247 247 bool copy_if_fallback)
248 248 {
249   - QPDFObjectHandle result;
250   - QPDFObjectHandle dict;
251   - bool is_form_xobject = this->oh.isFormXObject();
  249 + const bool is_form_xobject = this->oh.isFormXObject();
252 250 bool inherited = false;
253   - if (is_form_xobject) {
254   - dict = this->oh.getDict();
255   - result = dict.getKey(name);
256   - } else {
257   - dict = this->oh;
258   - bool inheritable =
259   - ((name == "/MediaBox") || (name == "/CropBox") ||
260   - (name == "/Resources") || (name == "/Rotate"));
  251 + auto dict = is_form_xobject ? oh.getDict() : oh;
  252 + auto result = dict.getKey(name);
261 253  
  254 + if (!is_form_xobject && result.isNull() &&
  255 + (name == "/MediaBox" || name == "/CropBox" || name == "/Resources" ||
  256 + name == "/Rotate")) {
262 257 QPDFObjectHandle node = dict;
263   - result = node.getKey(name);
264   - std::set<QPDFObjGen> seen;
265   - while (inheritable && result.isNull() && node.hasKey("/Parent")) {
266   - seen.insert(node.getObjGen());
  258 + QPDFObjGen::set seen{};
  259 + while (seen.add(node) && node.hasKey("/Parent")) {
267 260 node = node.getKey("/Parent");
268   - if (seen.count(node.getObjGen())) {
269   - break;
270   - }
271 261 result = node.getKey(name);
272 262 if (!result.isNull()) {
273 263 QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance");
274 264 inherited = true;
  265 + break;
275 266 }
276 267 }
277 268 }
... ... @@ -361,30 +352,27 @@ QPDFPageObjectHelper::forEachXObject(
361 352 "QPDFPageObjectHelper::forEachXObject",
362 353 recursive ? (this->oh.isFormXObject() ? 0 : 1)
363 354 : (this->oh.isFormXObject() ? 2 : 3));
364   - std::set<QPDFObjGen> seen;
  355 + QPDFObjGen::set seen;
365 356 std::list<QPDFPageObjectHelper> queue;
366 357 queue.push_back(*this);
367 358 while (!queue.empty()) {
368   - QPDFPageObjectHelper ph = queue.front();
369   - queue.pop_front();
370   - QPDFObjGen og = ph.oh.getObjGen();
371   - if (seen.count(og)) {
372   - continue;
373   - }
374   - seen.insert(og);
375   - QPDFObjectHandle resources = ph.getAttribute("/Resources", false);
376   - if (resources.isDictionary() && resources.hasKey("/XObject")) {
377   - QPDFObjectHandle xobj_dict = resources.getKey("/XObject");
378   - for (auto const& key: xobj_dict.getKeys()) {
379   - QPDFObjectHandle obj = xobj_dict.getKey(key);
380   - if ((!selector) || selector(obj)) {
381   - action(obj, xobj_dict, key);
382   - }
383   - if (recursive && obj.isFormXObject()) {
384   - queue.push_back(QPDFPageObjectHelper(obj));
  359 + auto& ph = queue.front();
  360 + if (seen.add(ph)) {
  361 + auto xobj_dict =
  362 + ph.getAttribute("/Resources", false).getKeyIfDict("/XObject");
  363 + if (xobj_dict.isDictionary()) {
  364 + for (auto const& key: xobj_dict.getKeys()) {
  365 + QPDFObjectHandle obj = xobj_dict.getKey(key);
  366 + if ((!selector) || selector(obj)) {
  367 + action(obj, xobj_dict, key);
  368 + }
  369 + if (recursive && obj.isFormXObject()) {
  370 + queue.emplace_back(obj);
  371 + }
385 372 }
386 373 }
387 374 }
  375 + queue.pop_front();
388 376 }
389 377 }
390 378  
... ...
libqpdf/QPDF_optimization.cc
... ... @@ -284,7 +284,7 @@ QPDF::updateObjectMaps(
284 284 QPDFObjectHandle oh,
285 285 std::function<int(QPDFObjectHandle&)> skip_stream_parameters)
286 286 {
287   - std::set<QPDFObjGen> visited;
  287 + QPDFObjGen::set visited;
288 288 updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true);
289 289 }
290 290  
... ... @@ -293,7 +293,7 @@ QPDF::updateObjectMapsInternal(
293 293 ObjUser const& ou,
294 294 QPDFObjectHandle oh,
295 295 std::function<int(QPDFObjectHandle&)> skip_stream_parameters,
296   - std::set<QPDFObjGen>& visited,
  296 + QPDFObjGen::set& visited,
297 297 bool top)
298 298 {
299 299 // Traverse the object tree from this point taking care to avoid
... ... @@ -310,13 +310,12 @@ QPDF::updateObjectMapsInternal(
310 310  
311 311 if (oh.isIndirect()) {
312 312 QPDFObjGen og(oh.getObjGen());
313   - if (visited.count(og)) {
  313 + if (!visited.add(og)) {
314 314 QTC::TC("qpdf", "QPDF opt loop detected");
315 315 return;
316 316 }
317 317 this->m->obj_user_to_objects[ou].insert(og);
318 318 this->m->object_to_obj_users[og].insert(ou);
319   - visited.insert(og);
320 319 }
321 320  
322 321 if (oh.isArray()) {
... ...
libqpdf/QPDF_pages.cc
... ... @@ -55,13 +55,13 @@ QPDF::getAllPages()
55 55 // initialize this->m->all_pages.
56 56 if (this->m->all_pages.empty()) {
57 57 this->m->ever_called_get_all_pages = true;
58   - std::set<QPDFObjGen> visited;
59   - std::set<QPDFObjGen> seen;
  58 + QPDFObjGen::set visited;
  59 + QPDFObjGen::set seen;
60 60 QPDFObjectHandle pages = getRoot().getKey("/Pages");
61 61 bool warned = false;
62 62 bool changed_pages = false;
63 63 while (pages.isDictionary() && pages.hasKey("/Parent")) {
64   - if (seen.count(pages.getObjGen())) {
  64 + if (!seen.add(pages)) {
65 65 // loop -- will be detected again and reported later
66 66 break;
67 67 }
... ... @@ -74,7 +74,6 @@ QPDF::getAllPages()
74 74 " to the root of the page tree; attempting to correct");
75 75 warned = true;
76 76 }
77   - seen.insert(pages.getObjGen());
78 77 changed_pages = true;
79 78 pages = pages.getKey("/Parent");
80 79 }
... ... @@ -92,12 +91,9 @@ QPDF::getAllPages()
92 91  
93 92 void
94 93 QPDF::getAllPagesInternal(
95   - QPDFObjectHandle cur_node,
96   - std::set<QPDFObjGen>& visited,
97   - std::set<QPDFObjGen>& seen)
  94 + QPDFObjectHandle cur_node, QPDFObjGen::set& visited, QPDFObjGen::set& seen)
98 95 {
99   - QPDFObjGen cur_node_og = cur_node.getObjGen();
100   - if (visited.count(cur_node_og) > 0) {
  96 + if (!visited.add(cur_node)) {
101 97 throw QPDFExc(
102 98 qpdf_e_pages,
103 99 this->m->file->getName(),
... ... @@ -105,7 +101,6 @@ QPDF::getAllPagesInternal(
105 101 0,
106 102 "Loop detected in /Pages structure (getAllPages)");
107 103 }
108   - visited.insert(cur_node_og);
109 104 if (!cur_node.isDictionaryOfType("/Pages")) {
110 105 cur_node.warnIfPossible(
111 106 "/Type key should be /Pages but is not; overriding");
... ... @@ -125,7 +120,7 @@ QPDF::getAllPagesInternal(
125 120 " (from 0) is direct; converting to indirect");
126 121 kid = makeIndirectObject(kid);
127 122 kids.setArrayItem(i, kid);
128   - } else if (seen.count(kid.getObjGen())) {
  123 + } else if (!seen.add(kid)) {
129 124 // Make a copy of the page. This does the same as
130 125 // shallowCopyPage in QPDFPageObjectHelper.
131 126 QTC::TC("qpdf", "QPDF resolve duplicated page object");
... ... @@ -134,6 +129,7 @@ QPDF::getAllPagesInternal(
134 129 " (from 0) appears more than once in the pages tree;"
135 130 " creating a new page object as a copy");
136 131 kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
  132 + seen.add(kid);
137 133 kids.setArrayItem(i, kid);
138 134 }
139 135 if (!kid.isDictionaryOfType("/Page")) {
... ... @@ -141,7 +137,6 @@ QPDF::getAllPagesInternal(
141 137 "/Type key should be /Page but is not; overriding");
142 138 kid.replaceKey("/Type", "/Page"_qpdf);
143 139 }
144   - seen.insert(kid.getObjGen());
145 140 m->all_pages.push_back(kid);
146 141 }
147 142 }
... ...