/src/libjxl/tools/transforms_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/memory_manager.h> |
7 | | |
8 | | #include <algorithm> |
9 | | #include <cstddef> |
10 | | #include <cstdint> |
11 | | #include <cstdio> |
12 | | #include <vector> |
13 | | |
14 | | #include "lib/jxl/base/bits.h" |
15 | | #include "lib/jxl/base/common.h" |
16 | | #include "lib/jxl/base/compiler_specific.h" |
17 | | #include "lib/jxl/base/random.h" |
18 | | #include "lib/jxl/base/span.h" |
19 | | #include "lib/jxl/base/status.h" |
20 | | #include "lib/jxl/dec_bit_reader.h" |
21 | | #include "lib/jxl/fields.h" |
22 | | #include "lib/jxl/fuzztest.h" |
23 | | #include "lib/jxl/modular/encoding/encoding.h" |
24 | | #include "lib/jxl/modular/modular_image.h" |
25 | | #include "lib/jxl/modular/options.h" |
26 | | #include "lib/jxl/modular/transform/transform.h" |
27 | | #include "tools/tracking_memory_manager.h" |
28 | | |
29 | | namespace { |
30 | | |
31 | | using ::jpegxl::tools::kGiB; |
32 | | using ::jpegxl::tools::TrackingMemoryManager; |
33 | | using ::jxl::BitReader; |
34 | | using ::jxl::BitReaderScopedCloser; |
35 | | using ::jxl::Bytes; |
36 | | using ::jxl::Channel; |
37 | | using ::jxl::GroupHeader; |
38 | | using ::jxl::Image; |
39 | | using ::jxl::ModularOptions; |
40 | | using ::jxl::pixel_type; |
41 | | using ::jxl::Rng; |
42 | | using ::jxl::Status; |
43 | | using ::jxl::Transform; |
44 | | using ::jxl::weighted::Header; |
45 | | |
46 | 121k | void FillChannel(Channel& ch, Rng& rng) { |
47 | 121k | auto* p = &ch.plane; |
48 | 121k | const size_t w = ch.w; |
49 | 121k | const size_t h = ch.h; |
50 | 196M | for (size_t y = 0; y < h; ++y) { |
51 | 196M | pixel_type* row = p->Row(y); |
52 | 3.46G | for (size_t x = 0; x < w; ++x) { |
53 | 3.26G | row[x] = rng.UniformU(0, 0x80000000); |
54 | 3.26G | } |
55 | 196M | } |
56 | 121k | } |
57 | | |
58 | 28.7k | void CheckImpl(bool ok, const char* conndition, const char* file, int line) { |
59 | 28.7k | if (!ok) { |
60 | 0 | fprintf(stderr, "Check(%s) failed at %s:%d\n", conndition, file, line); |
61 | 0 | JXL_CRASH(); |
62 | 0 | } |
63 | 28.7k | } |
64 | 28.7k | #define Check(OK) CheckImpl((OK), #OK, __FILE__, __LINE__) |
65 | | |
66 | 2.29k | void Run(BitReader& reader, JxlMemoryManager* memory_manager) { |
67 | 2.29k | Rng rng(reader.ReadFixedBits<56>()); |
68 | | |
69 | | // One of {0, 1, _2_, 3}; "2" will be filtered out soon. |
70 | 2.29k | size_t nb_chans = static_cast<size_t>(reader.ReadFixedBits<8>()) & 0x3; |
71 | 2.29k | size_t nb_extra = static_cast<size_t>(reader.ReadFixedBits<8>()) & 0x7; |
72 | | // 1..32 |
73 | 2.29k | size_t bit_depth = |
74 | 2.29k | (static_cast<size_t>(reader.ReadFixedBits<8>()) & 0x1F) + 1; |
75 | | // {0, 1, 2, 3} |
76 | 2.29k | size_t log_upsampling = |
77 | 2.29k | (static_cast<size_t>(reader.ReadFixedBits<8>()) & 0x3); |
78 | 2.29k | size_t upsampling = 1 << log_upsampling; |
79 | | |
80 | 2.29k | size_t w_orig = static_cast<size_t>(reader.ReadFixedBits<16>()); |
81 | 2.29k | size_t h_orig = static_cast<size_t>(reader.ReadFixedBits<16>()); |
82 | 2.29k | size_t w = jxl::DivCeil(w_orig, upsampling); |
83 | 2.29k | size_t h = jxl::DivCeil(h_orig, upsampling); |
84 | | |
85 | 2.29k | if ((nb_chans == 2) || ((nb_chans + nb_extra) == 0) || (w * h == 0) || |
86 | 2.28k | ((w_orig * h_orig * (nb_chans + nb_extra)) > (1 << 23))) { |
87 | 35 | return; |
88 | 35 | } |
89 | | |
90 | 2.26k | std::vector<int> hshift; |
91 | 2.26k | std::vector<int> vshift; |
92 | 2.26k | std::vector<size_t> ec_upsampling; |
93 | | |
94 | 2.61k | for (size_t c = 0; c < nb_chans; c++) { |
95 | 350 | hshift.push_back(static_cast<int>(reader.ReadFixedBits<8>()) & 1); |
96 | 350 | vshift.push_back(static_cast<int>(reader.ReadFixedBits<8>()) & 1); |
97 | 350 | } |
98 | | |
99 | 7.48k | for (size_t ec = 0; ec < nb_extra; ec++) { |
100 | 5.22k | size_t log_ec_upsampling = |
101 | 5.22k | (static_cast<size_t>(reader.ReadFixedBits<8>()) & 0x3); |
102 | 5.22k | log_ec_upsampling = std::max(log_ec_upsampling, log_upsampling); |
103 | 5.22k | ec_upsampling.push_back(1 << log_ec_upsampling); |
104 | 5.22k | } |
105 | | |
106 | 2.26k | Image image{memory_manager}; |
107 | 2.26k | bool ok = [&]() -> Status { |
108 | 2.26k | JXL_ASSIGN_OR_RETURN(image, Image::Create(memory_manager, w, h, bit_depth, |
109 | 2.26k | nb_chans + nb_extra)); |
110 | 2.26k | return true; |
111 | 2.26k | }(); |
112 | | // OOM is ok here. |
113 | 2.26k | if (!ok) return; |
114 | | |
115 | 2.61k | for (size_t c = 0; c < nb_chans; c++) { |
116 | 350 | Channel& ch = image.channel[c]; |
117 | 350 | ch.hshift = hshift[c]; |
118 | 350 | ch.vshift = vshift[c]; |
119 | 350 | Check(ch.shrink(jxl::DivCeil(w, 1 << hshift[c]), |
120 | 350 | jxl::DivCeil(h, 1 << vshift[c]))); |
121 | 350 | } |
122 | | |
123 | 7.48k | for (size_t ec = 0; ec < nb_extra; ec++) { |
124 | 5.22k | Channel& ch = image.channel[ec + nb_chans]; |
125 | 5.22k | size_t ch_up = ec_upsampling[ec]; |
126 | 5.22k | int up_level = |
127 | 5.22k | jxl::CeilLog2Nonzero(ch_up) - jxl::CeilLog2Nonzero(upsampling); |
128 | 5.22k | Check(ch.shrink(jxl::DivCeil(w_orig, ch_up), jxl::DivCeil(h_orig, ch_up))); |
129 | 5.22k | ch.hshift = ch.vshift = up_level; |
130 | 5.22k | } |
131 | | |
132 | 2.26k | GroupHeader header; |
133 | 2.26k | if (!jxl::Bundle::Read(&reader, &header)) return; |
134 | 1.98k | Header w_header; |
135 | 1.98k | if (!jxl::Bundle::Read(&reader, &w_header)) return; |
136 | | |
137 | | // TODO(eustas): give it a try? |
138 | 1.97k | if (!reader.AllReadsWithinBounds()) return; |
139 | | |
140 | 1.97k | image.transform = header.transforms; |
141 | 6.09k | for (Transform& transform : image.transform) { |
142 | 6.09k | if (!transform.MetaApply(image)) return; |
143 | 6.09k | } |
144 | 1.72k | if (image.error) return; |
145 | | |
146 | 1.72k | ModularOptions options; |
147 | 1.72k | if (!ValidateChannelDimensions(image, options)) return; |
148 | | |
149 | 121k | for (Channel& ch : image.channel) { |
150 | 121k | FillChannel(ch, rng); |
151 | 121k | } |
152 | | |
153 | 1.72k | image.undo_transforms(w_header); |
154 | | |
155 | 1.72k | Check(!image.error); |
156 | 1.72k | Check(image.nb_meta_channels == 0); |
157 | 1.72k | Check(image.channel.size() == nb_chans + nb_extra); |
158 | | |
159 | 1.81k | for (size_t c = 0; c < nb_chans; c++) { |
160 | 95 | const Channel& ch = image.channel[c]; |
161 | 95 | Check(ch.hshift == hshift[c]); |
162 | 95 | Check(ch.vshift == vshift[c]); |
163 | 95 | Check(ch.w == jxl::DivCeil(w, 1 << hshift[c])); |
164 | 95 | Check(ch.h == jxl::DivCeil(h, 1 << vshift[c])); |
165 | 95 | } |
166 | | |
167 | 5.56k | for (size_t ec = 0; ec < nb_extra; ec++) { |
168 | 3.83k | const Channel& ch = image.channel[ec + nb_chans]; |
169 | 3.83k | size_t ch_up = ec_upsampling[ec]; |
170 | 3.83k | int up_level = |
171 | 3.83k | jxl::CeilLog2Nonzero(ch_up) - jxl::CeilLog2Nonzero(upsampling); |
172 | 3.83k | Check(ch.w == jxl::DivCeil(w_orig, ch_up)); |
173 | 3.83k | Check(ch.h == jxl::DivCeil(h_orig, ch_up)); |
174 | 3.83k | Check(ch.hshift == up_level); |
175 | 3.83k | Check(ch.vshift == up_level); |
176 | 3.83k | } |
177 | 1.72k | } |
178 | | |
179 | 2.30k | int DoTestOneInput(const uint8_t* data, size_t size) { |
180 | 2.30k | if (size < 15) return 0; |
181 | | |
182 | 2.29k | TrackingMemoryManager memory_manager{/* cap */ 1 * kGiB, |
183 | 2.29k | /* total_cap */ 5 * kGiB}; |
184 | 2.29k | { |
185 | 2.29k | static Status nevermind = true; |
186 | 2.29k | BitReader reader(Bytes(data, size)); |
187 | 2.29k | BitReaderScopedCloser reader_closer(reader, nevermind); |
188 | 2.29k | Run(reader, memory_manager.get()); |
189 | 2.29k | } |
190 | 2.29k | Check(memory_manager.Reset()); |
191 | | |
192 | 2.29k | return 0; |
193 | 2.30k | } |
194 | | |
195 | | } // namespace |
196 | | |
197 | 67.9k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
198 | 67.9k | return DoTestOneInput(data, size); |
199 | 67.9k | } |
200 | | |
201 | 0 | void TestOneInput(const std::vector<uint8_t>& data) { |
202 | 0 | DoTestOneInput(data.data(), data.size()); |
203 | 0 | } |
204 | | |
205 | | FUZZ_TEST(TransformsFuzzTest, TestOneInput); |