summaryrefslogtreecommitdiffstats
path: root/layout/svg/AutoReferenceLimiter.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/svg/AutoReferenceLimiter.h')
-rw-r--r--layout/svg/AutoReferenceLimiter.h127
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