зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
5e83fc51e7
Коммит
e8af6c032a
|
@ -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',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче