зеркало из https://github.com/mozilla/gecko-dev.git
175 строки
6.4 KiB
C++
175 строки
6.4 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef NS_AUTOREFERENCELIMITER_H
|
|
#define NS_AUTOREFERENCELIMITER_H
|
|
|
|
#include "Element.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/ReentrancyGuard.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "nsDebug.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsIFrame.h"
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
* This helper class helps us to protect against two related issues that can
|
|
* occur in SVG content: reference loops, and reference chains that we deem to
|
|
* be too long.
|
|
*
|
|
* Some SVG effects can reference another effect of the same type to produce
|
|
* a chain of effects to be applied (e.g. clipPath), while in other cases it is
|
|
* possible that while processing an effect of a certain type another effect
|
|
* of the same type may be encountered indirectly (e.g. pattern). In order to
|
|
* avoid stack overflow crashes and performance issues we need to impose an
|
|
* arbitrary limit on the length of the reference chains that SVG content may
|
|
* try to create. (Some SVG authoring tools have been known to create absurdly
|
|
* long reference chains. For example, bug 1253590 details a case where Adobe
|
|
* Illustrator was used to created an SVG with a chain of 5000 clip paths which
|
|
* could cause us to run out of stack space and crash.)
|
|
*
|
|
* This class is intended to be used with the nsIFrame's of SVG effects that
|
|
* may involve reference chains. To use it add a boolean member, something
|
|
* like this:
|
|
*
|
|
* // Flag used to indicate whether a methods that may reenter due to
|
|
* // following a reference to another instance is currently executing.
|
|
* bool mIsBeingProcessed;
|
|
*
|
|
* Make sure to initialize the member to false in the class' constructons.
|
|
*
|
|
* Then add the following to the top of any methods that may be reentered due
|
|
* to following a reference:
|
|
*
|
|
* static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
|
|
*
|
|
* AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
|
|
* &sRefChainLengthCounter);
|
|
* if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
|
|
* return; // Break reference chain
|
|
* }
|
|
*
|
|
* Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
|
|
* by the frame except when it initialize them as indicated above.
|
|
*/
|
|
class MOZ_RAII AutoReferenceChainGuard {
|
|
static const int16_t sDefaultMaxChainLength = 10; // arbitrary length
|
|
|
|
public:
|
|
static const int16_t noChain = -2;
|
|
|
|
/**
|
|
* @param aFrame The frame for an effect that may involve a reference chain.
|
|
* @param aFrameInUse The member variable on aFrame that is used to indicate
|
|
* whether one of aFrame's methods that may involve following a reference
|
|
* to another effect of the same type is currently being executed.
|
|
* @param aChainCounter A static variable in the method in which this class
|
|
* is instantiated that is used to keep track of how many times the method
|
|
* is reentered (and thus how long the a reference chain is).
|
|
* @param aMaxChainLength The maximum number of links that are allowed in
|
|
* a reference chain.
|
|
*/
|
|
AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse,
|
|
int16_t* aChainCounter,
|
|
int16_t aMaxChainLength = sDefaultMaxChainLength
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
|
: mFrame(aFrame),
|
|
mFrameInUse(aFrameInUse),
|
|
mChainCounter(aChainCounter),
|
|
mMaxChainLength(aMaxChainLength),
|
|
mBrokeReference(false) {
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
|
MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
|
|
MOZ_ASSERT(aMaxChainLength > 0);
|
|
MOZ_ASSERT(*aChainCounter == noChain ||
|
|
(*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
|
|
}
|
|
|
|
~AutoReferenceChainGuard() {
|
|
if (mBrokeReference) {
|
|
// We didn't change mFrameInUse or mChainCounter
|
|
return;
|
|
}
|
|
|
|
*mFrameInUse = false;
|
|
|
|
// If we fail this assert then there were more destructor calls than
|
|
// Reference() calls (a consumer forgot to to call Reference()), or else
|
|
// someone messed with the variable pointed to by mChainCounter.
|
|
MOZ_ASSERT(*mChainCounter < mMaxChainLength);
|
|
|
|
(*mChainCounter)++;
|
|
|
|
if (*mChainCounter == mMaxChainLength) {
|
|
*mChainCounter = noChain; // reset ready for use next time
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true on success (no reference loop/reference chain length is
|
|
* within the specified limits), else returns false on failure (there is a
|
|
* reference loop/the reference chain has exceeded the specified limits).
|
|
* If it returns false then an error message will be reported to the DevTools
|
|
* console (only once).
|
|
*/
|
|
MOZ_MUST_USE bool Reference() {
|
|
if (MOZ_UNLIKELY(*mFrameInUse)) {
|
|
mBrokeReference = true;
|
|
ReportErrorToConsole();
|
|
return false;
|
|
}
|
|
|
|
if (*mChainCounter == noChain) {
|
|
// Initialize - we start at aMaxChainLength and decrement towards zero.
|
|
*mChainCounter = mMaxChainLength;
|
|
} else {
|
|
// If we fail this assertion then either a consumer failed to break a
|
|
// reference loop/chain, or else they called Reference() more than once
|
|
MOZ_ASSERT(*mChainCounter >= 0);
|
|
|
|
if (MOZ_UNLIKELY(*mChainCounter < 1)) {
|
|
mBrokeReference = true;
|
|
ReportErrorToConsole();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We only set these once we know we're returning true.
|
|
*mFrameInUse = true;
|
|
(*mChainCounter)--;
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
void ReportErrorToConsole() {
|
|
nsAutoString tag, id;
|
|
dom::Element* element = mFrame->GetContent()->AsElement();
|
|
element->GetTagName(tag);
|
|
element->GetId(id);
|
|
const char16_t* params[] = {tag.get(), id.get()};
|
|
auto doc = mFrame->GetContent()->OwnerDoc();
|
|
auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop
|
|
: dom::Document::eSVGRefChainLengthExceeded;
|
|
doc->WarnOnceAbout(warning, /* asError */ true, params,
|
|
ArrayLength(params));
|
|
}
|
|
|
|
nsIFrame* mFrame;
|
|
bool* mFrameInUse;
|
|
int16_t* mChainCounter;
|
|
const int16_t mMaxChainLength;
|
|
bool mBrokeReference;
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // NS_AUTOREFERENCELIMITER_H
|