/src/libjxl/tools/streaming_fuzzer.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include <jxl/codestream_header.h> |
7 | | #include <jxl/color_encoding.h> |
8 | | #include <jxl/decode.h> |
9 | | #include <jxl/decode_cxx.h> |
10 | | #include <jxl/encode.h> |
11 | | #include <jxl/encode_cxx.h> |
12 | | #include <jxl/thread_parallel_runner.h> |
13 | | #include <jxl/thread_parallel_runner_cxx.h> |
14 | | #include <jxl/types.h> |
15 | | |
16 | | #include <cstdint> |
17 | | #include <cstdlib> |
18 | | #include <cstring> |
19 | | #include <vector> |
20 | | |
21 | | #include "lib/jxl/base/compiler_specific.h" |
22 | | #include "lib/jxl/base/status.h" |
23 | | #include "lib/jxl/fuzztest.h" |
24 | | #include "tools/tracking_memory_manager.h" |
25 | | |
26 | | namespace { |
27 | | |
28 | | using ::jpegxl::tools::kGiB; |
29 | | using ::jpegxl::tools::TrackingMemoryManager; |
30 | | using ::jxl::Status; |
31 | | using ::jxl::StatusOr; |
32 | | |
33 | 0 | void Check(bool ok) { |
34 | 0 | if (!ok) { |
35 | 0 | JXL_CRASH(); |
36 | 0 | } |
37 | 0 | } |
38 | | |
39 | | struct FuzzSpec { |
40 | | uint32_t xsize; |
41 | | uint32_t ysize; |
42 | | bool grayscale; |
43 | | bool alpha; |
44 | | uint8_t bit_depth; // 1 - 16 |
45 | | |
46 | | struct IntOptionSpec { |
47 | | JxlEncoderFrameSettingId flag; |
48 | | int min; |
49 | | int max; |
50 | | int value; |
51 | | }; |
52 | | |
53 | | std::vector<IntOptionSpec> int_options = { |
54 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_EFFORT, 1, 9, 0}, |
55 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_DECODING_SPEED, 0, 4, 0}, |
56 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_NOISE, -1, 1, 0}, |
57 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_DOTS, -1, 1, 0}, |
58 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_PATCHES, -1, 1, 0}, |
59 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_EPF, -1, 3, 0}, |
60 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_GABORISH, -1, 1, 0}, |
61 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR, -1, 1, 0}, |
62 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE, -1, 1, 0}, |
63 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_RESPONSIVE, -1, 1, 0}, |
64 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, -1, 1, 0}, |
65 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, -1, 1, 0}, |
66 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, -1, 1, 0}, |
67 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_PALETTE_COLORS, -1, 255, 0}, |
68 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, -1, 1, 0}, |
69 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, -1, 2, 0}, |
70 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, -1, 41, 0}, |
71 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, -1, 3, 0}, |
72 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, -1, 15, 0}, |
73 | | IntOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, -1, 11, 0}, |
74 | | }; |
75 | | |
76 | | struct FloatOptionSpec { |
77 | | JxlEncoderFrameSettingId flag; |
78 | | float possible_values[4]; |
79 | | float value; |
80 | | }; |
81 | | |
82 | | std::vector<FloatOptionSpec> float_options = { |
83 | | FloatOptionSpec{JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, |
84 | | {-1, 0, 50, 100}, |
85 | | -1}, |
86 | | FloatOptionSpec{JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, |
87 | | {-1, 0, 50, 100}, |
88 | | -1}, |
89 | | FloatOptionSpec{JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, |
90 | | {-1, 0, 50, 100}, |
91 | | -1}, |
92 | | FloatOptionSpec{ |
93 | | JXL_ENC_FRAME_SETTING_PHOTON_NOISE, {-1, 200, 1600, 10000}, -1}, |
94 | | }; |
95 | | |
96 | | uint8_t num_threads; |
97 | | |
98 | | float distance; // 0.01 - 25 |
99 | | |
100 | | // Tiled to cover the entire image area. |
101 | | uint16_t pixel_data[4][64][64]; |
102 | | |
103 | 0 | static FuzzSpec FromData(const uint8_t* data, size_t len) { |
104 | 0 | size_t pos = 0; |
105 | 0 | auto u8 = [&]() -> uint8_t { |
106 | 0 | if (pos == len) return 0; |
107 | 0 | return data[pos++]; |
108 | 0 | }; |
109 | 0 | auto b1 = [&]() -> bool { return static_cast<bool>(u8() % 2); }; |
110 | 0 | auto u16 = [&]() -> uint16_t { return (uint16_t{u8()} << 8) | u8(); }; |
111 | 0 | FuzzSpec spec; |
112 | 0 | spec.xsize = uint32_t{u16()} + 1; |
113 | 0 | spec.ysize = uint32_t{u16()} + 1; |
114 | 0 | constexpr uint64_t kMaxSize = 1 << 24; |
115 | 0 | if (spec.xsize * uint64_t{spec.ysize} > kMaxSize) { |
116 | 0 | spec.ysize = kMaxSize / spec.xsize; |
117 | 0 | } |
118 | 0 | spec.grayscale = b1(); |
119 | 0 | spec.alpha = b1(); |
120 | 0 | spec.bit_depth = u8() % 16 + 1; |
121 | | // constants chosen so to cover the entire 0.01 - 25 range. |
122 | 0 | spec.distance = u8() % 2 ? 0.0 : 0.01 + 0.00038132 * u16(); |
123 | |
|
124 | 0 | Check(spec.float_options[2].flag == |
125 | 0 | JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT); |
126 | 0 | Check(spec.int_options[15].flag == JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM); |
127 | 0 | if (spec.distance != 0 || spec.int_options[15].value == 0) { |
128 | 0 | spec.float_options[2].possible_values[1] = 1; |
129 | 0 | } |
130 | |
|
131 | 0 | spec.num_threads = u8(); |
132 | |
|
133 | 0 | for (auto& int_opt : spec.int_options) { |
134 | 0 | int_opt.value = u8() % (int_opt.max - int_opt.min + 1) + int_opt.min; |
135 | 0 | } |
136 | 0 | for (auto& float_opt : spec.float_options) { |
137 | 0 | float_opt.value = float_opt.possible_values[u8() % 4]; |
138 | 0 | } |
139 | |
|
140 | 0 | for (auto& x : spec.pixel_data) { |
141 | 0 | for (auto& y : x) { |
142 | 0 | for (auto& p : y) { |
143 | 0 | p = u16(); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | |
|
148 | 0 | return spec; |
149 | 0 | } |
150 | | }; |
151 | | |
152 | | StatusOr<std::vector<uint8_t>> Encode(const FuzzSpec& spec, |
153 | | TrackingMemoryManager& memory_manager, |
154 | 0 | bool streaming) { |
155 | 0 | auto runner = JxlThreadParallelRunnerMake(nullptr, spec.num_threads); |
156 | 0 | JxlEncoderPtr enc_ptr = JxlEncoderMake(memory_manager.get()); |
157 | 0 | JxlEncoder* enc = enc_ptr.get(); |
158 | |
|
159 | 0 | Check(JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner, |
160 | 0 | runner.get()) == JXL_ENC_SUCCESS); |
161 | 0 | JxlEncoderFrameSettings* frame_settings = |
162 | 0 | JxlEncoderFrameSettingsCreate(enc, nullptr); |
163 | |
|
164 | 0 | Check(JxlEncoderSetFrameDistance(frame_settings, spec.distance) == |
165 | 0 | JXL_ENC_SUCCESS); |
166 | |
|
167 | 0 | for (const auto& opt : spec.int_options) { |
168 | 0 | Check(JxlEncoderFrameSettingsSetOption(frame_settings, opt.flag, |
169 | 0 | opt.value) == JXL_ENC_SUCCESS); |
170 | 0 | } |
171 | 0 | for (const auto& opt : spec.float_options) { |
172 | 0 | if (opt.value != -1) { |
173 | 0 | Check(JxlEncoderFrameSettingsSetFloatOption( |
174 | 0 | frame_settings, opt.flag, opt.value) == JXL_ENC_SUCCESS); |
175 | 0 | } |
176 | 0 | } |
177 | |
|
178 | 0 | Check(JxlEncoderFrameSettingsSetOption(frame_settings, |
179 | 0 | JXL_ENC_FRAME_SETTING_BUFFERING, |
180 | 0 | streaming ? 3 : 0) == JXL_ENC_SUCCESS); |
181 | |
|
182 | 0 | JxlBasicInfo basic_info; |
183 | 0 | JxlEncoderInitBasicInfo(&basic_info); |
184 | 0 | basic_info.num_color_channels = spec.grayscale ? 1 : 3; |
185 | 0 | basic_info.xsize = spec.xsize; |
186 | 0 | basic_info.ysize = spec.ysize; |
187 | 0 | basic_info.bits_per_sample = spec.bit_depth; |
188 | 0 | basic_info.uses_original_profile = JXL_FALSE; |
189 | 0 | uint32_t nchan = basic_info.num_color_channels; |
190 | 0 | if (spec.alpha) { |
191 | 0 | nchan += 1; |
192 | 0 | basic_info.alpha_bits = spec.bit_depth; |
193 | 0 | basic_info.num_extra_channels = 1; |
194 | 0 | } |
195 | 0 | Check(JxlEncoderSetBasicInfo(enc, &basic_info) == JXL_ENC_SUCCESS); |
196 | 0 | if (spec.alpha) { |
197 | 0 | JxlExtraChannelInfo info; |
198 | 0 | memset(&info, 0, sizeof(info)); |
199 | 0 | info.type = JxlExtraChannelType::JXL_CHANNEL_ALPHA; |
200 | 0 | info.bits_per_sample = spec.bit_depth; |
201 | 0 | JxlEncoderSetExtraChannelInfo(enc, 0, &info); |
202 | 0 | } |
203 | 0 | JxlColorEncoding color_encoding; |
204 | 0 | memset(&color_encoding, 0, sizeof(color_encoding)); |
205 | 0 | color_encoding.color_space = spec.grayscale |
206 | 0 | ? JxlColorSpace::JXL_COLOR_SPACE_GRAY |
207 | 0 | : JxlColorSpace::JXL_COLOR_SPACE_RGB; |
208 | 0 | color_encoding.transfer_function = |
209 | 0 | JxlTransferFunction::JXL_TRANSFER_FUNCTION_SRGB; |
210 | 0 | color_encoding.primaries = JxlPrimaries::JXL_PRIMARIES_2100; |
211 | 0 | color_encoding.white_point = JxlWhitePoint::JXL_WHITE_POINT_D65; |
212 | 0 | color_encoding.rendering_intent = |
213 | 0 | JxlRenderingIntent::JXL_RENDERING_INTENT_RELATIVE; |
214 | 0 | Check(JxlEncoderSetColorEncoding(enc, &color_encoding) == JXL_ENC_SUCCESS); |
215 | |
|
216 | 0 | JxlFrameHeader frame_header; |
217 | 0 | JxlEncoderInitFrameHeader(&frame_header); |
218 | | // TODO(szabadka) Add more frame header options. |
219 | 0 | Check(JxlEncoderSetFrameHeader(frame_settings, &frame_header) == |
220 | 0 | JXL_ENC_SUCCESS); |
221 | 0 | JxlPixelFormat pixelformat = {nchan, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}; |
222 | 0 | std::vector<uint16_t> pixels(spec.xsize * static_cast<uint64_t>(spec.ysize) * |
223 | 0 | nchan); |
224 | 0 | for (size_t y = 0; y < spec.ysize; y++) { |
225 | 0 | for (size_t x = 0; x < spec.xsize; x++) { |
226 | 0 | for (size_t c = 0; c < nchan; c++) { |
227 | 0 | pixels[(y * spec.xsize + x) * nchan + c] = |
228 | 0 | spec.pixel_data[c][y % 64][x % 64]; |
229 | 0 | } |
230 | 0 | } |
231 | 0 | } |
232 | 0 | JxlEncoderStatus status = |
233 | 0 | JxlEncoderAddImageFrame(frame_settings, &pixelformat, pixels.data(), |
234 | 0 | pixels.size() * sizeof(uint16_t)); |
235 | | // TODO(eustas): update when API will provide OOM status. |
236 | 0 | if (memory_manager.seen_oom) { |
237 | | // Actually, that is fine. |
238 | 0 | return JXL_FAILURE("OOM"); |
239 | 0 | } |
240 | 0 | Check(status == JXL_ENC_SUCCESS); |
241 | 0 | JxlEncoderCloseInput(enc); |
242 | | // Reading compressed output |
243 | 0 | JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; |
244 | 0 | std::vector<uint8_t> buf(1024); |
245 | 0 | size_t written = 0; |
246 | 0 | while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { |
247 | 0 | buf.resize(buf.size() * 2); |
248 | 0 | uint8_t* next_out = buf.data() + written; |
249 | 0 | size_t avail_out = buf.size() - written; |
250 | 0 | process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); |
251 | 0 | written = next_out - buf.data(); |
252 | 0 | } |
253 | | // TODO(eustas): update when API will provide OOM status. |
254 | 0 | if (memory_manager.seen_oom) { |
255 | | // Actually, that is fine. |
256 | 0 | return JXL_FAILURE("OOM"); |
257 | 0 | } |
258 | 0 | Check(process_result == JXL_ENC_SUCCESS); |
259 | 0 | buf.resize(written); |
260 | |
|
261 | 0 | return buf; |
262 | 0 | } |
263 | | |
264 | | StatusOr<std::vector<float>> Decode(const std::vector<uint8_t>& data, |
265 | 0 | TrackingMemoryManager& memory_manager) { |
266 | | // Multi-threaded parallel runner. |
267 | 0 | auto dec = JxlDecoderMake(memory_manager.get()); |
268 | 0 | Check(JxlDecoderSubscribeEvents(dec.get(), |
269 | 0 | JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) == |
270 | 0 | JXL_DEC_SUCCESS); |
271 | |
|
272 | 0 | JxlBasicInfo info; |
273 | 0 | JxlPixelFormat format = {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; |
274 | |
|
275 | 0 | std::vector<float> pixels; |
276 | |
|
277 | 0 | JxlDecoderSetInput(dec.get(), data.data(), data.size()); |
278 | 0 | JxlDecoderCloseInput(dec.get()); |
279 | |
|
280 | 0 | for (;;) { |
281 | 0 | JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); |
282 | |
|
283 | 0 | if (status == JXL_DEC_BASIC_INFO) { |
284 | 0 | Check(JxlDecoderGetBasicInfo(dec.get(), &info) == JXL_DEC_SUCCESS); |
285 | 0 | } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { |
286 | 0 | size_t buffer_size; |
287 | 0 | Check(JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size) == |
288 | 0 | JXL_DEC_SUCCESS); |
289 | 0 | pixels.resize(buffer_size / sizeof(float)); |
290 | 0 | void* pixels_buffer = static_cast<void*>(pixels.data()); |
291 | 0 | size_t pixels_buffer_size = pixels.size() * sizeof(float); |
292 | 0 | Check(JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, |
293 | 0 | pixels_buffer_size) == JXL_DEC_SUCCESS); |
294 | 0 | } else if (status == JXL_DEC_FULL_IMAGE || status == JXL_DEC_SUCCESS) { |
295 | 0 | return pixels; |
296 | 0 | } else { |
297 | | // TODO(eustas): update when API will provide OOM status. |
298 | 0 | if (memory_manager.seen_oom) { |
299 | | // Actually, that is fine. |
300 | 0 | return JXL_FAILURE("OOM"); |
301 | 0 | } |
302 | | // Unexpected status |
303 | 0 | Check(false); |
304 | 0 | } |
305 | 0 | } |
306 | 0 | } |
307 | | |
308 | 0 | Status Run(const FuzzSpec& spec, TrackingMemoryManager& memory_manager) { |
309 | 0 | std::vector<uint8_t> enc_default; |
310 | 0 | std::vector<uint8_t> enc_streaming; |
311 | |
|
312 | 0 | const auto encode = [&]() -> Status { |
313 | | // It is not clear, which approach eatc more memory. |
314 | 0 | JXL_ASSIGN_OR_RETURN(enc_default, Encode(spec, memory_manager, false)); |
315 | 0 | Check(memory_manager.Reset()); |
316 | 0 | JXL_ASSIGN_OR_RETURN(enc_streaming, Encode(spec, memory_manager, true)); |
317 | 0 | Check(memory_manager.Reset()); |
318 | 0 | return true; |
319 | 0 | }; |
320 | | // It is fine, if encoder OOMs. |
321 | 0 | if (!encode()) return true; |
322 | | |
323 | | // It is NOT OK, it decoder OOMs - it should not consume more than encoder. |
324 | 0 | JXL_ASSIGN_OR_RETURN(auto dec_default, Decode(enc_default, memory_manager)); |
325 | 0 | Check(memory_manager.Reset()); |
326 | 0 | JXL_ASSIGN_OR_RETURN(auto dec_streaming, |
327 | 0 | Decode(enc_streaming, memory_manager)); |
328 | 0 | Check(memory_manager.Reset()); |
329 | |
|
330 | 0 | Check(dec_default == dec_streaming); |
331 | |
|
332 | 0 | return true; |
333 | 0 | } |
334 | | |
335 | 0 | int DoTestOneInput(const uint8_t* data, size_t size) { |
336 | 0 | auto spec = FuzzSpec::FromData(data, size); |
337 | |
|
338 | 0 | TrackingMemoryManager memory_manager{/* cap */ 1 * kGiB, |
339 | 0 | /* total_cap */ 5 * kGiB}; |
340 | 0 | Check(Run(spec, memory_manager)); |
341 | 0 | return 0; |
342 | 0 | } |
343 | | |
344 | | } // namespace |
345 | | |
346 | 46.9k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
347 | 46.9k | return DoTestOneInput(data, size); |
348 | 46.9k | } |
349 | | |
350 | 0 | void TestOneInput(const std::vector<uint8_t>& data) { |
351 | 0 | DoTestOneInput(data.data(), data.size()); |
352 | 0 | } |
353 | | |
354 | | FUZZ_TEST(StreamingFuzzTest, TestOneInput); |