Coverage Report

Created: 2026-01-17 06:56

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