/src/mozilla-central/layout/svg/AutoReferenceChainGuard.h
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #ifndef NS_AUTOREFERENCELIMITER_H |
8 | | #define NS_AUTOREFERENCELIMITER_H |
9 | | |
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 "nsIDocument.h" |
17 | | #include "nsIFrame.h" |
18 | | |
19 | | namespace mozilla { |
20 | | |
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. |
25 | | * |
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.) |
36 | | * |
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: |
40 | | * |
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; |
44 | | * |
45 | | * Make sure to initialize the member to false in the class' constructons. |
46 | | * |
47 | | * Then add the following to the top of any methods that may be reentered due |
48 | | * to following a reference: |
49 | | * |
50 | | * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; |
51 | | * |
52 | | * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, |
53 | | * &sRefChainLengthCounter); |
54 | | * if (MOZ_UNLIKELY(!refChainGuard.Reference())) { |
55 | | * return; // Break reference chain |
56 | | * } |
57 | | * |
58 | | * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used |
59 | | * by the frame except when it initialize them as indicated above. |
60 | | */ |
61 | | class MOZ_RAII AutoReferenceChainGuard |
62 | | { |
63 | | static const int16_t sDefaultMaxChainLength = 10; // arbitrary length |
64 | | |
65 | | public: |
66 | | static const int16_t noChain = -2; |
67 | | |
68 | | /** |
69 | | * @param aFrame The frame for an effect that may involve a reference chain. |
70 | | * @param aFrameInUse The member variable on aFrame that is used to indicate |
71 | | * whether one of aFrame's methods that may involve following a reference |
72 | | * to another effect of the same type is currently being executed. |
73 | | * @param aChainCounter A static variable in the method in which this class |
74 | | * is instantiated that is used to keep track of how many times the method |
75 | | * is reentered (and thus how long the a reference chain is). |
76 | | * @param aMaxChainLength The maximum number of links that are allowed in |
77 | | * a reference chain. |
78 | | */ |
79 | | AutoReferenceChainGuard(nsIFrame* aFrame, |
80 | | bool* aFrameInUse, |
81 | | int16_t* aChainCounter, |
82 | | int16_t aMaxChainLength = sDefaultMaxChainLength |
83 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
84 | | : mFrame(aFrame) |
85 | | , mFrameInUse(aFrameInUse) |
86 | | , mChainCounter(aChainCounter) |
87 | | , mMaxChainLength(aMaxChainLength) |
88 | | , mBrokeReference(false) |
89 | 0 | { |
90 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
91 | 0 | MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter); |
92 | 0 | MOZ_ASSERT(aMaxChainLength > 0); |
93 | 0 | MOZ_ASSERT(*aChainCounter == noChain || |
94 | 0 | (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength)); |
95 | 0 | } |
96 | | |
97 | 0 | ~AutoReferenceChainGuard() { |
98 | 0 | if (mBrokeReference) { |
99 | 0 | // We didn't change mFrameInUse or mChainCounter |
100 | 0 | return; |
101 | 0 | } |
102 | 0 | |
103 | 0 | *mFrameInUse = false; |
104 | 0 |
|
105 | 0 | // If we fail this assert then there were more destructor calls than |
106 | 0 | // Reference() calls (a consumer forgot to to call Reference()), or else |
107 | 0 | // someone messed with the variable pointed to by mChainCounter. |
108 | 0 | MOZ_ASSERT(*mChainCounter < mMaxChainLength); |
109 | 0 |
|
110 | 0 | (*mChainCounter)++; |
111 | 0 |
|
112 | 0 | if (*mChainCounter == mMaxChainLength) { |
113 | 0 | *mChainCounter = noChain; // reset ready for use next time |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | /** |
118 | | * Returns true on success (no reference loop/reference chain length is |
119 | | * within the specified limits), else returns false on failure (there is a |
120 | | * reference loop/the reference chain has exceeded the specified limits). |
121 | | * If it returns false then an error message will be reported to the DevTools |
122 | | * console (only once). |
123 | | */ |
124 | 0 | MOZ_MUST_USE bool Reference() { |
125 | 0 | if (MOZ_UNLIKELY(*mFrameInUse)) { |
126 | 0 | mBrokeReference = true; |
127 | 0 | ReportErrorToConsole(); |
128 | 0 | return false; |
129 | 0 | } |
130 | 0 | |
131 | 0 | if (*mChainCounter == noChain) { |
132 | 0 | // Initialize - we start at aMaxChainLength and decrement towards zero. |
133 | 0 | *mChainCounter = mMaxChainLength; |
134 | 0 | } else { |
135 | 0 | // If we fail this assertion then either a consumer failed to break a |
136 | 0 | // reference loop/chain, or else they called Reference() more than once |
137 | 0 | MOZ_ASSERT(*mChainCounter >= 0); |
138 | 0 |
|
139 | 0 | if (MOZ_UNLIKELY(*mChainCounter < 1)) { |
140 | 0 | mBrokeReference = true; |
141 | 0 | ReportErrorToConsole(); |
142 | 0 | return false; |
143 | 0 | } |
144 | 0 | } |
145 | 0 | |
146 | 0 | // We only set these once we know we're returning true. |
147 | 0 | *mFrameInUse = true; |
148 | 0 | (*mChainCounter)--; |
149 | 0 |
|
150 | 0 | return true; |
151 | 0 | } |
152 | | |
153 | | private: |
154 | 0 | void ReportErrorToConsole() { |
155 | 0 | nsAutoString tag, id; |
156 | 0 | dom::Element* element = mFrame->GetContent()->AsElement(); |
157 | 0 | element->GetTagName(tag); |
158 | 0 | element->GetId(id); |
159 | 0 | const char16_t* params[] = { tag.get(), id.get() }; |
160 | 0 | auto doc = mFrame->GetContent()->OwnerDoc(); |
161 | 0 | auto warning = *mFrameInUse ? |
162 | 0 | nsIDocument::eSVGRefLoop : |
163 | 0 | nsIDocument::eSVGRefChainLengthExceeded; |
164 | 0 | doc->WarnOnceAbout(warning, /* asError */ true, |
165 | 0 | params, ArrayLength(params)); |
166 | 0 | } |
167 | | |
168 | | nsIFrame* mFrame; |
169 | | bool* mFrameInUse; |
170 | | int16_t* mChainCounter; |
171 | | const int16_t mMaxChainLength; |
172 | | bool mBrokeReference; |
173 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
174 | | }; |
175 | | |
176 | | } // namespace mozilla |
177 | | |
178 | | #endif // NS_AUTOREFERENCELIMITER_H |