Skip to content

Commit f61aac8

Browse files
ezbrcopybara-github
authored andcommitted
Add validation against use of moved-from hash tables.
We enable this validation when sanitizers are enabled rather than in debug mode because debug mode can be enabled/disabled separately in different translation units, which can cause bugs for this type of validation. E.g. a hashtable that was moved from in a TU with debug enabled is then destroyed in a TU with debug disabled. PiperOrigin-RevId: 668492783 Change-Id: Ifab6894de0890aa0a7d8ea338b53b6759b097cb1
1 parent 0f93828 commit f61aac8

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

absl/container/internal/raw_hash_set.h

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ enum InvalidCapacity : size_t {
545545
kAboveMaxValidCapacity = ~size_t{} - 100,
546546
kReentrance,
547547
kDestroyed,
548+
kMovedFrom,
548549
};
549550

550551
// Returns a pointer to a control byte group that can be used by empty tables.
@@ -2402,6 +2403,10 @@ class raw_hash_set {
24022403
alignof(slot_type) <= alignof(HeapOrSoo);
24032404
}
24042405

2406+
constexpr static size_t DefaultCapacity() {
2407+
return SooEnabled() ? SooCapacity() : 0;
2408+
}
2409+
24052410
// Whether `size` fits in the SOO capacity of this table.
24062411
bool fits_in_soo(size_t size) const {
24072412
return SooEnabled() && size <= SooCapacity();
@@ -2639,7 +2644,7 @@ class raw_hash_set {
26392644
const allocator_type& alloc = allocator_type())
26402645
: settings_(CommonFields::CreateDefault<SooEnabled()>(), hash, eq,
26412646
alloc) {
2642-
if (bucket_count > (SooEnabled() ? SooCapacity() : 0)) {
2647+
if (bucket_count > DefaultCapacity()) {
26432648
resize(NormalizeCapacity(bucket_count));
26442649
}
26452650
}
@@ -2822,15 +2827,15 @@ class raw_hash_set {
28222827
transfer(soo_slot(), that.soo_slot());
28232828
}
28242829
that.common() = CommonFields::CreateDefault<SooEnabled()>();
2825-
maybe_increment_generation_or_rehash_on_move();
2830+
annotate_for_bug_detection_on_move(that);
28262831
}
28272832

28282833
raw_hash_set(raw_hash_set&& that, const allocator_type& a)
28292834
: settings_(CommonFields::CreateDefault<SooEnabled()>(), that.hash_ref(),
28302835
that.eq_ref(), a) {
28312836
if (a == that.alloc_ref()) {
28322837
swap_common(that);
2833-
maybe_increment_generation_or_rehash_on_move();
2838+
annotate_for_bug_detection_on_move(that);
28342839
} else {
28352840
move_elements_allocs_unequal(std::move(that));
28362841
}
@@ -2896,15 +2901,19 @@ class raw_hash_set {
28962901
size_t size() const { return common().size(); }
28972902
size_t capacity() const {
28982903
const size_t cap = common().capacity();
2899-
// Compiler complains when using functions in assume so use local variables.
2900-
ABSL_ATTRIBUTE_UNUSED static constexpr bool kEnabled = SooEnabled();
2901-
ABSL_ATTRIBUTE_UNUSED static constexpr size_t kCapacity = SooCapacity();
2902-
ABSL_ASSUME(!kEnabled || cap >= kCapacity);
2904+
// Compiler complains when using functions in ASSUME so use local variable.
2905+
ABSL_ATTRIBUTE_UNUSED static constexpr size_t kDefaultCapacity =
2906+
DefaultCapacity();
2907+
ABSL_ASSUME(cap >= kDefaultCapacity);
29032908
return cap;
29042909
}
29052910
size_t max_size() const { return (std::numeric_limits<size_t>::max)(); }
29062911

29072912
ABSL_ATTRIBUTE_REINITIALIZES void clear() {
2913+
if (SwisstableGenerationsEnabled() &&
2914+
capacity() == InvalidCapacity::kMovedFrom) {
2915+
common().set_capacity(DefaultCapacity());
2916+
}
29082917
AssertNotDebugCapacity();
29092918
// Iterating over this container is O(bucket_count()). When bucket_count()
29102919
// is much greater than size(), iteration becomes prohibitively expensive.
@@ -3586,6 +3595,10 @@ class raw_hash_set {
35863595
}
35873596

35883597
inline void destructor_impl() {
3598+
if (SwisstableGenerationsEnabled() &&
3599+
capacity() == InvalidCapacity::kMovedFrom) {
3600+
return;
3601+
}
35893602
if (capacity() == 0) return;
35903603
if (is_soo()) {
35913604
if (!empty()) {
@@ -3759,8 +3772,16 @@ class raw_hash_set {
37593772
move_common(that_is_full_soo, that.alloc_ref(), common(), std::move(tmp));
37603773
}
37613774

3762-
void maybe_increment_generation_or_rehash_on_move() {
3763-
if (!SwisstableGenerationsEnabled() || capacity() == 0 || is_soo()) {
3775+
void annotate_for_bug_detection_on_move(
3776+
ABSL_ATTRIBUTE_UNUSED raw_hash_set& that) {
3777+
// We only enable moved-from validation when generations are enabled (rather
3778+
// than using NDEBUG) to avoid issues in which NDEBUG is enabled in some
3779+
// translation units but not in others.
3780+
if (SwisstableGenerationsEnabled() && this != &that) {
3781+
that.common().set_capacity(InvalidCapacity::kMovedFrom);
3782+
}
3783+
if (!SwisstableGenerationsEnabled() || capacity() == DefaultCapacity() ||
3784+
capacity() > kAboveMaxValidCapacity) {
37643785
return;
37653786
}
37663787
common().increment_generation();
@@ -3782,7 +3803,7 @@ class raw_hash_set {
37823803
CopyAlloc(alloc_ref(), that.alloc_ref(),
37833804
std::integral_constant<bool, propagate_alloc>());
37843805
that.common() = CommonFields::CreateDefault<SooEnabled()>();
3785-
maybe_increment_generation_or_rehash_on_move();
3806+
annotate_for_bug_detection_on_move(that);
37863807
return *this;
37873808
}
37883809

@@ -3796,7 +3817,7 @@ class raw_hash_set {
37963817
}
37973818
if (!that.is_soo()) that.dealloc();
37983819
that.common() = CommonFields::CreateDefault<SooEnabled()>();
3799-
maybe_increment_generation_or_rehash_on_move();
3820+
annotate_for_bug_detection_on_move(that);
38003821
return *this;
38013822
}
38023823

@@ -3875,27 +3896,30 @@ class raw_hash_set {
38753896
// Asserts for correctness that we run on find/find_or_prepare_insert.
38763897
template <class K>
38773898
void AssertOnFind(ABSL_ATTRIBUTE_UNUSED const K& key) {
3878-
#ifdef NDEBUG
3879-
return;
3880-
#endif
38813899
AssertHashEqConsistent(key);
38823900
AssertNotDebugCapacity();
38833901
}
38843902

38853903
// Asserts that the capacity is not a sentinel invalid value.
3886-
// TODO(b/296061262): also add asserts for moved-from state.
38873904
void AssertNotDebugCapacity() const {
38883905
assert(capacity() != InvalidCapacity::kReentrance &&
3889-
"reentrant container access during element construction/destruction "
3906+
"Reentrant container access during element construction/destruction "
38903907
"is not allowed.");
38913908
assert(capacity() != InvalidCapacity::kDestroyed &&
3892-
"use of destroyed hash table.");
3909+
"Use of destroyed hash table.");
3910+
if (SwisstableGenerationsEnabled() &&
3911+
ABSL_PREDICT_FALSE(capacity() == InvalidCapacity::kMovedFrom)) {
3912+
ABSL_RAW_LOG(FATAL, "Use of moved-from hash table.");
3913+
}
38933914
}
38943915

38953916
// Asserts that hash and equal functors provided by the user are consistent,
38963917
// meaning that `eq(k1, k2)` implies `hash(k1)==hash(k2)`.
38973918
template <class K>
38983919
void AssertHashEqConsistent(const K& key) {
3920+
#ifdef NDEBUG
3921+
return;
3922+
#endif
38993923
// If the hash/eq functors are known to be consistent, then skip validation.
39003924
if (std::is_same<hasher, absl::container_internal::StringHash>::value &&
39013925
std::is_same<key_equal, absl::container_internal::StringEq>::value) {

absl/container/internal/raw_hash_set_test.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3674,6 +3674,29 @@ TEST(Table, DestroyedCallsFail) {
36743674
#endif
36753675
}
36763676

3677+
TEST(Table, MovedFromCallsFail) {
3678+
if (!SwisstableGenerationsEnabled()) {
3679+
GTEST_SKIP() << "Moved-from checks only enabled in sanitizer mode.";
3680+
return;
3681+
}
3682+
3683+
{
3684+
ABSL_ATTRIBUTE_UNUSED IntTable t1, t2;
3685+
t1.insert(1);
3686+
t2 = std::move(t1);
3687+
// NOLINTNEXTLINE(bugprone-use-after-move)
3688+
EXPECT_DEATH_IF_SUPPORTED(t1.contains(1), "");
3689+
}
3690+
{
3691+
ABSL_ATTRIBUTE_UNUSED IntTable t1;
3692+
t1.insert(1);
3693+
ABSL_ATTRIBUTE_UNUSED IntTable t2(std::move(t1));
3694+
// NOLINTNEXTLINE(bugprone-use-after-move)
3695+
EXPECT_DEATH_IF_SUPPORTED(t1.contains(1), "");
3696+
t1.clear(); // Clearing a moved-from table is allowed.
3697+
}
3698+
}
3699+
36773700
} // namespace
36783701
} // namespace container_internal
36793702
ABSL_NAMESPACE_END

0 commit comments

Comments
 (0)