LCOV - code coverage report
Current view: top level - layout/svg - AutoReferenceChainGuard.h (source / functions) Hit Total Coverage
Test: output.info Lines: 25 39 64.1 %
Date: 2017-07-14 16:53:18 Functions: 3 4 75.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13