Bug 1568157 - Part 5: Move the NodePicker initialization into a getter. r=yulia
[gecko.git] / layout / svg / AutoReferenceChainGuard.h
blobc362f58247ce5800d20beb6f8ca7aa1298a3aaab
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef NS_AUTOREFERENCELIMITER_H
8 #define NS_AUTOREFERENCELIMITER_H
10 #include "Element.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/ReentrancyGuard.h"
14 #include "mozilla/Likely.h"
15 #include "nsDebug.h"
16 #include "mozilla/dom/Document.h"
17 #include "nsIFrame.h"
19 namespace mozilla {
21 /**
22 * This helper class helps us to protect against two related issues that can
23 * occur in SVG content: reference loops, and reference chains that we deem to
24 * be too long.
26 * Some SVG effects can reference another effect of the same type to produce
27 * a chain of effects to be applied (e.g. clipPath), while in other cases it is
28 * possible that while processing an effect of a certain type another effect
29 * of the same type may be encountered indirectly (e.g. pattern). In order to
30 * avoid stack overflow crashes and performance issues we need to impose an
31 * arbitrary limit on the length of the reference chains that SVG content may
32 * try to create. (Some SVG authoring tools have been known to create absurdly
33 * long reference chains. For example, bug 1253590 details a case where Adobe
34 * Illustrator was used to created an SVG with a chain of 5000 clip paths which
35 * could cause us to run out of stack space and crash.)
37 * This class is intended to be used with the nsIFrame's of SVG effects that
38 * may involve reference chains. To use it add a boolean member, something
39 * like this:
41 * // Flag used to indicate whether a methods that may reenter due to
42 * // following a reference to another instance is currently executing.
43 * bool mIsBeingProcessed;
45 * Make sure to initialize the member to false in the class' constructons.
47 * Then add the following to the top of any methods that may be reentered due
48 * to following a reference:
50 * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
52 * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
53 * &sRefChainLengthCounter);
54 * if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
55 * return; // Break reference chain
56 * }
58 * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
59 * by the frame except when it initialize them as indicated above.
61 class MOZ_RAII AutoReferenceChainGuard {
62 static const int16_t sDefaultMaxChainLength = 10; // arbitrary length
64 public:
65 static const int16_t noChain = -2;
67 /**
68 * @param aFrame The frame for an effect that may involve a reference chain.
69 * @param aFrameInUse The member variable on aFrame that is used to indicate
70 * whether one of aFrame's methods that may involve following a reference
71 * to another effect of the same type is currently being executed.
72 * @param aChainCounter A static variable in the method in which this class
73 * is instantiated that is used to keep track of how many times the method
74 * is reentered (and thus how long the a reference chain is).
75 * @param aMaxChainLength The maximum number of links that are allowed in
76 * a reference chain.
78 AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse,
79 int16_t* aChainCounter,
80 int16_t aMaxChainLength = sDefaultMaxChainLength
81 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
82 : mFrame(aFrame),
83 mFrameInUse(aFrameInUse),
84 mChainCounter(aChainCounter),
85 mMaxChainLength(aMaxChainLength),
86 mBrokeReference(false) {
87 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
88 MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
89 MOZ_ASSERT(aMaxChainLength > 0);
90 MOZ_ASSERT(*aChainCounter == noChain ||
91 (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
94 ~AutoReferenceChainGuard() {
95 if (mBrokeReference) {
96 // We didn't change mFrameInUse or mChainCounter
97 return;
100 *mFrameInUse = false;
102 // If we fail this assert then there were more destructor calls than
103 // Reference() calls (a consumer forgot to to call Reference()), or else
104 // someone messed with the variable pointed to by mChainCounter.
105 MOZ_ASSERT(*mChainCounter < mMaxChainLength);
107 (*mChainCounter)++;
109 if (*mChainCounter == mMaxChainLength) {
110 *mChainCounter = noChain; // reset ready for use next time
115 * Returns true on success (no reference loop/reference chain length is
116 * within the specified limits), else returns false on failure (there is a
117 * reference loop/the reference chain has exceeded the specified limits).
118 * If it returns false then an error message will be reported to the DevTools
119 * console (only once).
121 MOZ_MUST_USE bool Reference() {
122 if (MOZ_UNLIKELY(*mFrameInUse)) {
123 mBrokeReference = true;
124 ReportErrorToConsole();
125 return false;
128 if (*mChainCounter == noChain) {
129 // Initialize - we start at aMaxChainLength and decrement towards zero.
130 *mChainCounter = mMaxChainLength;
131 } else {
132 // If we fail this assertion then either a consumer failed to break a
133 // reference loop/chain, or else they called Reference() more than once
134 MOZ_ASSERT(*mChainCounter >= 0);
136 if (MOZ_UNLIKELY(*mChainCounter < 1)) {
137 mBrokeReference = true;
138 ReportErrorToConsole();
139 return false;
143 // We only set these once we know we're returning true.
144 *mFrameInUse = true;
145 (*mChainCounter)--;
147 return true;
150 private:
151 void ReportErrorToConsole() {
152 AutoTArray<nsString, 2> params;
153 dom::Element* element = mFrame->GetContent()->AsElement();
154 element->GetTagName(*params.AppendElement());
155 element->GetId(*params.AppendElement());
156 auto doc = mFrame->GetContent()->OwnerDoc();
157 auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop
158 : dom::Document::eSVGRefChainLengthExceeded;
159 doc->WarnOnceAbout(warning, /* asError */ true, params);
162 nsIFrame* mFrame;
163 bool* mFrameInUse;
164 int16_t* mChainCounter;
165 const int16_t mMaxChainLength;
166 bool mBrokeReference;
167 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
170 } // namespace mozilla
172 #endif // NS_AUTOREFERENCELIMITER_H