Coverage Report

Created: 2018-09-25 14:53

/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