summaryrefslogtreecommitdiffstats
path: root/mfbt/tests/TestNonDereferenceable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mfbt/tests/TestNonDereferenceable.cpp')
-rw-r--r--mfbt/tests/TestNonDereferenceable.cpp171
1 files changed, 171 insertions, 0 deletions
diff --git a/mfbt/tests/TestNonDereferenceable.cpp b/mfbt/tests/TestNonDereferenceable.cpp
new file mode 100644
index 0000000000..2f8f7c1dd1
--- /dev/null
+++ b/mfbt/tests/TestNonDereferenceable.cpp
@@ -0,0 +1,171 @@
+/* -*- 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/. */
+
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/NonDereferenceable.h"
+
+using mozilla::NonDereferenceable;
+
+#define CHECK MOZ_RELEASE_ASSERT
+
+void TestNonDereferenceableSimple() {
+ // Default construction.
+ NonDereferenceable<int> nd0;
+ CHECK(!nd0);
+ CHECK(!nd0.value());
+
+ int i = 1;
+ int i2 = 2;
+
+ // Construction with pointer.
+ NonDereferenceable<int> nd1(&i);
+ CHECK(!!nd1);
+ CHECK(nd1.value() == reinterpret_cast<uintptr_t>(&i));
+
+ // Assignment with pointer.
+ nd1 = &i2;
+ CHECK(nd1.value() == reinterpret_cast<uintptr_t>(&i2));
+
+ // Copy-construction.
+ NonDereferenceable<int> nd2(nd1);
+ CHECK(nd2.value() == reinterpret_cast<uintptr_t>(&i2));
+
+ // Copy-assignment.
+ nd2 = nd0;
+ CHECK(!nd2.value());
+
+ // Move-construction.
+ NonDereferenceable<int> nd3{NonDereferenceable<int>(&i)};
+ CHECK(nd3.value() == reinterpret_cast<uintptr_t>(&i));
+
+ // Move-assignment.
+ nd3 = std::move(nd1);
+ CHECK(nd3.value() == reinterpret_cast<uintptr_t>(&i2));
+ // Note: Not testing nd1's value because we don't want to assume what state
+ // it is left in after move. But at least it should be reusable:
+ nd1 = &i;
+ CHECK(nd1.value() == reinterpret_cast<uintptr_t>(&i));
+}
+
+void TestNonDereferenceableHierarchy() {
+ struct Base1 {
+ // Member variable, to make sure Base1 is not empty.
+ int x1;
+ };
+ struct Base2 {
+ int x2;
+ };
+ struct Derived : Base1, Base2 {};
+
+ Derived d;
+
+ // Construct NonDereferenceable from raw pointer.
+ NonDereferenceable<Derived> ndd = NonDereferenceable<Derived>(&d);
+ CHECK(ndd);
+ CHECK(ndd.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Cast Derived to Base1.
+ NonDereferenceable<Base1> ndb1 = ndd;
+ CHECK(ndb1);
+ CHECK(ndb1.value() == reinterpret_cast<uintptr_t>(static_cast<Base1*>(&d)));
+
+ // Cast Base1 back to Derived.
+ NonDereferenceable<Derived> nddb1 = ndb1;
+ CHECK(nddb1.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Cast Derived to Base2.
+ NonDereferenceable<Base2> ndb2 = ndd;
+ CHECK(ndb2);
+ CHECK(ndb2.value() == reinterpret_cast<uintptr_t>(static_cast<Base2*>(&d)));
+ // Sanity check that Base2 should be offset from the start of Derived.
+ CHECK(ndb2.value() != ndd.value());
+
+ // Cast Base2 back to Derived.
+ NonDereferenceable<Derived> nddb2 = ndb2;
+ CHECK(nddb2.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Note that it's not possible to jump between bases, as they're not obviously
+ // related, i.e.: `NonDereferenceable<Base2> ndb22 = ndb1;` doesn't compile.
+ // However it's possible to explicitly navigate through the derived object:
+ NonDereferenceable<Base2> ndb22 = NonDereferenceable<Derived>(ndb1);
+ CHECK(ndb22.value() == reinterpret_cast<uintptr_t>(static_cast<Base2*>(&d)));
+
+ // Handling nullptr; should stay nullptr even for offset bases.
+ ndd = nullptr;
+ CHECK(!ndd);
+ CHECK(!ndd.value());
+ ndb1 = ndd;
+ CHECK(!ndb1);
+ CHECK(!ndb1.value());
+ ndb2 = ndd;
+ CHECK(!ndb2);
+ CHECK(!ndb2.value());
+ nddb2 = ndb2;
+ CHECK(!nddb2);
+ CHECK(!nddb2.value());
+}
+
+template <typename T, size_t Index>
+struct CRTPBase {
+ // Convert `this` from `CRTPBase*` to `T*` while construction is still in
+ // progress; normally UBSan -fsanitize=vptr would catch this, but using
+ // NonDereferenceable should keep UBSan happy.
+ CRTPBase() : mDerived(this) {}
+ NonDereferenceable<T> mDerived;
+};
+
+void TestNonDereferenceableCRTP() {
+ struct Derived : CRTPBase<Derived, 1>, CRTPBase<Derived, 2> {};
+ using Base1 = Derived::CRTPBase<Derived, 1>;
+ using Base2 = Derived::CRTPBase<Derived, 2>;
+
+ Derived d;
+ // Verify that base constructors have correctly captured the address of the
+ // (at the time still incomplete) derived object.
+ CHECK(d.Base1::mDerived.value() == reinterpret_cast<uintptr_t>(&d));
+ CHECK(d.Base2::mDerived.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Construct NonDereferenceable from raw pointer.
+ NonDereferenceable<Derived> ndd = NonDereferenceable<Derived>(&d);
+ CHECK(ndd);
+ CHECK(ndd.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Cast Derived to Base1.
+ NonDereferenceable<Base1> ndb1 = ndd;
+ CHECK(ndb1);
+ CHECK(ndb1.value() == reinterpret_cast<uintptr_t>(static_cast<Base1*>(&d)));
+
+ // Cast Base1 back to Derived.
+ NonDereferenceable<Derived> nddb1 = ndb1;
+ CHECK(nddb1.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Cast Derived to Base2.
+ NonDereferenceable<Base2> ndb2 = ndd;
+ CHECK(ndb2);
+ CHECK(ndb2.value() == reinterpret_cast<uintptr_t>(static_cast<Base2*>(&d)));
+ // Sanity check that Base2 should be offset from the start of Derived.
+ CHECK(ndb2.value() != ndd.value());
+
+ // Cast Base2 back to Derived.
+ NonDereferenceable<Derived> nddb2 = ndb2;
+ CHECK(nddb2.value() == reinterpret_cast<uintptr_t>(&d));
+
+ // Note that it's not possible to jump between bases, as they're not obviously
+ // related, i.e.: `NonDereferenceable<Base2> ndb22 = ndb1;` doesn't compile.
+ // However it's possible to explicitly navigate through the derived object:
+ NonDereferenceable<Base2> ndb22 = NonDereferenceable<Derived>(ndb1);
+ CHECK(ndb22.value() == reinterpret_cast<uintptr_t>(static_cast<Base2*>(&d)));
+}
+
+int main() {
+ TestNonDereferenceableSimple();
+ TestNonDereferenceableHierarchy();
+ TestNonDereferenceableCRTP();
+
+ return 0;
+}