diff --git a/docs/source/CppUsage.md b/docs/source/CppUsage.md index f55dd95c9..5fe14cdad 100755 --- a/docs/source/CppUsage.md +++ b/docs/source/CppUsage.md @@ -47,7 +47,7 @@ we can construct them in a familiar way. We have now serialized the non-scalar components of of the monster example, so we could create the monster something like this: - auto mloc = CreateMonster(fbb, &vec, 150, 80, name, inventory, Color_Red, Offset(0), Any_NONE); + auto mloc = CreateMonster(fbb, &vec, 150, 80, name, inventory, Color_Red, 0, Any_NONE); Note that we're passing `150` for the `mana` field, which happens to be the default value: this means the field will not actually be written to the buffer, @@ -58,7 +58,8 @@ since they won't bloat up the buffer sizes if they're not actually used. We do something similarly for the union field `test` by specifying a `0` offset and the `NONE` enum value (part of every union) to indicate we don't actually -want to write this field. +want to write this field. You can use `0` also as a default for other +non-scalar types, such as strings, vectors and tables. Tables (like `Monster`) give you full flexibility on what fields you write (unlike `Vec3`, which always has all fields set because it is a `struct`). @@ -155,6 +156,38 @@ machines, so only use tricks like this if you can guarantee you're not shipping on a big endian machine (an `assert(FLATBUFFERS_LITTLEENDIAN)` would be wise). +### Access of untrusted buffers + +The generated accessor functions access fields over offsets, which is +very quick. These offsets are not verified at run-time, so a malformed +buffer could cause a program to crash by accessing random memory. + +When you're processing large amounts of data from a source you know (e.g. +your own generated data on disk), this is acceptable, but when reading +data from the network that can potentially have been modified by an +attacker, this is undesirable. + +For this reason, you can optionally use a buffer verifier before you +access the data. This verifier will check all offsets, all sizes of +fields, and null termination of strings to ensure that when a buffer +is accessed, all reads will end up inside the buffer. + +Each root type will have a verification function generated for it, +e.g. for `Monster`, you can call: + + bool ok = VerifyMonsterBuffer(Verifier(buf, len)); + +if `ok` is true, the buffer is safe to read. + +Besides untrusted data, this function may be useful to call in debug +mode, as extra insurance against data being corrupted somewhere along +the way. + +While verifying a buffer isn't "free", it is typically faster than +a full traversal (since any scalar data is not actually touched), +and since it may cause the buffer to be brought into cache before +reading, the actual overhead may be even lower than expected. + ## Text & schema parsing Using binary buffers with the generated header provides a super low diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 47acdf3dd..5b3c9f167 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -76,7 +76,7 @@ typedef uintmax_t largest_scalar_t; template struct Offset { uoffset_t o; Offset() : o(0) {} - explicit Offset(uoffset_t _o) : o(_o) {} + Offset(uoffset_t _o) : o(_o) {} Offset Union() const { return Offset(o); } }; @@ -407,13 +407,13 @@ class FlatBufferBuilder { buf_.fill(numfields * sizeof(voffset_t)); auto table_object_size = vtableoffsetloc - start; assert(table_object_size < 0x10000); // Vtable use 16bit offsets. - PushElement(table_object_size); + PushElement(static_cast(table_object_size)); PushElement(FieldIndexToOffset(numfields)); // Write the offsets into the table for (auto field_location = offsetbuf_.begin(); field_location != offsetbuf_.end(); ++field_location) { - auto pos = (vtableoffsetloc - field_location->off); + auto pos = static_cast(vtableoffsetloc - field_location->off); // If this asserts, it means you've set a field twice. assert(!ReadScalar(buf_.data() + field_location->id)); WriteScalar(buf_.data() + field_location->id, pos); @@ -563,7 +563,90 @@ template const T *GetRoot(const void *buf) { EndianScalar(*reinterpret_cast(buf))); } -// "structs_" are flat structures that do not have an offset table, thus +// Helper class to verify the integrity of a FlatBuffer +class Verifier { + public: + Verifier(const uint8_t *buf, size_t buf_len) + : buf_(buf), end_(buf + buf_len) + {} + + // Verify any range within the buffer. + bool Verify(const void *elem, size_t elem_len) const { + bool ok = elem >= buf_ && elem <= end_ - elem_len; + assert(ok); + return ok; + } + + // Verify a range indicated by sizeof(T). + template bool Verify(const void *elem) const { + return Verify(elem, sizeof(T)); + } + + // Verify a pointer (may be NULL) of any vector type. + template bool Verify(const Vector *vec) const { + const uint8_t *end; + return !vec || + VerifyVector(reinterpret_cast(vec), sizeof(T), + &end); + } + + // Verify a pointer (may be NULL) to string. + bool Verify(const String *str) const { + const uint8_t *end; + return !str || + (VerifyVector(reinterpret_cast(str), 1, &end) && + Verify(end, 1) && // Must have terminator + *end == '\0'); // Terminating byte must be 0. + } + + // Common code between vectors and strings. + bool VerifyVector(const uint8_t *vec, size_t elem_size, + const uint8_t **end) const { + // Check we can read the size field. + if (!Verify(vec)) return false; + // Check the whole array. If this is a string, the byte past the array + // must be 0. + auto size = ReadScalar(vec); + auto byte_size = sizeof(size) + elem_size * size; + *end = vec + byte_size; + return Verify(vec, byte_size); + } + + // Special case for string contents, after the above has been called. + bool VerifyVectorOfStrings(const Vector> *vec) const { + if (vec) { + for (uoffset_t i = 0; i < vec->Length(); i++) { + if (!Verify(vec->Get(i))) return false; + } + } + return true; + } + + // Special case for table contents, after the above has been called. + template bool VerifyVectorOfTables(const Vector> *vec) + const { + if (vec) { + for (uoffset_t i = 0; i < vec->Length(); i++) { + if (!vec->Get(i)->Verify(*this)) return false; + } + } + return true; + } + + // Verify this whole buffer, starting with root type T. + template bool VerifyBuffer() const { + // Call T::Verify, which must be in the generated code for this type. + return Verify(buf_) && + reinterpret_cast(buf_ + ReadScalar(buf_))-> + Verify(*this); + } + + private: + const uint8_t *buf_; + const uint8_t *end_; +}; + +// "structs" are flat structures that do not have an offset table, thus // always have all members present and do not support forwards/backwards // compatible extensions. @@ -594,7 +677,7 @@ class Table { // if the field was not present. voffset_t GetOptionalFieldOffset(voffset_t field) const { // The vtable offset is always at the start. - auto vtable = &data_ - ReadScalar(&data_); + auto vtable = data_ - ReadScalar(data_); // The first element is the size of the vtable (fields + type id + itself). auto vtsize = ReadScalar(vtable); // If the field we're accessing is outside the vtable, we're reading older @@ -604,12 +687,12 @@ class Table { template T GetField(voffset_t field, T defaultval) const { auto field_offset = GetOptionalFieldOffset(field); - return field_offset ? ReadScalar(&data_[field_offset]) : defaultval; + return field_offset ? ReadScalar(data_ + field_offset) : defaultval; } template P GetPointer(voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); - auto p = &data_[field_offset]; + auto p = data_ + field_offset; return field_offset ? reinterpret_cast

(p + ReadScalar(p)) : nullptr; @@ -617,7 +700,7 @@ class Table { template P GetStruct(voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); - return field_offset ? reinterpret_cast

(&data_[field_offset]) : nullptr; + return field_offset ? reinterpret_cast

(data_ + field_offset) : nullptr; } template void SetField(voffset_t field, T val) { @@ -626,18 +709,39 @@ class Table { // (or should we return a bool instead?). // check if it exists first using CheckField() assert(field_offset); - WriteScalar(&data_[field_offset], val); + WriteScalar(data_ + field_offset, val); } bool CheckField(voffset_t field) const { return GetOptionalFieldOffset(field) != 0; } + // Verify the vtable of this table. + // Call this once per table, followed by VerifyField once per field. + bool VerifyTable(const Verifier &verifier) const { + // Check the vtable offset. + if (!verifier.Verify(data_)) return false; + auto vtable = data_ - ReadScalar(data_); + // Check the vtable size field, then check vtable fits in its entirety. + return verifier.Verify(vtable) && + verifier.Verify(vtable, ReadScalar(vtable)); + } + + // Verify a particular field. + template bool VerifyField(const Verifier &verifier, + voffset_t field) const { + // Calling GetOptionalFieldOffset should be safe now thanks to + // VerifyTable(). + auto field_offset = GetOptionalFieldOffset(field); + // Check the actual field. + return !field_offset || verifier.Verify(data_ + field_offset); + } + private: // private constructor & copy constructor: you obtain instances of this // class by pointing to existing data only - Table() {}; - Table(const Table &other) {}; + Table(); + Table(const Table &other); uint8_t data_[1]; }; @@ -645,10 +749,10 @@ class Table { // Utility function for reverse lookups on the EnumNames*() functions // (in the generated C++ code) // names must be NULL terminated. -inline size_t LookupEnum(const char **names, const char *name) { +inline int LookupEnum(const char **names, const char *name) { for (const char **p = names; *p; p++) if (!strcmp(*p, name)) - return p - names; + return static_cast(p - names); return -1; } diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index e0b2be6ef..bba15af76 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -62,6 +62,16 @@ static std::string GenTypeWire(const Type &type, const char *postfix) { : "flatbuffers::Offset<" + GenTypePointer(type) + ">" + postfix; } +// Return a C++ type for any type (scalar/pointer) that reflects its +// serialized size. +static std::string GenTypeSize(const Type &type) { + return IsScalar(type.base_type) + ? GenTypeBasic(type) + : IsStruct(type) + ? GenTypePointer(type) + : "flatbuffers::uoffset_t"; +} + // Return a C++ type for any type (scalar/pointer) specifically for // using a flatbuffer. static std::string GenTypeGet(const Type &type, const char *afterbasic, @@ -82,9 +92,11 @@ static void GenComment(const std::string &dc, } // Generate an enum declaration and an enum string lookup table. -static void GenEnum(EnumDef &enum_def, std::string *code_ptr) { + static void GenEnum(EnumDef &enum_def, std::string *code_ptr, + std::string *code_ptr_post) { if (enum_def.generated) return; std::string &code = *code_ptr; + std::string &code_post = *code_ptr_post; GenComment(enum_def.doc_comment, code_ptr); code += "enum {\n"; for (auto it = enum_def.vals.vec.begin(); @@ -123,6 +135,32 @@ static void GenEnum(EnumDef &enum_def, std::string *code_ptr) { code += " - " + enum_def.name + "_" + enum_def.vals.vec.front()->name; code += "]; }\n\n"; } + + if (enum_def.is_union) { + // Generate a verifier function for this union that can be called by the + // table verifier functions. It uses a switch case to select a specific + // verifier function to call, this should be safe even if the union type + // has been corrupted, since the verifiers will simply fail when called + // on the wrong type. + auto signature = "bool Verify" + enum_def.name + + "(const flatbuffers::Verifier &verifier, " + + "const void *union_obj, uint8_t type)"; + code += signature + ";\n\n"; + code_post += signature + " {\n switch (type) {\n"; + for (auto it = enum_def.vals.vec.begin(); + it != enum_def.vals.vec.end(); + ++it) { + auto &ev = **it; + code_post += " case " + enum_def.name + "_" + ev.name; + if (!ev.value) { + code_post += ": return true;\n"; // "NONE" enum value. + } else { + code_post += ": return reinterpret_castname; + code_post += " *>(union_obj)->Verify(verifier);\n"; + } + } + code_post += " default: return false;\n }\n}\n\n"; + } } // Generate an accessor struct, builder structs & function for a table. @@ -155,6 +193,58 @@ static void GenTable(StructDef &struct_def, std::string *code_ptr) { code += "); }\n"; } } + // Generate a verifier function that can check a buffer from an untrusted + // source will never cause reads outside the buffer. + code += " bool Verify(const flatbuffers::Verifier &verifier) const {\n"; + code += " return VerifyTable(verifier)"; + std::string prefix = " &&\n "; + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); + ++it) { + auto &field = **it; + if (!field.deprecated) { + code += prefix + "VerifyField<" + GenTypeSize(field.value.type); + code += ">(verifier, " + NumToString(field.value.offset); + code += " /* " + field.name + " */)"; + switch (field.value.type.base_type) { + case BASE_TYPE_UNION: + code += prefix + "Verify" + field.value.type.enum_def->name; + code += "(verifier, " + field.name + "(), " + field.name + "_type())"; + break; + case BASE_TYPE_STRUCT: + if (!field.value.type.struct_def->fixed) { + code += prefix + field.value.type.struct_def->name; + code += "()->Verify()"; + } + break; + case BASE_TYPE_STRING: + code += prefix + "verifier.Verify(" + field.name + "())"; + break; + case BASE_TYPE_VECTOR: + code += prefix + "verifier.Verify(" + field.name + "())"; + switch (field.value.type.element) { + case BASE_TYPE_STRING: { + code += prefix + "verifier.VerifyVectorOfStrings(" + field.name; + code += "())"; + break; + } + case BASE_TYPE_STRUCT: { + if (!field.value.type.struct_def->fixed) { + code += prefix + "verifier.VerifyVectorOfTables(" + field.name; + code += "())"; + } + break; + } + default: + break; + } + break; + default: + break; + } + } + } + code += ";\n }\n"; code += "};\n\n"; // Generate a builder struct, with methods of the form: @@ -302,10 +392,10 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i using namespace cpp; // Generate code for all the enum declarations. - std::string enum_code; + std::string enum_code, enum_code_post; for (auto it = parser.enums_.vec.begin(); it != parser.enums_.vec.end(); ++it) { - GenEnum(**it, &enum_code); + GenEnum(**it, &enum_code, &enum_code_post); } // Generate forward declarations for all structs/tables, since they may @@ -361,13 +451,20 @@ std::string GenerateCPP(const Parser &parser, const std::string &include_guard_i code += forward_decl_code; code += "\n"; code += decl_code; + code += enum_code_post; - // Generate convenient root datatype accessor. + // Generate convenient root datatype accessor, and root verifier. if (parser.root_struct_def) { code += "inline const " + parser.root_struct_def->name + " *Get"; code += parser.root_struct_def->name; code += "(const void *buf) { return flatbuffers::GetRoot<"; code += parser.root_struct_def->name + ">(buf); }\n\n"; + + code += "inline bool Verify"; + code += parser.root_struct_def->name; + code += "Buffer(const flatbuffers::Verifier &verifier) { " + "return verifier.VerifyBuffer<"; + code += parser.root_struct_def->name + ">(); }\n\n"; } // Close the namespaces. diff --git a/tests/MyGame/Example/Monster.java b/tests/MyGame/Example/Monster.java index e07d9b8cf..d9c59bdf6 100755 --- a/tests/MyGame/Example/Monster.java +++ b/tests/MyGame/Example/Monster.java @@ -24,8 +24,13 @@ public class Monster extends Table { public Test test4(int j) { return test4(new Test(), j); } public Test test4(Test obj, int j) { int o = __offset(22); return o != 0 ? obj.__init(__vector(o) + j * 4, bb) : null; } public int test4Length() { int o = __offset(22); return o != 0 ? __vector_len(o) : 0; } + public String testarrayofstring(int j) { int o = __offset(24); return o != 0 ? __string(__vector(o) + j * 4) : null; } + public int testarrayofstringLength() { int o = __offset(24); return o != 0 ? __vector_len(o) : 0; } + public Monster testarrayoftables(int j) { return testarrayoftables(new Monster(), j); } + public Monster testarrayoftables(Monster obj, int j) { int o = __offset(26); return o != 0 ? obj.__init(__indirect(__vector(o) + j * 4), bb) : null; } + public int testarrayoftablesLength() { int o = __offset(26); return o != 0 ? __vector_len(o) : 0; } - public static void startMonster(FlatBufferBuilder builder) { builder.startObject(10); } + public static void startMonster(FlatBufferBuilder builder) { builder.startObject(12); } public static void addPos(FlatBufferBuilder builder, int pos) { builder.addStruct(0, pos, 0); } public static void addMana(FlatBufferBuilder builder, short mana) { builder.addShort(1, mana, 150); } public static void addHp(FlatBufferBuilder builder, short hp) { builder.addShort(2, hp, 100); } @@ -37,6 +42,10 @@ public class Monster extends Table { public static void addTest(FlatBufferBuilder builder, int test) { builder.addOffset(8, test, 0); } public static void addTest4(FlatBufferBuilder builder, int test4) { builder.addOffset(9, test4, 0); } public static void startTest4Vector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems); } + public static void addTestarrayofstring(FlatBufferBuilder builder, int testarrayofstring) { builder.addOffset(10, testarrayofstring, 0); } + public static void startTestarrayofstringVector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems); } + public static void addTestarrayoftables(FlatBufferBuilder builder, int testarrayoftables) { builder.addOffset(11, testarrayoftables, 0); } + public static void startTestarrayoftablesVector(FlatBufferBuilder builder, int numElems) { builder.startVector(4, numElems); } public static int endMonster(FlatBufferBuilder builder) { return builder.endObject(); } }; diff --git a/tests/monster_test.fbs b/tests/monster_test.fbs index d4c3a72fe..d3735e861 100755 --- a/tests/monster_test.fbs +++ b/tests/monster_test.fbs @@ -29,6 +29,8 @@ table Monster { color:Color = Blue; test:Any; test4:[Test]; + testarrayofstring:[string]; + testarrayoftables:[Monster]; } root_type Monster; diff --git a/tests/monster_test_generated.h b/tests/monster_test_generated.h index 6b85c6b30..7601dc945 100755 --- a/tests/monster_test_generated.h +++ b/tests/monster_test_generated.h @@ -33,6 +33,8 @@ inline const char **EnumNamesAny() { inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; } +bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type); + struct Test; struct Vec3; struct Monster; @@ -88,6 +90,30 @@ struct Monster : private flatbuffers::Table { uint8_t test_type() const { return GetField(18, 0); } const void *test() const { return GetPointer(20); } const flatbuffers::Vector *test4() const { return GetPointer *>(22); } + const flatbuffers::Vector> *testarrayofstring() const { return GetPointer> *>(24); } + const flatbuffers::Vector> *testarrayoftables() const { return GetPointer> *>(26); } + bool Verify(const flatbuffers::Verifier &verifier) const { + return VerifyTable(verifier) && + VerifyField(verifier, 4 /* pos */) && + VerifyField(verifier, 6 /* mana */) && + VerifyField(verifier, 8 /* hp */) && + VerifyField(verifier, 10 /* name */) && + verifier.Verify(name()) && + VerifyField(verifier, 14 /* inventory */) && + verifier.Verify(inventory()) && + VerifyField(verifier, 16 /* color */) && + VerifyField(verifier, 18 /* test_type */) && + VerifyField(verifier, 20 /* test */) && + VerifyAny(verifier, test(), test_type()) && + VerifyField(verifier, 22 /* test4 */) && + verifier.Verify(test4()) && + VerifyField(verifier, 24 /* testarrayofstring */) && + verifier.Verify(testarrayofstring()) && + verifier.VerifyVectorOfStrings(testarrayofstring()) && + VerifyField(verifier, 26 /* testarrayoftables */) && + verifier.Verify(testarrayoftables()) && + verifier.VerifyVectorOfTables(testarrayoftables()); + } }; struct MonsterBuilder { @@ -102,12 +128,17 @@ struct MonsterBuilder { void add_test_type(uint8_t test_type) { fbb_.AddElement(18, test_type, 0); } void add_test(flatbuffers::Offset test) { fbb_.AddOffset(20, test); } void add_test4(flatbuffers::Offset> test4) { fbb_.AddOffset(22, test4); } + void add_testarrayofstring(flatbuffers::Offset>> testarrayofstring) { fbb_.AddOffset(24, testarrayofstring); } + void add_testarrayoftables(flatbuffers::Offset>> testarrayoftables) { fbb_.AddOffset(26, testarrayoftables); } MonsterBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - flatbuffers::Offset Finish() { return flatbuffers::Offset(fbb_.EndTable(start_, 10)); } + MonsterBuilder &operator=(const MonsterBuilder &); + flatbuffers::Offset Finish() { return flatbuffers::Offset(fbb_.EndTable(start_, 12)); } }; -inline flatbuffers::Offset CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const Vec3 *pos, int16_t mana, int16_t hp, flatbuffers::Offset name, flatbuffers::Offset> inventory, int8_t color, uint8_t test_type, flatbuffers::Offset test, flatbuffers::Offset> test4) { +inline flatbuffers::Offset CreateMonster(flatbuffers::FlatBufferBuilder &_fbb, const Vec3 *pos, int16_t mana, int16_t hp, flatbuffers::Offset name, flatbuffers::Offset> inventory, int8_t color, uint8_t test_type, flatbuffers::Offset test, flatbuffers::Offset> test4, flatbuffers::Offset>> testarrayofstring, flatbuffers::Offset>> testarrayoftables) { MonsterBuilder builder_(_fbb); + builder_.add_testarrayoftables(testarrayoftables); + builder_.add_testarrayofstring(testarrayofstring); builder_.add_test4(test4); builder_.add_test(test); builder_.add_inventory(inventory); @@ -120,8 +151,18 @@ inline flatbuffers::Offset CreateMonster(flatbuffers::FlatBufferBuilder return builder_.Finish(); } +bool VerifyAny(const flatbuffers::Verifier &verifier, const void *union_obj, uint8_t type) { + switch (type) { + case Any_NONE: return true; + case Any_Monster: return reinterpret_cast(union_obj)->Verify(verifier); + default: return false; + } +} + inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot(buf); } +inline bool VerifyMonsterBuffer(const flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer(); } + }; // namespace MyGame }; // namespace Example diff --git a/tests/monsterdata_test_wire.bin b/tests/monsterdata_test_wire.bin index 0d3744816..9a7b16dcc 100755 Binary files a/tests/monsterdata_test_wire.bin and b/tests/monsterdata_test_wire.bin differ diff --git a/tests/test.cpp b/tests/test.cpp index 9be9747b8..fdb8ba3ba 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -79,10 +79,19 @@ std::string CreateFlatBufferTest() { mb.add_hp(20); auto mloc2 = mb.Finish(); + // Create an array of strings: + flatbuffers::Offset strings[2]; + strings[0] = builder.CreateString("bob"); + strings[1] = builder.CreateString("fred"); + auto vecofstrings = builder.CreateVector(strings, 2); + + // Create an array of tables: + auto vecoftables = builder.CreateVector(&mloc2, 1); + // shortcut for creating monster with all fields set: auto mloc = CreateMonster(builder, &vec, 150, 80, name, inventory, Color_Blue, Any_Monster, mloc2.Union(), // Store a union. - testv); + testv, vecofstrings, vecoftables); builder.Finish(mloc); @@ -101,6 +110,13 @@ std::string CreateFlatBufferTest() { // example of accessing a buffer loaded in memory: void AccessFlatBufferTest(const std::string &flatbuf) { + // First, verify the buffers integrity (optional) + flatbuffers::Verifier verifier( + reinterpret_cast(flatbuf.c_str()), + flatbuf.length()); + TEST_EQ(VerifyMonsterBuffer(verifier), true); + + // Access the buffer from the root. auto monster = GetMonster(flatbuf.c_str()); TEST_EQ(monster->hp(), 80); @@ -128,11 +144,22 @@ void AccessFlatBufferTest(const std::string &flatbuf) { TEST_NOTNULL(monster2); TEST_EQ(monster2->hp(), 20); + // Example of accessing a vector of strings: + auto vecofstrings = monster->testarrayofstring(); + TEST_EQ(vecofstrings->Length(), 2U); + TEST_EQ(strcmp(vecofstrings->Get(0)->c_str(), "bob"), 0); + TEST_EQ(strcmp(vecofstrings->Get(1)->c_str(), "fred"), 0); + + // Example of accessing a vector of tables: + auto vecoftables = monster->testarrayoftables(); + TEST_EQ(vecoftables->Length(), 1U); + TEST_EQ(vecoftables->Get(0)->hp(), 20); + // Since Flatbuffers uses explicit mechanisms to override the default // compiler alignment, double check that the compiler indeed obeys them: // (Test consists of a short and byte): - TEST_EQ(flatbuffers::AlignOf(), static_cast(2)); - TEST_EQ(sizeof(Test), static_cast(4)); + TEST_EQ(flatbuffers::AlignOf(), 2UL); + TEST_EQ(sizeof(Test), 4UL); auto tests = monster->test4(); TEST_NOTNULL(tests); @@ -163,6 +190,11 @@ void ParseAndGenerateTextTest() { // here, parser.builder_ contains a binary buffer that is the parsed data. + // First, verify it, just in case: + flatbuffers::Verifier verifier(parser.builder_.GetBufferPointer(), + parser.builder_.GetSize()); + TEST_EQ(VerifyMonsterBuffer(verifier), true); + // to ensure it is correct, we now generate text back from the binary, // and compare the two: std::string jsongen;