/* -*- 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