Fix aliasing Set calls. Aliasing input make the memcpy calls overlap source and destination. PiperOrigin-RevId: 738101367
diff --git a/src/google/protobuf/micro_string.cc b/src/google/protobuf/micro_string.cc index ef0b0f2..acc4f97 100644 --- a/src/google/protobuf/micro_string.cc +++ b/src/google/protobuf/micro_string.cc
@@ -65,7 +65,7 @@ auto* h = micro_rep(); if (h->capacity >= data.size()) { h->size = data.size(); - memcpy(h->data(), data.data(), data.size()); + memmove(h->data(), data.data(), data.size()); return; } if (arena == nullptr) { @@ -77,7 +77,7 @@ auto* h = large_rep(); if (h->capacity >= data.size()) { h->size = data.size(); - memcpy(h->payload, data.data(), data.size()); + memmove(h->payload, data.data(), data.size()); return; } break; @@ -105,7 +105,7 @@ // If we fit in the inline space, use it. if (kHasInlineRep && data.size() <= inline_capacity) { set_inline_size(data.size()); - memcpy(inline_head(), data.data(), data.size()); + memmove(inline_head(), data.data(), data.size()); return; }
diff --git a/src/google/protobuf/micro_string.h b/src/google/protobuf/micro_string.h index deca870..9c1c723 100644 --- a/src/google/protobuf/micro_string.h +++ b/src/google/protobuf/micro_string.h
@@ -263,7 +263,12 @@ } void set_inline_size(size_t size) { ABSL_DCHECK(kHasInlineRep); - rep_ = reinterpret_cast<void*>(size << kTagShift); + size <<= kTagShift; + PROTOBUF_ASSUME(size <= 0xFF); + // Only overwrite the size byte to avoid clobbering the char bytes in case + // of aliasing. + rep_ = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(rep_) & ~0xFF) | + size); ABSL_DCHECK(is_inline()); } char* inline_head() {
diff --git a/src/google/protobuf/micro_string_test.cc b/src/google/protobuf/micro_string_test.cc index 8c52f92..e40f34b 100644 --- a/src/google/protobuf/micro_string_test.cc +++ b/src/google/protobuf/micro_string_test.cc
@@ -412,6 +412,60 @@ str_.SpaceUsedExcludingSelfLong()); } +TEST_P(MicroStringPrevTest, SelfSetView) { + const std::string control(str_.Get()); + + const size_t used = arena_space_used(); + const bool will_reuse = str_.Capacity() != 0; + const size_t self_used = str_.SpaceUsedExcludingSelfLong(); + + str_.Set(str_.Get(), arena()); + EXPECT_EQ(str_.Get(), control); + + if (will_reuse) { + ExpectMemoryUsed(used, false, self_used); + } +} + +TEST_P(MicroStringPrevTest, SelfSetSubstrView) { + const std::string control(str_.Get()); + if (control.empty()) { + GTEST_SKIP() << "Can't substr an empty input."; + } + + const size_t used = arena_space_used(); + const bool will_reuse = str_.Capacity() != 0; + const size_t self_used = str_.SpaceUsedExcludingSelfLong(); + + str_.Set(str_.Get().substr(1), arena()); + EXPECT_EQ(str_.Get(), absl::string_view(control).substr(1)); + + if (will_reuse) { + ExpectMemoryUsed(used, false, self_used); + } +} + +TEST_P(MicroStringPrevTest, SelfSetSubstrViewConstantSize) { + const std::string control(str_.Get()); + if (control.size() < 3) { + GTEST_SKIP() << "Can't substr an empty input."; + } + + const size_t used = arena_space_used(); + const bool will_reuse = str_.Capacity() != 0; + const size_t self_used = str_.SpaceUsedExcludingSelfLong(); + + // Here we test the fastpath in SetMaybeConstant. + // The input is an aliasing substr that overlaps with the destination, but + // with constant size to trigger the fastpath. + str_.Set(str_.Get().substr(1, 2), arena()); + EXPECT_EQ(str_.Get(), absl::string_view(control).substr(1, 2)); + + if (will_reuse) { + ExpectMemoryUsed(used, false, self_used); + } +} + TEST_P(MicroStringPrevTest, CopyConstruct) { const size_t used = arena_space_used(); MicroString copy(arena(), str_);