diff options
Diffstat (limited to 'layout/svg/AutoReferenceLimiter.h')
-rw-r--r-- | layout/svg/AutoReferenceLimiter.h | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceLimiter.h b/layout/svg/AutoReferenceLimiter.h new file mode 100644 index 000000000..5f822ba13 --- /dev/null +++ b/layout/svg/AutoReferenceLimiter.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_AUTOREFERENCELIMITER_H +#define NS_AUTOREFERENCELIMITER_H + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ReentrancyGuard.h" +#include "nsDebug.h" + +namespace mozilla { + +/** + * This helper allows us to handle two related issues in SVG content: reference + * loops, and reference chains that we deem to be too long. + * + * SVG content may contain reference loops where an SVG effect (a clipPath, + * say) may reference itself either directly or, perhaps more likely, + * indirectly via a reference chain to other elements that eventually leads + * back to itself. This helper class allows us to detect and immediately break + * such reference loops when applying an effect so that we can prevent + * reference loops causing us to recurse until we run out of stack space and + * crash. + * + * SVG also allows for (non-loop) reference chains of arbitrary length, the + * length depending entirely on the SVG content. Some SVG authoring tools have + * been known to create absurdly long reference chains. (For example, bug + * 1253590 details a case where Adobe Illustrator created an SVG with a chain + * of 5000 clip paths which could cause us to run out of stack space and + * crash.) This helper class also allows us to limit the number of times we + * recurse into a function, thereby allowing us to limit the length ofreference + * chains. + * + * Consumers that need to handle the reference loop case should add a member + * variable (mReferencing, say) to the class that represents and applies the + * SVG effect in question (typically an nsIFrame sub-class), initialize that + * member to AutoReferenceLimiter::notReferencing in the class' constructor + * (and never touch that variable again), and then add something like the + * following at the top of the method(s) that may recurse to follow references + * when applying an effect: + * + * AutoReferenceLimiter refLoopDetector(&mInUse, 1); // only one ref allowed + * if (!refLoopDetector.Reference()) { + * return; // reference loop + * } + * + * Consumers that need to limit reference chain lengths should add something + * like the following code at the top of the method(s) that may recurse to + * follow references when applying an effect: + * + * static int16_t sChainLengthCounter = AutoReferenceLimiter::notReferencing; + * + * AutoReferenceLimiter refChainLengthLimiter(&sChainLengthCounter, MAX_LEN); + * if (!refChainLengthLimiter.Reference()) { + * return; // reference chain too long + * } + */ +class MOZ_RAII AutoReferenceLimiter +{ +public: + static const int16_t notReferencing = -2; + + AutoReferenceLimiter(int16_t* aRefCounter, int16_t aMaxReferenceCount) + { + MOZ_ASSERT(aMaxReferenceCount > 0 && + aRefCounter && + (*aRefCounter == notReferencing || + (*aRefCounter >= 0 && *aRefCounter < aMaxReferenceCount))); + + if (*aRefCounter == notReferencing) { + // initialize + *aRefCounter = aMaxReferenceCount; + } + mRefCounter = aRefCounter; + mMaxReferenceCount = aMaxReferenceCount; + } + + ~AutoReferenceLimiter() { + // If we fail this assert then there were more destructor calls than + // Reference() calls (a consumer forgot to to call Reference()), or else + // someone messed with the variable pointed to by mRefCounter. + MOZ_ASSERT(*mRefCounter < mMaxReferenceCount); + + (*mRefCounter)++; + + if (*mRefCounter == mMaxReferenceCount) { + *mRefCounter = notReferencing; // reset ready for use next time + } + } + + /** + * Returns true on success (no reference loop/reference chain length is + * within the specified limits), else returns false on failure (there is a + * reference loop/the reference chain has exceeded the specified limits). + */ + MOZ_MUST_USE bool Reference() { + // If we fail this assertion then either a consumer failed to break a + // reference loop/chain, or else they called Reference() more than once + MOZ_ASSERT(*mRefCounter >= 0); + + (*mRefCounter)--; + + if (*mRefCounter < 0) { + // TODO: This is an issue with the document, not with Mozilla code. We + // should stop using NS_WARNING and send a message to the console + // instead (but only once per document, not over and over as we repaint). + if (mMaxReferenceCount == 1) { + NS_WARNING("Reference loop detected!"); + } else { + NS_WARNING("Reference chain length limit exceeded!"); + } + return false; + } + return true; + } + +private: + int16_t* mRefCounter; + int16_t mMaxReferenceCount; +}; + +} // namespace mozilla + +#endif // NS_AUTOREFERENCELIMITER_H |