skia2/tests/RefCntTest.cpp
bungeman c901c11549 Add element_type, swap, operators, fix reset on sk_sp.
The 'element_type' typedef is to play nice with std::pointer_traits.

The full complement of operators and swap to match unique_ptr so that
sk_sp can be properly compared to nullptr and used with standard
containers.

Update to 'reset' so that calling 'unref' is the last operation.

This also adds tests for these changes, and sets the fPtr to nullptr
in debug for easier bug finding.

Review URL: https://codereview.chromium.org/1773453002
2016-03-08 08:35:23 -08:00

388 lines
10 KiB
C++

/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkRefCnt.h"
#include "SkThreadUtils.h"
#include "SkTypes.h"
#include "SkWeakRefCnt.h"
#include "Test.h"
static void bounce_ref(void* data) {
SkRefCnt* ref = static_cast<SkRefCnt*>(data);
for (int i = 0; i < 100000; ++i) {
ref->ref();
ref->unref();
}
}
static void test_refCnt(skiatest::Reporter* reporter) {
SkRefCnt* ref = new SkRefCnt();
SkThread thing1(bounce_ref, ref);
SkThread thing2(bounce_ref, ref);
SkASSERT(thing1.start());
SkASSERT(thing2.start());
thing1.join();
thing2.join();
REPORTER_ASSERT(reporter, ref->unique());
ref->unref();
}
static void bounce_weak_ref(void* data) {
SkWeakRefCnt* ref = static_cast<SkWeakRefCnt*>(data);
for (int i = 0; i < 100000; ++i) {
if (ref->try_ref()) {
ref->unref();
}
}
}
static void bounce_weak_weak_ref(void* data) {
SkWeakRefCnt* ref = static_cast<SkWeakRefCnt*>(data);
for (int i = 0; i < 100000; ++i) {
ref->weak_ref();
ref->weak_unref();
}
}
static void test_weakRefCnt(skiatest::Reporter* reporter) {
SkWeakRefCnt* ref = new SkWeakRefCnt();
SkThread thing1(bounce_ref, ref);
SkThread thing2(bounce_ref, ref);
SkThread thing3(bounce_weak_ref, ref);
SkThread thing4(bounce_weak_weak_ref, ref);
SkASSERT(thing1.start());
SkASSERT(thing2.start());
SkASSERT(thing3.start());
SkASSERT(thing4.start());
thing1.join();
thing2.join();
thing3.join();
thing4.join();
REPORTER_ASSERT(reporter, ref->unique());
REPORTER_ASSERT(reporter, ref->getWeakCnt() == 1);
ref->unref();
}
DEF_TEST(RefCnt, reporter) {
test_refCnt(reporter);
test_weakRefCnt(reporter);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static int gRefCounter;
static int gUnrefCounter;
static int gNewCounter;
static int gDeleteCounter;
#define check(reporter, ref, unref, make, kill) \
REPORTER_ASSERT(reporter, gRefCounter == ref); \
REPORTER_ASSERT(reporter, gUnrefCounter == unref); \
REPORTER_ASSERT(reporter, gNewCounter == make); \
REPORTER_ASSERT(reporter, gDeleteCounter == kill);
class Effect {
public:
Effect() : fRefCnt(1) {
gNewCounter += 1;
}
virtual ~Effect() {}
int fRefCnt;
void ref() {
gRefCounter += 1;
fRefCnt += 1;
}
void unref() {
gUnrefCounter += 1;
SkASSERT(fRefCnt > 0);
if (0 == --fRefCnt) {
gDeleteCounter += 1;
delete this;
}
}
int* method() const { return new int; }
};
static sk_sp<Effect> Create() {
return sk_make_sp<Effect>();
}
class Paint {
public:
sk_sp<Effect> fEffect;
const sk_sp<Effect>& get() const { return fEffect; }
void set(sk_sp<Effect> value) {
fEffect = std::move(value);
}
};
struct EffectImpl : public Effect {
~EffectImpl() override {}
static sk_sp<EffectImpl> Create() {
return sk_sp<EffectImpl>(new EffectImpl);
}
int fValue;
};
static sk_sp<Effect> make_effect() {
auto foo = EffectImpl::Create();
foo->fValue = 42;
return std::move(foo);
}
static void reset_counters() {
gRefCounter = 0;
gUnrefCounter = 0;
gNewCounter = 0;
gDeleteCounter = 0;
}
DEF_TEST(sk_sp, reporter) {
reset_counters();
Paint paint;
REPORTER_ASSERT(reporter, paint.fEffect.get() == nullptr);
REPORTER_ASSERT(reporter, !paint.get());
check(reporter, 0, 0, 0, 0);
paint.set(Create());
check(reporter, 0, 0, 1, 0);
REPORTER_ASSERT(reporter, paint.fEffect.get()->fRefCnt == 1);
if (paint.get()) {
REPORTER_ASSERT(reporter, true);
} else {
REPORTER_ASSERT(reporter, false);
}
if (!paint.get()) {
REPORTER_ASSERT(reporter, false);
} else {
REPORTER_ASSERT(reporter, true);
}
paint.set(nullptr);
check(reporter, 0, 1, 1, 1);
if (paint.get()) {
REPORTER_ASSERT(reporter, false);
} else {
REPORTER_ASSERT(reporter, true);
}
if (!paint.get()) {
REPORTER_ASSERT(reporter, true);
} else {
REPORTER_ASSERT(reporter, false);
}
auto e = Create();
REPORTER_ASSERT(reporter, sizeof(e) == sizeof(void*));
check(reporter, 0, 1, 2, 1);
paint.set(e);
check(reporter, 1, 1, 2, 1);
REPORTER_ASSERT(reporter, paint.fEffect.get()->fRefCnt == 2);
Paint paint2;
paint2.set(paint.get());
check(reporter, 2, 1, 2, 1);
REPORTER_ASSERT(reporter, paint.fEffect.get()->fRefCnt == 3);
// Test sk_sp::operator->
delete paint.get()->method();
check(reporter, 2, 1, 2, 1);
// Test sk_sp::operator*
delete (*paint.get()).method();
check(reporter, 2, 1, 2, 1);
paint.set(nullptr);
e = nullptr;
paint2.set(nullptr);
check(reporter, 2, 4, 2, 2);
reset_counters();
{
// Test convertible sk_sp assignment.
check(reporter, 0, 0, 0, 0);
sk_sp<Effect> foo(nullptr);
REPORTER_ASSERT(reporter, !foo);
foo = make_effect();
REPORTER_ASSERT(reporter, foo);
check(reporter, 0, 0, 1, 0);
}
check(reporter, 0, 1, 1, 1);
// Test passing convertible rvalue into funtion.
reset_counters();
paint.set(EffectImpl::Create());
check(reporter, 0, 0, 1, 0);
paint.set(nullptr);
check(reporter, 0, 1, 1, 1);
reset_counters();
auto baz = EffectImpl::Create();
check(reporter, 0, 0, 1, 0);
paint.set(std::move(baz));
check(reporter, 0, 0, 1, 0);
REPORTER_ASSERT(reporter, !baz);
paint.set(nullptr);
check(reporter, 0, 1, 1, 1);
reset_counters();
{
// test comparison operator with convertible type.
sk_sp<EffectImpl> bar1 = EffectImpl::Create();
sk_sp<Effect> bar2(bar1); // convertible copy constructor
check(reporter, 1, 0, 1, 0);
REPORTER_ASSERT(reporter, bar1);
REPORTER_ASSERT(reporter, bar2);
REPORTER_ASSERT(reporter, bar1 == bar2);
REPORTER_ASSERT(reporter, bar2 == bar1);
REPORTER_ASSERT(reporter, !(bar1 != bar2));
REPORTER_ASSERT(reporter, !(bar2 != bar1));
sk_sp<Effect> bar3(nullptr);
bar3 = bar1; // convertible copy assignment
check(reporter, 2, 0, 1, 0);
}
check(reporter, 2, 3, 1, 1);
// test passing convertible copy into funtion.
reset_counters();
baz = EffectImpl::Create();
check(reporter, 0, 0, 1, 0);
paint.set(baz);
check(reporter, 1, 0, 1, 0);
baz = nullptr;
check(reporter, 1, 1, 1, 0);
paint.set(nullptr);
check(reporter, 1, 2, 1, 1);
{
sk_sp<SkRefCnt> empty;
sk_sp<SkRefCnt> notEmpty = sk_make_sp<SkRefCnt>();
REPORTER_ASSERT(reporter, empty == sk_sp<SkRefCnt>());
REPORTER_ASSERT(reporter, notEmpty != empty);
REPORTER_ASSERT(reporter, empty != notEmpty);
REPORTER_ASSERT(reporter, nullptr == empty);
REPORTER_ASSERT(reporter, empty == nullptr);
REPORTER_ASSERT(reporter, empty == empty);
REPORTER_ASSERT(reporter, nullptr <= empty);
REPORTER_ASSERT(reporter, empty <= nullptr);
REPORTER_ASSERT(reporter, empty <= empty);
REPORTER_ASSERT(reporter, nullptr >= empty);
REPORTER_ASSERT(reporter, empty >= nullptr);
REPORTER_ASSERT(reporter, empty >= empty);
}
{
sk_sp<SkRefCnt> a = sk_make_sp<SkRefCnt>();
sk_sp<SkRefCnt> b = sk_make_sp<SkRefCnt>();
REPORTER_ASSERT(reporter, a != b);
REPORTER_ASSERT(reporter, (a < b) != (b < a));
REPORTER_ASSERT(reporter, (b > a) != (a > b));
REPORTER_ASSERT(reporter, (a <= b) != (b <= a));
REPORTER_ASSERT(reporter, (b >= a) != (a >= b));
REPORTER_ASSERT(reporter, a == a);
REPORTER_ASSERT(reporter, a <= a);
REPORTER_ASSERT(reporter, a >= a);
}
// http://wg21.cmeerw.net/lwg/issue998
{
class foo : public SkRefCnt {
public:
foo() : bar(this) {}
void reset() { bar.reset(); }
private:
sk_sp<foo> bar;
};
// The following should properly delete the object and not cause undefined behavior.
// This is an ugly example, but the same issue can arise in more subtle ways.
(new foo)->reset();
}
// https://crrev.com/0d4ef2583a6f19c3e61be04d36eb1a60b133832c
{
struct StructB;
struct StructA : public SkRefCnt {
sk_sp<StructB> b;
};
struct StructB : public SkRefCnt {
sk_sp<StructA> a;
~StructB() override {}; // Some clang versions don't emit this implicitly.
};
// Create a reference cycle.
StructA* a = new StructA;
a->b.reset(new StructB);
a->b->a.reset(a);
// Break the cycle by calling reset(). This will cause |a| (and hence, |a.b|)
// to be deleted before the call to reset() returns. This tests that the
// implementation of sk_sp::reset() doesn't access |this| after it
// deletes the underlying pointer. This behaviour is consistent with the
// definition of unique_ptr::reset in C++11.
a->b.reset();
}
}
namespace {
struct FooAbstract : public SkRefCnt {
virtual void f() = 0;
};
struct FooConcrete : public FooAbstract {
void f() override {}
};
}
static sk_sp<FooAbstract> make_foo() {
// can not cast FooConcrete to FooAbstract.
// can cast FooConcrete* to FooAbstract*.
return sk_make_sp<FooConcrete>();
}
DEF_TEST(sk_make_sp, r) {
auto x = make_foo();
}
// Test that reset() "adopts" ownership from the caller, even if we are given the same ptr twice
//
DEF_TEST(sk_sp_reset, r) {
SkRefCnt* rc = new SkRefCnt;
REPORTER_ASSERT(r, rc->unique());
sk_sp<SkRefCnt> sp;
sp.reset(rc);
// We have transfered our ownership over to sp
REPORTER_ASSERT(r, rc->unique());
rc->ref(); // now "rc" is also an owner
REPORTER_ASSERT(r, !rc->unique());
sp.reset(rc); // this should transfer our ownership over to sp
REPORTER_ASSERT(r, rc->unique());
}