Coverage Report

Created: 2025-07-23 07:47

/src/libjxl/tools/djxl_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/decode.h>
8
#include <jxl/decode_cxx.h>
9
#include <jxl/memory_manager.h>
10
#include <jxl/thread_parallel_runner.h>
11
#include <jxl/thread_parallel_runner_cxx.h>
12
#include <jxl/types.h>
13
14
#include <algorithm>
15
#include <cstdint>
16
#include <cstdio>
17
#include <cstdlib>
18
#include <cstring>
19
#include <hwy/targets.h>
20
#include <map>
21
#include <memory>
22
#include <mutex>
23
#include <random>
24
#include <vector>
25
26
#include "lib/jxl/base/common.h"
27
#include "lib/jxl/base/compiler_specific.h"
28
#include "lib/jxl/fuzztest.h"
29
#include "tools/tracking_memory_manager.h"
30
31
namespace {
32
33
// Externally visible value to ensure pixels are used in the fuzzer.
34
int external_code = 0;
35
36
constexpr const size_t kStreamingTargetNumberOfChunks = 128;
37
38
using ::jpegxl::tools::kGiB;
39
using ::jpegxl::tools::TrackingMemoryManager;
40
41
22.3k
void CheckImpl(bool ok, const char* conndition, const char* file, int line) {
42
22.3k
  if (!ok) {
43
0
    fprintf(stderr, "Check(%s) failed at %s:%d\n", conndition, file, line);
44
0
    JXL_CRASH();
45
0
  }
46
22.3k
}
47
22.3k
#define Check(OK) CheckImpl((OK), #OK, __FILE__, __LINE__)
48
49
// Options for the fuzzing
50
struct FuzzSpec {
51
  JxlDataType output_type;
52
  JxlEndianness output_endianness;
53
  size_t output_align;
54
  bool get_alpha;
55
  bool get_grayscale;
56
  bool use_streaming;
57
  bool jpeg_to_pixels;  // decode to pixels even if it is JPEG-reconstructible
58
  // Whether to use the callback mechanism for the output image or not.
59
  bool use_callback;
60
  bool keep_orientation;
61
  bool decode_boxes;
62
  bool coalescing;
63
  // Used for random variation of chunk sizes, extra channels, ... to get
64
  uint32_t random_seed;
65
};
66
67
template <typename It>
68
197k
void Consume(const It& begin, const It& end) {
69
6.43G
  for (auto it = begin; it < end; ++it) {
70
6.43G
    if (*it == 0) {
71
931M
      external_code ^= ~0;
72
5.49G
    } else {
73
5.49G
      external_code ^= *it;
74
5.49G
    }
75
6.43G
  }
76
197k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<unsigned char const*>(unsigned char const* const&, unsigned char const* const&)
Line
Count
Source
68
78.3k
void Consume(const It& begin, const It& end) {
69
6.67M
  for (auto it = begin; it < end; ++it) {
70
6.59M
    if (*it == 0) {
71
6.21M
      external_code ^= ~0;
72
6.21M
    } else {
73
387k
      external_code ^= *it;
74
387k
    }
75
6.59M
  }
76
78.3k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<char const*> >(std::__1::__wrap_iter<char const*> const&, std::__1::__wrap_iter<char const*> const&)
Line
Count
Source
68
60.7k
void Consume(const It& begin, const It& end) {
69
148k
  for (auto it = begin; it < end; ++it) {
70
87.7k
    if (*it == 0) {
71
65.7k
      external_code ^= ~0;
72
65.7k
    } else {
73
22.0k
      external_code ^= *it;
74
22.0k
    }
75
87.7k
  }
76
60.7k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<unsigned char const*> >(std::__1::__wrap_iter<unsigned char const*> const&, std::__1::__wrap_iter<unsigned char const*> const&)
Line
Count
Source
68
47.6k
void Consume(const It& begin, const It& end) {
69
6.38G
  for (auto it = begin; it < end; ++it) {
70
6.38G
    if (*it == 0) {
71
925M
      external_code ^= ~0;
72
5.45G
    } else {
73
5.45G
      external_code ^= *it;
74
5.45G
    }
75
6.38G
  }
76
47.6k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<unsigned char*> >(std::__1::__wrap_iter<unsigned char*> const&, std::__1::__wrap_iter<unsigned char*> const&)
Line
Count
Source
68
10.5k
void Consume(const It& begin, const It& end) {
69
42.3M
  for (auto it = begin; it < end; ++it) {
70
42.3M
    if (*it == 0) {
71
26.3k
      external_code ^= ~0;
72
42.2M
    } else {
73
42.2M
      external_code ^= *it;
74
42.2M
    }
75
42.3M
  }
76
10.5k
}
77
78
template <typename T>
79
78.3k
void Consume(const T& entry) {
80
78.3k
  const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry);
81
78.3k
  Consume(begin, begin + sizeof(T));
82
78.3k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlBasicInfo>(JxlBasicInfo const&)
Line
Count
Source
79
17.6k
void Consume(const T& entry) {
80
17.6k
  const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry);
81
17.6k
  Consume(begin, begin + sizeof(T));
82
17.6k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlExtraChannelInfo>(JxlExtraChannelInfo const&)
Line
Count
Source
79
33.2k
void Consume(const T& entry) {
80
33.2k
  const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry);
81
33.2k
  Consume(begin, begin + sizeof(T));
82
33.2k
}
djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlFrameHeader>(JxlFrameHeader const&)
Line
Count
Source
79
27.5k
void Consume(const T& entry) {
80
27.5k
  const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry);
81
27.5k
  Consume(begin, begin + sizeof(T));
82
27.5k
}
83
84
// use_streaming: if true, decodes the data in small chunks, if false, decodes
85
// it in one shot.
86
bool DecodeJpegXl(const uint8_t* jxl, size_t size,
87
                  JxlMemoryManager* memory_manager, size_t max_pixels,
88
                  size_t max_total_pixels, const FuzzSpec& spec,
89
                  std::vector<uint8_t>* pixels, std::vector<uint8_t>* jpeg,
90
                  size_t* xsize, size_t* ysize,
91
22.3k
                  std::vector<uint8_t>* icc_profile) {
92
22.3k
  size_t total_pixels = 0;
93
  // Multi-threaded parallel runner. Limit to max 2 threads since the fuzzer
94
  // itself is already multithreaded.
95
22.3k
  size_t num_threads =
96
22.3k
      std::min<size_t>(2, JxlThreadParallelRunnerDefaultNumWorkerThreads());
97
22.3k
  auto runner = JxlThreadParallelRunnerMake(memory_manager, num_threads);
98
99
22.3k
  auto mt = jxl::make_unique<std::mt19937>(spec.random_seed);
100
22.3k
  std::exponential_distribution<> dis_streaming(kStreamingTargetNumberOfChunks);
101
102
22.3k
  auto dec = JxlDecoderMake(memory_manager);
103
22.3k
  if (JXL_DEC_SUCCESS !=
104
22.3k
      JxlDecoderSubscribeEvents(
105
22.3k
          dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
106
22.3k
                         JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME |
107
22.3k
                         JXL_DEC_FULL_IMAGE | JXL_DEC_JPEG_RECONSTRUCTION |
108
22.3k
                         JXL_DEC_BOX)) {
109
0
    return false;
110
0
  }
111
22.3k
  if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
112
22.3k
                                                     JxlThreadParallelRunner,
113
22.3k
                                                     runner.get())) {
114
0
    return false;
115
0
  }
116
22.3k
  if (JXL_DEC_SUCCESS != JxlDecoderSetKeepOrientation(
117
22.3k
                             dec.get(), TO_JXL_BOOL(spec.keep_orientation))) {
118
0
    abort();
119
0
  }
120
22.3k
  if (JXL_DEC_SUCCESS !=
121
22.3k
      JxlDecoderSetCoalescing(dec.get(), TO_JXL_BOOL(spec.coalescing))) {
122
0
    abort();
123
0
  }
124
22.3k
  JxlBasicInfo info;
125
22.3k
  uint32_t channels = (spec.get_grayscale ? 1 : 3) + (spec.get_alpha ? 1 : 0);
126
22.3k
  JxlPixelFormat format = {channels, spec.output_type, spec.output_endianness,
127
22.3k
                           spec.output_align};
128
129
22.3k
  if (!spec.use_streaming) {
130
    // Set all input at once
131
11.0k
    JxlDecoderSetInput(dec.get(), jxl, size);
132
11.0k
    JxlDecoderCloseInput(dec.get());
133
11.0k
  }
134
135
22.3k
  bool seen_basic_info = false;
136
22.3k
  bool seen_color_encoding = false;
137
22.3k
  bool seen_preview = false;
138
22.3k
  bool seen_need_image_out = false;
139
22.3k
  bool seen_full_image = false;
140
22.3k
  bool seen_frame = false;
141
22.3k
  uint32_t num_frames = 0;
142
22.3k
  bool seen_jpeg_reconstruction = false;
143
22.3k
  bool seen_jpeg_need_more_output = false;
144
  // If streaming and seen around half the input, test flushing
145
22.3k
  bool tested_flush = false;
146
147
  // Size made available for the streaming input, emulating a subset of the
148
  // full input size.
149
22.3k
  size_t streaming_size = 0;
150
22.3k
  size_t leftover = size;
151
22.3k
  size_t preview_xsize = 0;
152
22.3k
  size_t preview_ysize = 0;
153
22.3k
  bool want_preview = false;
154
22.3k
  std::vector<uint8_t> preview_pixels;
155
156
22.3k
  std::vector<uint8_t> extra_channel_pixels;
157
158
22.3k
  struct CalledRow {
159
22.3k
    std::vector<int> delta;
160
    // Use the pixel values.
161
22.3k
    uint32_t value = 0;
162
22.3k
  };
163
164
  // Callback function used when decoding with use_callback.
165
22.3k
  struct DecodeCallbackData {
166
22.3k
    JxlBasicInfo info;
167
22.3k
    size_t xsize = 0;
168
22.3k
    size_t ysize = 0;
169
22.3k
    std::unique_ptr<std::mutex[]> called_rows_mutex;
170
    // For each row stores the segments of the row being called. For each row
171
    // the sum of all the int values in the map up to [i] (inclusive) tell how
172
    // many times a callback included the pixel i of that row.
173
22.3k
    std::vector<CalledRow> called_rows;
174
22.3k
  };
175
22.3k
  DecodeCallbackData decode_callback_data;
176
22.3k
  auto decode_callback = +[](void* opaque, size_t x, size_t y,
177
27.3M
                             size_t num_pixels, const void* pixels) {
178
27.3M
    DecodeCallbackData* data = static_cast<DecodeCallbackData*>(opaque);
179
27.3M
    if (num_pixels > data->xsize) abort();
180
27.3M
    if (x + num_pixels > data->xsize) abort();
181
27.3M
    if (y >= data->ysize) abort();
182
27.3M
    if (num_pixels && !pixels) abort();
183
    // Keep track of the segments being called by the callback.
184
27.3M
    {
185
27.3M
      const std::lock_guard<std::mutex> lock(data->called_rows_mutex.get()[y]);
186
27.3M
      auto& called_row = data->called_rows[y];
187
27.3M
      called_row.delta[x]++;
188
27.3M
      called_row.delta[x + num_pixels]--;
189
27.3M
      called_row.value += *static_cast<const uint8_t*>(pixels);
190
27.3M
    }
191
27.3M
  };
192
193
22.3k
  JxlExtraChannelInfo extra_channel_info;
194
195
22.3k
  std::vector<uint8_t> box_buffer;
196
197
22.3k
  if (spec.decode_boxes &&
198
22.3k
      JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
199
    // error ignored, can still fuzz if it doesn't brotli-decompress brob boxes.
200
0
  }
201
202
812k
  for (;;) {
203
812k
    JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
204
812k
    if (status == JXL_DEC_ERROR) {
205
20.9k
      return false;
206
791k
    } else if (status == JXL_DEC_NEED_MORE_INPUT) {
207
659k
      if (spec.use_streaming) {
208
659k
        size_t remaining = JxlDecoderReleaseInput(dec.get());
209
        // move any remaining bytes to the front if necessary
210
659k
        size_t used = streaming_size - remaining;
211
659k
        jxl += used;
212
659k
        leftover -= used;
213
659k
        streaming_size -= used;
214
659k
        size_t chunk_size = std::max<size_t>(
215
659k
            1, size * std::min<double>(1.0, dis_streaming(*mt)));
216
659k
        size_t add_size =
217
659k
            std::min<size_t>(chunk_size, leftover - streaming_size);
218
659k
        if (add_size == 0) {
219
          // End of the streaming data reached
220
8
          return false;
221
8
        }
222
659k
        streaming_size += add_size;
223
659k
        if (JXL_DEC_SUCCESS !=
224
659k
            JxlDecoderSetInput(dec.get(), jxl, streaming_size)) {
225
0
          return false;
226
0
        }
227
659k
        if (leftover == streaming_size) {
228
          // All possible input bytes given
229
8.19k
          JxlDecoderCloseInput(dec.get());
230
8.19k
        }
231
232
659k
        if (!tested_flush && seen_frame) {
233
          // Test flush max once to avoid too slow fuzzer run
234
2.30k
          tested_flush = true;
235
2.30k
          JxlDecoderFlushImage(dec.get());
236
2.30k
        }
237
659k
      } else {
238
0
        return false;
239
0
      }
240
659k
    } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
241
321
      if (want_preview) abort();  // expected preview before frame
242
321
      if (spec.jpeg_to_pixels) abort();
243
321
      if (!seen_jpeg_reconstruction) abort();
244
321
      seen_jpeg_need_more_output = true;
245
321
      size_t used_jpeg_output =
246
321
          jpeg->size() - JxlDecoderReleaseJPEGBuffer(dec.get());
247
321
      jpeg->resize(std::max<size_t>(4096, jpeg->size() * 2));
248
321
      uint8_t* jpeg_buffer = jpeg->data() + used_jpeg_output;
249
321
      size_t jpeg_buffer_size = jpeg->size() - used_jpeg_output;
250
251
321
      if (JXL_DEC_SUCCESS !=
252
321
          JxlDecoderSetJPEGBuffer(dec.get(), jpeg_buffer, jpeg_buffer_size)) {
253
0
        return false;
254
0
      }
255
132k
    } else if (status == JXL_DEC_BASIC_INFO) {
256
17.6k
      if (seen_basic_info) abort();  // already seen basic info
257
17.6k
      seen_basic_info = true;
258
259
17.6k
      memset(&info, 0, sizeof(info));
260
17.6k
      if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
261
0
        return false;
262
0
      }
263
17.6k
      Consume(info);
264
265
17.6k
      *xsize = info.xsize;
266
17.6k
      *ysize = info.ysize;
267
17.6k
      decode_callback_data.info = info;
268
17.6k
      size_t num_pixels = *xsize * *ysize;
269
      // num_pixels overflow
270
17.6k
      if (*xsize != 0 && num_pixels / *xsize != *ysize) return false;
271
      // limit max memory of this fuzzer test
272
17.6k
      if (num_pixels > max_pixels) return false;
273
274
17.5k
      if (info.have_preview) {
275
1.18k
        want_preview = true;
276
1.18k
        preview_xsize = info.preview.xsize;
277
1.18k
        preview_ysize = info.preview.ysize;
278
1.18k
        size_t preview_num_pixels = preview_xsize * preview_ysize;
279
        // num_pixels overflow
280
1.18k
        if (preview_xsize != 0 &&
281
1.18k
            preview_num_pixels / preview_xsize != preview_ysize) {
282
0
          return false;
283
0
        }
284
        // limit max memory of this fuzzer test
285
1.18k
        if (preview_num_pixels > max_pixels) return false;
286
1.18k
      }
287
288
50.8k
      for (size_t ec = 0; ec < info.num_extra_channels; ++ec) {
289
33.2k
        memset(&extra_channel_info, 0, sizeof(extra_channel_info));
290
33.2k
        if (JXL_DEC_SUCCESS !=
291
33.2k
            JxlDecoderGetExtraChannelInfo(dec.get(), ec, &extra_channel_info)) {
292
0
          abort();
293
0
        }
294
33.2k
        Consume(extra_channel_info);
295
33.2k
        std::vector<char> ec_name(extra_channel_info.name_length + 1);
296
33.2k
        if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName(dec.get(), ec,
297
33.2k
                                                             ec_name.data(),
298
33.2k
                                                             ec_name.size())) {
299
0
          abort();
300
0
        }
301
33.2k
        Consume(ec_name.cbegin(), ec_name.cend());
302
33.2k
      }
303
114k
    } else if (status == JXL_DEC_COLOR_ENCODING) {
304
15.8k
      if (!seen_basic_info) abort();     // expected basic info first
305
15.8k
      if (seen_color_encoding) abort();  // already seen color encoding
306
15.8k
      seen_color_encoding = true;
307
308
      // Get the ICC color profile of the pixel data
309
15.8k
      size_t icc_size;
310
15.8k
      if (JXL_DEC_SUCCESS !=
311
15.8k
          JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
312
15.8k
                                      &icc_size)) {
313
2
        return false;
314
2
      }
315
15.8k
      icc_profile->resize(icc_size);
316
15.8k
      if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
317
15.8k
                                 dec.get(), JXL_COLOR_PROFILE_TARGET_DATA,
318
15.8k
                                 icc_profile->data(), icc_profile->size())) {
319
0
        return false;
320
0
      }
321
15.8k
      if (want_preview) {
322
1.16k
        size_t preview_size;
323
1.16k
        if (JXL_DEC_SUCCESS !=
324
1.16k
            JxlDecoderPreviewOutBufferSize(dec.get(), &format, &preview_size)) {
325
32
          return false;
326
32
        }
327
1.12k
        preview_pixels.resize(preview_size);
328
1.12k
        if (JXL_DEC_SUCCESS != JxlDecoderSetPreviewOutBuffer(
329
1.12k
                                   dec.get(), &format, preview_pixels.data(),
330
1.12k
                                   preview_pixels.size())) {
331
0
          abort();
332
0
        }
333
1.12k
      }
334
99.0k
    } else if (status == JXL_DEC_PREVIEW_IMAGE) {
335
      // TODO(eustas): test JXL_DEC_NEED_PREVIEW_OUT_BUFFER
336
671
      if (seen_preview) abort();
337
671
      if (!want_preview) abort();
338
671
      if (!seen_color_encoding) abort();
339
671
      want_preview = false;
340
671
      seen_preview = true;
341
671
      Consume(preview_pixels.cbegin(), preview_pixels.cend());
342
98.3k
    } else if (status == JXL_DEC_FRAME ||
343
98.3k
               status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
344
43.8k
      if (want_preview) abort();          // expected preview before frame
345
43.8k
      if (!seen_color_encoding) abort();  // expected color encoding first
346
43.8k
      if (status == JXL_DEC_FRAME) {
347
27.5k
        if (seen_frame) abort();  // already seen JXL_DEC_FRAME
348
27.5k
        seen_frame = true;
349
27.5k
        JxlFrameHeader frame_header;
350
27.5k
        memset(&frame_header, 0, sizeof(frame_header));
351
27.5k
        if (JXL_DEC_SUCCESS !=
352
27.5k
            JxlDecoderGetFrameHeader(dec.get(), &frame_header)) {
353
0
          abort();
354
0
        }
355
27.5k
        decode_callback_data.xsize = frame_header.layer_info.xsize;
356
27.5k
        decode_callback_data.ysize = frame_header.layer_info.ysize;
357
27.5k
        size_t num_pixels = static_cast<size_t>(frame_header.layer_info.xsize) *
358
27.5k
                            frame_header.layer_info.ysize;
359
27.5k
        if (num_pixels > max_pixels) return false;
360
27.5k
        if (total_pixels > max_total_pixels) return false;
361
27.5k
        total_pixels += num_pixels;
362
27.5k
        if (!spec.coalescing) {
363
25.9k
          decode_callback_data.called_rows.clear();
364
25.9k
        }
365
27.5k
        decode_callback_data.called_rows_mutex =
366
27.5k
            jxl::make_unique<std::mutex[]>(decode_callback_data.ysize);
367
27.5k
        decode_callback_data.called_rows.resize(decode_callback_data.ysize);
368
2.57M
        for (size_t y = 0; y < decode_callback_data.ysize; ++y) {
369
2.55M
          decode_callback_data.called_rows[y].delta.clear();
370
2.55M
          decode_callback_data.called_rows[y].delta.resize(
371
2.55M
              decode_callback_data.xsize + 1);
372
2.55M
        }
373
27.5k
        Consume(frame_header);
374
27.5k
        std::vector<char> frame_name(frame_header.name_length + 1);
375
27.5k
        if (JXL_DEC_SUCCESS != JxlDecoderGetFrameName(dec.get(),
376
27.5k
                                                      frame_name.data(),
377
27.5k
                                                      frame_name.size())) {
378
0
          abort();
379
0
        }
380
27.5k
        Consume(frame_name.cbegin(), frame_name.cend());
381
        // When not testing streaming, test that JXL_DEC_NEED_IMAGE_OUT_BUFFER
382
        // occurs instead, so do not set buffer now.
383
27.5k
        if (!spec.use_streaming) continue;
384
27.5k
      }
385
27.1k
      if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
386
        // expected JXL_DEC_FRAME instead
387
16.2k
        if (!seen_frame) abort();
388
        // already should have set buffer if streaming
389
16.2k
        if (spec.use_streaming) abort();
390
        // already seen need image out
391
16.2k
        if (seen_need_image_out) abort();
392
16.2k
        seen_need_image_out = true;
393
16.2k
      }
394
395
27.1k
      if (info.num_extra_channels > 0) {
396
7.61k
        std::uniform_int_distribution<> dis(0, info.num_extra_channels);
397
7.61k
        size_t ec_index = dis(*mt);
398
        // There is also a probability no extra channel is chosen
399
7.61k
        if (ec_index < info.num_extra_channels) {
400
          // TODO(eustas): that looks suspicious to me
401
4.69k
          size_t last_ec_index = info.num_extra_channels - 1;
402
4.69k
          size_t ec_size;
403
4.69k
          if (JXL_DEC_SUCCESS !=
404
4.69k
              JxlDecoderExtraChannelBufferSize(dec.get(), &format, &ec_size,
405
4.69k
                                               last_ec_index)) {
406
4
            return false;
407
4
          }
408
4.69k
          extra_channel_pixels.resize(ec_size);
409
4.69k
          if (JXL_DEC_SUCCESS !=
410
4.69k
              JxlDecoderSetExtraChannelBuffer(dec.get(), &format,
411
4.69k
                                              extra_channel_pixels.data(),
412
4.69k
                                              ec_size, last_ec_index)) {
413
0
            return false;
414
0
          }
415
4.69k
        }
416
7.61k
      }
417
418
27.1k
      if (spec.use_callback) {
419
8.53k
        if (JXL_DEC_SUCCESS !=
420
8.53k
            JxlDecoderSetImageOutCallback(dec.get(), &format, decode_callback,
421
8.53k
                                          &decode_callback_data)) {
422
28
          return false;
423
28
        }
424
18.6k
      } else {
425
        // Use the pixels output buffer.
426
18.6k
        size_t buffer_size;
427
18.6k
        if (JXL_DEC_SUCCESS !=
428
18.6k
            JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
429
56
          return false;
430
56
        }
431
18.5k
        pixels->resize(buffer_size);
432
18.5k
        void* pixels_buffer = static_cast<void*>(pixels->data());
433
18.5k
        size_t pixels_buffer_size = pixels->size();
434
18.5k
        if (JXL_DEC_SUCCESS !=
435
18.5k
            JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer,
436
18.5k
                                        pixels_buffer_size)) {
437
0
          return false;
438
0
        }
439
18.5k
      }
440
54.5k
    } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
441
      // Do not check preview precedence here, since this event only declares
442
      // that JPEG is going to be decoded; though, when first byte of JPEG
443
      // arrives (JXL_DEC_JPEG_NEED_MORE_OUTPUT) it is certain that preview
444
      // should have been produced already.
445
413
      if (seen_jpeg_reconstruction) abort();
446
413
      seen_jpeg_reconstruction = true;
447
413
      if (!spec.jpeg_to_pixels) {
448
        // Make sure buffer is allocated, but current size is too small to
449
        // contain valid JPEG.
450
406
        jpeg->resize(1);
451
406
        uint8_t* jpeg_buffer = jpeg->data();
452
406
        size_t jpeg_buffer_size = jpeg->size();
453
406
        if (JXL_DEC_SUCCESS !=
454
406
            JxlDecoderSetJPEGBuffer(dec.get(), jpeg_buffer, jpeg_buffer_size)) {
455
0
          return false;
456
0
        }
457
406
      }
458
54.1k
    } else if (status == JXL_DEC_FULL_IMAGE) {
459
23.4k
      if (want_preview) abort();  // expected preview before frame
460
23.4k
      if (!spec.jpeg_to_pixels && seen_jpeg_reconstruction) {
461
112
        if (!seen_jpeg_need_more_output) abort();
462
112
        jpeg->resize(jpeg->size() - JxlDecoderReleaseJPEGBuffer(dec.get()));
463
23.3k
      } else {
464
        // expected need image out or frame first
465
23.3k
        if (!seen_need_image_out && !seen_frame) abort();
466
23.3k
      }
467
468
23.4k
      seen_full_image = true;  // there may be multiple if animated
469
470
      // There may be a next animation frame so expect those again:
471
23.4k
      seen_need_image_out = false;
472
23.4k
      seen_frame = false;
473
23.4k
      num_frames++;
474
475
      // "Use" all the pixels; MSAN needs a conditional to count as usage.
476
23.4k
      Consume(pixels->cbegin(), pixels->cend());
477
23.4k
      Consume(jpeg->cbegin(), jpeg->cend());
478
479
      // When not coalescing, check that the whole (possibly cropped) frame was
480
      // sent
481
23.4k
      if (seen_need_image_out && spec.use_callback && spec.coalescing) {
482
        // Check that the callback sent all the pixels
483
0
        for (uint32_t y = 0; y < decode_callback_data.ysize; y++) {
484
0
          const auto& deltas = decode_callback_data.called_rows[y].delta;
485
0
          if (deltas[0] != 1) abort();
486
0
          if (deltas[decode_callback_data.xsize] != -1) abort();
487
0
          for (size_t i = 1; i < decode_callback_data.xsize; ++i) {
488
0
            if (deltas[i] != 0) abort();
489
0
          }
490
0
        }
491
0
      }
492
      // Nothing to do. Do not yet return. If the image is an animation, more
493
      // full frames may be decoded. This example only keeps the last one.
494
30.6k
    } else if (status == JXL_DEC_SUCCESS) {
495
1.21k
      if (!seen_full_image) abort();  // expected full image before finishing
496
497
      // When decoding we may not get seen_need_image_out unless we were
498
      // decoding the image to pixels.
499
1.21k
      if (seen_need_image_out && spec.use_callback && spec.coalescing) {
500
        // Check that the callback sent all the pixels
501
0
        for (uint32_t y = 0; y < decode_callback_data.ysize; y++) {
502
0
          const auto& deltas = decode_callback_data.called_rows[y].delta;
503
0
          if (deltas[0] != static_cast<int>(num_frames)) abort();
504
0
          if (deltas[decode_callback_data.xsize] != -1) abort();
505
0
          for (size_t i = 1; i < decode_callback_data.xsize; ++i) {
506
0
            if (deltas[i] != 0) abort();
507
0
          }
508
0
        }
509
0
      }
510
511
      // All decoding successfully finished.
512
      // It's not required to call JxlDecoderReleaseInput(dec.get()) here since
513
      // the decoder will be destroyed.
514
1.21k
      return true;
515
29.4k
    } else if (status == JXL_DEC_BOX) {
516
22.9k
      if (spec.decode_boxes) {
517
13.6k
        if (!box_buffer.empty()) {
518
10.7k
          size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
519
10.7k
          size_t box_size = box_buffer.size() - remaining;
520
10.7k
          if (box_size != 0) {
521
10.5k
            Consume(box_buffer.begin(), box_buffer.begin() + box_size);
522
10.5k
            box_buffer.clear();
523
10.5k
          }
524
10.7k
        }
525
13.6k
        box_buffer.resize(64);
526
13.6k
        JxlDecoderSetBoxBuffer(dec.get(), box_buffer.data(), box_buffer.size());
527
13.6k
      }
528
22.9k
    } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
529
6.43k
      if (!spec.decode_boxes) {
530
0
        abort();  // Not expected when not setting output buffer
531
0
      }
532
6.43k
      size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
533
6.43k
      size_t box_size = box_buffer.size() - remaining;
534
6.43k
      box_buffer.resize(box_buffer.size() * 2);
535
6.43k
      JxlDecoderSetBoxBuffer(dec.get(), box_buffer.data() + box_size,
536
6.43k
                             box_buffer.size() - box_size);
537
6.43k
    } else {
538
0
      return false;
539
0
    }
540
812k
  }
541
22.3k
}
542
543
22.3k
int DoTestOneInput(const uint8_t* data, size_t size) {
544
22.3k
  if (size < 4) return 0;
545
22.3k
  uint32_t flags = 0;
546
22.3k
  size_t used_flag_bits = 0;
547
22.3k
  memcpy(&flags, data + size - 4, 4);
548
22.3k
  size -= 4;
549
550
267k
  const auto getFlag = [&flags, &used_flag_bits](size_t max_value) {
551
267k
    size_t limit = 1;
552
714k
    while (limit <= max_value) {
553
446k
      limit <<= 1;
554
446k
      used_flag_bits++;
555
446k
      if (used_flag_bits > 32) abort();
556
446k
    }
557
267k
    uint32_t result = flags % limit;
558
267k
    flags /= limit;
559
267k
    return result % (max_value + 1);
560
267k
  };
561
178k
  const auto getBoolFlag = [&getFlag]() -> bool {
562
178k
    return static_cast<bool>(getFlag(1));
563
178k
  };
564
565
22.3k
  FuzzSpec spec;
566
  // Allows some different possible variations in the chunk sizes of the
567
  // streaming case
568
22.3k
  spec.random_seed = flags ^ size;
569
22.3k
  spec.get_alpha = getBoolFlag();
570
22.3k
  spec.get_grayscale = getBoolFlag();
571
22.3k
  spec.use_streaming = getBoolFlag();
572
22.3k
  spec.jpeg_to_pixels = getBoolFlag();
573
22.3k
  spec.use_callback = getBoolFlag();
574
22.3k
  spec.keep_orientation = getBoolFlag();
575
22.3k
  spec.coalescing = getBoolFlag();
576
22.3k
  spec.output_type = static_cast<JxlDataType>(getFlag(JXL_TYPE_FLOAT16));
577
22.3k
  spec.output_endianness = static_cast<JxlEndianness>(getFlag(JXL_BIG_ENDIAN));
578
22.3k
  spec.output_align = getFlag(16);
579
22.3k
  spec.decode_boxes = getBoolFlag();
580
581
22.3k
  std::vector<uint8_t> pixels;
582
22.3k
  std::vector<uint8_t> jpeg;
583
22.3k
  std::vector<uint8_t> icc;
584
22.3k
  size_t xsize;
585
22.3k
  size_t ysize;
586
22.3k
  size_t max_pixels = 1 << 21;
587
22.3k
  size_t max_total_pixels = 5 * max_pixels;
588
589
22.3k
  TrackingMemoryManager memory_manager{/* cap */ 1 * kGiB,
590
22.3k
                                       /* total_cap */ 5 * kGiB};
591
22.3k
  const auto targets = hwy::SupportedAndGeneratedTargets();
592
22.3k
  hwy::SetSupportedTargetsForTest(targets[getFlag(targets.size() - 1)]);
593
22.3k
  DecodeJpegXl(data, size, memory_manager.get(), max_pixels, max_total_pixels,
594
22.3k
               spec, &pixels, &jpeg, &xsize, &ysize, &icc);
595
22.3k
  hwy::SetSupportedTargetsForTest(0);
596
22.3k
  Check(memory_manager.Reset());
597
598
22.3k
  return 0;
599
22.3k
}
600
601
}  // namespace
602
603
69.7k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
604
69.7k
  return DoTestOneInput(data, size);
605
69.7k
}
606
607
0
void TestOneInput(const std::vector<uint8_t>& data) {
608
0
  DoTestOneInput(data.data(), data.size());
609
0
}
610
611
FUZZ_TEST(DjxlFuzzTest, TestOneInput);