Files
tubestation/dom/base/AttrArray.cpp
serge-sans-paille 4026d176c2 Bug 1930826 - Fix memcpy usage to meet standard requirements r=jgilbert,necko-reviewers,xpcom-reviewers,glandium,karlt,valentin,nika,gsvelto,peterv
As mentioned in https://en.cppreference.com/w/cpp/string/byte/memcpy,
memcpy requires trivially copyable types. Same property holds for
memset.

GCC checks this, and clang will do in next release. In both cases it can
be silenced locally by casting the destination parameter to void*.

This patch introduces the following changes:

- AssignRangeAlgorithm was dispatching on the trivially constructable
  trait, not trivially copyable. Note that it is basically a
  reimplementation of std::uninitialized_copy_n, but using the later would
  by-pass some specialization of nsTArrayElementTraits<T>::Construct.

- ShBuiltInResources default constructor already performs a memset

- mCachedAnonymousContentStyleIndexes used to contain std::pair<uint8_t,
  uint8_t> which is not trivially copyable (!) at least under older
  libstdc++. Use a more explicit POD type instead.

- PodOperations.h was not checking type requirements, it now does and
  issue a compilation error in case of misuse.

- NetAddr copy and default constructors are already defined, so use them
  instead of plain memset/memcpy.

- NetAddr has a copy constructor from PSNetAddr*, use it instead of a
  plain memcpy.

- Do not create a temporary mozilla::phc::AddrInfo through a memcpy when
  what we actually want is a reintepret_cast. Still perform a memcpy on
  the bytes to enforce alignment.

Differential Revision: https://phabricator.services.mozilla.com/D231203
2024-12-12 07:08:39 +00:00

380 lines
10 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/. */
/*
* Storage of the children and attributes of a DOM node; storage for
* the two is unified to minimize footprint.
*/
#include "AttrArray.h"
#include "mozilla/AttributeStyles.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/ServoBindings.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsContentUtils.h" // nsAutoScriptBlocker
using mozilla::CheckedUint32;
AttrArray::Impl::~Impl() {
for (InternalAttr& attr : Attrs()) {
attr.~InternalAttr();
}
if (auto* decl = GetMappedDeclarationBlock()) {
Servo_DeclarationBlock_Release(decl);
mMappedAttributeBits = 0;
}
}
void AttrArray::SetMappedDeclarationBlock(
already_AddRefed<mozilla::StyleLockedDeclarationBlock> aBlock) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mImpl);
MOZ_ASSERT(IsPendingMappedAttributeEvaluation());
if (auto* decl = GetMappedDeclarationBlock()) {
Servo_DeclarationBlock_Release(decl);
}
mImpl->mMappedAttributeBits = reinterpret_cast<uintptr_t>(aBlock.take());
MOZ_ASSERT(!IsPendingMappedAttributeEvaluation());
}
const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName) const {
NS_ASSERTION(aLocalName, "Must have attr name");
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName)) {
return &attr.mValue;
}
}
return nullptr;
}
const nsAttrValue* AttrArray::GetAttr(const nsAtom* aLocalName,
int32_t aNamespaceID) const {
NS_ASSERTION(aLocalName, "Must have attr name");
NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown, "Must have namespace");
if (aNamespaceID == kNameSpaceID_None) {
// This should be the common case so lets use the optimized loop
return GetAttr(aLocalName);
}
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName, aNamespaceID)) {
return &attr.mValue;
}
}
return nullptr;
}
const nsAttrValue* AttrArray::GetAttr(const nsAString& aLocalName) const {
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName)) {
return &attr.mValue;
}
}
return nullptr;
}
const nsAttrValue* AttrArray::GetAttr(const nsAString& aName,
nsCaseTreatment aCaseSensitive) const {
// Check whether someone is being silly and passing non-lowercase
// attr names.
if (aCaseSensitive == eIgnoreCase &&
nsContentUtils::StringContainsASCIIUpper(aName)) {
// Try again with a lowercased name, but make sure we can't reenter this
// block by passing eCaseSensitive for aCaseSensitive.
nsAutoString lowercase;
nsContentUtils::ASCIIToLower(aName, lowercase);
return GetAttr(lowercase, eCaseMatters);
}
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.QualifiedNameEquals(aName)) {
return &attr.mValue;
}
}
return nullptr;
}
const nsAttrValue* AttrArray::AttrAt(uint32_t aPos) const {
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
return &mImpl->Attrs()[aPos].mValue;
}
template <typename Name>
inline nsresult AttrArray::AddNewAttribute(Name* aName, nsAttrValue& aValue) {
MOZ_ASSERT(!mImpl || mImpl->mCapacity >= mImpl->mAttrCount);
if (!mImpl || mImpl->mCapacity == mImpl->mAttrCount) {
if (!GrowBy(1)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
new (&attr.mName) nsAttrName(aName);
new (&attr.mValue) nsAttrValue();
attr.mValue.SwapValueWith(aValue);
return NS_OK;
}
nsresult AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
bool* aHadValue) {
*aHadValue = false;
for (InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName)) {
attr.mValue.SwapValueWith(aValue);
*aHadValue = true;
return NS_OK;
}
}
return AddNewAttribute(aLocalName, aValue);
}
nsresult AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
nsAttrValue& aValue, bool* aHadValue) {
int32_t namespaceID = aName->NamespaceID();
nsAtom* localName = aName->NameAtom();
if (namespaceID == kNameSpaceID_None) {
return SetAndSwapAttr(localName, aValue, aHadValue);
}
*aHadValue = false;
for (InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(localName, namespaceID)) {
attr.mName.SetTo(aName);
attr.mValue.SwapValueWith(aValue);
*aHadValue = true;
return NS_OK;
}
}
return AddNewAttribute(aName, aValue);
}
nsresult AttrArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue) {
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
mImpl->mBuffer[aPos].mValue.SwapValueWith(aValue);
mImpl->mBuffer[aPos].~InternalAttr();
// InternalAttr are not trivially copyable *but* we manually called the
// destructor so the memmove should be ok.
memmove((void*)(mImpl->mBuffer + aPos), mImpl->mBuffer + aPos + 1,
(mImpl->mAttrCount - aPos - 1) * sizeof(InternalAttr));
--mImpl->mAttrCount;
return NS_OK;
}
mozilla::dom::BorrowedAttrInfo AttrArray::AttrInfoAt(uint32_t aPos) const {
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
InternalAttr& attr = mImpl->mBuffer[aPos];
return BorrowedAttrInfo(&attr.mName, &attr.mValue);
}
const nsAttrName* AttrArray::AttrNameAt(uint32_t aPos) const {
NS_ASSERTION(aPos < AttrCount(), "out-of-bounds access in AttrArray");
return &mImpl->mBuffer[aPos].mName;
}
const nsAttrName* AttrArray::GetSafeAttrNameAt(uint32_t aPos) const {
if (aPos >= AttrCount()) {
return nullptr;
}
return &mImpl->mBuffer[aPos].mName;
}
const nsAttrName* AttrArray::GetExistingAttrNameFromQName(
const nsAString& aName) const {
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.QualifiedNameEquals(aName)) {
return &attr.mName;
}
}
return nullptr;
}
int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName) const {
int32_t i = 0;
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName)) {
return i;
}
++i;
}
return -1;
}
int32_t AttrArray::IndexOfAttr(const nsAtom* aLocalName,
int32_t aNamespaceID) const {
if (aNamespaceID == kNameSpaceID_None) {
// This should be the common case so lets use the optimized loop
return IndexOfAttr(aLocalName);
}
int32_t i = 0;
for (const InternalAttr& attr : Attrs()) {
if (attr.mName.Equals(aLocalName, aNamespaceID)) {
return i;
}
++i;
}
return -1;
}
void AttrArray::Compact() {
if (!mImpl) {
return;
}
if (!mImpl->mAttrCount && !mImpl->mMappedAttributeBits) {
mImpl.reset();
return;
}
// Nothing to do.
if (mImpl->mAttrCount == mImpl->mCapacity) {
return;
}
Impl* oldImpl = mImpl.release();
Impl* impl = static_cast<Impl*>(
realloc(oldImpl, Impl::AllocationSizeForAttributes(oldImpl->mAttrCount)));
if (!impl) {
mImpl.reset(oldImpl);
return;
}
impl->mCapacity = impl->mAttrCount;
mImpl.reset(impl);
}
nsresult AttrArray::EnsureCapacityToClone(const AttrArray& aOther) {
MOZ_ASSERT(!mImpl,
"AttrArray::EnsureCapacityToClone requires the array be empty "
"when called");
uint32_t attrCount = aOther.AttrCount();
if (!attrCount) {
return NS_OK;
}
// No need to use a CheckedUint32 because we are cloning. We know that we
// have already allocated an AttrArray of this size.
mImpl.reset(
static_cast<Impl*>(malloc(Impl::AllocationSizeForAttributes(attrCount))));
NS_ENSURE_TRUE(mImpl, NS_ERROR_OUT_OF_MEMORY);
mImpl->mMappedAttributeBits = 0;
mImpl->mCapacity = attrCount;
mImpl->mAttrCount = 0;
return NS_OK;
}
bool AttrArray::GrowBy(uint32_t aGrowSize) {
const uint32_t kLinearThreshold = 16;
const uint32_t kLinearGrowSize = 4;
CheckedUint32 capacity = mImpl ? mImpl->mCapacity : 0;
CheckedUint32 minCapacity = capacity;
minCapacity += aGrowSize;
if (!minCapacity.isValid()) {
return false;
}
if (capacity.value() <= kLinearThreshold) {
do {
capacity += kLinearGrowSize;
if (!capacity.isValid()) {
return false;
}
} while (capacity.value() < minCapacity.value());
} else {
uint32_t shift = mozilla::CeilingLog2(minCapacity.value());
if (shift >= 32) {
return false;
}
capacity = 1u << shift;
}
return GrowTo(capacity.value());
}
bool AttrArray::GrowTo(uint32_t aCapacity) {
uint32_t oldCapacity = mImpl ? mImpl->mCapacity : 0;
if (aCapacity <= oldCapacity) {
return true;
}
CheckedUint32 sizeInBytes = aCapacity;
sizeInBytes *= sizeof(InternalAttr);
if (!sizeInBytes.isValid()) {
return false;
}
sizeInBytes += sizeof(Impl);
if (!sizeInBytes.isValid()) {
return false;
}
MOZ_ASSERT(sizeInBytes.value() ==
Impl::AllocationSizeForAttributes(aCapacity));
const bool needToInitialize = !mImpl;
Impl* oldImpl = mImpl.release();
Impl* newImpl = static_cast<Impl*>(realloc(oldImpl, sizeInBytes.value()));
if (!newImpl) {
mImpl.reset(oldImpl);
return false;
}
mImpl.reset(newImpl);
// Set initial counts if we didn't have a buffer before
if (needToInitialize) {
mImpl->mMappedAttributeBits = 0;
mImpl->mAttrCount = 0;
}
mImpl->mCapacity = aCapacity;
return true;
}
size_t AttrArray::SizeOfExcludingThis(
mozilla::MallocSizeOf aMallocSizeOf) const {
if (!mImpl) {
return 0;
}
size_t n = aMallocSizeOf(mImpl.get());
for (const InternalAttr& attr : Attrs()) {
n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
}
return n;
}
int32_t AttrArray::FindAttrValueIn(int32_t aNameSpaceID, const nsAtom* aName,
AttrValuesArray* aValues,
nsCaseTreatment aCaseSensitive) const {
NS_ASSERTION(aName, "Must have attr name");
NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown, "Must have namespace");
NS_ASSERTION(aValues, "Null value array");
const nsAttrValue* val = GetAttr(aName, aNameSpaceID);
if (val) {
for (int32_t i = 0; aValues[i]; ++i) {
if (val->Equals(aValues[i], aCaseSensitive)) {
return i;
}
}
return ATTR_VALUE_NO_MATCH;
}
return ATTR_MISSING;
}