AutoReferenceChainGuard.h (6383B)
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 LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_ 8 #define LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_ 9 10 #include "Element.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/Attributes.h" 13 #include "mozilla/Likely.h" 14 #include "mozilla/dom/Document.h" 15 #include "nsDebug.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 static const int16_t sDefaultMaxChainLength = 10; // arbitrary length 62 63 public: 64 static const int16_t noChain = -2; 65 66 /** 67 * @param aFrame The frame for an effect that may involve a reference chain. 68 * @param aFrameInUse The member variable on aFrame that is used to indicate 69 * whether one of aFrame's methods that may involve following a reference 70 * to another effect of the same type is currently being executed. 71 * @param aChainCounter A static variable in the method in which this class 72 * is instantiated that is used to keep track of how many times the method 73 * is reentered (and thus how long the a reference chain is). 74 * @param aMaxChainLength The maximum number of links that are allowed in 75 * a reference chain. 76 */ 77 AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse, 78 int16_t* aChainCounter, 79 int16_t aMaxChainLength = sDefaultMaxChainLength) 80 : mFrame(aFrame), 81 mFrameInUse(aFrameInUse), 82 mChainCounter(aChainCounter), 83 mMaxChainLength(aMaxChainLength), 84 mBrokeReference(false) { 85 MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter); 86 MOZ_ASSERT(aMaxChainLength > 0); 87 MOZ_ASSERT(*aChainCounter == noChain || 88 (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength)); 89 } 90 91 ~AutoReferenceChainGuard() { 92 if (mBrokeReference) { 93 // We didn't change mFrameInUse or mChainCounter 94 return; 95 } 96 97 *mFrameInUse = false; 98 99 // If we fail this assert then there were more destructor calls than 100 // Reference() calls (a consumer forgot to to call Reference()), or else 101 // someone messed with the variable pointed to by mChainCounter. 102 MOZ_ASSERT(*mChainCounter < mMaxChainLength); 103 104 (*mChainCounter)++; 105 106 if (*mChainCounter == mMaxChainLength) { 107 *mChainCounter = noChain; // reset ready for use next time 108 } 109 } 110 111 /** 112 * Returns true on success (no reference loop/reference chain length is 113 * within the specified limits), else returns false on failure (there is a 114 * reference loop/the reference chain has exceeded the specified limits). 115 * If it returns false then an error message will be reported to the DevTools 116 * console (only once). 117 */ 118 [[nodiscard]] bool Reference() { 119 if (MOZ_UNLIKELY(*mFrameInUse)) { 120 mBrokeReference = true; 121 ReportErrorToConsole(); 122 return false; 123 } 124 125 if (*mChainCounter == noChain) { 126 // Initialize - we start at aMaxChainLength and decrement towards zero. 127 *mChainCounter = mMaxChainLength; 128 } else { 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 MOZ_ASSERT(*mChainCounter >= 0); 132 133 if (MOZ_UNLIKELY(*mChainCounter < 1)) { 134 mBrokeReference = true; 135 ReportErrorToConsole(); 136 return false; 137 } 138 } 139 140 // We only set these once we know we're returning true. 141 *mFrameInUse = true; 142 (*mChainCounter)--; 143 144 return true; 145 } 146 147 private: 148 void ReportErrorToConsole() { 149 AutoTArray<nsString, 2> params; 150 dom::Element* element = mFrame->GetContent()->AsElement(); 151 element->GetTagName(*params.AppendElement()); 152 element->GetId(*params.AppendElement()); 153 auto doc = mFrame->GetContent()->OwnerDoc(); 154 auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop 155 : dom::Document::eSVGRefChainLengthExceeded; 156 doc->WarnOnceAbout(warning, /* asError */ true, params); 157 } 158 159 nsIFrame* mFrame; 160 bool* mFrameInUse; 161 int16_t* mChainCounter; 162 const int16_t mMaxChainLength; 163 bool mBrokeReference; 164 }; 165 166 } // namespace mozilla 167 168 #endif // LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_