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>
This commit is contained in:
Ben Clayton 2021-06-28 10:26:08 +00:00 committed by Dawn LUCI CQ
parent 220ff07ffd
commit db7def5816
4 changed files with 371 additions and 39 deletions

View File

@ -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 @@ class RefBase {
}
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 @@ class RefBase {
// 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 @@ class RefBase {
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 @@ class RefBase {
}
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 @@ class RefBase {
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;
}
}

View File

@ -174,6 +174,7 @@ test("dawn_unittests") {
"unittests/PerStageTests.cpp",
"unittests/PerThreadProcTests.cpp",
"unittests/PlacementAllocatedTests.cpp",
"unittests/RefBaseTests.cpp",
"unittests/RefCountedTests.cpp",
"unittests/ResultTests.cpp",
"unittests/RingBufferAllocatorTests.cpp",

View File

@ -120,6 +120,24 @@ TEST(ObjectBase, CopyAssignment) {
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;

View File

@ -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}));
}