Coverage Report

Created: 2026-05-27 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/common/internal/reference_count.h
Line
Count
Source
1
// Copyright 2023 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
// This header contains primitives for reference counting, roughly equivalent to
16
// the primitives used to implement `std::shared_ptr`. These primitives should
17
// not be used directly in most cases, instead `cel::Shared` should be
18
// used instead.
19
20
#ifndef THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_
21
#define THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_
22
23
#include <atomic>
24
#include <cstdint>
25
#include <new>
26
#include <string>
27
#include <type_traits>
28
#include <utility>
29
30
#include "absl/base/attributes.h"
31
#include "absl/base/nullability.h"
32
#include "absl/base/optimization.h"
33
#include "absl/log/absl_check.h"
34
#include "absl/strings/string_view.h"
35
#include "common/data.h"
36
#include "google/protobuf/arena.h"
37
#include "google/protobuf/message_lite.h"
38
39
namespace cel::common_internal {
40
41
struct AdoptRef final {
42
  explicit AdoptRef() = default;
43
};
44
45
inline constexpr AdoptRef kAdoptRef{};
46
47
class ReferenceCount;
48
struct ReferenceCountFromThis;
49
50
void SetReferenceCountForThat(ReferenceCountFromThis& that,
51
                              ReferenceCount* absl_nullable refcount);
52
53
ReferenceCount* absl_nullable GetReferenceCountForThat(
54
    const ReferenceCountFromThis& that);
55
56
// `ReferenceCountFromThis` is similar to `std::enable_shared_from_this`. It
57
// allows the derived object to inspect its own reference count. It should not
58
// be used directly, but should be used through
59
// `cel::EnableManagedMemoryFromThis`.
60
struct ReferenceCountFromThis {
61
 private:
62
  friend void SetReferenceCountForThat(ReferenceCountFromThis& that,
63
                                       ReferenceCount* absl_nullable refcount);
64
  friend ReferenceCount* absl_nullable GetReferenceCountForThat(
65
      const ReferenceCountFromThis& that);
66
67
  static constexpr uintptr_t kNullPtr = uintptr_t{0};
68
  static constexpr uintptr_t kSentinelPtr = ~kNullPtr;
69
70
  void* absl_nullable refcount = reinterpret_cast<void*>(kSentinelPtr);
71
};
72
73
inline void SetReferenceCountForThat(ReferenceCountFromThis& that,
74
0
                                     ReferenceCount* absl_nullable refcount) {
75
0
  ABSL_DCHECK_EQ(that.refcount,
76
0
                 reinterpret_cast<void*>(ReferenceCountFromThis::kSentinelPtr));
77
0
  that.refcount = static_cast<void*>(refcount);
78
0
}
79
80
inline ReferenceCount* absl_nullable GetReferenceCountForThat(
81
0
    const ReferenceCountFromThis& that) {
82
0
  ABSL_DCHECK_NE(that.refcount,
83
0
                 reinterpret_cast<void*>(ReferenceCountFromThis::kSentinelPtr));
84
0
  return static_cast<ReferenceCount*>(that.refcount);
85
0
}
86
87
void StrongRef(const ReferenceCount& refcount) noexcept;
88
89
void StrongRef(const ReferenceCount* absl_nullable refcount) noexcept;
90
91
void StrongUnref(const ReferenceCount& refcount) noexcept;
92
93
void StrongUnref(const ReferenceCount* absl_nullable refcount) noexcept;
94
95
ABSL_MUST_USE_RESULT
96
bool StrengthenRef(const ReferenceCount& refcount) noexcept;
97
98
ABSL_MUST_USE_RESULT
99
bool StrengthenRef(const ReferenceCount* absl_nullable refcount) noexcept;
100
101
void WeakRef(const ReferenceCount& refcount) noexcept;
102
103
void WeakRef(const ReferenceCount* absl_nullable refcount) noexcept;
104
105
void WeakUnref(const ReferenceCount& refcount) noexcept;
106
107
void WeakUnref(const ReferenceCount* absl_nullable refcount) noexcept;
108
109
ABSL_MUST_USE_RESULT
110
bool IsUniqueRef(const ReferenceCount& refcount) noexcept;
111
112
ABSL_MUST_USE_RESULT
113
bool IsUniqueRef(const ReferenceCount* absl_nullable refcount) noexcept;
114
115
ABSL_MUST_USE_RESULT
116
bool IsExpiredRef(const ReferenceCount& refcount) noexcept;
117
118
ABSL_MUST_USE_RESULT
119
bool IsExpiredRef(const ReferenceCount* absl_nullable refcount) noexcept;
120
121
// `ReferenceCount` is similar to the control block used by `std::shared_ptr`.
122
// It is not meant to be interacted with directly in most cases, instead
123
// `cel::Shared` should be used.
124
class alignas(8) ReferenceCount {
125
 public:
126
5.46k
  ReferenceCount() = default;
127
128
  ReferenceCount(const ReferenceCount&) = delete;
129
  ReferenceCount(ReferenceCount&&) = delete;
130
  ReferenceCount& operator=(const ReferenceCount&) = delete;
131
  ReferenceCount& operator=(ReferenceCount&&) = delete;
132
133
5.46k
  virtual ~ReferenceCount() = default;
134
135
 private:
136
  friend void StrongRef(const ReferenceCount& refcount) noexcept;
137
  friend void StrongUnref(const ReferenceCount& refcount) noexcept;
138
  friend bool StrengthenRef(const ReferenceCount& refcount) noexcept;
139
  friend void WeakRef(const ReferenceCount& refcount) noexcept;
140
  friend void WeakUnref(const ReferenceCount& refcount) noexcept;
141
  friend bool IsUniqueRef(const ReferenceCount& refcount) noexcept;
142
  friend bool IsExpiredRef(const ReferenceCount& refcount) noexcept;
143
144
  virtual void Finalize() noexcept = 0;
145
146
  virtual void Delete() noexcept = 0;
147
148
  mutable std::atomic<int32_t> strong_refcount_ = 1;
149
  mutable std::atomic<int32_t> weak_refcount_ = 1;
150
};
151
152
// ReferenceCount and its derivations must be at least as aligned as
153
// google::protobuf::Arena. This is a requirement for the pointer tagging defined in
154
// common/internal/metadata.h.
155
static_assert(alignof(ReferenceCount) >= alignof(google::protobuf::Arena));
156
157
// `ReferenceCounted` is a base class for classes which should be reference
158
// counted. It provides default implementations for `Finalize()` and `Delete()`.
159
class ReferenceCounted : public ReferenceCount {
160
 private:
161
5.46k
  void Finalize() noexcept override {}
162
163
2
  void Delete() noexcept override { delete this; }
164
};
165
166
// `EmplacedReferenceCount` adapts `T` to make it reference countable, by
167
// storing `T` inside the reference count. This only works when `T` has not yet
168
// been allocated.
169
template <typename T>
170
class EmplacedReferenceCount final : public ReferenceCounted {
171
 public:
172
  static_assert(std::is_destructible_v<T>, "T must be destructible");
173
  static_assert(!std::is_reference_v<T>, "T must not be a reference");
174
  static_assert(!std::is_volatile_v<T>, "T must not be volatile qualified");
175
  static_assert(!std::is_const_v<T>, "T must not be const qualified");
176
  static_assert(!std::is_array_v<T>, "T must not be an array");
177
178
  template <typename... Args>
179
  explicit EmplacedReferenceCount(T*& value, Args&&... args) noexcept(
180
      std::is_nothrow_constructible_v<T, Args...>) {
181
    value =
182
        ::new (static_cast<void*>(&value_[0])) T(std::forward<Args>(args)...);
183
  }
184
185
 private:
186
  void Finalize() noexcept override {
187
    std::destroy_at(std::launder(reinterpret_cast<T*>(&value_[0])));
188
  }
189
190
  // We store the instance of `T` in a char buffer and use placement new and
191
  // direct calls to the destructor. The reason for this is `Finalize()` is
192
  // called when the strong reference count hits 0. This allows us to destroy
193
  // our instance of `T` once we are no longer strongly reachable and deallocate
194
  // the memory once we are no longer weakly reachable.
195
  alignas(T) char value_[sizeof(T)];
196
};
197
198
// `DeletingReferenceCount` adapts `T` to make it reference countable, by taking
199
// ownership of `T` and deleting it. This only works when `T` has already been
200
// allocated and is to expensive to move or copy.
201
template <typename T>
202
class DeletingReferenceCount final : public ReferenceCounted {
203
 public:
204
  explicit DeletingReferenceCount(const T* absl_nonnull to_delete) noexcept
205
0
      : to_delete_(to_delete) {}
206
207
 private:
208
0
  void Finalize() noexcept override { delete to_delete_; }
209
210
  const T* absl_nonnull const to_delete_;
211
};
212
213
extern template class DeletingReferenceCount<google::protobuf::MessageLite>;
214
215
template <typename T>
216
const ReferenceCount* absl_nonnull MakeDeletingReferenceCount(
217
    const T* absl_nonnull to_delete) {
218
  if constexpr (google::protobuf::Arena::is_arena_constructable<T>::value) {
219
    ABSL_DCHECK_EQ(to_delete->GetArena(), nullptr);
220
  }
221
  if constexpr (std::is_base_of_v<google::protobuf::MessageLite, T>) {
222
    return new DeletingReferenceCount<google::protobuf::MessageLite>(to_delete);
223
  } else {
224
    auto* refcount = new DeletingReferenceCount<T>(to_delete);
225
    if constexpr (std::is_base_of_v<Data, T>) {
226
      common_internal::SetDataReferenceCount(to_delete, refcount);
227
    }
228
    return refcount;
229
  }
230
}
231
232
template <typename T, typename... Args>
233
std::pair<T* absl_nonnull, const ReferenceCount* absl_nonnull>
234
MakeEmplacedReferenceCount(Args&&... args) {
235
  using U = std::remove_const_t<T>;
236
  U* pointer;
237
  auto* const refcount =
238
      new EmplacedReferenceCount<U>(pointer, std::forward<Args>(args)...);
239
  if constexpr (google::protobuf::Arena::is_arena_constructable<U>::value) {
240
    ABSL_DCHECK_EQ(pointer->GetArena(), nullptr);
241
  }
242
  if constexpr (std::is_base_of_v<Data, T>) {
243
    common_internal::SetDataReferenceCount(pointer, refcount);
244
  }
245
  return std::pair{static_cast<T* absl_nonnull>(pointer),
246
                   static_cast<const ReferenceCount* absl_nonnull>(refcount)};
247
}
248
249
template <typename T>
250
class InlinedReferenceCount final : public ReferenceCounted {
251
 public:
252
  template <typename... Args>
253
  explicit InlinedReferenceCount(std::in_place_t, Args&&... args)
254
      : ReferenceCounted() {
255
    ::new (static_cast<void*>(value())) T(std::forward<Args>(args)...);
256
  }
257
258
  ABSL_ATTRIBUTE_ALWAYS_INLINE T* absl_nonnull value() {
259
    return reinterpret_cast<T*>(&value_[0]);
260
  }
261
262
  ABSL_ATTRIBUTE_ALWAYS_INLINE const T* absl_nonnull value() const {
263
    return reinterpret_cast<const T*>(&value_[0]);
264
  }
265
266
 private:
267
  void Finalize() noexcept override { value()->~T(); }
268
269
  // We store the instance of `T` in a char buffer and use placement new and
270
  // direct calls to the destructor. The reason for this is `Finalize()` is
271
  // called when the strong reference count hits 0. This allows us to destroy
272
  // our instance of `T` once we are no longer strongly reachable and deallocate
273
  // the memory once we are no longer weakly reachable.
274
  alignas(T) char value_[sizeof(T)];
275
};
276
277
template <typename T, typename... Args>
278
std::pair<T* absl_nonnull, ReferenceCount* absl_nonnull> MakeReferenceCount(
279
    Args&&... args) {
280
  using U = std::remove_const_t<T>;
281
  auto* const refcount =
282
      new InlinedReferenceCount<U>(std::in_place, std::forward<Args>(args)...);
283
  auto* const pointer = refcount->value();
284
  if constexpr (std::is_base_of_v<ReferenceCountFromThis, U>) {
285
    SetReferenceCountForThat(*pointer, refcount);
286
  }
287
  return std::make_pair(static_cast<T*>(pointer),
288
                        static_cast<ReferenceCount*>(refcount));
289
}
290
291
11.4k
inline void StrongRef(const ReferenceCount& refcount) noexcept {
292
11.4k
  const auto count =
293
11.4k
      refcount.strong_refcount_.fetch_add(1, std::memory_order_relaxed);
294
11.4k
  ABSL_DCHECK_GT(count, 0);
295
11.4k
}
296
297
12.2k
inline void StrongRef(const ReferenceCount* absl_nullable refcount) noexcept {
298
12.2k
  if (refcount != nullptr) {
299
11.4k
    StrongRef(*refcount);
300
11.4k
  }
301
12.2k
}
302
303
16.9k
inline void StrongUnref(const ReferenceCount& refcount) noexcept {
304
16.9k
  const auto count =
305
16.9k
      refcount.strong_refcount_.fetch_sub(1, std::memory_order_acq_rel);
306
16.9k
  ABSL_DCHECK_GT(count, 0);
307
16.9k
  ABSL_ASSUME(count > 0);
308
16.9k
  if (ABSL_PREDICT_FALSE(count == 1)) {
309
5.46k
    const_cast<ReferenceCount&>(refcount).Finalize();
310
5.46k
    WeakUnref(refcount);
311
5.46k
  }
312
16.9k
}
313
314
76.4k
inline void StrongUnref(const ReferenceCount* absl_nullable refcount) noexcept {
315
76.4k
  if (refcount != nullptr) {
316
16.9k
    StrongUnref(*refcount);
317
16.9k
  }
318
76.4k
}
319
320
ABSL_MUST_USE_RESULT
321
0
inline bool StrengthenRef(const ReferenceCount& refcount) noexcept {
322
0
  auto count = refcount.strong_refcount_.load(std::memory_order_relaxed);
323
0
  while (true) {
324
0
    ABSL_DCHECK_GE(count, 0);
325
0
    ABSL_ASSUME(count >= 0);
326
0
    if (count == 0) {
327
0
      return false;
328
0
    }
329
0
    if (refcount.strong_refcount_.compare_exchange_weak(
330
0
            count, count + 1, std::memory_order_release,
331
0
            std::memory_order_relaxed)) {
332
0
      return true;
333
0
    }
334
0
  }
335
0
}
336
337
ABSL_MUST_USE_RESULT
338
inline bool StrengthenRef(
339
0
    const ReferenceCount* absl_nullable refcount) noexcept {
340
0
  return refcount != nullptr ? StrengthenRef(*refcount) : false;
341
0
}
342
343
0
inline void WeakRef(const ReferenceCount& refcount) noexcept {
344
0
  const auto count =
345
0
      refcount.weak_refcount_.fetch_add(1, std::memory_order_relaxed);
346
0
  ABSL_DCHECK_GT(count, 0);
347
0
}
348
349
0
inline void WeakRef(const ReferenceCount* absl_nullable refcount) noexcept {
350
0
  if (refcount != nullptr) {
351
0
    WeakRef(*refcount);
352
0
  }
353
0
}
354
355
5.46k
inline void WeakUnref(const ReferenceCount& refcount) noexcept {
356
5.46k
  const auto count =
357
5.46k
      refcount.weak_refcount_.fetch_sub(1, std::memory_order_acq_rel);
358
5.46k
  ABSL_DCHECK_GT(count, 0);
359
5.46k
  ABSL_ASSUME(count > 0);
360
5.46k
  if (ABSL_PREDICT_FALSE(count == 1)) {
361
5.46k
    const_cast<ReferenceCount&>(refcount).Delete();
362
5.46k
  }
363
5.46k
}
364
365
0
inline void WeakUnref(const ReferenceCount* absl_nullable refcount) noexcept {
366
0
  if (refcount != nullptr) {
367
0
    WeakUnref(*refcount);
368
0
  }
369
0
}
370
371
ABSL_MUST_USE_RESULT
372
0
inline bool IsUniqueRef(const ReferenceCount& refcount) noexcept {
373
0
  const auto count = refcount.strong_refcount_.load(std::memory_order_acquire);
374
0
  ABSL_DCHECK_GT(count, 0);
375
0
  ABSL_ASSUME(count > 0);
376
0
  return count == 1;
377
0
}
378
379
ABSL_MUST_USE_RESULT
380
0
inline bool IsUniqueRef(const ReferenceCount* absl_nullable refcount) noexcept {
381
0
  return refcount != nullptr ? IsUniqueRef(*refcount) : false;
382
0
}
383
384
ABSL_MUST_USE_RESULT
385
0
inline bool IsExpiredRef(const ReferenceCount& refcount) noexcept {
386
0
  const auto count = refcount.strong_refcount_.load(std::memory_order_acquire);
387
0
  ABSL_DCHECK_GE(count, 0);
388
0
  ABSL_ASSUME(count >= 0);
389
0
  return count == 0;
390
0
}
391
392
ABSL_MUST_USE_RESULT
393
inline bool IsExpiredRef(
394
0
    const ReferenceCount* absl_nullable refcount) noexcept {
395
0
  return refcount != nullptr ? IsExpiredRef(*refcount) : false;
396
0
}
397
398
std::pair<const ReferenceCount* absl_nonnull, absl::string_view>
399
MakeReferenceCountedString(absl::string_view value);
400
401
std::pair<const ReferenceCount* absl_nonnull, absl::string_view>
402
MakeReferenceCountedString(std::string&& value);
403
404
}  // namespace cel::common_internal
405
406
#endif  // THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_