blob: 847b82c02cceede6c31767e237ac8e8ff6e2edae [file] [log] [blame]
// Copyright 2020 Google LLC
//
// 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.
// Test WP2Malloc() failure at different places.
#include <cstring>
#include <iostream>
#include <vector>
#include "include/helpers.h"
#include "src/utils/vector.h"
#include "src/wp2/decode.h"
//------------------------------------------------------------------------------
namespace WP2 {
namespace {
static struct FailMemoryTrace : public WP2MemoryHook {
bool Register(void* ptr, size_t) override {
return (countdown == 0 || --countdown > 0);
}
int countdown = 0;
} kFailMemory; // singleton, highly non-reentrant!
void SetMallocFailAt(int num_malloc_before_failure) {
kFailMemory.countdown = num_malloc_before_failure;
WP2SetMemoryHook(&kFailMemory);
}
// In order to generate only WP2_STATUS_OUT_OF_MEMORY failures, MemoryWriter is
// replaced by this class.
class NoAllocationFailureMemoryWriter : public Writer {
public:
void Reset() { buffer_.clear(); }
bool Append(const void* data, size_t data_size) override {
const uint8_t* const bytes = reinterpret_cast<const uint8_t*>(data);
buffer_.insert(buffer_.end(), bytes, bytes + data_size);
return true;
}
const uint8_t* Data() const {
return reinterpret_cast<const uint8_t*>(buffer_.data());
}
size_t Size() const { return buffer_.size(); }
private:
std::vector<uint8_t> buffer_;
};
class MallocFailureTest
: public testing::TestWithParam<
std::tuple<std::string, float, int, uint32_t, int, int>> {};
//------------------------------------------------------------------------------
TEST_P(MallocFailureTest, Encoding) {
const std::string& src_file_name = std::get<0>(GetParam());
const float quality = std::get<1>(GetParam());
const int effort = std::get<2>(GetParam());
const int thread_level = (std::get<3>(GetParam()) == 0) ? 0 : 1;
const int min_malloc_fail_at = std::get<4>(GetParam());
const int max_malloc_fail_at = std::get<5>(GetParam());
ArgbBuffer src;
NoAllocationFailureMemoryWriter data;
int malloc_fail_at = min_malloc_fail_at;
WP2Status status = WP2_STATUS_INVALID_PARAMETER;
while (malloc_fail_at <= max_malloc_fail_at) {
src.Deallocate();
data.Reset();
SetMallocFailAt(malloc_fail_at);
status = testutil::CompressImage(src_file_name, &data, &src, quality,
effort, thread_level);
if (status != WP2_STATUS_OK) {
ASSERT_EQ(status, WP2_STATUS_OUT_OF_MEMORY)
<< "Allocation failure number " << malloc_fail_at << " is uncaught";
} else {
break;
}
++malloc_fail_at;
}
std::cout << "Compressing " << src_file_name << " (quality " << quality
<< ", effort " << effort << ", mt " << thread_level << ")"
<< " took at least " << malloc_fail_at << " WP2Malloc() calls "
<< "(" << status << ")." << std::endl;
if (status == WP2_STATUS_OK) {
// Check a few more times that it was not a false positive.
for (int i = 1; i <= 5; ++i) {
SetMallocFailAt(malloc_fail_at + i);
ArgbBuffer another_src; // Keep first OK output though.
NoAllocationFailureMemoryWriter another_data;
ASSERT_WP2_OK(testutil::CompressImage(src_file_name, &another_data,
&another_src, quality, effort,
thread_level))
<< "False positives from " << malloc_fail_at << " to "
<< (malloc_fail_at + i - 1);
}
// Decode to see if it matches.
SetMallocFailAt(0);
ArgbBuffer output;
DecoderConfig config;
config.thread_level = 8;
ASSERT_WP2_OK(Decode(data.Data(), data.Size(), &output, config));
ASSERT_TRUE(testutil::Compare(src, output, src_file_name,
testutil::GetExpectedDistortion(quality)));
}
}
//------------------------------------------------------------------------------
TEST_P(MallocFailureTest, Decoding) {
const std::string& src_file_name = std::get<0>(GetParam());
const float quality = std::get<1>(GetParam());
const int effort = std::get<2>(GetParam());
const int thread_level = std::get<3>(GetParam());
const int min_malloc_fail_at = std::get<4>(GetParam());
const int max_malloc_fail_at = std::get<5>(GetParam());
// Compress without allocation failure.
ArgbBuffer src;
MemoryWriter data;
SetMallocFailAt(0);
ASSERT_WP2_OK(testutil::CompressImage(src_file_name, &data, &src, quality,
effort, /*thread_level=*/1));
ArgbBuffer output;
DecoderConfig config;
config.thread_level = (uint32_t)thread_level;
int malloc_fail_at = min_malloc_fail_at;
WP2Status status = WP2_STATUS_INVALID_PARAMETER;
while (malloc_fail_at <= max_malloc_fail_at) {
output.Deallocate();
SetMallocFailAt(malloc_fail_at);
status = Decode(data.mem_, data.size_, &output, config);
if (status != WP2_STATUS_OK) {
ASSERT_EQ(status, WP2_STATUS_OUT_OF_MEMORY)
<< "Allocation failure number " << malloc_fail_at << " is uncaught";
} else {
break;
}
++malloc_fail_at;
}
std::cout << "Decompressing " << src_file_name << " (quality " << quality
<< ", effort " << effort << ", mt " << thread_level << ")"
<< " took at least " << malloc_fail_at << " WP2Malloc() calls "
<< "(" << status << ")." << std::endl;
if (status == WP2_STATUS_OK) {
// Check a few more times that it was not a false positive.
for (int i = 1; i <= 5; ++i) {
SetMallocFailAt(malloc_fail_at + i);
ArgbBuffer another_output; // Keep first OK output though.
ASSERT_WP2_OK(Decode(data.mem_, data.size_, &another_output, config))
<< "False positives from " << malloc_fail_at << " to "
<< (malloc_fail_at + i - 1);
}
SetMallocFailAt(0);
ASSERT_TRUE(testutil::Compare(src, output, src_file_name,
testutil::GetExpectedDistortion(quality)));
}
}
//------------------------------------------------------------------------------
INSTANTIATE_TEST_SUITE_P(
MallocFailureTestInstantiationLossy, MallocFailureTest,
testing::Combine(testing::Values("source1_64x48.png"),
testing::Values(75.f) /* quality */,
testing::Values(0) /* effort */,
testing::Values(8) /* multithreading */,
testing::Values(3) /* min_malloc_fail_at */,
testing::Values(4) /* max_malloc_fail_at */));
INSTANTIATE_TEST_SUITE_P(
MallocFailureTestInstantiationLossless, MallocFailureTest,
testing::Combine(testing::Values("source1_64x48.png"),
testing::Values(100.f) /* quality */,
testing::Values(0) /* effort */,
testing::Values(8) /* multithreading */,
testing::Values(8) /* min_malloc_fail_at */,
testing::Values(9) /* max_malloc_fail_at */));
INSTANTIATE_TEST_SUITE_P(
MallocFailureTestInstantiationSmall, MallocFailureTest,
testing::Combine(testing::Values("source1_1x1.png"),
testing::Values(0.f, 99.f) /* quality */,
testing::Values(5) /* effort */,
testing::Values(0) /* multithreading */,
testing::Values(19) /* min_malloc_fail_at */,
testing::Values(20) /* max_malloc_fail_at */));
// This one might take ages so it is disabled.
// Can still be run with flag --test_arg=--gunit_also_run_disabled_tests
INSTANTIATE_TEST_SUITE_P(
DISABLED_MallocFailureTestInstantiation, MallocFailureTest,
testing::Combine(testing::Values("source1.png", "source1_1x1.png",
"source1_1x48.png", "source1_64x1.png",
"source1_64x48.png",
"alpha_ramp.lossy.webp", "source3.jpg",
"test_exif_xmp.webp"),
testing::Values(0.f, 50.f, 99.f, 100.f) /* quality */,
testing::Values(0, 5, 9) /* effort */,
testing::Values(0, 8) /* multithreading */,
testing::Values(1) /* min_malloc_fail_at */,
testing::Values(1000000) /* max_malloc_fail_at */));
//------------------------------------------------------------------------------
struct CtorStruct {
CtorStruct() noexcept : value(0) { ++counter; }
explicit CtorStruct(int v) : value(v) { ++counter; }
CtorStruct(const CtorStruct& o) noexcept : value(o.value) { ++counter; }
CtorStruct(CtorStruct&& o) noexcept : value(o.value) { ++counter; }
~CtorStruct() { --counter; }
int value;
static int counter;
};
int CtorStruct::counter;
TEST(MallocFailureTest, WP2VectorResize) {
CtorStruct::counter = -1;
const CtorStruct element(13);
{
Vector<CtorStruct> vector;
// Successfully put at least one element.
SetMallocFailAt(2);
size_t valid_size = 0;
while (vector.resize(valid_size + 1)) {
ASSERT_EQ(vector.size(), valid_size + 1);
valid_size = vector.size();
vector.back().value = element.value;
ASSERT_EQ(CtorStruct::counter, (int)valid_size);
}
ASSERT_GT(valid_size, 0u);
ASSERT_EQ(vector.size(), valid_size);
// Make sure data stored before failure can be retrieved.
SetMallocFailAt(0);
for (const CtorStruct& constructed : vector) {
ASSERT_EQ(constructed.value, element.value);
}
}
ASSERT_EQ(CtorStruct::counter, 0);
}
TEST(MallocFailureTest, WP2VectorPushBack) {
CtorStruct::counter = -1;
const CtorStruct element(13);
{
Vector<CtorStruct> vector;
// Successfully put at least one element.
SetMallocFailAt(2);
size_t valid_size = 0;
while (vector.push_back(element, /*resize_if_needed=*/true)) {
ASSERT_EQ(vector.size(), valid_size + 1);
valid_size = vector.size();
ASSERT_EQ(CtorStruct::counter, (int)valid_size);
}
ASSERT_GT(valid_size, 0u);
ASSERT_EQ(vector.size(), valid_size);
// Make sure data stored before failure can be retrieved.
SetMallocFailAt(0);
for (const CtorStruct& constructed : vector) {
ASSERT_EQ(constructed.value, element.value);
}
}
ASSERT_EQ(CtorStruct::counter, 0);
}
TEST(MallocFailureTest, WP2VectorReservePushBack) {
CtorStruct::counter = -1;
const CtorStruct element(13);
{
Vector<CtorStruct> vector;
// Successfully put at least one element.
SetMallocFailAt(2);
size_t valid_size = 0;
while (vector.reserve(valid_size + 1)) {
ASSERT_EQ(vector.size(), valid_size);
ASSERT_TRUE(vector.push_back(element, /*resize_if_needed=*/false));
ASSERT_EQ(vector.size(), valid_size + 1);
valid_size = vector.size();
ASSERT_EQ(CtorStruct::counter, (int)valid_size);
}
ASSERT_FALSE(vector.push_back(element, /*resize_if_needed=*/false));
ASSERT_GT(valid_size, 0u);
ASSERT_EQ(vector.size(), valid_size);
ASSERT_EQ(vector.capacity(), valid_size);
// Make sure data stored before failure can be retrieved.
SetMallocFailAt(0);
for (const CtorStruct& constructed : vector) {
ASSERT_EQ(constructed.value, element.value);
}
}
ASSERT_EQ(CtorStruct::counter, 0);
}
//------------------------------------------------------------------------------
} // namespace
} // namespace WP2