Skip to content

Commit 72aa85a

Browse files
authored
[C++] Rare bad buffer content alignment if sizeof(T) != alignof(T) (#7520)
* [C++] Add a failing unit test for #7516 (Rare bad buffer content alignment if sizeof(T) != alignof(T)) * [C++] Fix final buffer alignment when using an array of structs * A struct can have an arbitrary size and therefore sizeof(struct) == alignof(struct) does not hold anymore as for value primitives. * This patch fixes this by introducing alignment parameters to various CreateVector*/StartVector calls. * Closes #7516
1 parent bfceebb commit 72aa85a

File tree

10 files changed

+611
-16
lines changed

10 files changed

+611
-16
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ set(FlatBuffers_Tests_SRCS
230230
tests/util_test.cpp
231231
tests/native_type_test_impl.h
232232
tests/native_type_test_impl.cpp
233+
tests/alignment_test.h
234+
tests/alignment_test.cpp
233235
include/flatbuffers/code_generators.h
234236
src/code_generators.cpp
235237
# file generate by running compiler on tests/monster_test.fbs
@@ -251,6 +253,8 @@ set(FlatBuffers_Tests_SRCS
251253
${CMAKE_CURRENT_BINARY_DIR}/tests/optional_scalars_generated.h
252254
# file generate by running compiler on tests/native_inline_table_test.fbs
253255
${CMAKE_CURRENT_BINARY_DIR}/tests/native_inline_table_test_generated.h
256+
# file generate by running compiler on tests/alignment_test.fbs
257+
${CMAKE_CURRENT_BINARY_DIR}/tests/alignment_test_generated.h
254258
)
255259

256260
set(FlatBuffers_Tests_CPP17_SRCS
@@ -623,6 +627,7 @@ if(FLATBUFFERS_BUILD_TESTS)
623627
compile_flatbuffers_schema_to_binary(tests/arrays_test.fbs)
624628
compile_flatbuffers_schema_to_embedded_binary(tests/monster_test.fbs "--no-includes;--gen-compare")
625629
compile_flatbuffers_schema_to_cpp(tests/native_inline_table_test.fbs "--gen-compare")
630+
compile_flatbuffers_schema_to_cpp(tests/alignment_test.fbs "--gen-compare")
626631
if(NOT (MSVC AND (MSVC_VERSION LESS 1900)))
627632
compile_flatbuffers_schema_to_cpp(tests/monster_extra.fbs) # Test floating-point NAN/INF.
628633
endif()

include/flatbuffers/flatbuffer_builder.h

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ class FlatBufferBuilder {
449449
}
450450
template<typename T> void PreAlign(size_t len) {
451451
AssertScalarT<T>();
452-
PreAlign(len, sizeof(T));
452+
PreAlign(len, AlignOf<T>());
453453
}
454454
/// @endcond
455455

@@ -589,11 +589,15 @@ class FlatBufferBuilder {
589589
return PushElement(static_cast<uoffset_t>(len));
590590
}
591591

592-
void StartVector(size_t len, size_t elemsize) {
592+
void StartVector(size_t len, size_t elemsize, size_t alignment) {
593593
NotNested();
594594
nested = true;
595595
PreAlign<uoffset_t>(len * elemsize);
596-
PreAlign(len * elemsize, elemsize); // Just in case elemsize > uoffset_t.
596+
PreAlign(len * elemsize, alignment); // Just in case elemsize > uoffset_t.
597+
}
598+
599+
template<typename T> void StartVector(size_t len) {
600+
return StartVector(len, sizeof(T), AlignOf<T>());
597601
}
598602

599603
// Call this right before StartVector/CreateVector if you want to force the
@@ -627,7 +631,7 @@ class FlatBufferBuilder {
627631
// If this assert hits, you're specifying a template argument that is
628632
// causing the wrong overload to be selected, remove it.
629633
AssertScalarT<T>();
630-
StartVector(len, sizeof(T));
634+
StartVector<T>(len);
631635
if (len == 0) { return Offset<Vector<T>>(EndVector(len)); }
632636
// clang-format off
633637
#if FLATBUFFERS_LITTLEENDIAN
@@ -668,7 +672,7 @@ class FlatBufferBuilder {
668672

669673
template<typename T>
670674
Offset<Vector<Offset<T>>> CreateVector(const Offset<T> *v, size_t len) {
671-
StartVector(len, sizeof(Offset<T>));
675+
StartVector<Offset<T>>(len);
672676
for (auto i = len; i > 0;) { PushElement(v[--i]); }
673677
return Offset<Vector<Offset<T>>>(EndVector(len));
674678
}
@@ -688,7 +692,7 @@ class FlatBufferBuilder {
688692
// an array. Instead, read elements manually.
689693
// Background: https://isocpp.org/blog/2012/11/on-vectorbool
690694
Offset<Vector<uint8_t>> CreateVector(const std::vector<bool> &v) {
691-
StartVector(v.size(), sizeof(uint8_t));
695+
StartVector<uint8_t>(v.size());
692696
for (auto i = v.size(); i > 0;) {
693697
PushElement(static_cast<uint8_t>(v[--i]));
694698
}
@@ -762,7 +766,7 @@ class FlatBufferBuilder {
762766
for (auto it = begin; it != end; ++it) {
763767
buf_.scratch_push_small(CreateString(*it));
764768
}
765-
StartVector(size, sizeof(Offset<String>));
769+
StartVector<Offset<String>>(size);
766770
for (auto i = 1; i <= size; i++) {
767771
// Note we re-evaluate the buf location each iteration to account for any
768772
// underlying buffer resizing that may occur.
@@ -782,7 +786,7 @@ class FlatBufferBuilder {
782786
/// where the vector is stored.
783787
template<typename T>
784788
Offset<Vector<const T *>> CreateVectorOfStructs(const T *v, size_t len) {
785-
StartVector(len * sizeof(T) / AlignOf<T>(), AlignOf<T>());
789+
StartVector(len * sizeof(T) / AlignOf<T>(), sizeof(T), AlignOf<T>());
786790
if (len > 0) {
787791
PushBytes(reinterpret_cast<const uint8_t *>(v), sizeof(T) * len);
788792
}
@@ -1025,16 +1029,22 @@ class FlatBufferBuilder {
10251029
/// written to at a later time to serialize the data into a `vector`
10261030
/// in the buffer.
10271031
uoffset_t CreateUninitializedVector(size_t len, size_t elemsize,
1028-
uint8_t **buf) {
1032+
size_t alignment, uint8_t **buf) {
10291033
NotNested();
1030-
StartVector(len, elemsize);
1034+
StartVector(len, elemsize, alignment);
10311035
buf_.make_space(len * elemsize);
10321036
auto vec_start = GetSize();
10331037
auto vec_end = EndVector(len);
10341038
*buf = buf_.data_at(vec_start);
10351039
return vec_end;
10361040
}
10371041

1042+
FLATBUFFERS_ATTRIBUTE([[deprecated("call the version above instead")]])
1043+
uoffset_t CreateUninitializedVector(size_t len, size_t elemsize,
1044+
uint8_t **buf) {
1045+
return CreateUninitializedVector(len, elemsize, elemsize, buf);
1046+
}
1047+
10381048
/// @brief Specialized version of `CreateVector` for non-copying use cases.
10391049
/// Write the data any time later to the returned buffer pointer `buf`.
10401050
/// @tparam T The data type of the data that will be stored in the buffer
@@ -1046,14 +1056,14 @@ class FlatBufferBuilder {
10461056
template<typename T>
10471057
Offset<Vector<T>> CreateUninitializedVector(size_t len, T **buf) {
10481058
AssertScalarT<T>();
1049-
return CreateUninitializedVector(len, sizeof(T),
1059+
return CreateUninitializedVector(len, sizeof(T), AlignOf<T>(),
10501060
reinterpret_cast<uint8_t **>(buf));
10511061
}
10521062

10531063
template<typename T>
10541064
Offset<Vector<const T *>> CreateUninitializedVectorOfStructs(size_t len,
10551065
T **buf) {
1056-
return CreateUninitializedVector(len, sizeof(T),
1066+
return CreateUninitializedVector(len, sizeof(T), AlignOf<T>(),
10571067
reinterpret_cast<uint8_t **>(buf));
10581068
}
10591069

@@ -1064,7 +1074,7 @@ class FlatBufferBuilder {
10641074
Offset<Vector<T>> CreateVectorScalarCast(const U *v, size_t len) {
10651075
AssertScalarT<T>();
10661076
AssertScalarT<U>();
1067-
StartVector(len, sizeof(T));
1077+
StartVector<T>(len);
10681078
for (auto i = len; i > 0;) { PushElement(static_cast<T>(v[--i])); }
10691079
return Offset<Vector<T>>(EndVector(len));
10701080
}
@@ -1173,7 +1183,7 @@ class FlatBufferBuilder {
11731183
// Allocates space for a vector of structures.
11741184
// Must be completed with EndVectorOfStructs().
11751185
template<typename T> T *StartVectorOfStructs(size_t vector_size) {
1176-
StartVector(vector_size * sizeof(T) / AlignOf<T>(), AlignOf<T>());
1186+
StartVector(vector_size * sizeof(T) / AlignOf<T>(), sizeof(T), AlignOf<T>());
11771187
return reinterpret_cast<T *>(buf_.make_space(vector_size * sizeof(T)));
11781188
}
11791189

src/idl_parser.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,7 @@ CheckedError Parser::ParseVector(const Type &type, uoffset_t *ovalue,
16141614
});
16151615
ECHECK(err);
16161616

1617+
const size_t alignment = InlineAlignment(type);
16171618
const size_t len = count * InlineSize(type) / InlineAlignment(type);
16181619
const size_t elemsize = InlineAlignment(type);
16191620
const auto force_align = field->attributes.Lookup("force_align");
@@ -1623,7 +1624,8 @@ CheckedError Parser::ParseVector(const Type &type, uoffset_t *ovalue,
16231624
if (align > 1) { builder_.ForceVectorAlignment(len, elemsize, align); }
16241625
}
16251626

1626-
builder_.StartVector(len, elemsize);
1627+
// TODO Fix using element alignment as size (`elemsize`)!
1628+
builder_.StartVector(len, elemsize, alignment);
16271629
for (uoffset_t i = 0; i < count; i++) {
16281630
// start at the back, since we're building the data backwards.
16291631
auto &val = field_stack_.back().first;

src/reflection.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,10 @@ Offset<const Table *> CopyTable(FlatBufferBuilder &fbb,
689689
FLATBUFFERS_FALLTHROUGH(); // fall thru
690690
default: { // Scalars and structs.
691691
auto element_size = GetTypeSize(element_base_type);
692+
auto element_alignment = element_size; // For primitive elements
692693
if (elemobjectdef && elemobjectdef->is_struct())
693694
element_size = elemobjectdef->bytesize();
694-
fbb.StartVector(vec->size(), element_size);
695+
fbb.StartVector(vec->size(), element_size, element_alignment);
695696
fbb.PushBytes(vec->Data(), element_size * vec->size());
696697
offset = fbb.EndVector(vec->size());
697698
break;

tests/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ cc_test(
88
name = "flatbuffers_test",
99
testonly = 1,
1010
srcs = [
11+
"alignment_test.cpp",
12+
"alignment_test.h",
13+
"alignment_test_generated.h",
1114
"evolution_test.cpp",
1215
"evolution_test.h",
1316
"evolution_test/evolution_v1_generated.h",
@@ -50,6 +53,7 @@ cc_test(
5053
"-DBAZEL_TEST_DATA_PATH",
5154
],
5255
data = [
56+
":alignment_test.fbs",
5357
":arrays_test.bfbs",
5458
":arrays_test.fbs",
5559
":arrays_test.golden",
@@ -90,6 +94,7 @@ cc_test(
9094
"include/",
9195
],
9296
deps = [
97+
":alignment_test_cc_fbs",
9398
":arrays_test_cc_fbs",
9499
":monster_extra_cc_fbs",
95100
":monster_test_cc_fbs",
@@ -214,3 +219,8 @@ flatbuffer_cc_library(
214219
"--cpp-ptr-type flatbuffers::unique_ptr",
215220
],
216221
)
222+
223+
flatbuffer_cc_library(
224+
name = "alignment_test_cc_fbs",
225+
srcs = ["alignment_test.fbs"],
226+
)

tests/alignment_test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include "alignment_test.h"
2+
3+
#include "flatbuffers/flatbuffer_builder.h"
4+
#include "alignment_test_generated.h"
5+
#include "test_assert.h"
6+
7+
namespace flatbuffers {
8+
namespace tests {
9+
10+
void AlignmentTest() {
11+
FlatBufferBuilder builder;
12+
13+
BadAlignmentLarge large;
14+
Offset<OuterLarge> outer_large = CreateOuterLarge(builder, &large);
15+
16+
BadAlignmentSmall *small;
17+
Offset<Vector<const BadAlignmentSmall *>> small_offset =
18+
builder.CreateUninitializedVectorOfStructs(9, &small);
19+
(void)small; // We do not have to write data to trigger the test failure
20+
21+
Offset<BadAlignmentRoot> root =
22+
CreateBadAlignmentRoot(builder, outer_large, small_offset);
23+
24+
builder.Finish(root);
25+
26+
Verifier verifier(builder.GetBufferPointer(), builder.GetSize());
27+
TEST_ASSERT(VerifyBadAlignmentRootBuffer(verifier));
28+
}
29+
30+
} // namespace tests
31+
} // namespace flatbuffers

tests/alignment_test.fbs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// sizeof(BadAlignmentSmall) == 12
2+
// alignof(BadAlignmentSmall) == 4
3+
struct BadAlignmentSmall {
4+
var_0: uint;
5+
var_1: uint;
6+
var_2: uint;
7+
}
8+
9+
// sizeof(BadAlignmentLarge) == 8
10+
// alignof(BadAlignmentLarge) == 8
11+
struct BadAlignmentLarge {
12+
var_0: ulong;
13+
}
14+
15+
table OuterLarge {
16+
large: BadAlignmentLarge;
17+
}
18+
19+
table BadAlignmentRoot {
20+
large: OuterLarge;
21+
small: [BadAlignmentSmall];
22+
}
23+
24+
root_type BadAlignmentRoot;

tests/alignment_test.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef TESTS_ALIGNMENT_TEST_H
2+
#define TESTS_ALIGNMENT_TEST_H
3+
4+
namespace flatbuffers {
5+
namespace tests {
6+
7+
void AlignmentTest();
8+
9+
} // namespace tests
10+
} // namespace flatbuffers
11+
12+
#endif

0 commit comments

Comments
 (0)