Coverage Report

Created: 2026-06-30 07:53

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