Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef NS_AUTOREFERENCELIMITER_H
7 : #define NS_AUTOREFERENCELIMITER_H
8 :
9 : #include "Element.h"
10 : #include "mozilla/Assertions.h"
11 : #include "mozilla/Attributes.h"
12 : #include "mozilla/ReentrancyGuard.h"
13 : #include "mozilla/Likely.h"
14 : #include "nsDebug.h"
15 : #include "nsIDocument.h"
16 : #include "nsIFrame.h"
17 :
18 : namespace mozilla {
19 :
20 : /**
21 : * This helper class helps us to protect against two related issues that can
22 : * occur in SVG content: reference loops, and reference chains that we deem to
23 : * be too long.
24 : *
25 : * Some SVG effects can reference another effect of the same type to produce
26 : * a chain of effects to be applied (e.g. clipPath), while in other cases it is
27 : * possible that while processing an effect of a certain type another effect
28 : * of the same type may be encountered indirectly (e.g. pattern). In order to
29 : * avoid stack overflow crashes and performance issues we need to impose an
30 : * arbitrary limit on the length of the reference chains that SVG content may
31 : * try to create. (Some SVG authoring tools have been known to create absurdly
32 : * long reference chains. For example, bug 1253590 details a case where Adobe
33 : * Illustrator was used to created an SVG with a chain of 5000 clip paths which
34 : * could cause us to run out of stack space and crash.)
35 : *
36 : * This class is intended to be used with the nsIFrame's of SVG effects that
37 : * may involve reference chains. To use it add a boolean member, something
38 : * like this:
39 : *
40 : * // Flag used to indicate whether a methods that may reenter due to
41 : * // following a reference to another instance is currently executing.
42 : * bool mIsBeingProcessed;
43 : *
44 : * Make sure to initialize the member to false in the class' constructons.
45 : *
46 : * Then add the following to the top of any methods that may be reentered due
47 : * to following a reference:
48 : *
49 : * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
50 : *
51 : * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
52 : * &sRefChainLengthCounter);
53 : * if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
54 : * return; // Break reference chain
55 : * }
56 : *
57 : * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
58 : * by the frame except when it initialize them as indicated above.
59 : */
60 : class MOZ_RAII AutoReferenceChainGuard
61 : {
62 : static const int16_t sDefaultMaxChainLength = 10; // arbitrary length
63 :
64 : public:
65 : static const int16_t noChain = -2;
66 :
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.
77 : */
78 2 : AutoReferenceChainGuard(nsIFrame* aFrame,
79 : bool* aFrameInUse,
80 : int16_t* aChainCounter,
81 : int16_t aMaxChainLength = sDefaultMaxChainLength
82 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
83 2 : : mFrame(aFrame)
84 : , mFrameInUse(aFrameInUse)
85 2 : , mMaxChainLength(aMaxChainLength)
86 : {
87 2 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
88 2 : MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
89 2 : MOZ_ASSERT(!(*mFrameInUse), "Undetected reference loop!");
90 2 : MOZ_ASSERT(aMaxChainLength > 0);
91 2 : MOZ_ASSERT(*aChainCounter == noChain ||
92 : (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
93 :
94 2 : if (*aChainCounter == noChain) {
95 : // Initialize - we start at aMaxChainLength and decrement towards zero.
96 2 : *aChainCounter = aMaxChainLength;
97 : }
98 2 : mChainCounter = aChainCounter;
99 2 : }
100 :
101 4 : ~AutoReferenceChainGuard() {
102 2 : *mFrameInUse = false;
103 :
104 : // If we fail this assert then there were more destructor calls than
105 : // Reference() calls (a consumer forgot to to call Reference()), or else
106 : // someone messed with the variable pointed to by mChainCounter.
107 2 : MOZ_ASSERT(*mChainCounter < mMaxChainLength);
108 :
109 2 : (*mChainCounter)++;
110 :
111 2 : if (*mChainCounter == mMaxChainLength) {
112 2 : *mChainCounter = noChain; // reset ready for use next time
113 : }
114 2 : }
115 :
116 : /**
117 : * Returns true on success (no reference loop/reference chain length is
118 : * within the specified limits), else returns false on failure (there is a
119 : * reference loop/the reference chain has exceeded the specified limits).
120 : * If it returns false then an error message will be reported to the DevTools
121 : * console (only once).
122 : */
123 2 : MOZ_MUST_USE bool Reference() {
124 2 : if (MOZ_UNLIKELY(*mFrameInUse)) {
125 0 : ReportErrorToConsole();
126 0 : return false;
127 : }
128 :
129 : // If we fail this assertion then either a consumer failed to break a
130 : // reference loop/chain, or else they called Reference() more than once
131 2 : MOZ_ASSERT(*mChainCounter >= 0);
132 :
133 2 : (*mChainCounter)--;
134 :
135 2 : if (MOZ_UNLIKELY(*mChainCounter < 0)) {
136 0 : ReportErrorToConsole();
137 0 : return false;
138 : }
139 2 : return true;
140 : }
141 :
142 : private:
143 0 : void ReportErrorToConsole() {
144 0 : nsAutoString tag;
145 0 : mFrame->GetContent()->AsElement()->GetTagName(tag);
146 0 : const char16_t* params[] = { tag.get() };
147 0 : auto doc = mFrame->GetContent()->OwnerDoc();
148 0 : auto warning = *mFrameInUse ?
149 : nsIDocument::eSVGReferenceLoop :
150 0 : nsIDocument::eSVGReferenceChainLengthExceeded;
151 0 : doc->WarnOnceAbout(warning, /* asError */ true,
152 0 : params, ArrayLength(params));
153 0 : }
154 :
155 : nsIFrame* mFrame;
156 : bool* mFrameInUse;
157 : int16_t* mChainCounter;
158 : const int16_t mMaxChainLength;
159 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
160 : };
161 :
162 : } // namespace mozilla
163 :
164 : #endif // NS_AUTOREFERENCELIMITER_H
|