Skip to content

Commit c149521

Browse files
sbenzaquencopybara-github
authored andcommitted
Fix edge case for MicroRep allocation.
Because of the interaction between Ceil and the rounding down we could end up with a `delete` that used less bytes than the `new`. Changed the test to not use an arena to exercise the `delete` path. PiperOrigin-RevId: 761087000
1 parent 7c6dc7b commit c149521

File tree

3 files changed

+46
-35
lines changed

3 files changed

+46
-35
lines changed

src/google/protobuf/micro_string.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,13 @@ MicroString::MicroRep* MicroString::AllocateMicroRep(size_t size,
113113
MicroRep* h;
114114
size_t capacity = size;
115115
if (arena == nullptr) {
116-
const internal::SizedPtr alloc = internal::AllocateAtLeast(
117-
ArenaAlignDefault::Ceil(MicroRepSize(capacity)));
116+
size_t requested_size = ArenaAlignDefault::Ceil(MicroRepSize(capacity));
117+
const internal::SizedPtr alloc = internal::AllocateAtLeast(requested_size);
118118
// Maybe we rounded up too much.
119119
capacity = std::min(kMaxMicroRepCapacity, alloc.n - sizeof(MicroRep));
120+
// Verify that the size we are going to free later is at least what we asked
121+
// for.
122+
ABSL_DCHECK_LE(requested_size, MicroRepSize(capacity));
120123
h = reinterpret_cast<MicroRep*>(alloc.p);
121124
} else {
122125
capacity =

src/google/protobuf/micro_string.h

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifndef GOOGLE_PROTOBUF_MICRO_STRING_H__
99
#define GOOGLE_PROTOBUF_MICRO_STRING_H__
1010

11+
#include <cstddef>
1112
#include <cstdint>
1213

1314
#include "absl/base/config.h"
@@ -97,12 +98,34 @@ class PROTOBUF_EXPORT MicroString {
9798
}
9899
};
99100

101+
struct MicroRep {
102+
uint8_t size;
103+
uint8_t capacity;
104+
105+
char* data() { return reinterpret_cast<char*>(this + 1); }
106+
const char* data() const { return reinterpret_cast<const char*>(this + 1); }
107+
absl::string_view view() const { return {data(), size}; }
108+
109+
void SetInitialSize(uint8_t size) {
110+
PoisonMemoryRegion(data() + size, capacity - size);
111+
this->size = size;
112+
}
113+
114+
void Unpoison() { UnpoisonMemoryRegion(data(), capacity); }
115+
116+
void ChangeSize(uint8_t new_size) {
117+
PoisonMemoryRegion(data() + new_size, capacity - new_size);
118+
UnpoisonMemoryRegion(data(), new_size);
119+
size = new_size;
120+
}
121+
};
122+
100123
public:
101124
// We don't allow extra capacity in big-endian because it is harder to manage
102125
// the pointer to the MicroString "base".
103126
static constexpr bool kAllowExtraCapacity = IsLittleEndian();
104127
static constexpr size_t kInlineCapacity = sizeof(uintptr_t) - 1;
105-
static constexpr size_t kMaxMicroRepCapacity = 255;
128+
static constexpr size_t kMaxMicroRepCapacity = 256 - sizeof(MicroRep);
106129

107130
// Empty string.
108131
constexpr MicroString() : rep_() {}
@@ -311,27 +334,6 @@ class PROTOBUF_EXPORT MicroString {
311334
return cap >= kOwned ? kOwned : static_cast<LargeRepKind>(cap);
312335
}
313336

314-
struct MicroRep {
315-
uint8_t size;
316-
uint8_t capacity;
317-
318-
char* data() { return reinterpret_cast<char*>(this + 1); }
319-
const char* data() const { return reinterpret_cast<const char*>(this + 1); }
320-
absl::string_view view() const { return {data(), size}; }
321-
322-
void SetInitialSize(uint8_t size) {
323-
PoisonMemoryRegion(data() + size, capacity - size);
324-
this->size = size;
325-
}
326-
327-
void Unpoison() { UnpoisonMemoryRegion(data(), capacity); }
328-
329-
void ChangeSize(uint8_t new_size) {
330-
PoisonMemoryRegion(data() + new_size, capacity - new_size);
331-
UnpoisonMemoryRegion(data(), new_size);
332-
size = new_size;
333-
}
334-
};
335337
// Micro-optimization: by using kIsMicroRepTag as 2, the MicroRep `rep_`
336338
// pointer (with the tag) is already pointing into the data buffer.
337339
static_assert(sizeof(MicroRep) == kIsMicroRepTag);

src/google/protobuf/micro_string_test.cc

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -319,19 +319,25 @@ TEST(MicroStringTest, CapacityIsRoundedUpOnHeap) {
319319
}
320320

321321
TEST(MicroStringTest, CapacityRoundingUpStaysWithinBoundsForMicroRep) {
322-
Arena arena;
323-
MicroString str;
322+
const auto get_capacity_for_size = [&](size_t size) {
323+
MicroString str;
324+
std::string input = std::string(size, 'x');
325+
str.Set(input, nullptr);
326+
EXPECT_EQ(str.Get(), input);
327+
size_t cap = str.Capacity();
328+
str.Destroy();
329+
return cap;
330+
};
324331

325-
std::string input = std::string(200, 'x');
326-
str.Set(input, &arena);
327-
EXPECT_EQ(str.Capacity(), 208 - kMicroRepSize);
328-
EXPECT_EQ(str.Get(), input);
332+
EXPECT_EQ(get_capacity_for_size(200), 208 - kMicroRepSize);
329333

330-
input = std::string(255, 'x');
331-
str.Set(input, &arena);
332-
// It caps at 255 even though the allocated block is larger.
333-
EXPECT_EQ(str.Capacity(), 255);
334-
EXPECT_EQ(str.Get(), input);
334+
// These are in the boundary
335+
EXPECT_EQ(get_capacity_for_size(253), 256 - kMicroRepSize);
336+
// This is the maximum capacity for MicroRep
337+
EXPECT_EQ(get_capacity_for_size(254), 256 - kMicroRepSize);
338+
339+
// This one jumps to LargeRep
340+
EXPECT_GE(get_capacity_for_size(255), 256);
335341
}
336342

337343
TEST(MicroStringTest, PoisonsTheUnusedCapacity) {

0 commit comments

Comments
 (0)