blob: d83d080a35fe563265f1ac3d340fee1f5fadfe82 [file] [log] [blame]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#ifndef LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_
#define LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_
#include <cstdint>
#include <cstdlib>
#include <vector>
#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "avifutil.h"
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
namespace avif {
namespace testutil {
//------------------------------------------------------------------------------
// C++ wrapper for scoped memory management of C API objects.
// Exposed for convenient fuzztest reproducer output.
ImagePtr CreateAvifImage8b(size_t width, size_t height,
avifPixelFormat pixel_format, bool has_alpha,
const std::vector<uint8_t>& samples);
ImagePtr CreateAvifImage16b(size_t width, size_t height, int depth,
avifPixelFormat pixel_format, bool has_alpha,
const std::vector<uint16_t>& samples);
std::vector<ImagePtr> CreateAvifAnim8b(size_t num_frames, size_t width,
size_t height,
avifPixelFormat pixel_format,
bool has_alpha,
const std::vector<uint8_t>& samples);
std::vector<ImagePtr> CreateAvifAnim16b(size_t num_frames, size_t width,
size_t height, int depth,
avifPixelFormat pixel_format,
bool has_alpha,
const std::vector<uint16_t>& samples);
EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads,
int min_quantizer, int max_quantizer,
int min_quantizer_alpha, int max_quantizer_alpha,
int tile_rows_log2, int tile_cols_log2, int speed);
DecoderPtr CreateAvifDecoder(avifCodecChoice codec_choice, int max_threads,
avifDecoderSource requested_source,
bool allow_progressive, bool allow_incremental,
bool ignore_exif, bool ignore_xmp,
uint32_t image_size_limit,
uint32_t image_dimension_limit,
uint32_t image_count_limit,
avifStrictFlags strict_flags);
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
enum class GainMapDecodeMode { kDontDecode, kMetadataOnly, kDecode };
DecoderPtr AddGainMapOptionsToDecoder(DecoderPtr decoder,
GainMapDecodeMode gain_map_decode_mode);
#endif
//------------------------------------------------------------------------------
// Custom fuzztest generators.
// See https://github.com/google/fuzztest/blob/main/doc/domains-reference.md.
// Do not generate images wider or taller than this.
inline constexpr size_t kMaxDimension = 512; // In pixels.
// Used to reduce kMaxDimension to keep the same complexity as a still image.
inline constexpr size_t kMaxNumFramesSquareRoot = 2;
// Do not generate animations with more than this number of frames.
inline constexpr size_t kMaxNumFrames =
kMaxNumFramesSquareRoot * kMaxNumFramesSquareRoot;
size_t GetNumSamples(size_t num_frames, size_t width, size_t height,
avifPixelFormat pixel_format, bool has_alpha);
// To avoid using fuzztest::internal, the return type of the functions below is
// auto.
inline auto ArbitraryPixelFormat() {
return fuzztest::ElementOf<avifPixelFormat>(
{AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422,
AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400});
}
// avifImage generator type: Width, height, pixel format and 8-bit samples.
inline auto ArbitraryAvifImage8b() {
return fuzztest::FlatMap(
[](size_t width, size_t height, avifPixelFormat pixel_format,
bool has_alpha) {
return fuzztest::Map(
CreateAvifImage8b, fuzztest::Just(width), fuzztest::Just(height),
fuzztest::Just(pixel_format), fuzztest::Just(has_alpha),
fuzztest::Arbitrary<std::vector<uint8_t>>().WithSize(GetNumSamples(
/*num_frames=*/1, width, height, pixel_format, has_alpha)));
},
fuzztest::InRange<uint16_t>(1, kMaxDimension),
fuzztest::InRange<uint16_t>(1, kMaxDimension), ArbitraryPixelFormat(),
fuzztest::Arbitrary<bool>());
}
// avifImage generator type: Width, height, depth, pixel format and 16-bit
// samples.
inline auto ArbitraryAvifImage16b() {
return fuzztest::FlatMap(
[](size_t width, size_t height, int depth, avifPixelFormat pixel_format,
bool has_alpha) {
return fuzztest::Map(
CreateAvifImage16b, fuzztest::Just(width), fuzztest::Just(height),
fuzztest::Just(depth), fuzztest::Just(pixel_format),
fuzztest::Just(has_alpha),
fuzztest::ContainerOf<std::vector<uint16_t>>(
fuzztest::InRange<uint16_t>(0, (1 << depth) - 1))
.WithSize(GetNumSamples(/*num_frames=*/1, width, height,
pixel_format, has_alpha)));
},
fuzztest::InRange<uint16_t>(1, kMaxDimension),
fuzztest::InRange<uint16_t>(1, kMaxDimension),
fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(),
fuzztest::Arbitrary<bool>());
}
// avifImage generator type: Number of frames, width, height, pixel format and
// 8-bit samples.
inline auto ArbitraryAvifAnim8b() {
return fuzztest::FlatMap(
[](size_t num_frames, size_t width, size_t height,
avifPixelFormat pixel_format, bool has_alpha) {
return fuzztest::Map(
CreateAvifAnim8b, fuzztest::Just(num_frames), fuzztest::Just(width),
fuzztest::Just(height), fuzztest::Just(pixel_format),
fuzztest::Just(has_alpha),
fuzztest::Arbitrary<std::vector<uint8_t>>().WithSize(GetNumSamples(
num_frames, width, height, pixel_format, has_alpha)));
},
fuzztest::InRange<uint16_t>(1, kMaxNumFrames),
fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
ArbitraryPixelFormat(), fuzztest::Arbitrary<bool>());
}
// avifImage generator type: Number of frames, width, height, depth, pixel
// format and 16-bit samples.
inline auto ArbitraryAvifAnim16b() {
return fuzztest::FlatMap(
[](size_t num_frames, size_t width, size_t height, int depth,
avifPixelFormat pixel_format, bool has_alpha) {
return fuzztest::Map(
CreateAvifAnim16b, fuzztest::Just(num_frames),
fuzztest::Just(width), fuzztest::Just(height),
fuzztest::Just(depth), fuzztest::Just(pixel_format),
fuzztest::Just(has_alpha),
fuzztest::ContainerOf<std::vector<uint16_t>>(
fuzztest::InRange<uint16_t>(0, (1 << depth) - 1))
.WithSize(GetNumSamples(num_frames, width, height, pixel_format,
has_alpha)));
},
fuzztest::InRange<uint16_t>(1, kMaxNumFrames),
fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(),
fuzztest::Arbitrary<bool>());
}
// Generator for an arbitrary ImagePtr.
inline auto ArbitraryAvifImage() {
return fuzztest::OneOf(ArbitraryAvifImage8b(), ArbitraryAvifImage16b());
}
// Generator for an arbitrary std::vector<ImagePtr>.
inline auto ArbitraryAvifAnim() {
return fuzztest::OneOf(ArbitraryAvifAnim8b(), ArbitraryAvifAnim16b());
}
// Generator for an arbitrary EncoderPtr.
inline auto ArbitraryAvifEncoder() {
const auto codec_choice = fuzztest::ElementOf<avifCodecChoice>(
{AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_CHOICE_AOM});
// MAX_NUM_THREADS from libaom/aom_util/aom_thread.h
const auto max_threads = fuzztest::InRange(0, 64);
const auto min_quantizer = fuzztest::InRange(AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY);
const auto max_quantizer = fuzztest::InRange(AVIF_QUANTIZER_BEST_QUALITY,
AVIF_QUANTIZER_WORST_QUALITY);
const auto min_quantizer_alpha = fuzztest::InRange(
AVIF_QUANTIZER_BEST_QUALITY, AVIF_QUANTIZER_WORST_QUALITY);
const auto max_quantizer_alpha = fuzztest::InRange(
AVIF_QUANTIZER_BEST_QUALITY, AVIF_QUANTIZER_WORST_QUALITY);
const auto tile_rows_log2 = fuzztest::InRange(0, 6);
const auto tile_cols_log2 = fuzztest::InRange(0, 6);
// Fuzz only a small range of 'speed' values to avoid slowing down the fuzzer
// too much. The main goal is to fuzz libavif, not the underlying AV1 encoder.
const auto speed = fuzztest::InRange(6, AVIF_SPEED_FASTEST);
return fuzztest::Map(CreateAvifEncoder, codec_choice, max_threads,
min_quantizer, max_quantizer, min_quantizer_alpha,
max_quantizer_alpha, tile_rows_log2, tile_cols_log2,
speed);
}
// Generator for an arbitrary DecoderPtr with base options fuzzed (i.e.
// without "experimental" options hidden behind compile flags).
inline auto ArbitraryBaseAvifDecoder() {
// MAX_NUM_THREADS from libaom/aom_util/aom_thread.h
const auto max_threads = fuzztest::InRange(0, 64);
return fuzztest::Map(
CreateAvifDecoder,
fuzztest::ElementOf<avifCodecChoice>({AVIF_CODEC_CHOICE_AUTO,
AVIF_CODEC_CHOICE_AOM,
AVIF_CODEC_CHOICE_DAV1D}),
max_threads,
/*requested_source=*/
fuzztest::ElementOf(
{AVIF_DECODER_SOURCE_AUTO, AVIF_DECODER_SOURCE_PRIMARY_ITEM}),
/*allow_progressive=*/fuzztest::Arbitrary<bool>(),
/*allow_incremental=*/fuzztest::Arbitrary<bool>(),
/*ignore_exif=*/fuzztest::Arbitrary<bool>(),
/*ignore_xmp=*/fuzztest::Arbitrary<bool>(),
/*image_size_limit=*/fuzztest::Just(kMaxDimension * kMaxDimension),
/*image_dimension_limit=*/fuzztest::Just(kMaxDimension),
/*image_count_limit=*/fuzztest::Just(10),
/*strict_flags=*/
fuzztest::BitFlagCombinationOf<avifStrictFlags>(
{AVIF_STRICT_PIXI_REQUIRED, AVIF_STRICT_CLAP_VALID,
AVIF_STRICT_ALPHA_ISPE_REQUIRED}));
}
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
// Generator for an arbitrary DecoderPtr with base options and gain map
// options fuzzed, with the exception of 'ignoreColorAndAlpha' (because it would
// break most tests' assumptions).
inline auto ArbitraryAvifDecoderWithGainMapOptions() {
return fuzztest::Map(AddGainMapOptionsToDecoder, ArbitraryBaseAvifDecoder(),
fuzztest::ElementOf({GainMapDecodeMode::kDontDecode,
GainMapDecodeMode::kMetadataOnly,
GainMapDecodeMode::kDecode}));
}
// Generator for an arbitrary DecoderPtr.
inline auto ArbitraryAvifDecoder() {
return ArbitraryAvifDecoderWithGainMapOptions();
}
#else
// Generator for an arbitrary DecoderPtr.
inline auto ArbitraryAvifDecoder() { return ArbitraryBaseAvifDecoder(); }
#endif // AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP
//------------------------------------------------------------------------------
// Environment setup
// Sets the environment variable 'name' to the 'value' during the setup step.
::testing::Environment* SetEnv(const char* name, const char* value);
inline ::testing::Environment* SetStackLimitTo512x1024Bytes() {
return SetEnv("FUZZTEST_STACK_LIMIT", "524288");
}
//------------------------------------------------------------------------------
// Returns the paths contained in the 'TEST_DATA_DIRS' environment variable.
// Several paths can be set in the variable, separated by ';'.
// Returns nullptr if not set.
// Tests that use ArbitraryImageWithSeeds() should
// ASSERT_FALSE(GetSeedDataDirs().empty()) if they want to make sure that seeds
// are actually used.
std::vector<std::string> GetSeedDataDirs();
// Returns a list of test images contents (not paths) from the directory set in
// the 'TEST_DATA_DIRS' environment variable, that are smaller than
// 'max_file_size' and have one of the formats in 'image_formats' (or any format
// if 'image_formats' is empty).
// If TEST_DATA_DIRS is not set, returns an empty set.
// Tests that use this should ASSERT_FALSE(GetSeedDataDirs().empty())
// if they want to make sure that seeds are actually used.
// Terminates the program with abort() if TEST_DATA_DIRS is set but doesn't
// contain any matching images.
std::vector<std::string> GetTestImagesContents(
size_t max_file_size, const std::vector<avifAppFileFormat>& image_formats);
// Generator for an arbitrary ImagePtr that uses test image files as seeds.
// Uses the 'TEST_DATA_DIRS' environment variable to load the seeds.
// If TEST_DATA_DIRS is not set, no seeds are used.
// Tests that use this should ASSERT_FALSE(GetSeedDataDirs().empty())
// if they want to make sure that seeds are actually used.
// Terminates the program with abort() if TEST_DATA_DIRS is set but doesn't
// contain any matching images.
inline auto ArbitraryImageWithSeeds(
const std::vector<avifAppFileFormat>& image_formats) {
constexpr uint32_t kMaxSeedFileSize = 1024 * 1024; // 1MB.
return fuzztest::Arbitrary<std::string>()
.WithMaxSize(kMaxSeedFileSize)
.WithSeeds(GetTestImagesContents(kMaxSeedFileSize, image_formats));
}
//------------------------------------------------------------------------------
} // namespace testutil
} // namespace avif
#endif // LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_