From 57fdd4f9951b16411bdd823eff08f5d8dea34fde Mon Sep 17 00:00:00 2001 From: Derek Bailey Date: Fri, 19 Dec 2025 14:32:51 -0800 Subject: [PATCH] Default Vector Support C++ (#8870) --- .gitignore | 5 +- CMakeLists.txt | 19 +- build_defs.bzl | 12 +- include/flatbuffers/table.h | 60 ++++++ scripts/generate_code.py | 4 + src/idl_gen_cpp.cpp | 131 +++++++++++-- src/idl_parser.cpp | 3 +- tests/BUILD.bazel | 16 ++ tests/default_vectors_strings_test.cpp | 249 +++++++++++++++++++++++++ tests/default_vectors_strings_test.fbs | 39 ++++ tests/default_vectors_strings_test.h | 17 ++ tests/test.cpp | 2 + 12 files changed, 526 insertions(+), 31 deletions(-) create mode 100644 tests/default_vectors_strings_test.cpp create mode 100644 tests/default_vectors_strings_test.fbs create mode 100644 tests/default_vectors_strings_test.h diff --git a/.gitignore b/.gitignore index 0296f8fac..f27c9b41b 100644 --- a/.gitignore +++ b/.gitignore @@ -156,4 +156,7 @@ kotlin/**/generated MODULE.bazel.lock # Ignore the generated docs -docs/site \ No newline at end of file +docs/site + +# Ignore generated files +*.fbs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e39c49cf..7bf48e6ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,6 +218,8 @@ set(FlatHash_SRCS set(FlatBuffers_Tests_SRCS ${FlatBuffers_Library_SRCS} src/idl_gen_fbs.cpp + tests/default_vectors_strings_test.cpp + tests/default_vectors_strings_test.h tests/evolution_test.cpp tests/flexbuffers_test.cpp tests/fuzz_test.cpp @@ -496,28 +498,34 @@ if(FLATBUFFERS_BUILD_SHAREDLIB) endif() endif() -function(compile_schema SRC_FBS OPT OUT_GEN_FILE) +function(compile_schema SRC_FBS OPT SUFFIX OUT_GEN_FILE) get_filename_component(SRC_FBS_DIR ${SRC_FBS} PATH) - string(REGEX REPLACE "\\.fbs$" "_generated.h" GEN_HEADER ${SRC_FBS}) + string(REGEX REPLACE "\\.fbs$" "${SUFFIX}.h" GEN_HEADER ${SRC_FBS}) add_custom_command( OUTPUT ${GEN_HEADER} COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" ${OPT} + --filename-suffix ${SUFFIX} -o "${SRC_FBS_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${SRC_FBS}" DEPENDS flatc ${SRC_FBS} COMMENT "flatc generation: `${SRC_FBS}` -> `${GEN_HEADER}`" - ) + ) set(${OUT_GEN_FILE} ${GEN_HEADER} PARENT_SCOPE) endfunction() function(compile_schema_for_test SRC_FBS OPT) - compile_schema("${SRC_FBS}" "${OPT}" GEN_FILE) + compile_schema("${SRC_FBS}" "${OPT}" "_generated" GEN_FILE) + target_sources(flattests PRIVATE ${GEN_FILE}) +endfunction() + +function(compile_schema_for_test_fbsh SRC_FBS OPT) + compile_schema("${SRC_FBS}" "${OPT}" ".fbs" GEN_FILE) target_sources(flattests PRIVATE ${GEN_FILE}) endfunction() function(compile_schema_for_samples SRC_FBS OPT) - compile_schema("${SRC_FBS}" "${OPT}" GEN_FILE) + compile_schema("${SRC_FBS}" "${OPT}" "_generated" GEN_FILE) target_sources(flatsample PRIVATE ${GEN_FILE}) endfunction() @@ -542,6 +550,7 @@ if(FLATBUFFERS_BUILD_TESTS) SET(FLATC_OPT_SCOPED_ENUMS ${FLATC_OPT_COMP};--scoped-enums) compile_schema_for_test(tests/alignment_test.fbs "${FLATC_OPT_COMP}") + compile_schema_for_test_fbsh(tests/default_vectors_strings_test.fbs "${FLATC_OPT_COMP}") compile_schema_for_test(tests/arrays_test.fbs "${FLATC_OPT_SCOPED_ENUMS}") compile_schema_for_test(tests/native_inline_table_test.fbs "${FLATC_OPT_COMP}") compile_schema_for_test(tests/native_type_test.fbs "${FLATC_OPT_COMP}") diff --git a/build_defs.bzl b/build_defs.bzl index 51d3ce20f..b22fdfef0 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -187,6 +187,7 @@ def flatbuffer_cc_library( visibility = None, compatible_with = None, restricted_to = None, + filename_suffix = "_generated", target_compatible_with = None, srcs_filegroup_visibility = None, gen_reflections = False): @@ -230,10 +231,13 @@ def flatbuffer_cc_library( Fileset([name]_reflection): (Optional) all generated reflection binaries. cc_library([name]): library with sources and flatbuffers deps. """ - output_headers = [ - (out_prefix + "%s_generated.h") % (s.replace(".fbs", "").split("/")[-1].split(":")[-1]) - for s in srcs - ] + + output_headers = [] + for s in srcs: + base_name = s.split("/")[-1].split(":")[-1].replace(".fbs", "") + header = out_prefix + base_name + filename_suffix + ".h" + output_headers.append(header) + if deps and includes: # There is no inherent reason we couldn't support both, but this discourages # use of includes without good reason. diff --git a/include/flatbuffers/table.h b/include/flatbuffers/table.h index 0149216ff..622d13408 100644 --- a/include/flatbuffers/table.h +++ b/include/flatbuffers/table.h @@ -18,6 +18,7 @@ #define FLATBUFFERS_TABLE_H_ #include "flatbuffers/base.h" +#include "flatbuffers/vector.h" #include "flatbuffers/verifier.h" namespace flatbuffers { @@ -70,6 +71,32 @@ class Table { return GetPointer(field); } + template + const Vector* GetVectorPointerOrEmpty(voffset_t field) const { + auto* ptr = GetPointer*, OffsetSize>(field); + return ptr ? ptr : EmptyVector(); + } + + template + const Vector* GetVectorPointer64OrEmpty(voffset_t field) const { + return GetVectorPointerOrEmpty(field); + } + + template + Vector* GetMutableVectorPointerOrEmpty(voffset_t field) { + auto* ptr = GetPointer*, OffsetSize>(field); + // This is a const_cast, but safe, since all mutable operations on an + // empty vector are NOPs. + return ptr ? ptr : const_cast*>(EmptyVector()); + } + + template + Vector* GetMutableVectorPointer64OrEmpty(voffset_t field) { + return GetMutableVectorPointerOrEmpty(field); + } + template P GetStruct(voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); @@ -177,6 +204,39 @@ class Table { return VerifyOffsetRequired(verifier, field); } + // Verify a string that may have a default value. + template + bool VerifyStringWithDefault(const Verifier& verifier, + voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + return field_offset == 0 || + verifier.VerifyString(GetPointer(field)); + } + + // Verify a vector that has a default empty value. + template + bool VerifyVectorWithDefault(const Verifier& verifier, + voffset_t field) const { + auto field_offset = GetOptionalFieldOffset(field); + return field_offset == 0 || + verifier.VerifyVector( + GetPointer*, OffsetSize>(field)); + } + + template + bool VerifyVector64WithDefault(const Verifier& verifier, + voffset_t field) const { + return VerifyVectorWithDefault(verifier, field); + } + + protected: + template + static const Vector* EmptyVector() { + static const SizeT empty_vector_length = 0; + return reinterpret_cast*>(&empty_vector_length); + } + private: // private constructor & copy constructor: you obtain instances of this // class by pointing to existing data only diff --git a/scripts/generate_code.py b/scripts/generate_code.py index 09ebf88de..c89403efa 100755 --- a/scripts/generate_code.py +++ b/scripts/generate_code.py @@ -400,6 +400,10 @@ flatc( schema="nested_union_test.fbs", ) +flatc( + NO_INCL_OPTS + CPP_OPTS, + schema="default_vectors_strings_test.fbs", +) # Optional Scalars optional_scalars_schema = "optional_scalars.fbs" diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 54706b037..f6e61ff2a 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -1924,6 +1924,10 @@ class CppGenerator : public BaseGenerator { } else { return "0"; } + } else if (IsVector(type) && field.value.constant == "[]") { + return "0"; + } else if (IsString(type) && field.value.constant != "0") { + return "0"; } else if (IsStruct(type) && (field.value.constant == "0")) { return "nullptr"; } else { @@ -2427,12 +2431,40 @@ class CppGenerator : public BaseGenerator { break; } case BASE_TYPE_STRING: { - code_ += "{{PRE}}verifier.VerifyString({{NAME}}())\\"; + if (field.value.constant != "0") { + if (field.offset64) { + code_ += + "{{PRE}}VerifyStringWithDefault<::flatbuffers::uoffset64_t>(" + "verifier, " + "{{OFFSET}})\\"; + } else { + code_ += "{{PRE}}VerifyStringWithDefault(verifier, {{OFFSET}})\\"; + } + } else { + code_ += "{{PRE}}verifier.VerifyString({{NAME}}())\\"; + } break; } case BASE_TYPE_VECTOR64: case BASE_TYPE_VECTOR: { - code_ += "{{PRE}}verifier.VerifyVector({{NAME}}())\\"; + if (field.value.constant == "[]") { + const auto& vec_type = field.value.type.VectorType(); + const std::string vtype_wire = GenTypeWire( + vec_type, "", VectorElementUserFacing(vec_type), field.offset64); + std::string verify_call; + if (field.offset64) { + verify_call = "{{PRE}}VerifyVector64WithDefault<" + vtype_wire; + } else { + verify_call = "{{PRE}}VerifyVectorWithDefault<" + vtype_wire; + } + if (field.value.type.base_type == BASE_TYPE_VECTOR64) { + verify_call += ", ::flatbuffers::uoffset64_t"; + } + verify_call += ">(verifier, {{OFFSET}})\\"; + code_ += verify_call; + } else { + code_ += "{{PRE}}verifier.VerifyVector({{NAME}}())\\"; + } switch (field.value.type.element) { case BASE_TYPE_STRING: { @@ -2723,7 +2755,37 @@ class CppGenerator : public BaseGenerator { code_.SetValue("FIELD_VALUE", GenUnderlyingCast(field, true, call)); code_.SetValue("NULLABLE_EXT", NullableExtension()); code_ += " {{FIELD_TYPE}}{{FIELD_NAME}}() const {"; - code_ += " return {{FIELD_VALUE}};"; + if (IsVector(type) && field.value.constant == "[]") { + const auto& vec_type = type.VectorType(); + const std::string vtype_wire = GenTypeWire( + vec_type, "", VectorElementUserFacing(vec_type), field.offset64); + std::string get_call; + if (field.offset64) { + get_call = " return GetVectorPointer64OrEmpty<" + vtype_wire; + } else { + get_call = " return GetVectorPointerOrEmpty<" + vtype_wire; + } + if (type.base_type == BASE_TYPE_VECTOR64) { + get_call += ", ::flatbuffers::uoffset64_t"; + } + get_call += ">(" + offset_str + ");"; + code_ += get_call; + } else if (IsString(type) && field.value.constant != "0") { + // TODO: Add logic to always convert the string to a valid C++ string + // literal by handling string escapes. + code_ += " auto* ptr = {{FIELD_VALUE}};"; + code_ += " if (ptr) return ptr;"; + code_ += " static const struct { uint32_t len; const char s[" + + NumToString(field.value.constant.length() + 1) + + "]; } bfbs_string = { " + + NumToString(field.value.constant.length()) + ", \"" + + field.value.constant + "\" };"; + code_ += + " return reinterpret_cast(&bfbs_string);"; + } else { + code_ += " return {{FIELD_VALUE}};"; + } code_ += " }"; } else { auto wire_type = GenTypeBasic(type, false); @@ -2910,22 +2972,43 @@ class CppGenerator : public BaseGenerator { } else { auto postptr = " *" + NullableExtension(); auto wire_type = GenTypeGet(type, " ", "", postptr.c_str(), true); - const std::string accessor = [&]() { - if (IsStruct(type)) { - return "GetStruct<"; - } - if (field.offset64) { - return "GetPointer64<"; - } - return "GetPointer<"; - }(); - auto underlying = accessor + wire_type + ">(" + offset_str + ")"; code_.SetValue("FIELD_TYPE", wire_type); - code_.SetValue("FIELD_VALUE", GenUnderlyingCast(field, true, underlying)); - code_ += " {{FIELD_TYPE}}mutable_{{FIELD_NAME}}() {"; - code_ += " return {{FIELD_VALUE}};"; - code_ += " }"; + if (IsVector(type) && field.value.constant == "[]") { + const auto& vec_type = type.VectorType(); + const std::string vtype_wire = GenTypeWire( + vec_type, "", VectorElementUserFacing(vec_type), field.offset64); + code_ += " {{FIELD_TYPE}}mutable_{{FIELD_NAME}}() {"; + std::string get_call; + if (field.offset64) { + get_call = + " return GetMutableVectorPointer64OrEmpty<" + vtype_wire; + } else { + get_call = " return GetMutableVectorPointerOrEmpty<" + vtype_wire; + } + if (type.base_type == BASE_TYPE_VECTOR64) { + get_call += ", ::flatbuffers::uoffset64_t"; + } + get_call += ">(" + offset_str + ");"; + code_ += get_call; + code_ += " }"; + } else { + const std::string accessor = [&]() { + if (IsStruct(type)) { + return "GetStruct<"; + } + if (field.offset64) { + return "GetPointer64<"; + } + return "GetPointer<"; + }(); + auto underlying = accessor + wire_type + ">(" + offset_str + ")"; + code_.SetValue("FIELD_VALUE", + GenUnderlyingCast(field, true, underlying)); + code_ += " {{FIELD_TYPE}}mutable_{{FIELD_NAME}}() {"; + code_ += " return {{FIELD_VALUE}};"; + code_ += " }"; + } } } @@ -3305,9 +3388,17 @@ class CppGenerator : public BaseGenerator { } else { code_.SetValue("CREATE_STRING", "CreateSharedString"); } - code_ += - " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " - "_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : 0;"; + if (field->value.constant != "0") { + code_ += + " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " + "_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : " + "_fbb.{{CREATE_STRING}}(\"" + + field->value.constant + "\");"; + } else { + code_ += + " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " + "_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : 0;"; + } } else if (IsVector(field->value.type)) { const std::string force_align_code = GenVectorForceAlign(*field, Name(*field) + "->size()"); diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index e01a6525d..0aaccbb2c 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -2772,7 +2772,8 @@ bool Parser::SupportsOptionalScalars() const { bool Parser::SupportsDefaultVectorsAndStrings() const { static FLATBUFFERS_CONSTEXPR unsigned long supported_langs = - IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kNim; + IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kNim | + IDLOptions::kCpp | IDLOptions::kBinary | IDLOptions::kJson; return !(opts.lang_to_generate & ~supported_langs); } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index d95eac83c..6a7373aaa 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -33,6 +33,8 @@ cc_test( "alignment_test.cpp", "alignment_test.h", "alignment_test_generated.h", + "default_vectors_strings_test.cpp", + "default_vectors_strings_test.h", "evolution_test.cpp", "evolution_test.h", "evolution_test/evolution_v1_generated.h", @@ -134,6 +136,7 @@ cc_test( deps = [ ":alignment_test_cc_fbs", ":arrays_test_cc_fbs", + ":default_vectors_strings_test_cc_fbs", ":monster_extra_cc_fbs", ":monster_test_cc_fbs", ":native_type_test_cc_fbs", @@ -272,3 +275,16 @@ flatbuffer_cc_library( name = "alignment_test_cc_fbs", srcs = ["alignment_test.fbs"], ) + +flatbuffer_cc_library( + name = "default_vectors_strings_test_cc_fbs", + srcs = ["default_vectors_strings_test.fbs"], + flatc_args = [ + "--gen-compare", + "--gen-mutable", + "--gen-object-api", + "--reflect-names", + "--filename-suffix .fbs", + ], + filename_suffix = ".fbs" +) diff --git a/tests/default_vectors_strings_test.cpp b/tests/default_vectors_strings_test.cpp new file mode 100644 index 000000000..ed9c80afe --- /dev/null +++ b/tests/default_vectors_strings_test.cpp @@ -0,0 +1,249 @@ +#include "tests/default_vectors_strings_test.h" + +#include +#include + +#include "include/flatbuffers/buffer.h" +#include "include/flatbuffers/flatbuffer_builder.h" +#include "include/flatbuffers/string.h" +#include "include/flatbuffers/vector.h" +#include "include/flatbuffers/verifier.h" +#include "tests/default_vectors_strings_test.fbs.h" +#include "tests/test_assert.h" + +namespace flatbuffers { +namespace tests { + +using flatbuffers::FlatBufferBuilder64; +using flatbuffers::Offset; +using flatbuffers::String; +using flatbuffers::Verifier; + +void DefaultVectorsStringsTest_EmptyOnDefault_Const() { + FlatBufferBuilder64 builder; + + // Create table without providing the fields with defaults. + DefaultVectorsStringsTest::TableWithDefaultVectorsBuilder tbl_builder( + builder); + tbl_builder.add_regular_int(100); + auto offset = tbl_builder.Finish(); + builder.Finish(offset); + + Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); + TEST_ASSERT( + DefaultVectorsStringsTest::VerifyTableWithDefaultVectorsBuffer(verifier)); + + const auto* table = DefaultVectorsStringsTest::GetTableWithDefaultVectors( + builder.GetBufferPointer()); + TEST_NOTNULL(table); + + // Verify default scalar vectors. + TEST_NOTNULL(table->int_vec()); + TEST_EQ(table->int_vec()->size(), 0); + TEST_NOTNULL(table->bool_vec()); + TEST_EQ(table->bool_vec()->size(), 0); + TEST_NOTNULL(table->char_vec()); + TEST_EQ(table->char_vec()->size(), 0); + TEST_NOTNULL(table->uchar_vec()); + TEST_EQ(table->uchar_vec()->size(), 0); + TEST_NOTNULL(table->short_vec()); + TEST_EQ(table->short_vec()->size(), 0); + TEST_NOTNULL(table->ushort_vec()); + TEST_EQ(table->ushort_vec()->size(), 0); + TEST_NOTNULL(table->uint_vec()); + TEST_EQ(table->uint_vec()->size(), 0); + TEST_NOTNULL(table->long_vec()); + TEST_EQ(table->long_vec()->size(), 0); + TEST_NOTNULL(table->ulong_vec()); + TEST_EQ(table->ulong_vec()->size(), 0); + TEST_NOTNULL(table->float_vec()); + TEST_EQ(table->float_vec()->size(), 0); + TEST_NOTNULL(table->double_vec()); + TEST_EQ(table->double_vec()->size(), 0); + + // Verify default string_vec. + TEST_NOTNULL(table->string_vec()); + TEST_EQ(table->string_vec()->size(), 0); + + // Verify default string fields. + TEST_NOTNULL(table->empty_string()); + TEST_EQ_STR(table->empty_string()->c_str(), ""); + TEST_NOTNULL(table->some_string()); + TEST_EQ_STR(table->some_string()->c_str(), "some"); + + // Verify default struct_vec. + TEST_NOTNULL(table->struct_vec()); + TEST_EQ(table->struct_vec()->size(), 0); + + // Verify default table_vec. + TEST_NOTNULL(table->table_vec()); + TEST_EQ(table->table_vec()->size(), 0); + + // Verify default enum_vec. + TEST_NOTNULL(table->enum_vec()); + TEST_EQ(table->enum_vec()->size(), 0); + + // Verify non-default vector field. + TEST_NULL(table->regular_int_vec()); + + // Verify pointer and offset64 combinations. + TEST_NOTNULL(table->int_vec64()); + TEST_EQ(table->int_vec64()->size(), 0); + TEST_NOTNULL(table->int_vec_offset64()); + TEST_EQ(table->int_vec_offset64()->size(), 0); + + // Verify non-default field. + TEST_EQ(table->regular_int(), 100); +} + +void DefaultVectorsStringsTest_EmptyOnDefault_Mutable() { + FlatBufferBuilder64 builder; + + // Create table without providing the fields with defaults. + DefaultVectorsStringsTest::TableWithDefaultVectorsBuilder tbl_builder( + builder); + tbl_builder.add_regular_int(100); + auto offset = tbl_builder.Finish(); + builder.Finish(offset); + + Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); + TEST_ASSERT( + DefaultVectorsStringsTest::VerifyTableWithDefaultVectorsBuffer(verifier)); + + auto* mutable_table = + DefaultVectorsStringsTest::GetMutableTableWithDefaultVectors( + builder.GetBufferPointer()); + TEST_NOTNULL(mutable_table); + + // Verify default scalar vectors. + TEST_NOTNULL(mutable_table->mutable_int_vec()); + TEST_EQ(mutable_table->mutable_int_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_bool_vec()); + TEST_EQ(mutable_table->mutable_bool_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_char_vec()); + TEST_EQ(mutable_table->mutable_char_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_uchar_vec()); + TEST_EQ(mutable_table->mutable_uchar_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_short_vec()); + TEST_EQ(mutable_table->mutable_short_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_ushort_vec()); + TEST_EQ(mutable_table->mutable_ushort_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_uint_vec()); + TEST_EQ(mutable_table->mutable_uint_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_long_vec()); + TEST_EQ(mutable_table->mutable_long_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_ulong_vec()); + TEST_EQ(mutable_table->mutable_ulong_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_float_vec()); + TEST_EQ(mutable_table->mutable_float_vec()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_double_vec()); + TEST_EQ(mutable_table->mutable_double_vec()->size(), 0); + + // Verify default struct_vec. + TEST_NOTNULL(mutable_table->mutable_struct_vec()); + TEST_EQ(mutable_table->mutable_struct_vec()->size(), 0); + + // Verify default table_vec. + TEST_NOTNULL(mutable_table->mutable_table_vec()); + TEST_EQ(mutable_table->mutable_table_vec()->size(), 0); + + // Verify default enum_vec. + TEST_NOTNULL(mutable_table->mutable_enum_vec()); + TEST_EQ(mutable_table->mutable_enum_vec()->size(), 0); + + // Verify non-default vector field. + TEST_NULL(mutable_table->mutable_regular_int_vec()); + + // Verify pointer and offset64 combinations. + TEST_NOTNULL(mutable_table->mutable_int_vec64()); + TEST_EQ(mutable_table->mutable_int_vec64()->size(), 0); + TEST_NOTNULL(mutable_table->mutable_int_vec_offset64()); + TEST_EQ(mutable_table->mutable_int_vec_offset64()->size(), 0); +} + +void DefaultVectorsStringsTest_WithValues() { + // Create a table with values for the defaulted vector fields. + FlatBufferBuilder64 builder; + auto int_vec64 = builder.CreateVector64(std::vector({30, 40})); + auto int_vec_offset64 = + builder.CreateVector64(std::vector({50, 60})); + auto int_vec = builder.CreateVector(std::vector({1, 2})); + auto bool_vec = builder.CreateVector(std::vector({true, false})); + auto string_vec = builder.CreateVector(std::vector>( + {builder.CreateString("a"), builder.CreateString("b")})); + auto empty_string = builder.CreateString("not empty"); + auto some_string = builder.CreateString("not some"); + DefaultVectorsStringsTest::MyStruct structs[] = { + DefaultVectorsStringsTest::MyStruct(1, 2), + DefaultVectorsStringsTest::MyStruct(3, 4)}; + auto struct_vec = builder.CreateVectorOfStructs(structs, 2); + auto regular_int_vec = builder.CreateVector(std::vector({10, 20})); + + DefaultVectorsStringsTest::TableWithDefaultVectorsBuilder tbl_builder( + builder); + tbl_builder.add_int_vec(int_vec); + tbl_builder.add_int_vec64(int_vec64); + tbl_builder.add_int_vec_offset64(int_vec_offset64); + + tbl_builder.add_bool_vec(bool_vec); + tbl_builder.add_string_vec(string_vec); + tbl_builder.add_empty_string(empty_string); + tbl_builder.add_some_string(some_string); + tbl_builder.add_struct_vec(struct_vec); + tbl_builder.add_regular_int_vec(regular_int_vec); + auto offset = tbl_builder.Finish(); + builder.Finish(offset); + + Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); + TEST_ASSERT( + DefaultVectorsStringsTest::VerifyTableWithDefaultVectorsBuffer(verifier)); + + const auto* table = DefaultVectorsStringsTest::GetTableWithDefaultVectors( + builder.GetBufferPointer()); + + TEST_EQ(table->int_vec()->size(), 2); + TEST_EQ(table->int_vec()->Get(1), 2); + TEST_EQ(table->bool_vec()->size(), 2); + TEST_EQ(table->bool_vec()->Get(0), true); + TEST_EQ(table->string_vec()->size(), 2); + TEST_EQ_STR(table->string_vec()->Get(1)->c_str(), "b"); + TEST_EQ_STR(table->empty_string()->c_str(), "not empty"); + TEST_EQ_STR(table->some_string()->c_str(), "not some"); + TEST_EQ(table->struct_vec()->size(), 2); + TEST_EQ(table->struct_vec()->Get(1)->b(), 4); + TEST_EQ(table->regular_int_vec()->size(), 2); + TEST_EQ(table->regular_int_vec()->Get(1), 20); + + TEST_EQ(table->int_vec64()->size(), 2); + TEST_EQ(table->int_vec64()->Get(1), 40); + TEST_EQ(table->int_vec_offset64()->size(), 2); + TEST_EQ(table->int_vec_offset64()->Get(1), 60); +} + +void DefaultVectorsStringsTest_BufferSize() { + FlatBufferBuilder64 builder; + // Create a table where all fields with default values are omitted. + DefaultVectorsStringsTest::TableWithDefaultVectorsBuilder tbl_builder( + builder); + auto offset = tbl_builder.Finish(); + builder.Finish(offset); + + Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); + TEST_ASSERT( + DefaultVectorsStringsTest::VerifyTableWithDefaultVectorsBuffer(verifier)); + + // The buffer should be small when only defaults are used. + // This value can be adjusted if the schema changes. + constexpr unsigned int buffer_size_threshold_in_bytes = 12; + TEST_ASSERT(builder.GetSize() <= buffer_size_threshold_in_bytes); +} + +void DefaultVectorsStringsTest() { + DefaultVectorsStringsTest_EmptyOnDefault_Const(); + DefaultVectorsStringsTest_EmptyOnDefault_Mutable(); + DefaultVectorsStringsTest_WithValues(); + DefaultVectorsStringsTest_BufferSize(); +} + +} // namespace tests +} // namespace flatbuffers diff --git a/tests/default_vectors_strings_test.fbs b/tests/default_vectors_strings_test.fbs new file mode 100644 index 000000000..05c504293 --- /dev/null +++ b/tests/default_vectors_strings_test.fbs @@ -0,0 +1,39 @@ +// C++ Only Schema for Default Vectors Test +namespace DefaultVectorsStringsTest; + +struct MyStruct { + a:int; + b:int; +} + +table MyTable { + a:int; +} + +enum MyEnum:byte { A, B } + +table TableWithDefaultVectors { + int_vec:[int] = []; + bool_vec:[bool] = []; + char_vec:[byte] = []; + uchar_vec:[ubyte] = []; + short_vec:[short] = []; + ushort_vec:[ushort] = []; + uint_vec:[uint] = []; + long_vec:[long] = []; + ulong_vec:[ulong] = []; + float_vec:[float] = []; + double_vec:[double] = []; + string_vec:[string] = []; + empty_string:string = ""; + some_string:string = "some"; + struct_vec:[MyStruct] = []; + table_vec:[MyTable] = []; + enum_vec:[MyEnum] = []; + regular_int_vec:[int]; + regular_int:int; + int_vec_offset64:[int] = [] (offset64); + int_vec64:[int]= [] (vector64); +} + +root_type TableWithDefaultVectors; \ No newline at end of file diff --git a/tests/default_vectors_strings_test.h b/tests/default_vectors_strings_test.h new file mode 100644 index 000000000..1d36cddbb --- /dev/null +++ b/tests/default_vectors_strings_test.h @@ -0,0 +1,17 @@ +#ifndef THIRDPARTY_FLATBUFFERS_TESTS_DEFAULT_VECTORS_STRINGS_TEST_H_ +#define THIRDPARTY_FLATBUFFERS_TESTS_DEFAULT_VECTORS_STRINGS_TEST_H_ + +namespace flatbuffers { +namespace tests { + +void DefaultVectorsStringsTest_EmptyOnDefault_Const(); +void DefaultVectorsStringsTest_EmptyOnDefault_Mutable(); +void DefaultVectorsStringsTest_EmptyOnDefault(); +void DefaultVectorsStringsTest_WithValues(); +void DefaultVectorsStringsTest_BufferSize(); +void DefaultVectorsStringsTest(); + +} // namespace tests +} // namespace flatbuffers + +#endif // THIRDPARTY_FLATBUFFERS_TESTS_DEFAULT_VECTORS_STRINGS_TEST_H_ diff --git a/tests/test.cpp b/tests/test.cpp index 05e2f3345..c94d21592 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -34,6 +34,7 @@ #include "third_party/absl/container/flat_hash_set.h" #endif #include "alignment_test.h" +#include "default_vectors_strings_test.h" #include "evolution_test.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/idl.h" @@ -1836,6 +1837,7 @@ int FlatBufferTests(const std::string& tests_data_path) { Offset64Tests(); UnionUnderlyingTypeTest(); StructsInHashTableTest(); + DefaultVectorsStringsTest(); return 0; } } // namespace