skia2/tests/TArrayTest.cpp
John Stiles d4f366e7da Update SkTArray tests to include arrays of objects with vtables.
These tests would be expected to trigger the CFI sanitizer (skia:13339).
We don't currently support CFI sanitizers, though (skia:13338).

Tests are confirmed to fail in Linux by copying TArray unit tests into
SkSLCompiler and running Chrome unit tests:
http://screen/3cYe9r4MYSXmTTa

Change-Id: If2d9e611bc9902e2ac6e0960a38e876bb1952884
Bug: skia:13338, skia:13339
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/542639
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
2022-05-20 19:47:12 +00:00

409 lines
14 KiB
C++

/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkRefCnt.h"
#include "include/private/SkTArray.h"
#include "include/utils/SkRandom.h"
#include "tests/Test.h"
// This class is used to test SkTArray's behavior with classes containing a vtable.
namespace {
class TestClass {
public:
TestClass() = default;
TestClass(const TestClass&) = default;
TestClass& operator=(const TestClass&) = default;
TestClass(int v) : value(v) {}
virtual ~TestClass() {}
bool operator==(const TestClass& c) const { return value == c.value; }
int value = 0;
};
} // namespace
// Tests the SkTArray<T> class template.
template <typename T, bool MEM_MOVE>
static void TestTSet_basic(skiatest::Reporter* reporter) {
SkTArray<T, MEM_MOVE> a;
// Starts empty.
REPORTER_ASSERT(reporter, a.empty());
REPORTER_ASSERT(reporter, a.count() == 0);
// { }, add a default constructed element
a.push_back() = T{0};
REPORTER_ASSERT(reporter, !a.empty());
REPORTER_ASSERT(reporter, a.count() == 1);
// { 0 }, removeShuffle the only element.
a.removeShuffle(0);
REPORTER_ASSERT(reporter, a.empty());
REPORTER_ASSERT(reporter, a.count() == 0);
// { }, add a default, add a 1, remove first
a.push_back() = T{0};
a.push_back() = T{1};
a.removeShuffle(0);
REPORTER_ASSERT(reporter, !a.empty());
REPORTER_ASSERT(reporter, a.count() == 1);
REPORTER_ASSERT(reporter, a[0] == T{1});
// { 1 }, replace with new array
T b[5] = {T{0}, T{1}, T{2}, T{3}, T{4}};
a.reset(b, SK_ARRAY_COUNT(b));
REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b));
REPORTER_ASSERT(reporter, a[2] == T{2});
REPORTER_ASSERT(reporter, a[4] == T{4});
// { 0, 1, 2, 3, 4 }, removeShuffle the last
a.removeShuffle(4);
REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b) - 1);
REPORTER_ASSERT(reporter, a[3] == T{3});
// { 0, 1, 2, 3 }, remove a middle, note shuffle
a.removeShuffle(1);
REPORTER_ASSERT(reporter, a.count() == SK_ARRAY_COUNT(b) - 2);
REPORTER_ASSERT(reporter, a[0] == T{0});
REPORTER_ASSERT(reporter, a[1] == T{3});
REPORTER_ASSERT(reporter, a[2] == T{2});
// { 0, 3, 2 }
}
template <typename T> static void test_construction(skiatest::Reporter* reporter) {
using ValueType = typename T::value_type;
// No arguments: Creates an empty array with no initial storage.
T arrayNoArgs;
REPORTER_ASSERT(reporter, arrayNoArgs.empty());
// Single integer: Creates an empty array that will preallocate space for reserveCount elements.
T arrayReserve(15);
REPORTER_ASSERT(reporter, arrayReserve.empty());
REPORTER_ASSERT(reporter, arrayReserve.capacity() == 15);
// Another array, const&: Copies one array to another.
T arrayInitial;
arrayInitial.push_back(ValueType{1});
arrayInitial.push_back(ValueType{2});
arrayInitial.push_back(ValueType{3});
T arrayCopy(arrayInitial);
REPORTER_ASSERT(reporter, arrayInitial.size() == 3);
REPORTER_ASSERT(reporter, arrayInitial[0] == ValueType{1});
REPORTER_ASSERT(reporter, arrayInitial[1] == ValueType{2});
REPORTER_ASSERT(reporter, arrayInitial[2] == ValueType{3});
REPORTER_ASSERT(reporter, arrayCopy.size() == 3);
REPORTER_ASSERT(reporter, arrayCopy[0] == ValueType{1});
REPORTER_ASSERT(reporter, arrayCopy[1] == ValueType{2});
REPORTER_ASSERT(reporter, arrayCopy[2] == ValueType{3});
// Another array, &&: Moves one array to another.
T arrayMove(std::move(arrayInitial));
REPORTER_ASSERT(reporter, arrayInitial.empty()); // NOLINT(bugprone-use-after-move)
REPORTER_ASSERT(reporter, arrayMove.size() == 3);
REPORTER_ASSERT(reporter, arrayMove[0] == ValueType{1});
REPORTER_ASSERT(reporter, arrayMove[1] == ValueType{2});
REPORTER_ASSERT(reporter, arrayMove[2] == ValueType{3});
// Pointer and count: Copies contents of a standard C array.
typename T::value_type data[3] = { 7, 8, 9 };
T arrayPtrCount(data, 3);
REPORTER_ASSERT(reporter, arrayPtrCount.size() == 3);
REPORTER_ASSERT(reporter, arrayPtrCount[0] == ValueType{7});
REPORTER_ASSERT(reporter, arrayPtrCount[1] == ValueType{8});
REPORTER_ASSERT(reporter, arrayPtrCount[2] == ValueType{9});
// Initializer list.
T arrayInitializer{8, 6, 7, 5, 3, 0, 9};
REPORTER_ASSERT(reporter, arrayInitializer.size() == 7);
REPORTER_ASSERT(reporter, arrayInitializer[0] == ValueType{8});
REPORTER_ASSERT(reporter, arrayInitializer[1] == ValueType{6});
REPORTER_ASSERT(reporter, arrayInitializer[2] == ValueType{7});
REPORTER_ASSERT(reporter, arrayInitializer[3] == ValueType{5});
REPORTER_ASSERT(reporter, arrayInitializer[4] == ValueType{3});
REPORTER_ASSERT(reporter, arrayInitializer[5] == ValueType{0});
REPORTER_ASSERT(reporter, arrayInitializer[6] == ValueType{9});
}
template <typename T, typename U>
static void test_skstarray_compatibility(skiatest::Reporter* reporter) {
// We expect SkTArrays of the same type to be copyable and movable, even when:
// - one side is an SkTArray, and the other side is an SkSTArray
// - both sides are SkSTArray, but each side has a different internal capacity
T tArray;
tArray.push_back(1);
tArray.push_back(2);
tArray.push_back(3);
T tArray2 = tArray;
// Copy construction from other-type array.
U arrayCopy(tArray);
REPORTER_ASSERT(reporter, tArray.size() == 3);
REPORTER_ASSERT(reporter, tArray[0] == 1);
REPORTER_ASSERT(reporter, tArray[1] == 2);
REPORTER_ASSERT(reporter, tArray[2] == 3);
REPORTER_ASSERT(reporter, arrayCopy.size() == 3);
REPORTER_ASSERT(reporter, arrayCopy[0] == 1);
REPORTER_ASSERT(reporter, arrayCopy[1] == 2);
REPORTER_ASSERT(reporter, arrayCopy[2] == 3);
// Assignment from other-type array.
U arrayAssignment;
arrayAssignment = tArray;
REPORTER_ASSERT(reporter, tArray.size() == 3);
REPORTER_ASSERT(reporter, tArray[0] == 1);
REPORTER_ASSERT(reporter, tArray[1] == 2);
REPORTER_ASSERT(reporter, tArray[2] == 3);
REPORTER_ASSERT(reporter, arrayAssignment.size() == 3);
REPORTER_ASSERT(reporter, arrayAssignment[0] == 1);
REPORTER_ASSERT(reporter, arrayAssignment[1] == 2);
REPORTER_ASSERT(reporter, arrayAssignment[2] == 3);
// Move construction from other-type array.
U arrayMove(std::move(tArray));
REPORTER_ASSERT(reporter, tArray.empty()); // NOLINT(bugprone-use-after-move)
REPORTER_ASSERT(reporter, arrayMove.size() == 3);
REPORTER_ASSERT(reporter, arrayMove[0] == 1);
REPORTER_ASSERT(reporter, arrayMove[1] == 2);
REPORTER_ASSERT(reporter, arrayMove[2] == 3);
// Move assignment from other-type array.
U arrayMoveAssign;
arrayMoveAssign = std::move(tArray2);
REPORTER_ASSERT(reporter, tArray2.empty()); // NOLINT(bugprone-use-after-move)
REPORTER_ASSERT(reporter, arrayMoveAssign.size() == 3);
REPORTER_ASSERT(reporter, arrayMoveAssign[0] == 1);
REPORTER_ASSERT(reporter, arrayMoveAssign[1] == 2);
REPORTER_ASSERT(reporter, arrayMoveAssign[2] == 3);
}
template <typename T> static void test_swap(skiatest::Reporter* reporter,
SkTArray<T>* (&arrays)[4],
int (&sizes)[7])
{
for (auto a : arrays) {
for (auto b : arrays) {
if (a == b) {
continue;
}
for (auto sizeA : sizes) {
for (auto sizeB : sizes) {
a->reset();
b->reset();
int curr = 0;
for (int i = 0; i < sizeA; i++) { a->push_back(curr++); }
for (int i = 0; i < sizeB; i++) { b->push_back(curr++); }
a->swap(*b);
REPORTER_ASSERT(reporter, b->count() == sizeA);
REPORTER_ASSERT(reporter, a->count() == sizeB);
curr = 0;
for (auto&& x : *b) { REPORTER_ASSERT(reporter, x == curr++); }
for (auto&& x : *a) { REPORTER_ASSERT(reporter, x == curr++); }
a->swap(*a);
curr = sizeA;
for (auto&& x : *a) { REPORTER_ASSERT(reporter, x == curr++); }
}}
}}
}
static void test_swap(skiatest::Reporter* reporter) {
int sizes[] = {0, 1, 5, 10, 15, 20, 25};
SkTArray<int> arr;
SkSTArray< 5, int> arr5;
SkSTArray<10, int> arr10;
SkSTArray<20, int> arr20;
SkTArray<int>* arrays[] = { &arr, &arr5, &arr10, &arr20 };
test_swap(reporter, arrays, sizes);
struct MoveOnlyInt {
MoveOnlyInt(int i) : fInt(i) {}
MoveOnlyInt(MoveOnlyInt&& that) : fInt(that.fInt) {}
bool operator==(int i) { return fInt == i; }
int fInt;
};
SkTArray<MoveOnlyInt> moi;
SkSTArray< 5, MoveOnlyInt> moi5;
SkSTArray<10, MoveOnlyInt> moi10;
SkSTArray<20, MoveOnlyInt> moi20;
SkTArray<MoveOnlyInt>* arraysMoi[] = { &moi, &moi5, &moi10, &moi20 };
test_swap(reporter, arraysMoi, sizes);
}
void test_unnecessary_alloc(skiatest::Reporter* reporter) {
{
SkTArray<int> a;
REPORTER_ASSERT(reporter, a.capacity() == 0);
}
{
SkSTArray<10, int> a;
REPORTER_ASSERT(reporter, a.capacity() == 10);
}
{
SkTArray<int> a(1);
REPORTER_ASSERT(reporter, a.capacity() >= 1);
}
{
SkTArray<int> a, b;
b = a;
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkSTArray<10, int> a;
SkTArray<int> b;
b = a;
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkTArray<int> a;
SkTArray<int> b(a); // NOLINT(performance-unnecessary-copy-initialization)
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkSTArray<10, int> a;
SkTArray<int> b(a); // NOLINT(performance-unnecessary-copy-initialization)
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkTArray<int> a;
SkTArray<int> b(std::move(a));
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkSTArray<10, int> a;
SkTArray<int> b(std::move(a));
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkTArray<int> a;
SkTArray<int> b;
b = std::move(a);
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
{
SkSTArray<10, int> a;
SkTArray<int> b;
b = std::move(a);
REPORTER_ASSERT(reporter, b.capacity() == 0);
}
}
static void test_self_assignment(skiatest::Reporter* reporter) {
SkTArray<int> a;
a.push_back(1);
REPORTER_ASSERT(reporter, !a.empty());
REPORTER_ASSERT(reporter, a.count() == 1);
REPORTER_ASSERT(reporter, a[0] == 1);
a = static_cast<decltype(a)&>(a);
REPORTER_ASSERT(reporter, !a.empty());
REPORTER_ASSERT(reporter, a.count() == 1);
REPORTER_ASSERT(reporter, a[0] == 1);
}
template <typename Array> static void test_array_reserve(skiatest::Reporter* reporter,
Array* array, int reserveCount) {
SkRandom random;
REPORTER_ASSERT(reporter, array->capacity() >= reserveCount);
array->push_back();
REPORTER_ASSERT(reporter, array->capacity() >= reserveCount);
array->pop_back();
REPORTER_ASSERT(reporter, array->capacity() >= reserveCount);
while (array->count() < reserveCount) {
// Two steps forward, one step back
if (random.nextULessThan(3) < 2) {
array->push_back();
} else if (array->count() > 0) {
array->pop_back();
}
REPORTER_ASSERT(reporter, array->capacity() >= reserveCount);
}
}
template<typename Array> static void test_reserve(skiatest::Reporter* reporter) {
// Test that our allocated space stays >= to the reserve count until the array is filled to
// the reserve count
for (int reserveCount : {1, 2, 10, 100}) {
// Test setting reserve in constructor.
Array array1(reserveCount);
test_array_reserve(reporter, &array1, reserveCount);
// Test setting reserve after constructor.
Array array2;
array2.reserve_back(reserveCount);
test_array_reserve(reporter, &array2, reserveCount);
// Test increasing reserve after constructor.
Array array3(reserveCount/2);
array3.reserve_back(reserveCount);
test_array_reserve(reporter, &array3, reserveCount);
// Test setting reserve on non-empty array.
Array array4;
array4.push_back_n(reserveCount);
array4.reserve_back(reserveCount);
array4.pop_back_n(reserveCount);
test_array_reserve(reporter, &array4, 2 * reserveCount);
}
}
DEF_TEST(TArray, reporter) {
// ints are POD types and can work with either MEM_MOVE=true or false.
TestTSet_basic<int, true>(reporter);
TestTSet_basic<int, false>(reporter);
// TestClass has a vtable and can only work with MEM_MOVE=false.
TestTSet_basic<TestClass, false>(reporter);
test_swap(reporter);
test_unnecessary_alloc(reporter);
test_self_assignment(reporter);
test_reserve<SkTArray<int>>(reporter);
test_reserve<SkSTArray<1, int>>(reporter);
test_reserve<SkSTArray<2, int>>(reporter);
test_reserve<SkSTArray<16, int>>(reporter);
test_reserve<SkTArray<TestClass>>(reporter);
test_reserve<SkSTArray<1, TestClass>>(reporter);
test_reserve<SkSTArray<2, TestClass>>(reporter);
test_reserve<SkSTArray<16, TestClass>>(reporter);
test_construction<SkTArray<int>>(reporter);
test_construction<SkTArray<double>>(reporter);
test_construction<SkTArray<TestClass>>(reporter);
test_construction<SkSTArray<1, int>>(reporter);
test_construction<SkSTArray<5, char>>(reporter);
test_construction<SkSTArray<7, TestClass>>(reporter);
test_construction<SkSTArray<10, float>>(reporter);
test_skstarray_compatibility<SkSTArray<1, int>, SkTArray<int>>(reporter);
test_skstarray_compatibility<SkSTArray<5, char>, SkTArray<char>>(reporter);
test_skstarray_compatibility<SkSTArray<10, float>, SkTArray<float>>(reporter);
test_skstarray_compatibility<SkTArray<int>, SkSTArray<1, int>>(reporter);
test_skstarray_compatibility<SkTArray<char>, SkSTArray<5, char>>(reporter);
test_skstarray_compatibility<SkTArray<float>, SkSTArray<10, float>>(reporter);
test_skstarray_compatibility<SkSTArray<10, uint8_t>, SkSTArray<1, uint8_t>>(reporter);
test_skstarray_compatibility<SkSTArray<1, long>, SkSTArray<10, long>>(reporter);
test_skstarray_compatibility<SkSTArray<3, double>, SkSTArray<4, double>>(reporter);
test_skstarray_compatibility<SkSTArray<2, short>, SkSTArray<1, short>>(reporter);
}