/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_ |