Coverage Report

Created: 2026-04-28 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/tests/gtest/avif_fuzztest_helpers.h
Line
Count
Source
1
// Copyright 2022 Google LLC
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#ifndef LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_
5
#define LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_
6
7
#include <cstdint>
8
#include <cstdlib>
9
#include <limits>
10
#include <type_traits>
11
#include <utility>
12
#include <vector>
13
14
#include "avif/avif.h"
15
#include "aviftest_helpers.h"
16
#include "avifutil.h"
17
#include "fuzztest/fuzztest.h"
18
#include "gtest/gtest.h"
19
20
namespace avif {
21
22
//------------------------------------------------------------------------------
23
// Custom FuzzTest printer for libavif types. Needed to avoid compile-time
24
// errors due to structs with C-style array fields.
25
//
26
// These should reside in the same namespace as the types they print to enable
27
// Argument-Dependent Lookup (ADL).
28
// See https://github.com/google/fuzztest/blob/main/doc/domains-reference.md.
29
template <typename Sink, typename Ptr,
30
          typename = std::enable_if_t<std::is_same_v<Ptr, ImagePtr> ||
31
                                      std::is_same_v<Ptr, EncoderPtr> ||
32
                                      std::is_same_v<Ptr, DecoderPtr>>>
33
void FuzzTestPrintSourceCode(Sink& sink, const Ptr& ptr) {
34
  // Stub to avoid compile error for structs with C-style array fields.
35
  // Consider adding a useful source code representation here:
36
  //   `absl::Format(&sink, ...);`
37
}
38
39
namespace testutil {
40
41
//------------------------------------------------------------------------------
42
// C++ wrapper for scoped memory management of C API objects.
43
44
// Exposed for convenient fuzztest reproducer output.
45
ImagePtr CreateAvifImage8b(size_t width, size_t height,
46
                           avifPixelFormat pixel_format, bool has_alpha,
47
                           const std::vector<uint8_t>& samples);
48
ImagePtr CreateAvifImage16b(size_t width, size_t height, int depth,
49
                            avifPixelFormat pixel_format, bool has_alpha,
50
                            const std::vector<uint16_t>& samples);
51
std::vector<ImagePtr> CreateAvifAnim8b(size_t num_frames, size_t width,
52
                                       size_t height,
53
                                       avifPixelFormat pixel_format,
54
                                       bool has_alpha,
55
                                       const std::vector<uint8_t>& samples);
56
std::vector<ImagePtr> CreateAvifAnim16b(size_t num_frames, size_t width,
57
                                        size_t height, int depth,
58
                                        avifPixelFormat pixel_format,
59
                                        bool has_alpha,
60
                                        const std::vector<uint16_t>& samples);
61
EncoderPtr CreateAvifEncoder(avifCodecChoice codec_choice, int max_threads,
62
                             int min_quantizer, int max_quantizer,
63
                             int min_quantizer_alpha, int max_quantizer_alpha,
64
                             int tile_rows_log2, int tile_cols_log2, int speed);
65
DecoderPtr CreateAvifDecoder(avifCodecChoice codec_choice, int max_threads,
66
                             avifDecoderSource requested_source,
67
                             bool allow_progressive, bool allow_incremental,
68
                             bool ignore_exif, bool ignore_xmp,
69
                             uint32_t image_size_limit,
70
                             uint32_t image_dimension_limit,
71
                             uint32_t image_count_limit,
72
                             avifStrictFlags strict_flags);
73
DecoderPtr AddGainMapOptionsToDecoder(
74
    DecoderPtr decoder, avifImageContentTypeFlags image_content_to_decode);
75
76
//------------------------------------------------------------------------------
77
// Custom fuzztest generators.
78
// See https://github.com/google/fuzztest/blob/main/doc/domains-reference.md.
79
80
// Do not generate images wider or taller than this.
81
inline constexpr size_t kMaxDimension = 512;  // In pixels.
82
83
// Used to reduce kMaxDimension to keep the same complexity as a still image.
84
inline constexpr size_t kMaxNumFramesSquareRoot = 2;
85
// Do not generate animations with more than this number of frames.
86
inline constexpr size_t kMaxNumFrames =
87
    kMaxNumFramesSquareRoot * kMaxNumFramesSquareRoot;
88
89
size_t GetNumSamples(size_t num_frames, size_t width, size_t height,
90
                     avifPixelFormat pixel_format, bool has_alpha);
91
92
// To avoid using fuzztest::internal, the return type of the functions below is
93
// auto.
94
95
38
inline auto ArbitraryPixelFormat() {
96
38
  return fuzztest::ElementOf<avifPixelFormat>(
97
38
      {AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV422,
98
38
       AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400});
99
38
}
100
101
// avifImage generator type: Width, height, pixel format and 8-bit samples.
102
16
inline auto ArbitraryAvifImage8b() {
103
16
  return fuzztest::FlatMap(
104
16
      [](size_t width, size_t height, avifPixelFormat pixel_format,
105
82.1k
         bool has_alpha) {
106
82.1k
        return fuzztest::Map(
107
82.1k
            CreateAvifImage8b, fuzztest::Just(width), fuzztest::Just(height),
108
82.1k
            fuzztest::Just(pixel_format), fuzztest::Just(has_alpha),
109
82.1k
            fuzztest::Arbitrary<std::vector<uint8_t>>().WithSize(GetNumSamples(
110
82.1k
                /*num_frames=*/1, width, height, pixel_format, has_alpha)));
111
82.1k
      },
112
16
      fuzztest::InRange<uint16_t>(1, kMaxDimension),
113
16
      fuzztest::InRange<uint16_t>(1, kMaxDimension), ArbitraryPixelFormat(),
114
16
      fuzztest::Arbitrary<bool>());
115
16
}
116
117
// avifImage generator type: Width, height, depth, pixel format and 16-bit
118
// samples.
119
16
inline auto ArbitraryAvifImage16b() {
120
16
  return fuzztest::FlatMap(
121
16
      [](size_t width, size_t height, int depth, avifPixelFormat pixel_format,
122
54.1k
         bool has_alpha) {
123
54.1k
        return fuzztest::Map(
124
54.1k
            CreateAvifImage16b, fuzztest::Just(width), fuzztest::Just(height),
125
54.1k
            fuzztest::Just(depth), fuzztest::Just(pixel_format),
126
54.1k
            fuzztest::Just(has_alpha),
127
54.1k
            fuzztest::ContainerOf<std::vector<uint16_t>>(
128
54.1k
                fuzztest::InRange<uint16_t>(0, (1 << depth) - 1))
129
54.1k
                .WithSize(GetNumSamples(/*num_frames=*/1, width, height,
130
54.1k
                                        pixel_format, has_alpha)));
131
54.1k
      },
132
16
      fuzztest::InRange<uint16_t>(1, kMaxDimension),
133
16
      fuzztest::InRange<uint16_t>(1, kMaxDimension),
134
16
      fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(),
135
16
      fuzztest::Arbitrary<bool>());
136
16
}
137
138
// avifImage generator type: Number of frames, width, height, pixel format and
139
// 8-bit samples.
140
2
inline auto ArbitraryAvifAnim8b() {
141
2
  return fuzztest::FlatMap(
142
2
      [](size_t num_frames, size_t width, size_t height,
143
33.9k
         avifPixelFormat pixel_format, bool has_alpha) {
144
33.9k
        return fuzztest::Map(
145
33.9k
            CreateAvifAnim8b, fuzztest::Just(num_frames), fuzztest::Just(width),
146
33.9k
            fuzztest::Just(height), fuzztest::Just(pixel_format),
147
33.9k
            fuzztest::Just(has_alpha),
148
33.9k
            fuzztest::Arbitrary<std::vector<uint8_t>>().WithSize(GetNumSamples(
149
33.9k
                num_frames, width, height, pixel_format, has_alpha)));
150
33.9k
      },
151
2
      fuzztest::InRange<uint16_t>(1, kMaxNumFrames),
152
2
      fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
153
2
      fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
154
2
      ArbitraryPixelFormat(), fuzztest::Arbitrary<bool>());
155
2
}
156
157
// avifImage generator type: Number of frames, width, height, depth, pixel
158
// format and 16-bit samples.
159
2
inline auto ArbitraryAvifAnim16b() {
160
2
  return fuzztest::FlatMap(
161
2
      [](size_t num_frames, size_t width, size_t height, int depth,
162
14.7k
         avifPixelFormat pixel_format, bool has_alpha) {
163
14.7k
        return fuzztest::Map(
164
14.7k
            CreateAvifAnim16b, fuzztest::Just(num_frames),
165
14.7k
            fuzztest::Just(width), fuzztest::Just(height),
166
14.7k
            fuzztest::Just(depth), fuzztest::Just(pixel_format),
167
14.7k
            fuzztest::Just(has_alpha),
168
14.7k
            fuzztest::ContainerOf<std::vector<uint16_t>>(
169
14.7k
                fuzztest::InRange<uint16_t>(0, (1 << depth) - 1))
170
14.7k
                .WithSize(GetNumSamples(num_frames, width, height, pixel_format,
171
14.7k
                                        has_alpha)));
172
14.7k
      },
173
2
      fuzztest::InRange<uint16_t>(1, kMaxNumFrames),
174
2
      fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
175
2
      fuzztest::InRange<uint16_t>(1, kMaxDimension / kMaxNumFramesSquareRoot),
176
2
      fuzztest::ElementOf({10, 12}), ArbitraryPixelFormat(),
177
2
      fuzztest::Arbitrary<bool>());
178
2
}
179
180
// Generator for an arbitrary ImagePtr.
181
16
inline auto ArbitraryAvifImage() {
182
16
  return fuzztest::OneOf(ArbitraryAvifImage8b(), ArbitraryAvifImage16b());
183
16
}
184
185
// Generator for an arbitrary std::vector<ImagePtr>.
186
2
inline auto ArbitraryAvifAnim() {
187
2
  return fuzztest::OneOf(ArbitraryAvifAnim8b(), ArbitraryAvifAnim16b());
188
2
}
189
190
// Generates two signed fractions where the first one is smaller than or equal
191
// to the second one.
192
12
inline auto ArbitraryMinMaxSignedFraction() {
193
12
  return fuzztest::FlatMap(
194
29.2k
      [](int32_t max_n, uint32_t max_d) {
195
29.2k
        return fuzztest::Map(
196
29.2k
            [max_n, max_d](int32_t min_n) {
197
              // For simplicity, use the same denominator for both fractions.
198
              // This does not cover all possible fractions but makes it easy
199
              // to guarantee that the first fraction is smaller.
200
9.68k
              return std::pair<avifSignedFraction, avifSignedFraction>(
201
9.68k
                  {min_n, max_d}, {max_n, max_d});
202
9.68k
            },
203
29.2k
            fuzztest::InRange<int32_t>(std::numeric_limits<int32_t>::min(),
204
29.2k
                                       max_n));
205
29.2k
      },
206
12
      fuzztest::Arbitrary<int32_t>(), fuzztest::NonZero<uint32_t>());
207
12
}
208
209
ImagePtr AddGainMapToImage(
210
    ImagePtr image, ImagePtr gain_map,
211
    const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max0,
212
    const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max1,
213
    const std::pair<avifSignedFraction, avifSignedFraction>& gain_map_min_max2,
214
    uint32_t gain_map_gamma_n0, uint32_t gain_map_gamma_n1,
215
    uint32_t gain_map_gamma_n2, uint32_t gain_map_gamma_d0,
216
    uint32_t gain_map_gamma_d1, uint32_t gain_map_gamma_d2,
217
    int32_t base_offset_n0, int32_t base_offset_n1, int32_t base_offset_n2,
218
    uint32_t base_offset_d0, uint32_t base_offset_d1, uint32_t base_offset_d2,
219
    int32_t alternate_offset_n0, int32_t alternate_offset_n1,
220
    int32_t alternate_offset_n2, uint32_t alternate_offset_d0,
221
    uint32_t alternate_offset_d1, uint32_t alternate_offset_d2,
222
    uint32_t base_hdr_headroom_n, uint32_t base_hdr_headroom_d,
223
    uint32_t alternate_hdr_headroom_n, uint32_t alternate_hdr_headroom_d,
224
    bool use_base_color_space);
225
226
4
inline auto ArbitraryAvifImageWithGainMap() {
227
4
  return fuzztest::Map(
228
4
      AddGainMapToImage, ArbitraryAvifImage(),
229
4
      /*gain_map=*/ArbitraryAvifImage(),
230
4
      /*gain_map_min_max0=*/ArbitraryMinMaxSignedFraction(),
231
4
      /*gain_map_min_max1=*/ArbitraryMinMaxSignedFraction(),
232
4
      /*gain_map_min_max2=*/ArbitraryMinMaxSignedFraction(),
233
4
      /*gain_map_gamma_n0=*/fuzztest::NonZero<uint32_t>(),
234
4
      /*gain_map_gamma_n1=*/fuzztest::NonZero<uint32_t>(),
235
4
      /*gain_map_gamma_n2=*/fuzztest::NonZero<uint32_t>(),
236
4
      /*gain_map_gamma_d0=*/fuzztest::NonZero<uint32_t>(),
237
4
      /*gain_map_gamma_d1=*/fuzztest::NonZero<uint32_t>(),
238
4
      /*gain_map_gamma_d2=*/fuzztest::NonZero<uint32_t>(),
239
4
      /*base_offset_n0=*/fuzztest::Arbitrary<int32_t>(),
240
4
      /*base_offset_n1=*/fuzztest::Arbitrary<int32_t>(),
241
4
      /*base_offset_n2=*/fuzztest::Arbitrary<int32_t>(),
242
4
      /*base_offset_d0=*/fuzztest::NonZero<uint32_t>(),
243
4
      /*base_offset_d1=*/fuzztest::NonZero<uint32_t>(),
244
4
      /*base_offset_d2=*/fuzztest::NonZero<uint32_t>(),
245
4
      /*alternate_offset_n0=*/fuzztest::Arbitrary<int32_t>(),
246
4
      /*alternate_offset_n1=*/fuzztest::Arbitrary<int32_t>(),
247
4
      /*alternate_offset_n2=*/fuzztest::Arbitrary<int32_t>(),
248
4
      /*alternate_offset_d0=*/fuzztest::NonZero<uint32_t>(),
249
4
      /*alternate_offset_d1=*/fuzztest::NonZero<uint32_t>(),
250
4
      /*alternate_offset_d2=*/fuzztest::NonZero<uint32_t>(),
251
4
      /*base_hdr_headroom_n=*/fuzztest::Arbitrary<uint32_t>(),
252
4
      /*base_hdr_headroom_d=*/fuzztest::NonZero<uint32_t>(),
253
4
      /*alternate_hdr_headroom_n=*/fuzztest::Arbitrary<uint32_t>(),
254
4
      /*alternate_hdr_headroom_d=*/fuzztest::NonZero<uint32_t>(),
255
4
      /*use_base_color_space=*/fuzztest::Arbitrary<bool>());
256
4
}
257
258
// Generator for an arbitrary EncoderPtr.
259
8
inline auto ArbitraryAvifEncoder() {
260
8
  const auto codec_choice = fuzztest::ElementOf<avifCodecChoice>(
261
8
      {AVIF_CODEC_CHOICE_AUTO, AVIF_CODEC_CHOICE_AOM});
262
  // MAX_NUM_THREADS from libaom/aom_util/aom_thread.h
263
8
  const auto max_threads = fuzztest::InRange(0, 64);
264
8
  const auto min_quantizer = fuzztest::InRange(AVIF_QUANTIZER_BEST_QUALITY,
265
8
                                               AVIF_QUANTIZER_WORST_QUALITY);
266
8
  const auto max_quantizer = fuzztest::InRange(AVIF_QUANTIZER_BEST_QUALITY,
267
8
                                               AVIF_QUANTIZER_WORST_QUALITY);
268
8
  const auto min_quantizer_alpha = fuzztest::InRange(
269
8
      AVIF_QUANTIZER_BEST_QUALITY, AVIF_QUANTIZER_WORST_QUALITY);
270
8
  const auto max_quantizer_alpha = fuzztest::InRange(
271
8
      AVIF_QUANTIZER_BEST_QUALITY, AVIF_QUANTIZER_WORST_QUALITY);
272
8
  const auto tile_rows_log2 = fuzztest::InRange(0, 6);
273
8
  const auto tile_cols_log2 = fuzztest::InRange(0, 6);
274
  // Fuzz only a small range of 'speed' values to avoid slowing down the fuzzer
275
  // too much. The main goal is to fuzz libavif, not the underlying AV1 encoder.
276
8
  const auto speed = fuzztest::InRange(6, AVIF_SPEED_FASTEST);
277
8
  return fuzztest::Map(CreateAvifEncoder, codec_choice, max_threads,
278
8
                       min_quantizer, max_quantizer, min_quantizer_alpha,
279
8
                       max_quantizer_alpha, tile_rows_log2, tile_cols_log2,
280
8
                       speed);
281
8
}
282
283
// Generator for an arbitrary DecoderPtr with base options fuzzed (i.e.
284
// without "experimental" options hidden behind compile flags).
285
16
inline auto ArbitraryBaseAvifDecoder() {
286
  // MAX_NUM_THREADS from libaom/aom_util/aom_thread.h
287
16
  const auto max_threads = fuzztest::InRange(0, 64);
288
16
  return fuzztest::Map(
289
16
      CreateAvifDecoder,
290
16
      fuzztest::ElementOf<avifCodecChoice>({AVIF_CODEC_CHOICE_AUTO,
291
16
                                            AVIF_CODEC_CHOICE_AOM,
292
16
                                            AVIF_CODEC_CHOICE_DAV1D}),
293
16
      max_threads,
294
      /*requested_source=*/
295
16
      fuzztest::ElementOf(
296
16
          {AVIF_DECODER_SOURCE_AUTO, AVIF_DECODER_SOURCE_PRIMARY_ITEM}),
297
16
      /*allow_progressive=*/fuzztest::Arbitrary<bool>(),
298
16
      /*allow_incremental=*/fuzztest::Arbitrary<bool>(),
299
16
      /*ignore_exif=*/fuzztest::Arbitrary<bool>(),
300
16
      /*ignore_xmp=*/fuzztest::Arbitrary<bool>(),
301
16
      /*image_size_limit=*/fuzztest::Just(kMaxDimension * kMaxDimension),
302
16
      /*image_dimension_limit=*/fuzztest::Just(kMaxDimension),
303
16
      /*image_count_limit=*/fuzztest::Just(10),
304
      /*strict_flags=*/
305
16
      fuzztest::BitFlagCombinationOf<avifStrictFlags>(
306
16
          {AVIF_STRICT_PIXI_REQUIRED, AVIF_STRICT_CLAP_VALID,
307
16
           AVIF_STRICT_ALPHA_ISPE_REQUIRED}));
308
16
}
309
310
// Generator for an arbitrary DecoderPtr with base options and gain map
311
// options fuzzed.
312
8
inline auto ArbitraryAvifDecoderWithGainMapOptions() {
313
  // Always decode at least color+alpha, since most tests
314
  // assume that if the file/buffer is successfully decoded.
315
8
  return fuzztest::Map(
316
8
      AddGainMapOptionsToDecoder, ArbitraryBaseAvifDecoder(),
317
8
      fuzztest::ElementOf<avifImageContentTypeFlags>(
318
8
          {AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
319
8
           AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP,
320
8
           AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA |
321
8
               AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS,
322
8
           AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA | AVIF_IMAGE_CONTENT_GAIN_MAP |
323
8
               AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}));
324
8
}
325
326
// Generator for an arbitrary DecoderPtr.
327
8
inline auto ArbitraryAvifDecoder() {
328
8
  return ArbitraryAvifDecoderWithGainMapOptions();
329
8
}
330
331
// Same as ArbitraryAvifDecoder() but imageContentToDecode can be set to
332
// AVIF_IMAGE_CONTENT_NONE.
333
8
inline auto ArbitraryAvifDecoderPossiblyNoContent() {
334
8
  return fuzztest::Map(
335
8
      AddGainMapOptionsToDecoder, ArbitraryBaseAvifDecoder(),
336
8
      fuzztest::BitFlagCombinationOf<avifImageContentTypeFlags>(
337
8
          {AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA, AVIF_IMAGE_CONTENT_GAIN_MAP}));
338
8
}
339
340
//------------------------------------------------------------------------------
341
342
// Returns the paths contained in the 'TEST_DATA_DIRS' environment variable.
343
// Several paths can be set in the variable, separated by ';'.
344
// Returns nullptr if not set.
345
// Tests that use ArbitraryImageWithSeeds() should
346
// ASSERT_FALSE(GetSeedDataDirs().empty()) if they want to make sure that seeds
347
// are actually used.
348
std::vector<std::string> GetSeedDataDirs();
349
350
// Returns a list of test images contents (not paths) from the directory set in
351
// the 'TEST_DATA_DIRS' environment variable, that are smaller than
352
// 'max_file_size' and have one of the formats in 'image_formats' (or any format
353
// if 'image_formats' is empty).
354
// If TEST_DATA_DIRS is not set, returns an empty set.
355
// Tests that use this should ASSERT_FALSE(GetSeedDataDirs().empty())
356
// if they want to make sure that seeds are actually used.
357
// Terminates the program with abort() if TEST_DATA_DIRS is set but doesn't
358
// contain any matching images.
359
std::vector<std::string> GetTestImagesContents(
360
    size_t max_file_size, const std::vector<avifAppFileFormat>& image_formats);
361
362
// Generator for an arbitrary ImagePtr that uses test image files as seeds.
363
// Uses the 'TEST_DATA_DIRS' environment variable to load the seeds.
364
// If TEST_DATA_DIRS is not set, no seeds are used.
365
// Tests that use this should ASSERT_FALSE(GetSeedDataDirs().empty())
366
// if they want to make sure that seeds are actually used.
367
// Terminates the program with abort() if TEST_DATA_DIRS is set but doesn't
368
// contain any matching images.
369
inline auto ArbitraryImageWithSeeds(
370
12
    const std::vector<avifAppFileFormat>& image_formats) {
371
12
  constexpr uint32_t kMaxSeedFileSize = 1024 * 1024;  // 1MB.
372
12
  return fuzztest::Arbitrary<std::string>()
373
12
      .WithMaxSize(kMaxSeedFileSize)
374
12
      .WithSeeds(GetTestImagesContents(kMaxSeedFileSize, image_formats));
375
12
}
376
377
//------------------------------------------------------------------------------
378
379
}  // namespace testutil
380
}  // namespace avif
381
382
#endif  // LIBAVIF_TESTS_OSS_FUZZ_AVIF_FUZZTEST_HELPERS_H_