Bug 1539538 - Introduce UnsafePtr r=froydnj,janv

This commit adds a smart pointer class that verifies that no dangling
pointers remain after the pointee went out of scope. This verification is
opt-in and can be controlled both statically and dynamically by the pointee.

Differential Revision: https://phabricator.services.mozilla.com/D25200

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yaron Tausky 2019-04-09 14:41:57 +00:00
Родитель 5e83fc51e7
Коммит e8af6c032a
5 изменённых файлов: 456 добавлений и 4 удалений

Просмотреть файл

@ -26,6 +26,7 @@
#include "mozilla/dom/PBackgroundLSSnapshotParent.h"
#include "mozilla/dom/StorageDBUpdater.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
@ -1853,7 +1854,9 @@ class PreparedDatastore {
* Actor class declarations
******************************************************************************/
class Database final : public PBackgroundLSDatabaseParent {
class Database final
: public PBackgroundLSDatabaseParent,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
RefPtr<Datastore> mDatastore;
Snapshot* mSnapshot;
const PrincipalInfo mPrincipalInfo;
@ -2207,7 +2210,10 @@ class LSRequestBase : public DatastoreOperationBase,
mozilla::ipc::IPCResult RecvFinish() final;
};
class PrepareDatastoreOp : public LSRequestBase, public OpenDirectoryListener {
class PrepareDatastoreOp
: public LSRequestBase,
public OpenDirectoryListener,
public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
class LoadDataOp;
enum class NestedState {
@ -2726,7 +2732,7 @@ class QuotaClient::MatchFunction final : public mozIStorageFunction {
bool gLocalStorageInitialized = false;
#endif
typedef nsTArray<PrepareDatastoreOp*> PrepareDatastoreOpArray;
typedef nsTArray<CheckedUnsafePtr<PrepareDatastoreOp>> PrepareDatastoreOpArray;
StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
@ -2741,7 +2747,7 @@ typedef nsClassHashtable<nsUint64HashKey, PreparedDatastore>
StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
typedef nsTArray<Database*> LiveDatabaseArray;
typedef nsTArray<CheckedUnsafePtr<Database>> LiveDatabaseArray;
StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;

Просмотреть файл

@ -0,0 +1,341 @@
/* 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/. */
// Diagnostic class template that helps finding dangling pointers.
#ifndef mozilla_CheckedUnsafePtr_h
#define mozilla_CheckedUnsafePtr_h
#include "mozilla/DataMutex.h"
#include "nsTArray.h"
#include <cstddef>
#include <type_traits>
#include <utility>
namespace mozilla {
enum class CheckingSupport {
Enabled,
Disabled,
};
namespace detail {
template <CheckingSupport>
class CheckedUnsafePtrBase;
struct CheckedUnsafePtrCheckData {
using Data = nsTArray<CheckedUnsafePtrBase<CheckingSupport::Enabled>*>;
DataMutex<Data> mPtrs{"mozilla::SupportsCheckedUnsafePtr"};
};
template <>
class CheckedUnsafePtrBase<CheckingSupport::Disabled> {
protected:
template <typename Ptr>
void CopyDanglingFlagIfAvailableFrom(const Ptr&) {}
template <typename Ptr, typename F>
void WithCheckedUnsafePtrsImpl(Ptr, F&&) {}
};
template <>
class CheckedUnsafePtrBase<CheckingSupport::Enabled> {
friend class CheckedUnsafePtrBaseAccess;
protected:
CheckedUnsafePtrBase() = default;
CheckedUnsafePtrBase(const CheckedUnsafePtrBase& aOther) = default;
// When copying an CheckedUnsafePtr, its mIsDangling member must be copied as
// well; otherwise the new copy might try to dereference a dangling pointer
// when destructed.
void CopyDanglingFlagIfAvailableFrom(
const CheckedUnsafePtrBase<CheckingSupport::Enabled>& aOther) {
mIsDangling = aOther.mIsDangling;
}
template <typename Ptr>
using DisableForCheckedUnsafePtr =
std::enable_if_t<!std::is_base_of<CheckedUnsafePtrBase, Ptr>::value>;
// When constructing an CheckedUnsafePtr from a different kind of pointer it's
// not possible to determine whether it's dangling; therefore it's undefined
// behavior to construct one from a dangling pointer, and we assume that any
// CheckedUnsafePtr thus constructed is not dangling.
template <typename Ptr>
DisableForCheckedUnsafePtr<Ptr> CopyDanglingFlagIfAvailableFrom(const Ptr&) {}
template <typename F>
void WithCheckedUnsafePtrsImpl(CheckedUnsafePtrCheckData* const aRawPtr,
F&& aClosure) {
if (!mIsDangling && aRawPtr) {
const auto CheckedUnsafePtrs = aRawPtr->mPtrs.Lock();
aClosure(this, *CheckedUnsafePtrs);
}
}
private:
bool mIsDangling = false;
};
class CheckedUnsafePtrBaseAccess {
protected:
static void SetDanglingFlag(
CheckedUnsafePtrBase<CheckingSupport::Enabled>& aBase) {
aBase.mIsDangling = true;
}
};
} // namespace detail
class CheckingPolicyAccess {
protected:
template <typename CheckingPolicy>
static void NotifyCheckFailure(CheckingPolicy& aPolicy) {
aPolicy.NotifyCheckFailure();
}
};
template <typename Derived>
class CheckCheckedUnsafePtrs : private CheckingPolicyAccess,
private detail::CheckedUnsafePtrBaseAccess {
public:
using SupportsChecking =
std::integral_constant<CheckingSupport, CheckingSupport::Enabled>;
protected:
CheckCheckedUnsafePtrs() {
static_assert(
std::is_base_of<CheckCheckedUnsafePtrs, Derived>::value,
"cannot instantiate with a type that's not a subclass of this class");
}
bool ShouldCheck() const { return true; }
void Check(detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) {
if (!aCheckedUnsafePtrs.IsEmpty()) {
for (const auto aCheckedUnsafePtrBase : aCheckedUnsafePtrs) {
SetDanglingFlag(*aCheckedUnsafePtrBase);
}
NotifyCheckFailure(*static_cast<Derived*>(this));
}
}
};
class CrashOnDanglingCheckedUnsafePtr
: public CheckCheckedUnsafePtrs<CrashOnDanglingCheckedUnsafePtr> {
friend class mozilla::CheckingPolicyAccess;
void NotifyCheckFailure() { MOZ_CRASH("Found dangling CheckedUnsafePtr"); }
};
struct DoNotCheckCheckedUnsafePtrs {
using SupportsChecking =
std::integral_constant<CheckingSupport, CheckingSupport::Disabled>;
};
namespace detail {
// Template parameter CheckingSupport controls the inclusion of
// CheckedUnsafePtrCheckData as a subobject of instantiations of
// SupportsCheckedUnsafePtr, ensuring that choosing a policy without checking
// support incurs no size overhead.
template <typename CheckingPolicy,
CheckingSupport = CheckingPolicy::SupportsChecking::value>
class SupportCheckedUnsafePtrImpl;
template <typename CheckingPolicy>
class SupportCheckedUnsafePtrImpl<CheckingPolicy, CheckingSupport::Disabled>
: public CheckingPolicy {
protected:
template <typename... Args>
explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs)
: CheckingPolicy(std::forward<Args>(aArgs)...) {}
};
template <typename CheckingPolicy>
class SupportCheckedUnsafePtrImpl<CheckingPolicy, CheckingSupport::Enabled>
: public CheckedUnsafePtrCheckData, public CheckingPolicy {
template <typename T>
friend class CheckedUnsafePtr;
protected:
template <typename... Args>
explicit SupportCheckedUnsafePtrImpl(Args&&... aArgs)
: CheckingPolicy(std::forward<Args>(aArgs)...) {}
~SupportCheckedUnsafePtrImpl() {
if (this->ShouldCheck()) {
const auto ptrs = mPtrs.Lock();
this->Check(*ptrs);
}
}
};
struct SupportsCheckedUnsafePtrTag {};
} // namespace detail
template <typename Condition,
typename CheckingPolicy = CrashOnDanglingCheckedUnsafePtr>
using CheckIf = std::conditional_t<Condition::value, CheckingPolicy,
DoNotCheckCheckedUnsafePtrs>;
using DiagnosticAssertEnabled = std::integral_constant<bool,
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
true
#else
false
#endif
>;
// A T class that publicly inherits from an instantiation of
// SupportsCheckedUnsafePtr and its subclasses can be pointed to by smart
// pointers of type CheckedUnsafePtr<T>. Whenever such a smart pointer is
// created, its existence is tracked by the pointee according to its
// CheckingPolicy. When the pointee goes out of scope it then uses the its
// CheckingPolicy to verify that no CheckedUnsafePtr pointers are left pointing
// to it.
//
// The CheckingPolicy type is used to control the kind of verification that
// happen at the end of the object's lifetime. By default, debug builds always
// check for dangling CheckedUnsafePtr pointers and assert that none are found,
// while release builds forgo all checks. (Release builds incur no size or
// runtime penalties compared to bare pointers.)
template <typename CheckingPolicy>
class SupportsCheckedUnsafePtr
: public detail::SupportCheckedUnsafePtrImpl<CheckingPolicy>,
public detail::SupportsCheckedUnsafePtrTag {
public:
template <typename... Args>
explicit SupportsCheckedUnsafePtr(Args&&... aArgs)
: detail::SupportCheckedUnsafePtrImpl<CheckingPolicy>(
std::forward<Args>(aArgs)...) {}
};
// CheckedUnsafePtr<T> is a smart pointer class that helps detect dangling
// pointers in cases where such pointers are not allowed. In order to use it,
// the pointee T must publicly inherit from an instantiation of
// SupportsCheckedUnsafePtr. An CheckedUnsafePtr<T> can be used anywhere a T*
// can be used, has the same size, and imposes no additional thread-safety
// restrictions.
template <typename T>
class CheckedUnsafePtr
: private detail::CheckedUnsafePtrBase<T::SupportsChecking::value> {
static_assert(
std::is_base_of<detail::SupportsCheckedUnsafePtrTag, T>::value,
"type T must be derived from instantiation of SupportsCheckedUnsafePtr");
template <typename U>
friend class CheckedUnsafePtr;
template <typename U, typename S = std::nullptr_t>
using EnableIfCompatible =
std::enable_if_t<std::is_base_of<T, std::remove_reference_t<decltype(
*std::declval<U>())>>::value,
S>;
public:
MOZ_IMPLICIT CheckedUnsafePtr(const std::nullptr_t = nullptr)
: mRawPtr(nullptr){};
template <typename U>
MOZ_IMPLICIT CheckedUnsafePtr(const U& aPtr,
EnableIfCompatible<U> = nullptr) {
Set(aPtr);
}
CheckedUnsafePtr(const CheckedUnsafePtr& aOther) { Set(aOther); }
~CheckedUnsafePtr() { Reset(); }
CheckedUnsafePtr& operator=(const std::nullptr_t) {
Reset();
return *this;
}
template <typename U>
EnableIfCompatible<U, CheckedUnsafePtr&> operator=(const U& aPtr) {
Replace(aPtr);
return *this;
}
CheckedUnsafePtr& operator=(const CheckedUnsafePtr& aOther) {
if (&aOther != this) {
Replace(aOther);
}
return *this;
}
T* operator->() const { return mRawPtr; }
T& operator*() const { return *operator->(); }
MOZ_IMPLICIT operator T*() const { return operator->(); }
template <typename U>
bool operator==(EnableIfCompatible<U, const U&> aRhs) const {
return mRawPtr == &*aRhs;
}
template <typename U>
friend bool operator==(EnableIfCompatible<U, const U&> aLhs,
const CheckedUnsafePtr& aRhs) {
return aRhs == aLhs;
}
template <typename U>
bool operator!=(EnableIfCompatible<U, const U&> aRhs) const {
return !(*this == aRhs);
}
template <typename U>
friend bool operator!=(EnableIfCompatible<U, const U&> aLhs,
const CheckedUnsafePtr& aRhs) {
return aRhs != aLhs;
}
private:
using Base = detail::CheckedUnsafePtrBase<CheckingSupport::Enabled>;
template <typename U>
void Replace(const U& aPtr) {
Reset();
Set(aPtr);
}
void Reset() {
WithCheckedUnsafePtrs(
[](Base* const aSelf,
detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) {
const auto index = aCheckedUnsafePtrs.IndexOf(aSelf);
aCheckedUnsafePtrs.UnorderedRemoveElementAt(index);
});
mRawPtr = nullptr;
}
template <typename U>
void Set(const U& aPtr) {
this->CopyDanglingFlagIfAvailableFrom(aPtr);
mRawPtr = &*aPtr;
WithCheckedUnsafePtrs(
[](Base* const aSelf,
detail::CheckedUnsafePtrCheckData::Data& aCheckedUnsafePtrs) {
aCheckedUnsafePtrs.AppendElement(aSelf);
});
}
template <typename F>
void WithCheckedUnsafePtrs(F&& aClosure) {
this->WithCheckedUnsafePtrsImpl(mRawPtr, std::forward<F>(aClosure));
}
T* mRawPtr;
};
} // namespace mozilla
// nsTArray<T> requires by default that T can be safely moved with std::memmove.
// Since CheckedUnsafePtr<T> has a non-trivial copy constructor, it has to opt
// into nsTArray<T> using them.
DECLARE_USE_COPY_CONSTRUCTORS_FOR_TEMPLATE(mozilla::CheckedUnsafePtr)
#endif // mozilla_CheckedUnsafePtr_h

Просмотреть файл

@ -36,6 +36,7 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla.dom.quota += [
'ActorsParent.h',
'CheckedUnsafePtr.h',
'Client.h',
'FileStreams.h',
'MemoryOutputStream.h',

Просмотреть файл

@ -0,0 +1,103 @@
/* 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/. */
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "gtest/gtest.h"
using namespace mozilla;
class TestCheckingPolicy : public CheckCheckedUnsafePtrs<TestCheckingPolicy> {
protected:
explicit TestCheckingPolicy(bool& aPassedCheck)
: mPassedCheck(aPassedCheck) {}
private:
friend class mozilla::CheckingPolicyAccess;
void NotifyCheckFailure() { mPassedCheck = false; }
bool& mPassedCheck;
};
struct BasePointee : public SupportsCheckedUnsafePtr<TestCheckingPolicy> {
explicit BasePointee(bool& aCheckPassed)
: SupportsCheckedUnsafePtr<TestCheckingPolicy>(aCheckPassed) {}
};
struct DerivedPointee : public BasePointee {
using BasePointee::BasePointee;
};
class CheckedUnsafePtrTest : public ::testing::Test {
protected:
bool mPassedCheck = true;
};
TEST_F(CheckedUnsafePtrTest, PointeeWithNoCheckedUnsafePtrs) {
{ DerivedPointee pointee{mPassedCheck}; }
ASSERT_TRUE(mPassedCheck);
}
template <typename PointerType>
class TypedCheckedUnsafePtrTest : public CheckedUnsafePtrTest {};
TYPED_TEST_CASE_P(TypedCheckedUnsafePtrTest);
TYPED_TEST_P(TypedCheckedUnsafePtrTest, PointeeWithOneCheckedUnsafePtr) {
{
DerivedPointee pointee{this->mPassedCheck};
CheckedUnsafePtr<TypeParam> ptr = &pointee;
}
ASSERT_TRUE(this->mPassedCheck);
}
TYPED_TEST_P(TypedCheckedUnsafePtrTest,
PointeeWithOneDanglingCheckedUnsafePtr) {
[this]() -> CheckedUnsafePtr<TypeParam> {
DerivedPointee pointee{this->mPassedCheck};
return &pointee;
}();
ASSERT_FALSE(this->mPassedCheck);
}
TYPED_TEST_P(TypedCheckedUnsafePtrTest,
PointeeWithOneCopiedDanglingCheckedUnsafePtr) {
const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
DerivedPointee pointee{this->mPassedCheck};
return &pointee;
}();
EXPECT_FALSE(this->mPassedCheck);
// With AddressSanitizer we would hopefully detect if the copy constructor
// tries to add dangling2 to the now-gone pointee's unsafe pointer array. No
// promises though, since it might be optimized away.
CheckedUnsafePtr<TypeParam> dangling2{dangling1};
ASSERT_TRUE(dangling2);
}
TYPED_TEST_P(TypedCheckedUnsafePtrTest,
PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr) {
const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
DerivedPointee pointee{this->mPassedCheck};
return &pointee;
}();
EXPECT_FALSE(this->mPassedCheck);
// With AddressSanitizer we would hopefully detect if the assignment tries to
// add dangling2 to the now-gone pointee's unsafe pointer array. No promises
// though, since it might be optimized away.
CheckedUnsafePtr<TypeParam> dangling2;
dangling2 = dangling1;
ASSERT_TRUE(dangling2);
}
REGISTER_TYPED_TEST_CASE_P(TypedCheckedUnsafePtrTest,
PointeeWithOneCheckedUnsafePtr,
PointeeWithOneDanglingCheckedUnsafePtr,
PointeeWithOneCopiedDanglingCheckedUnsafePtr,
PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr);
using BothTypes = ::testing::Types<BasePointee, DerivedPointee>;
INSTANTIATE_TYPED_TEST_CASE_P(InstantiationOf, TypedCheckedUnsafePtrTest,
BothTypes);

Просмотреть файл

@ -5,6 +5,7 @@
# file, you can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES = [
'TestCheckedUnsafePtr.cpp',
'TestQuotaManager.cpp',
]