common: RefBase fixes and improvements

Fix missing Release() on RefBase::operator=(const T&).

Always acquire the new reference before dropping the existing reference.
Consider:
* Object A holds the only reference to object B.
* Ref of A is assigned B.
If A were released before referencing B, then B would be a dead pointer.

Remove RefBase::kNullValue, just use Traits::kNullValue. Avoids an unnecessary constexpr copy.

Add even more tests.

Change-Id: I111079d56cbf8c09162aa6e493078bead9ae97fa
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/55882
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/common/RefBase.h b/src/common/RefBase.h
index 495a24f..6ba4f50 100644
--- a/src/common/RefBase.h
+++ b/src/common/RefBase.h
@@ -33,17 +33,13 @@
 // RefBase supports
 template <typename T, typename Traits>
 class RefBase {
-  private:
-    static constexpr T kNullValue = Traits::kNullValue;
-
   public:
     // Default constructor and destructor.
-    RefBase() : mValue(kNullValue) {
+    RefBase() : mValue(Traits::kNullValue) {
     }
 
     ~RefBase() {
-        Release();
-        mValue = kNullValue;
+        Release(mValue);
     }
 
     // Constructors from nullptr.
@@ -51,49 +47,39 @@
     }
 
     RefBase<T, Traits>& operator=(std::nullptr_t) {
-        Release();
-        mValue = kNullValue;
+        Set(Traits::kNullValue);
         return *this;
     }
 
     // Constructors from a value T.
     RefBase(T value) : mValue(value) {
-        Reference();
+        Reference(value);
     }
 
     RefBase<T, Traits>& operator=(const T& value) {
-        mValue = value;
-        Reference();
+        Set(value);
         return *this;
     }
 
     // Constructors from a RefBase<T>
     RefBase(const RefBase<T, Traits>& other) : mValue(other.mValue) {
-        Reference();
+        Reference(other.mValue);
     }
 
     RefBase<T, Traits>& operator=(const RefBase<T, Traits>& other) {
-        if (&other != this) {
-            other.Reference();
-            Release();
-            mValue = other.mValue;
-        }
-
+        Set(other.mValue);
         return *this;
     }
 
     RefBase(RefBase<T, Traits>&& other) {
-        mValue = other.mValue;
-        other.mValue = kNullValue;
+        mValue = other.Detach();
     }
 
     RefBase<T, Traits>& operator=(RefBase<T, Traits>&& other) {
         if (&other != this) {
-            Release();
-            mValue = other.mValue;
-            other.mValue = kNullValue;
+            Release(mValue);
+            mValue = other.Detach();
         }
-
         return *this;
     }
 
@@ -102,14 +88,12 @@
     // operators defined with `other` == RefBase<T, Traits>.
     template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type>
     RefBase(const RefBase<U, UTraits>& other) : mValue(other.mValue) {
-        Reference();
+        Reference(other.mValue);
     }
 
     template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type>
     RefBase<T, Traits>& operator=(const RefBase<U, UTraits>& other) {
-        other.Reference();
-        Release();
-        mValue = other.mValue;
+        Set(other.mValue);
         return *this;
     }
 
@@ -120,7 +104,7 @@
 
     template <typename U, typename UTraits, typename = typename std::is_convertible<U, T>::type>
     RefBase<T, Traits>& operator=(RefBase<U, UTraits>&& other) {
-        Release();
+        Release(mValue);
         mValue = other.Detach();
         return *this;
     }
@@ -150,18 +134,18 @@
     }
 
     T Detach() DAWN_NO_DISCARD {
-        T value = mValue;
-        mValue = kNullValue;
+        T value{std::move(mValue)};
+        mValue = Traits::kNullValue;
         return value;
     }
 
     void Acquire(T value) {
-        Release();
+        Release(mValue);
         mValue = value;
     }
 
     T* InitializeInto() DAWN_NO_DISCARD {
-        ASSERT(mValue == kNullValue);
+        ASSERT(mValue == Traits::kNullValue);
         return &mValue;
     }
 
@@ -171,14 +155,24 @@
     template <typename U, typename UTraits>
     friend class RefBase;
 
-    void Reference() const {
-        if (mValue != kNullValue) {
-            Traits::Reference(mValue);
+    static void Reference(T value) {
+        if (value != Traits::kNullValue) {
+            Traits::Reference(value);
         }
     }
-    void Release() const {
-        if (mValue != kNullValue) {
-            Traits::Release(mValue);
+    static void Release(T value) {
+        if (value != Traits::kNullValue) {
+            Traits::Release(value);
+        }
+    }
+
+    void Set(T value) {
+        if (mValue != value) {
+            // Ensure that the new value is referenced before the old is released to prevent any
+            // transitive frees that may affect the new value.
+            Reference(value);
+            Release(mValue);
+            mValue = value;
         }
     }
 
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index a0c6e89..9a26592 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -174,6 +174,7 @@
     "unittests/PerStageTests.cpp",
     "unittests/PerThreadProcTests.cpp",
     "unittests/PlacementAllocatedTests.cpp",
+    "unittests/RefBaseTests.cpp",
     "unittests/RefCountedTests.cpp",
     "unittests/ResultTests.cpp",
     "unittests/RingBufferAllocatorTests.cpp",
diff --git a/src/tests/unittests/ObjectBaseTests.cpp b/src/tests/unittests/ObjectBaseTests.cpp
index ab141d3..51244c7 100644
--- a/src/tests/unittests/ObjectBaseTests.cpp
+++ b/src/tests/unittests/ObjectBaseTests.cpp
@@ -120,6 +120,24 @@
     ASSERT_EQ(refcount, 2);
 }
 
+// Test the repeated copy assignment of C++ objects
+TEST(ObjectBase, RepeatedCopyAssignment) {
+    int refcount = 1;
+    Object source(&refcount);
+
+    Object destination;
+    for (int i = 0; i < 10; i++) {
+        destination = source;
+    }
+
+    ASSERT_EQ(source.Get(), &refcount);
+    ASSERT_EQ(destination.Get(), &refcount);
+    ASSERT_EQ(3, refcount);
+
+    destination = Object();
+    ASSERT_EQ(refcount, 2);
+}
+
 // Test the copy assignment of C++ objects onto themselves
 TEST(ObjectBase, CopyAssignmentSelf) {
     int refcount = 1;
diff --git a/src/tests/unittests/RefBaseTests.cpp b/src/tests/unittests/RefBaseTests.cpp
new file mode 100644
index 0000000..27fd922
--- /dev/null
+++ b/src/tests/unittests/RefBaseTests.cpp
@@ -0,0 +1,319 @@
+// Copyright 2021 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gmock/gmock.h>
+
+#include "common/RefBase.h"
+
+namespace {
+    using Id = uint32_t;
+
+    enum class Action {
+        kReference,
+        kRelease,
+        kAssign,
+        kMarker,
+    };
+
+    struct Event {
+        Action action;
+        Id thisId = 0;
+        Id otherId = 0;
+    };
+
+    std::ostream& operator<<(std::ostream& os, const Event& event) {
+        switch (event.action) {
+            case Action::kReference:
+                os << "Reference " << event.thisId;
+                break;
+            case Action::kRelease:
+                os << "Release " << event.thisId;
+                break;
+            case Action::kAssign:
+                os << "Assign " << event.thisId << " <- " << event.otherId;
+                break;
+            case Action::kMarker:
+                os << "Marker " << event.thisId;
+                break;
+        }
+        return os;
+    }
+
+    bool operator==(const Event& a, const Event& b) {
+        return a.action == b.action && a.thisId == b.thisId && a.otherId == b.otherId;
+    }
+
+    using Events = std::vector<Event>;
+
+    struct RefTracker {
+        explicit constexpr RefTracker(nullptr_t) : mId(0), mEvents(nullptr) {
+        }
+
+        constexpr RefTracker(const RefTracker& other) = default;
+
+        RefTracker(Id id, Events* events) : mId(id), mEvents(events) {
+        }
+
+        void Reference() const {
+            mEvents->emplace_back(Event{Action::kReference, mId});
+        }
+
+        void Release() const {
+            mEvents->emplace_back(Event{Action::kRelease, mId});
+        }
+
+        RefTracker& operator=(const RefTracker& other) {
+            if (mEvents || other.mEvents) {
+                Events* events = mEvents ? mEvents : other.mEvents;
+                events->emplace_back(Event{Action::kAssign, mId, other.mId});
+            }
+            mId = other.mId;
+            mEvents = other.mEvents;
+            return *this;
+        }
+
+        bool operator==(const RefTracker& other) const {
+            return mId == other.mId;
+        }
+
+        bool operator!=(const RefTracker& other) const {
+            return mId != other.mId;
+        }
+
+        Id mId;
+        Events* mEvents;
+    };
+
+    struct RefTrackerTraits {
+        static constexpr RefTracker kNullValue{nullptr};
+
+        static void Reference(const RefTracker& handle) {
+            handle.Reference();
+        }
+
+        static void Release(const RefTracker& handle) {
+            handle.Release();
+        }
+    };
+
+    constexpr RefTracker RefTrackerTraits::kNullValue;
+
+    using Ref = RefBase<RefTracker, RefTrackerTraits>;
+}  // namespace
+
+TEST(RefBase, Acquire) {
+    Events events;
+    RefTracker tracker1(1, &events);
+    RefTracker tracker2(2, &events);
+    Ref ref(tracker1);
+
+    events.clear();
+    { ref.Acquire(tracker2); }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kRelease, 1},   // release ref
+                                             Event{Action::kAssign, 1, 2}  // acquire tracker2
+                                             ));
+}
+
+TEST(RefBase, Detach) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref ref(tracker);
+
+    events.clear();
+    { DAWN_UNUSED(ref.Detach()); }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kAssign, 1, 0}  // nullify ref
+                                             ));
+}
+
+TEST(RefBase, Constructor) {
+    Ref ref;
+    EXPECT_EQ(ref.Get(), RefTrackerTraits::kNullValue);
+}
+
+TEST(RefBase, ConstructDestruct) {
+    Events events;
+    RefTracker tracker(1, &events);
+
+    events.clear();
+    {
+        Ref ref(tracker);
+        events.emplace_back(Event{Action::kMarker, 10});
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kReference, 1},  // reference tracker
+                                             Event{Action::kMarker, 10},    //
+                                             Event{Action::kRelease, 1}     // destruct ref
+                                             ));
+}
+
+TEST(RefBase, CopyConstruct) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref refA(tracker);
+
+    events.clear();
+    {
+        Ref refB(refA);
+        events.emplace_back(Event{Action::kMarker, 10});
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kReference, 1},  // reference tracker
+                                             Event{Action::kMarker, 10},    //
+                                             Event{Action::kRelease, 1}     // destruct ref
+                                             ));
+}
+
+TEST(RefBase, RefCopyAssignment) {
+    Events events;
+    RefTracker tracker1(1, &events);
+    RefTracker tracker2(2, &events);
+    Ref refA(tracker1);
+    Ref refB(tracker2);
+
+    events.clear();
+    {
+        Ref ref;
+        events.emplace_back(Event{Action::kMarker, 10});
+        ref = refA;
+        events.emplace_back(Event{Action::kMarker, 20});
+        ref = refB;
+        events.emplace_back(Event{Action::kMarker, 30});
+        ref = refA;
+        events.emplace_back(Event{Action::kMarker, 40});
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kMarker, 10},    //
+                                             Event{Action::kReference, 1},  // reference tracker1
+                                             Event{Action::kAssign, 0, 1},  // copy tracker1
+                                             Event{Action::kMarker, 20},    //
+                                             Event{Action::kReference, 2},  // reference tracker2
+                                             Event{Action::kRelease, 1},    // release tracker1
+                                             Event{Action::kAssign, 1, 2},  // copy tracker2
+                                             Event{Action::kMarker, 30},    //
+                                             Event{Action::kReference, 1},  // reference tracker1
+                                             Event{Action::kRelease, 2},    // release tracker2
+                                             Event{Action::kAssign, 2, 1},  // copy tracker1
+                                             Event{Action::kMarker, 40},    //
+                                             Event{Action::kRelease, 1}     // destruct ref
+                                             ));
+}
+
+TEST(RefBase, RefMoveAssignment) {
+    Events events;
+    RefTracker tracker1(1, &events);
+    RefTracker tracker2(2, &events);
+    Ref refA(tracker1);
+    Ref refB(tracker2);
+
+    events.clear();
+    {
+        Ref ref;
+        events.emplace_back(Event{Action::kMarker, 10});
+        ref = std::move(refA);
+        events.emplace_back(Event{Action::kMarker, 20});
+        ref = std::move(refB);
+        events.emplace_back(Event{Action::kMarker, 30});
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kMarker, 10},    //
+                                             Event{Action::kAssign, 1, 0},  // nullify refA
+                                             Event{Action::kAssign, 0, 1},  // move into ref
+                                             Event{Action::kMarker, 20},    //
+                                             Event{Action::kRelease, 1},    // release tracker1
+                                             Event{Action::kAssign, 2, 0},  // nullify refB
+                                             Event{Action::kAssign, 1, 2},  // move into ref
+                                             Event{Action::kMarker, 30},    //
+                                             Event{Action::kRelease, 2}     // destruct ref
+                                             ));
+}
+
+TEST(RefBase, RefCopyAssignmentSelf) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref ref(tracker);
+    Ref& self = ref;
+
+    events.clear();
+    {
+        ref = self;
+        ref = self;
+        ref = self;
+    }
+    EXPECT_THAT(events, testing::ElementsAre());
+}
+
+TEST(RefBase, RefMoveAssignmentSelf) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref ref(tracker);
+    Ref& self = ref;
+
+    events.clear();
+    {
+        ref = std::move(self);
+        ref = std::move(self);
+        ref = std::move(self);
+    }
+    EXPECT_THAT(events, testing::ElementsAre());
+}
+
+TEST(RefBase, TCopyAssignment) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref ref;
+
+    events.clear();
+    {
+        ref = tracker;
+        ref = tracker;
+        ref = tracker;
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kReference, 1},  //
+                                             Event{Action::kAssign, 0, 1}));
+}
+
+TEST(RefBase, TMoveAssignment) {
+    Events events;
+    RefTracker tracker(1, &events);
+    Ref ref;
+
+    events.clear();
+    { ref = std::move(tracker); }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kReference, 1},  //
+                                             Event{Action::kAssign, 0, 1}));
+}
+
+TEST(RefBase, TCopyAssignmentAlternate) {
+    Events events;
+    RefTracker tracker1(1, &events);
+    RefTracker tracker2(2, &events);
+    Ref ref;
+
+    events.clear();
+    {
+        ref = tracker1;
+        events.emplace_back(Event{Action::kMarker, 10});
+        ref = tracker2;
+        events.emplace_back(Event{Action::kMarker, 20});
+        ref = tracker1;
+        events.emplace_back(Event{Action::kMarker, 30});
+    }
+    EXPECT_THAT(events, testing::ElementsAre(Event{Action::kReference, 1},  // reference tracker1
+                                             Event{Action::kAssign, 0, 1},  // copy tracker1
+                                             Event{Action::kMarker, 10},    //
+                                             Event{Action::kReference, 2},  // reference tracker2
+                                             Event{Action::kRelease, 1},    // release tracker1
+                                             Event{Action::kAssign, 1, 2},  // copy tracker2
+                                             Event{Action::kMarker, 20},    //
+                                             Event{Action::kReference, 1},  // reference tracker1
+                                             Event{Action::kRelease, 2},    // release tracker2
+                                             Event{Action::kAssign, 2, 1},  // copy tracker1
+                                             Event{Action::kMarker, 30}));
+}