Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/padded_bytes.h
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
#ifndef LIB_JXL_BASE_PADDED_BYTES_H_
7
#define LIB_JXL_BASE_PADDED_BYTES_H_
8
9
// std::vector replacement with padding to reduce bounds checks in WriteBits
10
11
#include <jxl/memory_manager.h>
12
13
#include <algorithm>  // max
14
#include <cstddef>
15
#include <cstdint>
16
#include <cstring>  // memcpy
17
#include <initializer_list>
18
#include <utility>  // swap
19
20
#include "lib/jxl/base/status.h"
21
#include "lib/jxl/memory_manager_internal.h"
22
23
namespace jxl {
24
25
// Provides a subset of the std::vector interface with some differences:
26
// - allows BitWriter to write 64 bits at a time without bounds checking;
27
// - ONLY zero-initializes the first byte (required by BitWriter);
28
// - ensures cache-line alignment.
29
class PaddedBytes {
30
 public:
31
  // Required for output params.
32
  explicit PaddedBytes(JxlMemoryManager* memory_manager)
33
366k
      : memory_manager_(memory_manager), size_(0), capacity_(0) {}
34
35
  static StatusOr<PaddedBytes> WithInitialSpace(
36
15.9k
      JxlMemoryManager* memory_manager, size_t size) {
37
15.9k
    PaddedBytes result(memory_manager);
38
15.9k
    JXL_RETURN_IF_ERROR(result.Init(size));
39
15.9k
    return result;
40
15.9k
  }
41
42
  // Deleting copy constructor and copy assignment operator to prevent copying
43
  PaddedBytes(const PaddedBytes&) = delete;
44
  PaddedBytes& operator=(const PaddedBytes&) = delete;
45
46
  // default is not OK - need to set other.size_ to 0!
47
  PaddedBytes(PaddedBytes&& other) noexcept
48
90.3k
      : memory_manager_(other.memory_manager_),
49
90.3k
        size_(other.size_),
50
90.3k
        capacity_(other.capacity_),
51
90.3k
        data_(std::move(other.data_)) {
52
90.3k
    other.size_ = other.capacity_ = 0;
53
90.3k
  }
54
71.9k
  PaddedBytes& operator=(PaddedBytes&& other) noexcept {
55
71.9k
    memory_manager_ = other.memory_manager_;
56
71.9k
    size_ = other.size_;
57
71.9k
    capacity_ = other.capacity_;
58
71.9k
    data_ = std::move(other.data_);
59
60
71.9k
    if (&other != this) {
61
71.9k
      other.size_ = other.capacity_ = 0;
62
71.9k
    }
63
71.9k
    return *this;
64
71.9k
  }
65
66
24.6k
  JxlMemoryManager* memory_manager() const { return memory_manager_; }
67
68
0
  void swap(PaddedBytes& other) noexcept {
69
0
    std::swap(memory_manager_, other.memory_manager_);
70
0
    std::swap(size_, other.size_);
71
0
    std::swap(capacity_, other.capacity_);
72
0
    std::swap(data_, other.data_);
73
0
  }
74
75
  // If current capacity is greater than requested, then no-op. Otherwise
76
  // copies existing data to newly allocated "data_".
77
  // The new capacity will be at least 1.5 times the old capacity. This ensures
78
  // that we avoid quadratic behaviour.
79
981k
  Status reserve(size_t capacity) {
80
981k
    if (capacity <= capacity_) return true;
81
82
139k
    size_t new_capacity;
83
139k
    if (!SafeAdd(capacity_, capacity_ / 2, new_capacity)) {
84
0
      return JXL_FAILURE("PaddedBytes reserve: capacity overflow");
85
0
    }
86
139k
    new_capacity = std::max(capacity, new_capacity);
87
88
139k
    return change_capacity(new_capacity);
89
139k
  }
90
91
3.59k
  Status shrink_to_fit() { return change_capacity(size_); }
92
93
  // NOTE: unlike vector, this does not initialize the new data!
94
  // However, we guarantee that write_bits can safely append after
95
  // the resize, as we zero-initialize the first new byte of data.
96
  // If size < capacity(), does not invalidate the memory.
97
951k
  Status resize(size_t size) {
98
951k
    JXL_RETURN_IF_ERROR(reserve(size));
99
951k
    size_ = size;
100
951k
    return true;
101
951k
  }
102
103
  // resize(size) plus explicit initialization of the new data with `value`.
104
0
  Status resize(size_t size, uint8_t value) {
105
0
    size_t old_size = size_;
106
0
    JXL_RETURN_IF_ERROR(resize(size));
107
0
    if (size_ > old_size) {
108
0
      memset(data() + old_size, value, size_ - old_size);
109
0
    }
110
0
    return true;
111
0
  }
112
113
  // Amortized constant complexity due to exponential growth.
114
791k
  Status push_back(uint8_t x) {
115
791k
    if (size_ == capacity_) {
116
13.7k
      JXL_RETURN_IF_ERROR(reserve(capacity_ + 1));
117
13.7k
    }
118
119
791k
    data_.address<uint8_t>()[size_++] = x;
120
791k
    return true;
121
791k
  }
122
123
3.76M
  size_t size() const { return size_; }
124
0
  size_t capacity() const { return capacity_; }
125
126
67.8M
  uint8_t* data() { return data_.address<uint8_t>(); }
127
526k
  const uint8_t* data() const { return data_.address<uint8_t>(); }
128
129
  // std::vector operations implemented in terms of the public interface above.
130
131
1.68k
  void clear() {
132
    // Not passing on the Status, because resizing to 0 cannot fail.
133
1.68k
    static_cast<void>(resize(0));
134
1.68k
  }
135
1.47M
  bool empty() const { return size() == 0; }
136
137
0
  Status assign(std::initializer_list<uint8_t> il) {
138
0
    JXL_RETURN_IF_ERROR(resize(il.size()));
139
0
    memcpy(data(), il.begin(), il.size());
140
0
    return true;
141
0
  }
142
143
15.2k
  uint8_t* begin() { return data(); }
144
20.5k
  const uint8_t* begin() const { return data(); }
145
7.60k
  uint8_t* end() { return begin() + size(); }
146
10.2k
  const uint8_t* end() const { return begin() + size(); }
147
148
67.2M
  uint8_t& operator[](const size_t i) {
149
67.2M
    BoundsCheck(i);
150
67.2M
    return data()[i];
151
67.2M
  }
152
479k
  const uint8_t& operator[](const size_t i) const {
153
479k
    BoundsCheck(i);
154
479k
    return data()[i];
155
479k
  }
156
157
  template <typename T>
158
44.3k
  Status append(const T& other) {
159
44.3k
    return append(
160
44.3k
        reinterpret_cast<const uint8_t*>(other.data()),
161
44.3k
        reinterpret_cast<const uint8_t*>(other.data()) + other.size());
162
44.3k
  }
jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 4ul> >(std::__1::array<unsigned char, 4ul> const&)
Line
Count
Source
158
42.4k
  Status append(const T& other) {
159
42.4k
    return append(
160
42.4k
        reinterpret_cast<const uint8_t*>(other.data()),
161
42.4k
        reinterpret_cast<const uint8_t*>(other.data()) + other.size());
162
42.4k
  }
jxl::Status jxl::PaddedBytes::append<std::__1::array<unsigned char, 128ul> >(std::__1::array<unsigned char, 128ul> const&)
Line
Count
Source
158
1.86k
  Status append(const T& other) {
159
1.86k
    return append(
160
1.86k
        reinterpret_cast<const uint8_t*>(other.data()),
161
1.86k
        reinterpret_cast<const uint8_t*>(other.data()) + other.size());
162
1.86k
  }
jxl::Status jxl::PaddedBytes::append<jxl::PaddedBytes>(jxl::PaddedBytes const&)
Line
Count
Source
158
8
  Status append(const T& other) {
159
8
    return append(
160
8
        reinterpret_cast<const uint8_t*>(other.data()),
161
8
        reinterpret_cast<const uint8_t*>(other.data()) + other.size());
162
8
  }
163
164
61.8k
  Status append(const uint8_t* begin, const uint8_t* end) {
165
61.8k
    if (end > begin) {
166
52.2k
      size_t old_size = size();
167
52.2k
      size_t new_size;
168
52.2k
      if (!SafeAdd(old_size, static_cast<size_t>(end - begin), new_size)) {
169
0
        return JXL_FAILURE("PaddedBytes append: overflow");
170
0
      }
171
52.2k
      JXL_RETURN_IF_ERROR(resize(new_size));
172
52.2k
      memcpy(data() + old_size, begin, end - begin);
173
52.2k
    }
174
61.8k
    return true;
175
61.8k
  }
176
177
 private:
178
15.9k
  Status Init(size_t size) {
179
15.9k
    size_ = size;
180
15.9k
    return reserve(size);
181
15.9k
  }
182
183
67.7M
  void BoundsCheck(size_t i) const {
184
    // <= is safe due to padding and required by BitWriter.
185
67.7M
    JXL_DASSERT(i <= size());
186
67.7M
  }
187
188
143k
  Status change_capacity(size_t new_capacity) {
189
143k
    JXL_DASSERT(new_capacity >= size_);
190
191
143k
    new_capacity = std::max<size_t>(64, new_capacity);
192
193
    // BitWriter writes up to 7 bytes past the end.
194
143k
    size_t padded_capacity;
195
143k
    if (!SafeAdd(new_capacity, static_cast<size_t>(8), padded_capacity)) {
196
0
      return JXL_FAILURE("PaddedBytes capacity too large");
197
0
    }
198
143k
    JXL_ASSIGN_OR_RETURN(
199
143k
        AlignedMemory new_data,
200
143k
        AlignedMemory::Create(memory_manager_, padded_capacity));
201
202
143k
    if (data_.address<void>() == nullptr) {
203
      // First allocation: ensure first byte is initialized (won't be copied).
204
104k
      new_data.address<uint8_t>()[0] = 0;
205
104k
    } else {
206
      // Subsequent resize: copy existing data to new location.
207
38.4k
      memmove(new_data.address<void>(), data_.address<void>(), size_);
208
      // Ensure that the first new byte is initialized, to allow write_bits to
209
      // safely append to the newly-resized PaddedBytes.
210
38.4k
      new_data.address<uint8_t>()[size_] = 0;
211
38.4k
    }
212
213
143k
    capacity_ = new_capacity;
214
143k
    data_ = std::move(new_data);
215
143k
    return true;
216
143k
  }
217
218
  JxlMemoryManager* memory_manager_;
219
  size_t size_;
220
  size_t capacity_;
221
  AlignedMemory data_;
222
};
223
224
}  // namespace jxl
225
226
#endif  // LIB_JXL_BASE_PADDED_BYTES_H_