Coverage Report

Created: 2025-06-22 08:04

/src/libjxl/lib/jxl/dec_bit_reader.h
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
#ifndef LIB_JXL_DEC_BIT_READER_H_
7
#define LIB_JXL_DEC_BIT_READER_H_
8
9
// Bounds-checked bit reader; 64-bit buffer with support for deferred refills
10
// and switching to reading byte-aligned words.
11
12
#include <cstddef>
13
#include <cstdint>
14
#include <cstring>  // memcpy
15
16
#ifdef __BMI2__
17
#include <immintrin.h>
18
#endif
19
20
#include "lib/jxl/base/byte_order.h"
21
#include "lib/jxl/base/common.h"
22
#include "lib/jxl/base/compiler_specific.h"
23
#include "lib/jxl/base/status.h"
24
25
namespace jxl {
26
27
// Reads bits previously written to memory by BitWriter. Uses unaligned 8-byte
28
// little-endian loads.
29
class BitReader {
30
 public:
31
  static constexpr size_t kMaxBitsPerCall = 56;
32
33
  // Constructs an invalid BitReader, to be overwritten before usage.
34
  BitReader()
35
      : buf_(0),
36
        bits_in_buf_(0),
37
        next_byte_{nullptr},
38
        end_minus_8_{nullptr},
39
0
        first_byte_(nullptr) {}
40
  BitReader(const BitReader&) = delete;
41
42
  // bytes need not be aligned nor padded!
43
  template <class ArrayLike>
44
  explicit BitReader(const ArrayLike& bytes)
45
554k
      : buf_(0),
46
554k
        bits_in_buf_(0),
47
554k
        next_byte_(bytes.data()),
48
        // Assumes first_byte_ >= 8.
49
554k
        end_minus_8_(bytes.data() - 8 + bytes.size()),
50
554k
        first_byte_(bytes.data()) {
51
554k
    Refill();
52
554k
  }
53
554k
  ~BitReader() {
54
    // Close() must be called before destroying an initialized bit reader.
55
    // Invalid bit readers will have a nullptr in first_byte_.
56
554k
    JXL_DASSERT(close_called_ || !first_byte_);
57
554k
  }
58
59
  // Move operator needs to invalidate the other BitReader such that it is
60
  // irrelevant if we call Close() on it or not.
61
0
  BitReader& operator=(BitReader&& other) noexcept {
62
0
    // Ensure the current instance was already closed, before we overwrite it
63
0
    // with other.
64
0
    JXL_DASSERT(close_called_ || !first_byte_);
65
0
66
0
    JXL_DASSERT(!other.close_called_);
67
0
    buf_ = other.buf_;
68
0
    bits_in_buf_ = other.bits_in_buf_;
69
0
    next_byte_ = other.next_byte_;
70
0
    end_minus_8_ = other.end_minus_8_;
71
0
    first_byte_ = other.first_byte_;
72
0
    overread_bytes_ = other.overread_bytes_;
73
0
    close_called_ = other.close_called_;
74
0
75
0
    other.first_byte_ = nullptr;
76
0
    other.next_byte_ = nullptr;
77
0
    return *this;
78
0
  }
79
  BitReader& operator=(const BitReader& other) = delete;
80
81
  // For time-critical reads, refills can be shared by multiple reads.
82
  // Based on variant 4 (plus bounds-checking), see
83
  // fgiesen.wordpress.com/2018/02/20/reading-bits-in-far-too-many-ways-part-2/
84
732M
  JXL_INLINE void Refill() {
85
732M
    if (JXL_UNLIKELY(next_byte_ > end_minus_8_)) {
86
558M
      BoundsCheckedRefill();
87
558M
    } else {
88
      // It's safe to load 64 bits; insert valid (possibly nonzero) bits above
89
      // bits_in_buf_. The shift requires bits_in_buf_ < 64.
90
174M
      buf_ |= LoadLE64(next_byte_) << bits_in_buf_;
91
92
      // Advance by bytes fully absorbed into the buffer.
93
174M
      next_byte_ += (63 - bits_in_buf_) >> 3;
94
95
      // We absorbed a multiple of 8 bits, so the lower 3 bits of bits_in_buf_
96
      // must remain unchanged, otherwise the next refill's shifted bits will
97
      // not align with buf_. Set the three upper bits so the result >= 56.
98
174M
      bits_in_buf_ |= 56;
99
174M
      JXL_DASSERT(56 <= bits_in_buf_ && bits_in_buf_ < 64);
100
174M
    }
101
732M
  }
102
103
  // Returns the bits that would be returned by Read without calling Advance().
104
  // It is legal to PEEK at more bits than present in the bitstream (required
105
  // by Huffman), and those bits will be zero.
106
  template <size_t N>
107
325M
  JXL_INLINE uint64_t PeekFixedBits() const {
108
325M
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
325M
    JXL_DASSERT(!close_called_);
110
325M
    return buf_ & ((1ULL << N) - 1);
111
325M
  }
unsigned long jxl::BitReader::PeekFixedBits<16ul>() const
Line
Count
Source
107
313M
  JXL_INLINE uint64_t PeekFixedBits() const {
108
313M
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
313M
    JXL_DASSERT(!close_called_);
110
313M
    return buf_ & ((1ULL << N) - 1);
111
313M
  }
unsigned long jxl::BitReader::PeekFixedBits<2ul>() const
Line
Count
Source
107
2.80M
  JXL_INLINE uint64_t PeekFixedBits() const {
108
2.80M
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
2.80M
    JXL_DASSERT(!close_called_);
110
2.80M
    return buf_ & ((1ULL << N) - 1);
111
2.80M
  }
unsigned long jxl::BitReader::PeekFixedBits<4ul>() const
Line
Count
Source
107
227k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
227k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
227k
    JXL_DASSERT(!close_called_);
110
227k
    return buf_ & ((1ULL << N) - 1);
111
227k
  }
unsigned long jxl::BitReader::PeekFixedBits<8ul>() const
Line
Count
Source
107
96.3k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
96.3k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
96.3k
    JXL_DASSERT(!close_called_);
110
96.3k
    return buf_ & ((1ULL << N) - 1);
111
96.3k
  }
unsigned long jxl::BitReader::PeekFixedBits<12ul>() const
Line
Count
Source
107
61.9k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
61.9k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
61.9k
    JXL_DASSERT(!close_called_);
110
61.9k
    return buf_ & ((1ULL << N) - 1);
111
61.9k
  }
unsigned long jxl::BitReader::PeekFixedBits<1ul>() const
Line
Count
Source
107
682k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
682k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
682k
    JXL_DASSERT(!close_called_);
110
682k
    return buf_ & ((1ULL << N) - 1);
111
682k
  }
unsigned long jxl::BitReader::PeekFixedBits<3ul>() const
Line
Count
Source
107
30.8k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
30.8k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
30.8k
    JXL_DASSERT(!close_called_);
110
30.8k
    return buf_ & ((1ULL << N) - 1);
111
30.8k
  }
unsigned long jxl::BitReader::PeekFixedBits<0ul>() const
Line
Count
Source
107
3.22k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
3.22k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
3.22k
    JXL_DASSERT(!close_called_);
110
3.22k
    return buf_ & ((1ULL << N) - 1);
111
3.22k
  }
unsigned long jxl::BitReader::PeekFixedBits<7ul>() const
Line
Count
Source
107
317k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
317k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
317k
    JXL_DASSERT(!close_called_);
110
317k
    return buf_ & ((1ULL << N) - 1);
111
317k
  }
unsigned long jxl::BitReader::PeekFixedBits<32ul>() const
Line
Count
Source
107
41.5k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
41.5k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
41.5k
    JXL_DASSERT(!close_called_);
110
41.5k
    return buf_ & ((1ULL << N) - 1);
111
41.5k
  }
unsigned long jxl::BitReader::PeekFixedBits<5ul>() const
Line
Count
Source
107
7.38M
  JXL_INLINE uint64_t PeekFixedBits() const {
108
7.38M
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
7.38M
    JXL_DASSERT(!close_called_);
110
7.38M
    return buf_ & ((1ULL << N) - 1);
111
7.38M
  }
unsigned long jxl::BitReader::PeekFixedBits<10ul>() const
Line
Count
Source
107
50.3k
  JXL_INLINE uint64_t PeekFixedBits() const {
108
50.3k
    static_assert(N <= kMaxBitsPerCall, "Reading too many bits in one call.");
109
50.3k
    JXL_DASSERT(!close_called_);
110
50.3k
    return buf_ & ((1ULL << N) - 1);
111
50.3k
  }
112
113
432M
  JXL_INLINE uint64_t PeekBits(size_t nbits) const {
114
432M
    JXL_DASSERT(nbits <= kMaxBitsPerCall);
115
432M
    JXL_DASSERT(!close_called_);
116
117
    // Slightly faster but requires BMI2. It is infeasible to make the many
118
    // callers reside between begin/end_target, especially because only the
119
    // callers in dec_ans are time-critical. Therefore only enabled if the
120
    // entire binary is compiled for (and thus requires) BMI2.
121
#if defined(__BMI2__) && defined(__x86_64__)
122
    return _bzhi_u64(buf_, nbits);
123
#else
124
432M
    const uint64_t mask = (1ULL << nbits) - 1;
125
432M
    return buf_ & mask;
126
432M
#endif
127
432M
  }
128
129
  // Removes bits from the buffer. Need not match the previous Peek size, but
130
  // the buffer must contain at least num_bits (this prevents consuming more
131
  // than the total number of bits).
132
758M
  JXL_INLINE void Consume(size_t num_bits) {
133
758M
    JXL_DASSERT(!close_called_);
134
758M
    JXL_DASSERT(bits_in_buf_ >= num_bits);
135
758M
    if (JXL_CRASH_ON_ERROR) {
136
      // When JXL_CRASH_ON_ERROR is defined, it is a fatal error to read more
137
      // bits than available in the stream. A non-zero overread_bytes_ implies
138
      // that next_byte_ is already at the end of the stream, so we don't need
139
      // to check that.
140
0
      JXL_DASSERT(bits_in_buf_ >= num_bits + overread_bytes_ * kBitsPerByte);
141
0
    }
142
758M
    bits_in_buf_ -= num_bits;
143
758M
    buf_ >>= num_bits;
144
758M
  }
145
146
5.97M
  JXL_INLINE uint64_t ReadBits(size_t nbits) {
147
5.97M
    JXL_DASSERT(!close_called_);
148
5.97M
    Refill();
149
5.97M
    const uint64_t bits = PeekBits(nbits);
150
5.97M
    Consume(nbits);
151
5.97M
    return bits;
152
5.97M
  }
153
154
  template <size_t N>
155
4.13M
  JXL_INLINE uint64_t ReadFixedBits() {
156
4.13M
    JXL_DASSERT(!close_called_);
157
4.13M
    Refill();
158
4.13M
    const uint64_t bits = PeekFixedBits<N>();
159
4.13M
    Consume(N);
160
4.13M
    return bits;
161
4.13M
  }
unsigned long jxl::BitReader::ReadFixedBits<2ul>()
Line
Count
Source
155
2.80M
  JXL_INLINE uint64_t ReadFixedBits() {
156
2.80M
    JXL_DASSERT(!close_called_);
157
2.80M
    Refill();
158
2.80M
    const uint64_t bits = PeekFixedBits<N>();
159
2.80M
    Consume(N);
160
2.80M
    return bits;
161
2.80M
  }
unsigned long jxl::BitReader::ReadFixedBits<4ul>()
Line
Count
Source
155
88.6k
  JXL_INLINE uint64_t ReadFixedBits() {
156
88.6k
    JXL_DASSERT(!close_called_);
157
88.6k
    Refill();
158
88.6k
    const uint64_t bits = PeekFixedBits<N>();
159
88.6k
    Consume(N);
160
88.6k
    return bits;
161
88.6k
  }
unsigned long jxl::BitReader::ReadFixedBits<8ul>()
Line
Count
Source
155
96.3k
  JXL_INLINE uint64_t ReadFixedBits() {
156
96.3k
    JXL_DASSERT(!close_called_);
157
96.3k
    Refill();
158
96.3k
    const uint64_t bits = PeekFixedBits<N>();
159
96.3k
    Consume(N);
160
96.3k
    return bits;
161
96.3k
  }
unsigned long jxl::BitReader::ReadFixedBits<12ul>()
Line
Count
Source
155
61.9k
  JXL_INLINE uint64_t ReadFixedBits() {
156
61.9k
    JXL_DASSERT(!close_called_);
157
61.9k
    Refill();
158
61.9k
    const uint64_t bits = PeekFixedBits<N>();
159
61.9k
    Consume(N);
160
61.9k
    return bits;
161
61.9k
  }
unsigned long jxl::BitReader::ReadFixedBits<1ul>()
Line
Count
Source
155
682k
  JXL_INLINE uint64_t ReadFixedBits() {
156
682k
    JXL_DASSERT(!close_called_);
157
682k
    Refill();
158
682k
    const uint64_t bits = PeekFixedBits<N>();
159
682k
    Consume(N);
160
682k
    return bits;
161
682k
  }
unsigned long jxl::BitReader::ReadFixedBits<16ul>()
Line
Count
Source
155
271k
  JXL_INLINE uint64_t ReadFixedBits() {
156
271k
    JXL_DASSERT(!close_called_);
157
271k
    Refill();
158
271k
    const uint64_t bits = PeekFixedBits<N>();
159
271k
    Consume(N);
160
271k
    return bits;
161
271k
  }
unsigned long jxl::BitReader::ReadFixedBits<3ul>()
Line
Count
Source
155
30.8k
  JXL_INLINE uint64_t ReadFixedBits() {
156
30.8k
    JXL_DASSERT(!close_called_);
157
30.8k
    Refill();
158
30.8k
    const uint64_t bits = PeekFixedBits<N>();
159
30.8k
    Consume(N);
160
30.8k
    return bits;
161
30.8k
  }
unsigned long jxl::BitReader::ReadFixedBits<0ul>()
Line
Count
Source
155
3.22k
  JXL_INLINE uint64_t ReadFixedBits() {
156
3.22k
    JXL_DASSERT(!close_called_);
157
3.22k
    Refill();
158
3.22k
    const uint64_t bits = PeekFixedBits<N>();
159
3.22k
    Consume(N);
160
3.22k
    return bits;
161
3.22k
  }
unsigned long jxl::BitReader::ReadFixedBits<32ul>()
Line
Count
Source
155
41.5k
  JXL_INLINE uint64_t ReadFixedBits() {
156
41.5k
    JXL_DASSERT(!close_called_);
157
41.5k
    Refill();
158
41.5k
    const uint64_t bits = PeekFixedBits<N>();
159
41.5k
    Consume(N);
160
41.5k
    return bits;
161
41.5k
  }
unsigned long jxl::BitReader::ReadFixedBits<10ul>()
Line
Count
Source
155
50.3k
  JXL_INLINE uint64_t ReadFixedBits() {
156
50.3k
    JXL_DASSERT(!close_called_);
157
50.3k
    Refill();
158
50.3k
    const uint64_t bits = PeekFixedBits<N>();
159
50.3k
    Consume(N);
160
50.3k
    return bits;
161
50.3k
  }
162
163
  // Equivalent to calling ReadFixedBits(1) `skip` times, but much faster.
164
  // `skip` is typically large.
165
313k
  void SkipBits(size_t skip) {
166
313k
    JXL_DASSERT(!close_called_);
167
    // Buffer is large enough - don't zero buf_ below.
168
313k
    if (JXL_UNLIKELY(skip <= bits_in_buf_)) {
169
288k
      Consume(skip);
170
288k
      return;
171
288k
    }
172
173
    // First deduct what we can satisfy from the buffer
174
25.0k
    skip -= bits_in_buf_;
175
25.0k
    bits_in_buf_ = 0;
176
    // Not enough to call Advance - that may leave some bits in the buffer
177
    // which were previously ABOVE bits_in_buf.
178
25.0k
    buf_ = 0;
179
180
    // Skip whole bytes
181
25.0k
    const size_t whole_bytes = skip / kBitsPerByte;
182
25.0k
    skip %= kBitsPerByte;
183
25.0k
    if (JXL_UNLIKELY(whole_bytes >
184
25.0k
                     static_cast<size_t>(end_minus_8_ + 8 - next_byte_))) {
185
      // This is already an overflow condition (skipping past the end of the bit
186
      // stream). However if we increase next_byte_ too much we risk overflowing
187
      // that value and potentially making it valid again (next_byte_ < end).
188
      // This will set next_byte_ to the end of the stream and still consume
189
      // some bits in overread_bytes_, however the TotalBitsConsumed() will be
190
      // incorrect (still larger than the TotalBytes()).
191
21.1k
      next_byte_ = end_minus_8_ + 8;
192
21.1k
      skip += kBitsPerByte;
193
21.1k
    } else {
194
3.84k
      next_byte_ += whole_bytes;
195
3.84k
    }
196
197
25.0k
    Refill();
198
25.0k
    Consume(skip);
199
25.0k
  }
200
201
20.0M
  size_t TotalBitsConsumed() const {
202
20.0M
    const size_t bytes_read = static_cast<size_t>(next_byte_ - first_byte_);
203
20.0M
    return (bytes_read + overread_bytes_) * kBitsPerByte - bits_in_buf_;
204
20.0M
  }
205
206
213k
  Status JumpToByteBoundary() {
207
213k
    const size_t remainder = TotalBitsConsumed() % kBitsPerByte;
208
213k
    if (remainder == 0) return true;
209
181k
    if (JXL_UNLIKELY(ReadBits(kBitsPerByte - remainder) != 0)) {
210
301
      return JXL_FAILURE("Non-zero padding bits");
211
301
    }
212
180k
    return true;
213
181k
  }
214
215
  // For interoperability with other bitreaders (for resuming at
216
  // non-byte-aligned positions).
217
0
  const uint8_t* FirstByte() const { return first_byte_; }
218
9.38M
  size_t TotalBytes() const {
219
9.38M
    return static_cast<size_t>(end_minus_8_ + 8 - first_byte_);
220
9.38M
  }
221
222
  // Returns whether all the bits read so far have been within the input bounds.
223
  // When reading past the EOF, the Read*() and Consume() functions return zeros
224
  // but flag a failure when calling Close() without checking this function.
225
9.10M
  Status AllReadsWithinBounds() {
226
    // Mark up to which point the user checked the out of bounds condition. If
227
    // the user handles the condition at higher level (e.g. fetch more bytes
228
    // from network, return a custom JXL_FAILURE, ...), Close() should not
229
    // output a debug error (which would break tests with JXL_CRASH_ON_ERROR
230
    // even when legitimately handling the situation at higher level). This is
231
    // used by Bundle::CanRead.
232
9.10M
    checked_out_of_bounds_bits_ = TotalBitsConsumed();
233
9.10M
    if (TotalBitsConsumed() > TotalBytes() * kBitsPerByte) {
234
132k
      return false;
235
132k
    }
236
8.96M
    return true;
237
9.10M
  }
238
239
  // Close the bit reader and return whether all the previous reads were
240
  // successful. Close must be called once.
241
554k
  Status Close() {
242
554k
    JXL_DASSERT(!close_called_);
243
554k
    close_called_ = true;
244
554k
    if (!first_byte_) return true;
245
554k
    if (TotalBitsConsumed() > checked_out_of_bounds_bits_ &&
246
554k
        TotalBitsConsumed() > TotalBytes() * kBitsPerByte) {
247
0
      return JXL_FAILURE("Read more bits than available in the bit_reader");
248
0
    }
249
554k
    return true;
250
554k
  }
251
252
 private:
253
  // Separate function avoids inlining this relatively cold code into callers.
254
558M
  JXL_NOINLINE void BoundsCheckedRefill() {
255
558M
    const uint8_t* end = end_minus_8_ + 8;
256
257
    // Read whole bytes until we have [56, 64) bits (same as LoadLE64)
258
558M
    for (; bits_in_buf_ < 64 - kBitsPerByte; bits_in_buf_ += kBitsPerByte) {
259
131M
      if (next_byte_ >= end) break;
260
651k
      buf_ |= static_cast<uint64_t>(*next_byte_++) << bits_in_buf_;
261
651k
    }
262
558M
    JXL_DASSERT(bits_in_buf_ < 64);
263
264
    // Add extra bytes as 0 at the end of the stream in the bit_buffer_. If
265
    // these bits are read, Close() will return a failure.
266
558M
    size_t extra_bytes = (63 - bits_in_buf_) / kBitsPerByte;
267
558M
    overread_bytes_ += extra_bytes;
268
558M
    bits_in_buf_ += extra_bytes * kBitsPerByte;
269
270
558M
    JXL_DASSERT(bits_in_buf_ < 64);
271
558M
    JXL_DASSERT(bits_in_buf_ >= 56);
272
558M
  }
273
274
0
  JXL_NOINLINE uint32_t BoundsCheckedReadByteAlignedWord() {
275
0
    if (next_byte_ + 1 < end_minus_8_ + 8) {
276
0
      uint32_t ret = LoadLE16(next_byte_);
277
0
      next_byte_ += 2;
278
0
      return ret;
279
0
    }
280
0
    overread_bytes_ += 2;
281
0
    return 0;
282
0
  }
283
284
  uint64_t buf_;
285
  size_t bits_in_buf_;  // [0, 64)
286
  const uint8_t* JXL_RESTRICT next_byte_;
287
  const uint8_t* end_minus_8_;  // for refill bounds check
288
  const uint8_t* first_byte_;   // for GetSpan
289
290
  // Number of bytes past the end that were loaded into the buf_. These bytes
291
  // are not read from memory, but instead assumed 0. It is an error (likely due
292
  // to an invalid stream) to Consume() more bits than specified in the range
293
  // passed to the constructor.
294
  uint64_t overread_bytes_{0};
295
  bool close_called_{false};
296
297
  uint64_t checked_out_of_bounds_bits_{0};
298
};
299
300
// Closes a BitReader when the BitReaderScopedCloser goes out of scope. When
301
// closing the bit reader, if the status result was failure it sets this failure
302
// to the passed variable pointer. Typical usage.
303
//
304
// Status ret = true;
305
// {
306
//   BitReader reader(...);
307
//   BitReaderScopedCloser reader_closer(&reader, &ret);
308
//
309
//   // ... code that can return errors here ...
310
// }
311
// // ... more code that doesn't use the BitReader.
312
// return ret;
313
314
class BitReaderScopedCloser {
315
 public:
316
  BitReaderScopedCloser(BitReader& reader, Status& status)
317
0
      : reader_(&reader), status_(&status) {}
318
0
  ~BitReaderScopedCloser() {
319
0
    if (reader_ != nullptr) {
320
0
      Status close_ret = reader_->Close();
321
0
      if (!close_ret) *status_ = close_ret;
322
0
    }
323
0
  }
324
  BitReaderScopedCloser(const BitReaderScopedCloser&) = delete;
325
326
 private:
327
  BitReader* reader_;
328
  Status* status_;
329
};
330
331
}  // namespace jxl
332
333
#endif  // LIB_JXL_DEC_BIT_READER_H_