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 | 2010-04-25 Jay Berkenbilt <ejb@ql.org> | 7 | 2010-04-25 Jay Berkenbilt <ejb@ql.org> |
| 2 | 8 | ||
| 3 | * 2.1.5: release | 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 | Next | 1 | Next |
| 46 | ==== | 2 | ==== |
| 47 | 3 |
include/qpdf/QPDFObject.hh
| @@ -12,11 +12,33 @@ | @@ -12,11 +12,33 @@ | ||
| 12 | 12 | ||
| 13 | #include <string> | 13 | #include <string> |
| 14 | 14 | ||
| 15 | +class QPDF; | ||
| 16 | +class QPDFObjectHandle; | ||
| 17 | + | ||
| 15 | class QPDFObject | 18 | class QPDFObject |
| 16 | { | 19 | { |
| 17 | public: | 20 | public: |
| 18 | virtual ~QPDFObject() {} | 21 | virtual ~QPDFObject() {} |
| 19 | virtual std::string unparse() = 0; | 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 | #endif // __QPDFOBJECT_HH__ | 44 | #endif // __QPDFOBJECT_HH__ |
include/qpdf/QPDFObjectHandle.hh
| @@ -22,6 +22,8 @@ | @@ -22,6 +22,8 @@ | ||
| 22 | 22 | ||
| 23 | class Pipeline; | 23 | class Pipeline; |
| 24 | class QPDF; | 24 | class QPDF; |
| 25 | +class QPDF_Dictionary; | ||
| 26 | +class QPDF_Array; | ||
| 25 | 27 | ||
| 26 | class QPDFObjectHandle | 28 | class QPDFObjectHandle |
| 27 | { | 29 | { |
| @@ -247,6 +249,20 @@ class QPDFObjectHandle | @@ -247,6 +249,20 @@ class QPDFObjectHandle | ||
| 247 | }; | 249 | }; |
| 248 | friend class ObjAccessor; | 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 | private: | 266 | private: |
| 251 | QPDFObjectHandle(QPDF*, int objid, int generation); | 267 | QPDFObjectHandle(QPDF*, int objid, int generation); |
| 252 | QPDFObjectHandle(QPDFObject*); | 268 | QPDFObjectHandle(QPDFObject*); |
| @@ -262,6 +278,7 @@ class QPDFObjectHandle | @@ -262,6 +278,7 @@ class QPDFObjectHandle | ||
| 262 | void assertPageObject(); | 278 | void assertPageObject(); |
| 263 | void dereference(); | 279 | void dereference(); |
| 264 | void makeDirectInternal(std::set<int>& visited); | 280 | void makeDirectInternal(std::set<int>& visited); |
| 281 | + void releaseResolved(); | ||
| 265 | 282 | ||
| 266 | bool initialized; | 283 | bool initialized; |
| 267 | 284 |
libqpdf/QPDF.cc
| @@ -277,6 +277,13 @@ QPDF::QPDF() : | @@ -277,6 +277,13 @@ QPDF::QPDF() : | ||
| 277 | 277 | ||
| 278 | QPDF::~QPDF() | 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 | void | 289 | void |
libqpdf/QPDFObjectHandle.cc
| @@ -41,6 +41,22 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) : | @@ -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 | bool | 60 | bool |
| 45 | QPDFObjectHandle::isInitialized() const | 61 | QPDFObjectHandle::isInitialized() const |
| 46 | { | 62 | { |
libqpdf/QPDF_Array.cc
| @@ -10,6 +10,16 @@ QPDF_Array::~QPDF_Array() | @@ -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 | std::string | 23 | std::string |
| 14 | QPDF_Array::unparse() | 24 | QPDF_Array::unparse() |
| 15 | { | 25 | { |
libqpdf/QPDF_Dictionary.cc
| @@ -13,6 +13,17 @@ QPDF_Dictionary::~QPDF_Dictionary() | @@ -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 | std::string | 27 | std::string |
| 17 | QPDF_Dictionary::unparse() | 28 | QPDF_Dictionary::unparse() |
| 18 | { | 29 | { |
libqpdf/qpdf/QPDF_Array.hh
| @@ -16,6 +16,9 @@ class QPDF_Array: public QPDFObject | @@ -16,6 +16,9 @@ class QPDF_Array: public QPDFObject | ||
| 16 | QPDFObjectHandle getItem(int n) const; | 16 | QPDFObjectHandle getItem(int n) const; |
| 17 | void setItem(int, QPDFObjectHandle const&); | 17 | void setItem(int, QPDFObjectHandle const&); |
| 18 | 18 | ||
| 19 | + protected: | ||
| 20 | + virtual void releaseResolved(); | ||
| 21 | + | ||
| 19 | private: | 22 | private: |
| 20 | std::vector<QPDFObjectHandle> items; | 23 | std::vector<QPDFObjectHandle> items; |
| 21 | }; | 24 | }; |
libqpdf/qpdf/QPDF_Dictionary.hh
| @@ -27,6 +27,9 @@ class QPDF_Dictionary: public QPDFObject | @@ -27,6 +27,9 @@ class QPDF_Dictionary: public QPDFObject | ||
| 27 | // Remove key, doing nothing if key does not exist | 27 | // Remove key, doing nothing if key does not exist |
| 28 | void removeKey(std::string const& key); | 28 | void removeKey(std::string const& key); |
| 29 | 29 | ||
| 30 | + protected: | ||
| 31 | + virtual void releaseResolved(); | ||
| 32 | + | ||
| 30 | private: | 33 | private: |
| 31 | std::map<std::string, QPDFObjectHandle> items; | 34 | std::map<std::string, QPDFObjectHandle> items; |
| 32 | }; | 35 | }; |