Coverage Report

Created: 2024-09-08 07:14

/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);