Commit 9496b2cb20bfd0551e9510b6ccb41ca950d2c8ee
1 parent
64dc7388
fix memory leak
git-svn-id: svn+q:///qpdf/trunk@976 71b93d88-0707-0410-a8cf-f5a4172ac649
Showing
10 changed files
with
95 additions
and
44 deletions
ChangeLog
| 1 | +2010-06-06 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * Fix memory leak for QPDF objects whose underlying PDF objects | |
| 4 | + contain circular references. Thanks to Jian Ma | |
| 5 | + <stronghorse@tom.com> for calling my attention to the memory leak. | |
| 6 | + | |
| 1 | 7 | 2010-04-25 Jay Berkenbilt <ejb@ql.org> |
| 2 | 8 | |
| 3 | 9 | * 2.1.5: release | ... | ... |
TODO
| 1 | -Bug | |
| 2 | -=== | |
| 3 | - | |
| 4 | - * There is a memory leak that happens whenever object A refers to | |
| 5 | - object B which refers back to object A. We have the following | |
| 6 | - pattern: | |
| 7 | - | |
| 8 | - #include "PointerHolder.hh" | |
| 9 | - | |
| 10 | - class A | |
| 11 | - { | |
| 12 | - public: | |
| 13 | - PointerHolder<A> a; | |
| 14 | - }; | |
| 15 | - | |
| 16 | - int main() | |
| 17 | - { | |
| 18 | - A a1; | |
| 19 | - a1.a = new A(); | |
| 20 | - a1.a.getPointer()->a = new A(); | |
| 21 | - a1.a.getPointer()->a.getPointer()->a = a1.a; | |
| 22 | - return 0; | |
| 23 | - } | |
| 24 | - | |
| 25 | - In order to fix this, we have to explicitly break circular | |
| 26 | - references, but we have to do this at a time that doesn't | |
| 27 | - completely destroy performance. | |
| 28 | - | |
| 29 | - To debug, configure with | |
| 30 | - | |
| 31 | - ./configure --disable-shared --disable-test-compare-images \ | |
| 32 | - CFLAGS=-g CXXFLAGS=-g | |
| 33 | - | |
| 34 | - Current code causes test failures. linearize segfaults on | |
| 35 | - hybrid-xref.pdf, and the test suite fails in various places. Maybe | |
| 36 | - because we don't do any kind of loop detection? | |
| 37 | - | |
| 38 | - Use valgrind --leak-check=full to see leak details. | |
| 39 | - | |
| 40 | - The file memory-leak.pdf is a minimal case that shows the problem. | |
| 41 | - | |
| 42 | - Files in trace having tracing to show which objects haven't been | |
| 43 | - allocated. | |
| 44 | - | |
| 45 | 1 | Next |
| 46 | 2 | ==== |
| 47 | 3 | ... | ... |
include/qpdf/QPDFObject.hh
| ... | ... | @@ -12,11 +12,33 @@ |
| 12 | 12 | |
| 13 | 13 | #include <string> |
| 14 | 14 | |
| 15 | +class QPDF; | |
| 16 | +class QPDFObjectHandle; | |
| 17 | + | |
| 15 | 18 | class QPDFObject |
| 16 | 19 | { |
| 17 | 20 | public: |
| 18 | 21 | virtual ~QPDFObject() {} |
| 19 | 22 | virtual std::string unparse() = 0; |
| 23 | + | |
| 24 | + // Accessor to give specific access to non-public methods | |
| 25 | + class ObjAccessor | |
| 26 | + { | |
| 27 | + friend class QPDF; | |
| 28 | + friend class QPDFObjectHandle; | |
| 29 | + private: | |
| 30 | + static void releaseResolved(QPDFObject* o) | |
| 31 | + { | |
| 32 | + if (o) | |
| 33 | + { | |
| 34 | + o->releaseResolved(); | |
| 35 | + } | |
| 36 | + } | |
| 37 | + }; | |
| 38 | + friend class ObjAccessor; | |
| 39 | + | |
| 40 | + protected: | |
| 41 | + virtual void releaseResolved() {} | |
| 20 | 42 | }; |
| 21 | 43 | |
| 22 | 44 | #endif // __QPDFOBJECT_HH__ | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -22,6 +22,8 @@ |
| 22 | 22 | |
| 23 | 23 | class Pipeline; |
| 24 | 24 | class QPDF; |
| 25 | +class QPDF_Dictionary; | |
| 26 | +class QPDF_Array; | |
| 25 | 27 | |
| 26 | 28 | class QPDFObjectHandle |
| 27 | 29 | { |
| ... | ... | @@ -247,6 +249,20 @@ class QPDFObjectHandle |
| 247 | 249 | }; |
| 248 | 250 | friend class ObjAccessor; |
| 249 | 251 | |
| 252 | + // Provide access to specific classes for recursive | |
| 253 | + // reverseResolved(). | |
| 254 | + class ReleaseResolver | |
| 255 | + { | |
| 256 | + friend class QPDF_Dictionary; | |
| 257 | + friend class QPDF_Array; | |
| 258 | + private: | |
| 259 | + static void releaseResolved(QPDFObjectHandle& o) | |
| 260 | + { | |
| 261 | + o.releaseResolved(); | |
| 262 | + } | |
| 263 | + }; | |
| 264 | + friend class ReleaseResolver; | |
| 265 | + | |
| 250 | 266 | private: |
| 251 | 267 | QPDFObjectHandle(QPDF*, int objid, int generation); |
| 252 | 268 | QPDFObjectHandle(QPDFObject*); |
| ... | ... | @@ -262,6 +278,7 @@ class QPDFObjectHandle |
| 262 | 278 | void assertPageObject(); |
| 263 | 279 | void dereference(); |
| 264 | 280 | void makeDirectInternal(std::set<int>& visited); |
| 281 | + void releaseResolved(); | |
| 265 | 282 | |
| 266 | 283 | bool initialized; |
| 267 | 284 | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -277,6 +277,13 @@ QPDF::QPDF() : |
| 277 | 277 | |
| 278 | 278 | QPDF::~QPDF() |
| 279 | 279 | { |
| 280 | + this->xref_table.clear(); | |
| 281 | + for (std::map<ObjGen, ObjCache>::iterator iter = this->obj_cache.begin(); | |
| 282 | + iter != obj_cache.end(); ++iter) | |
| 283 | + { | |
| 284 | + QPDFObject::ObjAccessor::releaseResolved( | |
| 285 | + (*iter).second.object.getPointer()); | |
| 286 | + } | |
| 280 | 287 | } |
| 281 | 288 | |
| 282 | 289 | void | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -41,6 +41,22 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) : |
| 41 | 41 | { |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | +void | |
| 45 | +QPDFObjectHandle::releaseResolved() | |
| 46 | +{ | |
| 47 | + if (isIndirect()) | |
| 48 | + { | |
| 49 | + if (this->obj.getPointer()) | |
| 50 | + { | |
| 51 | + this->obj = 0; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + else | |
| 55 | + { | |
| 56 | + QPDFObject::ObjAccessor::releaseResolved(this->obj.getPointer()); | |
| 57 | + } | |
| 58 | +} | |
| 59 | + | |
| 44 | 60 | bool |
| 45 | 61 | QPDFObjectHandle::isInitialized() const |
| 46 | 62 | { | ... | ... |
libqpdf/QPDF_Array.cc
| ... | ... | @@ -10,6 +10,16 @@ QPDF_Array::~QPDF_Array() |
| 10 | 10 | { |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | +void | |
| 14 | +QPDF_Array::releaseResolved() | |
| 15 | +{ | |
| 16 | + for (std::vector<QPDFObjectHandle>::iterator iter = this->items.begin(); | |
| 17 | + iter != this->items.end(); ++iter) | |
| 18 | + { | |
| 19 | + QPDFObjectHandle::ReleaseResolver::releaseResolved(*iter); | |
| 20 | + } | |
| 21 | +} | |
| 22 | + | |
| 13 | 23 | std::string |
| 14 | 24 | QPDF_Array::unparse() |
| 15 | 25 | { | ... | ... |
libqpdf/QPDF_Dictionary.cc
| ... | ... | @@ -13,6 +13,17 @@ QPDF_Dictionary::~QPDF_Dictionary() |
| 13 | 13 | { |
| 14 | 14 | } |
| 15 | 15 | |
| 16 | +void | |
| 17 | +QPDF_Dictionary::releaseResolved() | |
| 18 | +{ | |
| 19 | + for (std::map<std::string, QPDFObjectHandle>::iterator iter = | |
| 20 | + this->items.begin(); | |
| 21 | + iter != this->items.end(); ++iter) | |
| 22 | + { | |
| 23 | + QPDFObjectHandle::ReleaseResolver::releaseResolved((*iter).second); | |
| 24 | + } | |
| 25 | +} | |
| 26 | + | |
| 16 | 27 | std::string |
| 17 | 28 | QPDF_Dictionary::unparse() |
| 18 | 29 | { | ... | ... |
libqpdf/qpdf/QPDF_Array.hh
libqpdf/qpdf/QPDF_Dictionary.hh
| ... | ... | @@ -27,6 +27,9 @@ class QPDF_Dictionary: public QPDFObject |
| 27 | 27 | // Remove key, doing nothing if key does not exist |
| 28 | 28 | void removeKey(std::string const& key); |
| 29 | 29 | |
| 30 | + protected: | |
| 31 | + virtual void releaseResolved(); | |
| 32 | + | |
| 30 | 33 | private: |
| 31 | 34 | std::map<std::string, QPDFObjectHandle> items; |
| 32 | 35 | }; | ... | ... |