From 7101224d868e15fa1d401f4b3afc54f3b5761e18 Mon Sep 17 00:00:00 2001 From: Wouter van Oortmerssen Date: Fri, 31 Jul 2015 13:55:53 -0700 Subject: [PATCH] Reworked reflection.h to be more general. e.g. support generic reading/writing from structs/vectors etc. Change-Id: I2eb6e24db088a72da444d5c8df7e506e53d5bc2d Tested: on Linux. Bug: 22660837 --- CMakeLists.txt | 10 +- android/jni/Android.mk | 4 +- include/flatbuffers/flatbuffers.h | 30 +- include/flatbuffers/reflection.h | 656 +++++++++++------------------- src/reflection.cpp | 470 +++++++++++++++++++++ tests/test.cpp | 2 +- 6 files changed, 733 insertions(+), 439 deletions(-) create mode 100644 src/reflection.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd0f86b24b3..6e8cdee4072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ set(FlatBuffers_Library_SRCS include/flatbuffers/reflection_generated.h src/idl_parser.cpp src/idl_gen_text.cpp + src/reflection.cpp ) set(FlatBuffers_Compiler_SRCS @@ -43,14 +44,9 @@ set(FlatHash_SRCS ) set(FlatBuffers_Tests_SRCS - include/flatbuffers/flatbuffers.h - include/flatbuffers/hash.h - include/flatbuffers/idl.h - include/flatbuffers/util.h - src/idl_parser.cpp - src/idl_gen_general.cpp - src/idl_gen_text.cpp + ${FlatBuffers_Library_SRCS} src/idl_gen_fbs.cpp + src/idl_gen_general.cpp tests/test.cpp # file generate by running compiler on tests/monster_test.fbs ${CMAKE_CURRENT_BINARY_DIR}/tests/monster_test_generated.h diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 01e1141c695..e007135284a 100755 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -31,7 +31,9 @@ LOCAL_SRC_FILES := main.cpp \ ../../tests/test.cpp \ ../../src/idl_parser.cpp \ ../../src/idl_gen_text.cpp \ - ../../src/idl_gen_fbs.cpp + ../../src/idl_gen_fbs.cpp \ + ../../src/idl_gen_general.cpp \ + ../../src/reflection.cpp LOCAL_LDLIBS := -llog -landroid LOCAL_STATIC_LIBRARIES := android_native_app_glue flatbuffers LOCAL_ARM_MODE := arm diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index c1e75bd90cb..3bf1f0ae293 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -353,6 +353,24 @@ template class Vector { } }; +// Represent a vector much like the template above, but in this case we +// don't know what the element types are (used with reflection.h). +class VectorOfAny { +public: + uoffset_t size() const { return EndianScalar(length_); } + + const uint8_t *Data() const { + return reinterpret_cast(&length_ + 1); + } + uint8_t *Data() { + return reinterpret_cast(&length_ + 1); + } +protected: + VectorOfAny(); + + uoffset_t length_; +}; + // Convenient helper function to get the length of any vector, regardless // of wether it is null or not (the field is not set). template static inline size_t VectorLength(const Vector *v) { @@ -995,6 +1013,9 @@ class Struct FLATBUFFERS_FINAL_CLASS { return reinterpret_cast(&data_[o]); } + const uint8_t *GetAddressOf(uoffset_t o) const { return &data_[o]; } + uint8_t *GetAddressOf(uoffset_t o) { return &data_[o]; } + private: uint8_t data_[1]; }; @@ -1027,7 +1048,6 @@ class Table { ? reinterpret_cast

(p + ReadScalar(p)) : nullptr; } - template P GetPointer(voffset_t field) const { return const_cast(this)->GetPointer

(field); } @@ -1052,6 +1072,14 @@ class Table { return true; } + uint8_t *GetAddressOf(voffset_t field) { + auto field_offset = GetOptionalFieldOffset(field); + return field_offset ? data_ + field_offset : nullptr; + } + const uint8_t *GetAddressOf(voffset_t field) const { + return const_cast

(this)->GetAddressOf(field); + } + uint8_t *GetVTable() { return data_ - ReadScalar(data_); } bool CheckField(voffset_t field) const { diff --git a/include/flatbuffers/reflection.h b/include/flatbuffers/reflection.h index 9dd5ef96833..cb18b14370d 100644 --- a/include/flatbuffers/reflection.h +++ b/include/flatbuffers/reflection.h @@ -17,8 +17,6 @@ #ifndef FLATBUFFERS_REFLECTION_H_ #define FLATBUFFERS_REFLECTION_H_ -#include "flatbuffers/util.h" - // This is somewhat of a circular dependency because flatc (and thus this // file) is needed to generate this header in the first place. // Should normally not be a problem since it can be generated by the @@ -30,12 +28,28 @@ namespace flatbuffers { +// ------------------------- GETTERS ------------------------- + +// Size of a basic type, don't use with structs. inline size_t GetTypeSize(reflection::BaseType base_type) { // This needs to correspond to the BaseType enum. static size_t sizes[] = { 0, 1, 1, 1, 1, 2, 2, 4, 4, 8, 8, 4, 8, 4, 4, 4, 4 }; return sizes[base_type]; } +// Same as above, but now correctly returns the size of a struct if +// the field (or vector element) is a struct. +inline size_t GetTypeSizeInline(reflection::BaseType base_type, + int type_index, + const reflection::Schema &schema) { + if (base_type == reflection::Obj && + schema.objects()->Get(type_index)->is_struct()) { + return schema.objects()->Get(type_index)->bytesize(); + } else { + return GetTypeSize(base_type); + } +} + // Get the root, regardless of what type it is. inline Table *GetAnyRoot(uint8_t *flatbuf) { return GetMutableRoot
(flatbuf); @@ -75,6 +89,14 @@ template Vector *GetFieldV(const Table &table, return table.GetPointer *>(field.offset()); } +// Get a field, if you know it's a vector, generically. +// To actually access elements, use the return value together with +// field.type()->element() in any of GetAnyVectorElemI below etc. +inline VectorOfAny *GetFieldAnyV(const Table &table, + const reflection::Field &field) { + return table.GetPointer(field.offset()); +} + // Get a field, if you know it's a table. inline Table *GetFieldT(const Table &table, const reflection::Field &field) { @@ -83,93 +105,124 @@ inline Table *GetFieldT(const Table &table, return table.GetPointer
(field.offset()); } -// Get any field as a 64bit int, regardless of what it is (bool/int/float/str). +// Raw helper functions used below: get any value in memory as a 64bit int, a +// double or a string. +// All scalars get static_cast to an int64_t, strings use strtoull, every other +// data type returns 0. +int64_t GetAnyValueI(reflection::BaseType type, const uint8_t *data); +// All scalars static cast to double, strings use strtod, every other data +// type is 0.0. +double GetAnyValueF(reflection::BaseType type, const uint8_t *data); +// All scalars converted using stringstream, strings as-is, and all other +// data types provide some level of debug-pretty-printing. +std::string GetAnyValueS(reflection::BaseType type, const uint8_t *data, + const reflection::Schema *schema, + int type_index); + +// Get any table field as a 64bit int, regardless of what type it is. inline int64_t GetAnyFieldI(const Table &table, const reflection::Field &field) { -# define FLATBUFFERS_GET(C, T) \ - static_cast(GetField##C(table, field)) - switch (field.type()->base_type()) { - case reflection::UType: - case reflection::Bool: - case reflection::UByte: return FLATBUFFERS_GET(I, uint8_t); - case reflection::Byte: return FLATBUFFERS_GET(I, int8_t); - case reflection::Short: return FLATBUFFERS_GET(I, int16_t); - case reflection::UShort: return FLATBUFFERS_GET(I, uint16_t); - case reflection::Int: return FLATBUFFERS_GET(I, int32_t); - case reflection::UInt: return FLATBUFFERS_GET(I, uint32_t); - case reflection::Long: return FLATBUFFERS_GET(I, int64_t); - case reflection::ULong: return FLATBUFFERS_GET(I, uint64_t); - case reflection::Float: return FLATBUFFERS_GET(F, float); - case reflection::Double: return FLATBUFFERS_GET(F, double); - case reflection::String: { - auto s = GetFieldS(table, field); - return s ? StringToInt(s->c_str()) : 0; - } - default: return 0; - } -# undef FLATBUFFERS_GET + auto field_ptr = table.GetAddressOf(field.offset()); + return field_ptr ? GetAnyValueI(field.type()->base_type(), field_ptr) + : field.default_integer(); } -// Get any field as a double, regardless of what it is (bool/int/float/str). +// Get any table field as a double, regardless of what type it is. inline double GetAnyFieldF(const Table &table, const reflection::Field &field) { - switch (field.type()->base_type()) { - case reflection::Float: return GetFieldF(table, field); - case reflection::Double: return GetFieldF(table, field); - case reflection::String: { - auto s = GetFieldS(table, field); - return s ? strtod(s->c_str(), nullptr) : 0.0; - } - default: return static_cast(GetAnyFieldI(table, field)); - } + auto field_ptr = table.GetAddressOf(field.offset()); + return field_ptr ? GetAnyValueF(field.type()->base_type(), field_ptr) + : field.default_real(); } -// Get any field as a string, regardless of what it is (bool/int/float/str). + +// Get any table field as a string, regardless of what type it is. +// You may pass nullptr for the schema if you don't care to have fields that +// are of table type pretty-printed. inline std::string GetAnyFieldS(const Table &table, const reflection::Field &field, - const reflection::Schema &schema) { - switch (field.type()->base_type()) { - case reflection::Float: - case reflection::Double: return NumToString(GetAnyFieldF(table, field)); - case reflection::String: { - auto s = GetFieldS(table, field); - return s ? s->c_str() : ""; - } - case reflection::Obj: { - // Convert the table to a string. This is mostly for debugging purposes, - // and does NOT promise to be JSON compliant. - // Also prefixes the type. - auto &objectdef = *schema.objects()->Get(field.type()->index()); - auto s = objectdef.name()->str(); - if (objectdef.is_struct()) { - s += "(struct)"; // TODO: implement this as well. - } else { - auto table_field = GetFieldT(table, field); - s += " { "; - auto fielddefs = objectdef.fields(); - for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { - auto &fielddef = **it; - if (!table.CheckField(fielddef.offset())) continue; - auto val = GetAnyFieldS(*table_field, fielddef, schema); - if (fielddef.type()->base_type() == reflection::String) - val = "\"" + val + "\""; // Doesn't deal with escape codes etc. - s += fielddef.name()->str(); - s += ": "; - s += val; - s += ", "; - } - s += "}"; - } - return s; - } - case reflection::Vector: - return "[(elements)]"; // TODO: implement this as well. - case reflection::Union: - return "(union)"; // TODO: implement this as well. - default: return NumToString(GetAnyFieldI(table, field)); - } + const reflection::Schema *schema) { + auto field_ptr = table.GetAddressOf(field.offset()); + return field_ptr ? GetAnyValueS(field.type()->base_type(), field_ptr, schema, + field.type()->index()) + : ""; +} + +// Get any struct field as a 64bit int, regardless of what type it is. +inline int64_t GetAnyFieldI(const Struct &st, + const reflection::Field &field) { + return GetAnyValueI(field.type()->base_type(), + st.GetAddressOf(field.offset())); } +// Get any struct field as a double, regardless of what type it is. +inline double GetAnyFieldF(const Struct &st, + const reflection::Field &field) { + return GetAnyValueF(field.type()->base_type(), + st.GetAddressOf(field.offset())); +} + +// Get any struct field as a string, regardless of what type it is. +inline std::string GetAnyFieldS(const Struct &st, + const reflection::Field &field) { + return GetAnyValueS(field.type()->base_type(), + st.GetAddressOf(field.offset()), nullptr, -1); +} + +// Get any vector element as a 64bit int, regardless of what type it is. +inline int64_t GetAnyVectorElemI(const VectorOfAny *vec, + reflection::BaseType elem_type, size_t i) { + return GetAnyValueI(elem_type, vec->Data() + GetTypeSize(elem_type) * i); +} + +// Get any vector element as a double, regardless of what type it is. +inline double GetAnyVectorElemF(const VectorOfAny *vec, + reflection::BaseType elem_type, size_t i) { + return GetAnyValueF(elem_type, vec->Data() + GetTypeSize(elem_type) * i); +} + +// Get any vector element as a string, regardless of what type it is. +inline std::string GetAnyVectorElemS(const VectorOfAny *vec, + reflection::BaseType elem_type, size_t i) { + return GetAnyValueS(elem_type, vec->Data() + GetTypeSize(elem_type) * i, + nullptr, -1); +} + +// Get a vector element that's a table/string/vector from a generic vector. +// Pass Table/String/VectorOfAny as template parameter. +// Warning: does no typechecking. +template T *GetAnyVectorElemPointer(const VectorOfAny *vec, + size_t i) { + auto elem_ptr = vec->Data() + sizeof(uoffset_t) * i; + return (T *)(elem_ptr + ReadScalar(elem_ptr)); +} + +// Get the inline-address of a vector element. Useful for Structs (pass Struct +// as template arg), or being able to address a range of scalars in-line. +// Get elem_size from GetTypeSizeInline(). +// Note: little-endian data on all platforms, use EndianScalar() instead of +// raw pointer access with scalars). +template T *GetAnyVectorElemAddressOf(const VectorOfAny *vec, + size_t i, + size_t elem_size) { + // C-cast to allow const conversion. + return (T *)(vec->Data() + elem_size * i); +} + +// Similarly, for elements of tables. +template T *GetAnyFieldAddressOf(const Table &table, + const reflection::Field &field) { + return (T *)table.GetAddressOf(field.offset()); +} + +// Similarly, for elements of structs. +template T *GetAnyFieldAddressOf(const Struct &st, + const reflection::Field &field) { + return (T *)st.GetAddressOf(field.offset()); +} + +// ------------------------- SETTERS ------------------------- + // Set any scalar field, if you know its exact type. template bool SetField(Table *table, const reflection::Field &field, T val) { @@ -177,52 +230,83 @@ template bool SetField(Table *table, const reflection::Field &field, return table->SetField(field.offset(), val); } -// Set any field as a 64bit int, regardless of what it is (bool/int/float/str). -inline void SetAnyFieldI(Table *table, const reflection::Field &field, +// Raw helper functions used below: set any value in memory as a 64bit int, a +// double or a string. +// These work for all scalar values, but do nothing for other data types. +// To set a string, see SetString below. +void SetAnyValueI(reflection::BaseType type, uint8_t *data, int64_t val); +void SetAnyValueF(reflection::BaseType type, uint8_t *data, double val); +void SetAnyValueS(reflection::BaseType type, uint8_t *data, const char *val); + +// Set any table field as a 64bit int, regardless of type what it is. +inline bool SetAnyFieldI(Table *table, const reflection::Field &field, int64_t val) { -# define FLATBUFFERS_SET(T) SetField(table, field, static_cast(val)) - switch (field.type()->base_type()) { - case reflection::UType: - case reflection::Bool: - case reflection::UByte: FLATBUFFERS_SET(uint8_t ); break; - case reflection::Byte: FLATBUFFERS_SET(int8_t ); break; - case reflection::Short: FLATBUFFERS_SET(int16_t ); break; - case reflection::UShort: FLATBUFFERS_SET(uint16_t ); break; - case reflection::Int: FLATBUFFERS_SET(int32_t ); break; - case reflection::UInt: FLATBUFFERS_SET(uint32_t ); break; - case reflection::Long: FLATBUFFERS_SET(int64_t ); break; - case reflection::ULong: FLATBUFFERS_SET(uint64_t ); break; - case reflection::Float: FLATBUFFERS_SET(float ); break; - case reflection::Double: FLATBUFFERS_SET(double ); break; - // TODO: support strings - default: break; - } -# undef FLATBUFFERS_SET + auto field_ptr = table->GetAddressOf(field.offset()); + if (!field_ptr) return false; + SetAnyValueI(field.type()->base_type(), field_ptr, val); + return true; } -// Set any field as a double, regardless of what it is (bool/int/float/str). -inline void SetAnyFieldF(Table *table, const reflection::Field &field, +// Set any table field as a double, regardless of what type it is. +inline bool SetAnyFieldF(Table *table, const reflection::Field &field, double val) { - switch (field.type()->base_type()) { - case reflection::Float: SetField (table, field, - static_cast(val)); break; - case reflection::Double: SetField(table, field, val); break; - // TODO: support strings. - default: SetAnyFieldI(table, field, static_cast(val)); break; - } + auto field_ptr = table->GetAddressOf(field.offset()); + if (!field_ptr) return false; + SetAnyValueF(field.type()->base_type(), field_ptr, val); + return true; +} + +// Set any table field as a string, regardless of what type it is. +inline bool SetAnyFieldS(Table *table, const reflection::Field &field, + const char *val) { + auto field_ptr = table->GetAddressOf(field.offset()); + if (!field_ptr) return false; + SetAnyValueS(field.type()->base_type(), field_ptr, val); + return true; } -// Set any field as a string, regardless of what it is (bool/int/float/str). -inline void SetAnyFieldS(Table *table, const reflection::Field &field, +// Set any struct field as a 64bit int, regardless of type what it is. +inline void SetAnyFieldI(Struct *st, const reflection::Field &field, + int64_t val) { + SetAnyValueI(field.type()->base_type(), st->GetAddressOf(field.offset()), + val); +} + +// Set any struct field as a double, regardless of type what it is. +inline void SetAnyFieldF(Struct *st, const reflection::Field &field, + double val) { + SetAnyValueF(field.type()->base_type(), st->GetAddressOf(field.offset()), + val); +} + +// Set any struct field as a string, regardless of type what it is. +inline void SetAnyFieldS(Struct *st, const reflection::Field &field, const char *val) { - switch (field.type()->base_type()) { - case reflection::Float: - case reflection::Double: SetAnyFieldF(table, field, strtod(val, nullptr)); - // TODO: support strings. - default: SetAnyFieldI(table, field, StringToInt(val)); break; - } + SetAnyValueS(field.type()->base_type(), st->GetAddressOf(field.offset()), + val); +} + +// Set any vector element as a 64bit int, regardless of type what it is. +inline void SetAnyVectorElemI(VectorOfAny *vec, reflection::BaseType elem_type, + size_t i, int64_t val) { + SetAnyValueI(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); +} + +// Set any vector element as a double, regardless of type what it is. +inline void SetAnyVectorElemF(VectorOfAny *vec, reflection::BaseType elem_type, + size_t i, double val) { + SetAnyValueF(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); +} + +// Set any vector element as a string, regardless of type what it is. +inline void SetAnyVectorElemS(VectorOfAny *vec, reflection::BaseType elem_type, + size_t i, const char *val) { + SetAnyValueS(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); } + +// ------------------------- RESIZING SETTERS ------------------------- + // "smart" pointer for use with resizing vectors: turns a pointer inside // a vector into a relative offset, such that it is not affected by resizes. template class pointer_inside_vector { @@ -265,194 +349,43 @@ inline const reflection::Object &GetUnionType( return *enumval->object(); } -// Resize a FlatBuffer in-place by iterating through all offsets in the buffer -// and adjusting them by "delta" if they straddle the start offset. -// Once that is done, bytes can now be inserted/deleted safely. -// "delta" may be negative (shrinking). -// Unless "delta" is a multiple of the largest alignment, you'll create a small -// amount of garbage space in the buffer (usually 0..7 bytes). -// If your FlatBuffer's root table is not the schema's root table, you should -// pass in your root_table type as well. -class ResizeContext { - public: - ResizeContext(const reflection::Schema &schema, uoffset_t start, int delta, - std::vector *flatbuf, - const reflection::Object *root_table = nullptr) - : schema_(schema), startptr_(flatbuf->data() + start), - delta_(delta), buf_(*flatbuf), - dag_check_(flatbuf->size() / sizeof(uoffset_t), false) { - auto mask = static_cast(sizeof(largest_scalar_t) - 1); - delta_ = (delta_ + mask) & ~mask; - if (!delta_) return; // We can't shrink by less than largest_scalar_t. - // Now change all the offsets by delta_. - auto root = GetAnyRoot(buf_.data()); - Straddle(buf_.data(), root, buf_.data()); - ResizeTable(root_table ? *root_table : *schema.root_table(), root); - // We can now add or remove bytes at start. - if (delta_ > 0) buf_.insert(buf_.begin() + start, delta_, 0); - else buf_.erase(buf_.begin() + start, buf_.begin() + start - delta_); - } - - // Check if the range between first (lower address) and second straddles - // the insertion point. If it does, change the offset at offsetloc (of - // type T, with direction D). - template void Straddle(void *first, void *second, - void *offsetloc) { - if (first <= startptr_ && second >= startptr_) { - WriteScalar(offsetloc, ReadScalar(offsetloc) + delta_ * D); - DagCheck(offsetloc) = true; - } - } - - // This returns a boolean that records if the corresponding offset location - // has been modified already. If so, we can't even read the corresponding - // offset, since it is pointing to a location that is illegal until the - // resize actually happens. - // This must be checked for every offset, since we can't know which offsets - // will straddle and which won't. - uint8_t &DagCheck(void *offsetloc) { - auto dag_idx = reinterpret_cast(offsetloc) - - reinterpret_cast(buf_.data()); - return dag_check_[dag_idx]; - } - - void ResizeTable(const reflection::Object &objectdef, Table *table) { - if (DagCheck(table)) - return; // Table already visited. - auto vtable = table->GetVTable(); - // Check if the vtable offset points beyond the insertion point. - Straddle(table, vtable, table); - // This direction shouldn't happen because vtables that sit before tables - // are always directly adjacent, but check just in case we ever change the - // way flatbuffers are built. - Straddle(vtable, table, table); - // Early out: since all fields inside the table must point forwards in - // memory, if the insertion point is before the table we can stop here. - auto tableloc = reinterpret_cast(table); - if (startptr_ <= tableloc) return; - // Check each field. - auto fielddefs = objectdef.fields(); - for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { - auto &fielddef = **it; - auto base_type = fielddef.type()->base_type(); - // Ignore scalars. - if (base_type <= reflection::Double) continue; - // Ignore fields that are not stored. - auto offset = table->GetOptionalFieldOffset(fielddef.offset()); - if (!offset) continue; - // Ignore structs. - auto subobjectdef = base_type == reflection::Obj ? - schema_.objects()->Get(fielddef.type()->index()) : nullptr; - if (subobjectdef && subobjectdef->is_struct()) continue; - // Get this fields' offset, and read it if safe. - auto offsetloc = tableloc + offset; - if (DagCheck(offsetloc)) - continue; // This offset already visited. - auto ref = offsetloc + ReadScalar(offsetloc); - Straddle(offsetloc, ref, offsetloc); - // Recurse. - switch (base_type) { - case reflection::Obj: { - ResizeTable(*subobjectdef, reinterpret_cast
(ref)); - break; - } - case reflection::Vector: { - auto elem_type = fielddef.type()->element(); - if (elem_type != reflection::Obj && elem_type != reflection::String) - break; - auto vec = reinterpret_cast *>(ref); - auto elemobjectdef = elem_type == reflection::Obj - ? schema_.objects()->Get(fielddef.type()->index()) - : nullptr; - if (elemobjectdef && elemobjectdef->is_struct()) break; - for (uoffset_t i = 0; i < vec->size(); i++) { - auto loc = vec->Data() + i * sizeof(uoffset_t); - if (DagCheck(loc)) - continue; // This offset already visited. - auto dest = loc + vec->Get(i); - Straddle(loc, dest ,loc); - if (elemobjectdef) - ResizeTable(*elemobjectdef, reinterpret_cast
(dest)); - } - break; - } - case reflection::Union: { - ResizeTable(GetUnionType(schema_, objectdef, fielddef, *table), - reinterpret_cast
(ref)); - break; - } - case reflection::String: - break; - default: - assert(false); - } - } - } - - void operator=(const ResizeContext &rc); - - private: - const reflection::Schema &schema_; - uint8_t *startptr_; - int delta_; - std::vector &buf_; - std::vector dag_check_; -}; - // Changes the contents of a string inside a FlatBuffer. FlatBuffer must // live inside a std::vector so we can resize the buffer if needed. // "str" must live inside "flatbuf" and may be invalidated after this call. // If your FlatBuffer's root table is not the schema's root table, you should // pass in your root_table type as well. -inline void SetString(const reflection::Schema &schema, const std::string &val, - const String *str, std::vector *flatbuf, - const reflection::Object *root_table = nullptr) { - auto delta = static_cast(val.size()) - static_cast(str->Length()); - auto start = static_cast(reinterpret_cast(str) - - flatbuf->data() + - sizeof(uoffset_t)); - if (delta) { - // Clear the old string, since we don't want parts of it remaining. - memset(flatbuf->data() + start, 0, str->Length()); - // Different size, we must expand (or contract). - ResizeContext(schema, start, delta, flatbuf, root_table); - } - // Copy new data. Safe because we created the right amount of space. - memcpy(flatbuf->data() + start, val.c_str(), val.size() + 1); -} +void SetString(const reflection::Schema &schema, const std::string &val, + const String *str, std::vector *flatbuf, + const reflection::Object *root_table = nullptr); // Resizes a flatbuffers::Vector inside a FlatBuffer. FlatBuffer must // live inside a std::vector so we can resize the buffer if needed. // "vec" must live inside "flatbuf" and may be invalidated after this call. // If your FlatBuffer's root table is not the schema's root table, you should // pass in your root_table type as well. +uint8_t *ResizeAnyVector(const reflection::Schema &schema, uoffset_t newsize, + const VectorOfAny *vec, uoffset_t num_elems, + uoffset_t elem_size, std::vector *flatbuf, + const reflection::Object *root_table = nullptr); + template void ResizeVector(const reflection::Schema &schema, uoffset_t newsize, T val, const Vector *vec, std::vector *flatbuf, const reflection::Object *root_table = nullptr) { auto delta_elem = static_cast(newsize) - static_cast(vec->size()); - auto delta_bytes = delta_elem * static_cast(sizeof(T)); - auto vec_start = reinterpret_cast(vec) - flatbuf->data(); - auto start = static_cast(vec_start + sizeof(uoffset_t) + - sizeof(T) * vec->size()); - if (delta_bytes) { - if (delta_elem < 0) { - // Clear elements we're throwing away, since some might remain in the - // buffer. - memset(flatbuf->data() + start + delta_elem * sizeof(T), 0, - -delta_elem * sizeof(T)); - } - ResizeContext(schema, start, delta_bytes, flatbuf, root_table); - WriteScalar(flatbuf->data() + vec_start, newsize); // Length field. - // Set new elements to "val". - for (int i = 0; i < delta_elem; i++) { - auto loc = flatbuf->data() + start + i * sizeof(T); - auto is_scalar = std::is_scalar::value; - if (is_scalar) { - WriteScalar(loc, val); - } else { // struct - *reinterpret_cast(loc) = val; - } + auto newelems = ResizeAnyVector(schema, newsize, + reinterpret_cast(vec), + vec->size(), + static_cast(sizeof(T)), flatbuf, + root_table); + // Set new elements to "val". + for (int i = 0; i < delta_elem; i++) { + auto loc = newelems + i * sizeof(T); + auto is_scalar = std::is_scalar::value; + if (is_scalar) { + WriteScalar(loc, val); + } else { // struct + *reinterpret_cast(loc) = val; } } } @@ -465,21 +398,8 @@ void ResizeVector(const reflection::Schema &schema, uoffset_t newsize, T val, // existing one. // The return value can now be set using Vector::MutateOffset or SetFieldT // below. -inline const uint8_t *AddFlatBuffer(std::vector &flatbuf, - const uint8_t *newbuf, size_t newlen) { - // Align to sizeof(uoffset_t) past sizeof(largest_scalar_t) since we're - // going to chop off the root offset. - while ((flatbuf.size() & (sizeof(uoffset_t) - 1)) || - !(flatbuf.size() & (sizeof(largest_scalar_t) - 1))) { - flatbuf.push_back(0); - } - auto insertion_point = static_cast(flatbuf.size()); - // Insert the entire FlatBuffer minus the root pointer. - flatbuf.insert(flatbuf.end(), newbuf + sizeof(uoffset_t), - newbuf + newlen - sizeof(uoffset_t)); - auto root_offset = ReadScalar(newbuf) - sizeof(uoffset_t); - return flatbuf.data() + insertion_point + root_offset; -} +const uint8_t *AddFlatBuffer(std::vector &flatbuf, + const uint8_t *newbuf, size_t newlen); inline bool SetFieldT(Table *table, const reflection::Field &field, const uint8_t *val) { @@ -487,6 +407,8 @@ inline bool SetFieldT(Table *table, const reflection::Field &field, return table->SetPointer(field.offset(), val); } +// ------------------------- COPYING ------------------------- + // Generic copying of tables from a FlatBuffer into a FlatBuffer builder. // Can be used to do any kind of merging/selecting you may want to do out // of existing buffers. Also useful to reconstruct a whole buffer if the @@ -495,134 +417,10 @@ inline bool SetFieldT(Table *table, const reflection::Field &field, // Note: this does not deal with DAGs correctly. If the table passed forms a // DAG, the copy will be a tree instead (with duplicates). -inline void CopyInline(FlatBufferBuilder &fbb, - const reflection::Field &fielddef, - const Table &table, - size_t align, size_t size) { - fbb.Align(align); - fbb.PushBytes(table.GetStruct(fielddef.offset()), size); - fbb.TrackField(fielddef.offset(), fbb.GetSize()); -} - -inline Offset CopyTable(FlatBufferBuilder &fbb, - const reflection::Schema &schema, - const reflection::Object &objectdef, - const Table &table) { - // Before we can construct the table, we have to first generate any - // subobjects, and collect their offsets. - std::vector offsets; - auto fielddefs = objectdef.fields(); - for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { - auto &fielddef = **it; - // Skip if field is not present in the source. - if (!table.CheckField(fielddef.offset())) continue; - uoffset_t offset = 0; - switch (fielddef.type()->base_type()) { - case reflection::String: { - offset = fbb.CreateString(GetFieldS(table, fielddef)).o; - break; - } - case reflection::Obj: { - auto &subobjectdef = *schema.objects()->Get(fielddef.type()->index()); - if (!subobjectdef.is_struct()) { - offset = CopyTable(fbb, schema, subobjectdef, - *GetFieldT(table, fielddef)).o; - } - break; - } - case reflection::Union: { - auto &subobjectdef = GetUnionType(schema, objectdef, fielddef, table); - offset = CopyTable(fbb, schema, subobjectdef, - *GetFieldT(table, fielddef)).o; - break; - } - case reflection::Vector: { - auto vec = table.GetPointer> *>( - fielddef.offset()); - auto element_base_type = fielddef.type()->element(); - auto elemobjectdef = element_base_type == reflection::Obj - ? schema.objects()->Get(fielddef.type()->index()) - : nullptr; - switch (element_base_type) { - case reflection::String: { - std::vector> elements(vec->size()); - auto vec_s = reinterpret_cast> *>(vec); - for (uoffset_t i = 0; i < vec_s->size(); i++) { - elements[i] = fbb.CreateString(vec_s->Get(i)).o; - } - offset = fbb.CreateVector(elements).o; - break; - } - case reflection::Obj: { - if (!elemobjectdef->is_struct()) { - std::vector> elements(vec->size()); - for (uoffset_t i = 0; i < vec->size(); i++) { - elements[i] = - CopyTable(fbb, schema, *elemobjectdef, *vec->Get(i)); - } - offset = fbb.CreateVector(elements).o; - break; - } - // FALL-THRU: - } - default: { // Scalars and structs. - auto element_size = GetTypeSize(element_base_type); - if (elemobjectdef && elemobjectdef->is_struct()) - element_size = elemobjectdef->bytesize(); - fbb.StartVector(element_size, vec->size()); - fbb.PushBytes(vec->Data(), element_size * vec->size()); - offset = fbb.EndVector(vec->size()); - break; - } - } - break; - } - default: // Scalars. - break; - } - if (offset) { - offsets.push_back(offset); - } - } - // Now we can build the actual table from either offsets or scalar data. - auto start = objectdef.is_struct() - ? fbb.StartStruct(objectdef.minalign()) - : fbb.StartTable(); - size_t offset_idx = 0; - for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { - auto &fielddef = **it; - if (!table.CheckField(fielddef.offset())) continue; - auto base_type = fielddef.type()->base_type(); - switch (base_type) { - case reflection::Obj: { - auto &subobjectdef = *schema.objects()->Get(fielddef.type()->index()); - if (subobjectdef.is_struct()) { - CopyInline(fbb, fielddef, table, subobjectdef.minalign(), - subobjectdef.bytesize()); - break; - } - // else: FALL-THRU: - } - case reflection::Union: - case reflection::String: - case reflection::Vector: - fbb.AddOffset(fielddef.offset(), Offset(offsets[offset_idx++])); - break; - default: { // Scalars. - auto size = GetTypeSize(base_type); - CopyInline(fbb, fielddef, table, size, size); - break; - } - } - } - assert(offset_idx == offsets.size()); - if (objectdef.is_struct()) { - fbb.ClearOffsets(); - return fbb.EndStruct(); - } else { - return fbb.EndTable(start, static_cast(fielddefs->size())); - } -} +Offset CopyTable(FlatBufferBuilder &fbb, + const reflection::Schema &schema, + const reflection::Object &objectdef, + const Table &table); } // namespace flatbuffers diff --git a/src/reflection.cpp b/src/reflection.cpp new file mode 100644 index 00000000000..aa1ffaf36ea --- /dev/null +++ b/src/reflection.cpp @@ -0,0 +1,470 @@ +/* + * Copyright 2015 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flatbuffers/reflection.h" +#include "flatbuffers/util.h" + +// Helper functionality for reflection. + +namespace flatbuffers { + +int64_t GetAnyValueI(reflection::BaseType type, const uint8_t *data) { +# define FLATBUFFERS_GET(T) static_cast(ReadScalar(data)) + switch (type) { + case reflection::UType: + case reflection::Bool: + case reflection::UByte: return FLATBUFFERS_GET(uint8_t); + case reflection::Byte: return FLATBUFFERS_GET(int8_t); + case reflection::Short: return FLATBUFFERS_GET(int16_t); + case reflection::UShort: return FLATBUFFERS_GET(uint16_t); + case reflection::Int: return FLATBUFFERS_GET(int32_t); + case reflection::UInt: return FLATBUFFERS_GET(uint32_t); + case reflection::Long: return FLATBUFFERS_GET(int64_t); + case reflection::ULong: return FLATBUFFERS_GET(uint64_t); + case reflection::Float: return FLATBUFFERS_GET(float); + case reflection::Double: return FLATBUFFERS_GET(double); + case reflection::String: { + auto s = reinterpret_cast(ReadScalar(data) + + data); + return s ? StringToInt(s->c_str()) : 0; + } + default: return 0; // Tables & vectors do not make sense. + } +# undef FLATBUFFERS_GET +} + +double GetAnyValueF(reflection::BaseType type, const uint8_t *data) { + switch (type) { + case reflection::Float: return static_cast(ReadScalar(data)); + case reflection::Double: return ReadScalar(data); + case reflection::String: { + auto s = reinterpret_cast(ReadScalar(data) + + data); + return s ? strtod(s->c_str(), nullptr) : 0.0; + } + default: return static_cast(GetAnyValueI(type, data)); + } +} + +std::string GetAnyValueS(reflection::BaseType type, const uint8_t *data, + const reflection::Schema *schema, int type_index) { + switch (type) { + case reflection::Float: + case reflection::Double: return NumToString(GetAnyValueF(type, data)); + case reflection::String: { + auto s = reinterpret_cast(ReadScalar(data) + + data); + return s ? s->c_str() : ""; + } + case reflection::Obj: + if (schema) { + // Convert the table to a string. This is mostly for debugging purposes, + // and does NOT promise to be JSON compliant. + // Also prefixes the type. + auto &objectdef = *schema->objects()->Get(type_index); + auto s = objectdef.name()->str(); + if (objectdef.is_struct()) { + s += "(struct)"; // TODO: implement this as well. + } else { + auto table_field = reinterpret_cast( + ReadScalar(data) + data); + s += " { "; + auto fielddefs = objectdef.fields(); + for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { + auto &fielddef = **it; + if (!table_field->CheckField(fielddef.offset())) continue; + auto val = GetAnyFieldS(*table_field, fielddef, schema); + if (fielddef.type()->base_type() == reflection::String) + val = "\"" + val + "\""; // Doesn't deal with escape codes etc. + s += fielddef.name()->str(); + s += ": "; + s += val; + s += ", "; + } + s += "}"; + } + return s; + } else { + return "(table)"; + } + case reflection::Vector: + return "[(elements)]"; // TODO: implement this as well. + case reflection::Union: + return "(union)"; // TODO: implement this as well. + default: return NumToString(GetAnyValueI(type, data)); + } +} + +void SetAnyValueI(reflection::BaseType type, uint8_t *data, int64_t val) { +# define FLATBUFFERS_SET(T) WriteScalar(data, static_cast(val)) + switch (type) { + case reflection::UType: + case reflection::Bool: + case reflection::UByte: FLATBUFFERS_SET(uint8_t ); break; + case reflection::Byte: FLATBUFFERS_SET(int8_t ); break; + case reflection::Short: FLATBUFFERS_SET(int16_t ); break; + case reflection::UShort: FLATBUFFERS_SET(uint16_t); break; + case reflection::Int: FLATBUFFERS_SET(int32_t ); break; + case reflection::UInt: FLATBUFFERS_SET(uint32_t); break; + case reflection::Long: FLATBUFFERS_SET(int64_t ); break; + case reflection::ULong: FLATBUFFERS_SET(uint64_t); break; + case reflection::Float: FLATBUFFERS_SET(float ); break; + case reflection::Double: FLATBUFFERS_SET(double ); break; + // TODO: support strings + default: break; + } +# undef FLATBUFFERS_SET +} + +void SetAnyValueF(reflection::BaseType type, uint8_t *data, double val) { + switch (type) { + case reflection::Float: WriteScalar(data, static_cast(val)); break; + case reflection::Double: WriteScalar(data, val); break; + // TODO: support strings. + default: SetAnyValueI(type, data, static_cast(val)); break; + } +} + +void SetAnyValueS(reflection::BaseType type, uint8_t *data, const char *val) { + switch (type) { + case reflection::Float: + case reflection::Double: SetAnyValueF(type, data, strtod(val, nullptr)); + // TODO: support strings. + default: SetAnyValueI(type, data, StringToInt(val)); break; + } +} + +// Resize a FlatBuffer in-place by iterating through all offsets in the buffer +// and adjusting them by "delta" if they straddle the start offset. +// Once that is done, bytes can now be inserted/deleted safely. +// "delta" may be negative (shrinking). +// Unless "delta" is a multiple of the largest alignment, you'll create a small +// amount of garbage space in the buffer (usually 0..7 bytes). +// If your FlatBuffer's root table is not the schema's root table, you should +// pass in your root_table type as well. +class ResizeContext { + public: + ResizeContext(const reflection::Schema &schema, uoffset_t start, int delta, + std::vector *flatbuf, + const reflection::Object *root_table = nullptr) + : schema_(schema), startptr_(flatbuf->data() + start), + delta_(delta), buf_(*flatbuf), + dag_check_(flatbuf->size() / sizeof(uoffset_t), false) { + auto mask = static_cast(sizeof(largest_scalar_t) - 1); + delta_ = (delta_ + mask) & ~mask; + if (!delta_) return; // We can't shrink by less than largest_scalar_t. + // Now change all the offsets by delta_. + auto root = GetAnyRoot(buf_.data()); + Straddle(buf_.data(), root, buf_.data()); + ResizeTable(root_table ? *root_table : *schema.root_table(), root); + // We can now add or remove bytes at start. + if (delta_ > 0) buf_.insert(buf_.begin() + start, delta_, 0); + else buf_.erase(buf_.begin() + start, buf_.begin() + start - delta_); + } + + // Check if the range between first (lower address) and second straddles + // the insertion point. If it does, change the offset at offsetloc (of + // type T, with direction D). + template void Straddle(void *first, void *second, + void *offsetloc) { + if (first <= startptr_ && second >= startptr_) { + WriteScalar(offsetloc, ReadScalar(offsetloc) + delta_ * D); + DagCheck(offsetloc) = true; + } + } + + // This returns a boolean that records if the corresponding offset location + // has been modified already. If so, we can't even read the corresponding + // offset, since it is pointing to a location that is illegal until the + // resize actually happens. + // This must be checked for every offset, since we can't know which offsets + // will straddle and which won't. + uint8_t &DagCheck(void *offsetloc) { + auto dag_idx = reinterpret_cast(offsetloc) - + reinterpret_cast(buf_.data()); + return dag_check_[dag_idx]; + } + + void ResizeTable(const reflection::Object &objectdef, Table *table) { + if (DagCheck(table)) + return; // Table already visited. + auto vtable = table->GetVTable(); + // Check if the vtable offset points beyond the insertion point. + Straddle(table, vtable, table); + // This direction shouldn't happen because vtables that sit before tables + // are always directly adjacent, but check just in case we ever change the + // way flatbuffers are built. + Straddle(vtable, table, table); + // Early out: since all fields inside the table must point forwards in + // memory, if the insertion point is before the table we can stop here. + auto tableloc = reinterpret_cast(table); + if (startptr_ <= tableloc) return; + // Check each field. + auto fielddefs = objectdef.fields(); + for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { + auto &fielddef = **it; + auto base_type = fielddef.type()->base_type(); + // Ignore scalars. + if (base_type <= reflection::Double) continue; + // Ignore fields that are not stored. + auto offset = table->GetOptionalFieldOffset(fielddef.offset()); + if (!offset) continue; + // Ignore structs. + auto subobjectdef = base_type == reflection::Obj ? + schema_.objects()->Get(fielddef.type()->index()) : nullptr; + if (subobjectdef && subobjectdef->is_struct()) continue; + // Get this fields' offset, and read it if safe. + auto offsetloc = tableloc + offset; + if (DagCheck(offsetloc)) + continue; // This offset already visited. + auto ref = offsetloc + ReadScalar(offsetloc); + Straddle(offsetloc, ref, offsetloc); + // Recurse. + switch (base_type) { + case reflection::Obj: { + ResizeTable(*subobjectdef, reinterpret_cast
(ref)); + break; + } + case reflection::Vector: { + auto elem_type = fielddef.type()->element(); + if (elem_type != reflection::Obj && elem_type != reflection::String) + break; + auto vec = reinterpret_cast *>(ref); + auto elemobjectdef = elem_type == reflection::Obj + ? schema_.objects()->Get(fielddef.type()->index()) + : nullptr; + if (elemobjectdef && elemobjectdef->is_struct()) break; + for (uoffset_t i = 0; i < vec->size(); i++) { + auto loc = vec->Data() + i * sizeof(uoffset_t); + if (DagCheck(loc)) + continue; // This offset already visited. + auto dest = loc + vec->Get(i); + Straddle(loc, dest ,loc); + if (elemobjectdef) + ResizeTable(*elemobjectdef, reinterpret_cast
(dest)); + } + break; + } + case reflection::Union: { + ResizeTable(GetUnionType(schema_, objectdef, fielddef, *table), + reinterpret_cast
(ref)); + break; + } + case reflection::String: + break; + default: + assert(false); + } + } + } + + void operator=(const ResizeContext &rc); + + private: + const reflection::Schema &schema_; + uint8_t *startptr_; + int delta_; + std::vector &buf_; + std::vector dag_check_; +}; + +void SetString(const reflection::Schema &schema, const std::string &val, + const String *str, std::vector *flatbuf, + const reflection::Object *root_table) { + auto delta = static_cast(val.size()) - static_cast(str->Length()); + auto start = static_cast(reinterpret_cast(str) - + flatbuf->data() + + sizeof(uoffset_t)); + if (delta) { + // Clear the old string, since we don't want parts of it remaining. + memset(flatbuf->data() + start, 0, str->Length()); + // Different size, we must expand (or contract). + ResizeContext(schema, start, delta, flatbuf, root_table); + } + // Copy new data. Safe because we created the right amount of space. + memcpy(flatbuf->data() + start, val.c_str(), val.size() + 1); +} + +uint8_t *ResizeAnyVector(const reflection::Schema &schema, uoffset_t newsize, + const VectorOfAny *vec, uoffset_t num_elems, + uoffset_t elem_size, std::vector *flatbuf, + const reflection::Object *root_table) { + auto delta_elem = static_cast(newsize) - static_cast(num_elems); + auto delta_bytes = delta_elem * static_cast(elem_size); + auto vec_start = reinterpret_cast(vec) - flatbuf->data(); + auto start = static_cast(vec_start + sizeof(uoffset_t) + + elem_size * num_elems); + if (delta_bytes) { + if (delta_elem < 0) { + // Clear elements we're throwing away, since some might remain in the + // buffer. + auto size_clear = -delta_elem * elem_size; + memset(flatbuf->data() + start - size_clear, 0, size_clear); + } + ResizeContext(schema, start, delta_bytes, flatbuf, root_table); + WriteScalar(flatbuf->data() + vec_start, newsize); // Length field. + // Set new elements to 0.. this can be overwritten by the caller. + if (delta_elem > 0) { + memset(flatbuf->data() + start, 0, delta_elem * elem_size); + } + } + return flatbuf->data() + start; +} + +const uint8_t *AddFlatBuffer(std::vector &flatbuf, + const uint8_t *newbuf, size_t newlen) { + // Align to sizeof(uoffset_t) past sizeof(largest_scalar_t) since we're + // going to chop off the root offset. + while ((flatbuf.size() & (sizeof(uoffset_t) - 1)) || + !(flatbuf.size() & (sizeof(largest_scalar_t) - 1))) { + flatbuf.push_back(0); + } + auto insertion_point = static_cast(flatbuf.size()); + // Insert the entire FlatBuffer minus the root pointer. + flatbuf.insert(flatbuf.end(), newbuf + sizeof(uoffset_t), + newbuf + newlen - sizeof(uoffset_t)); + auto root_offset = ReadScalar(newbuf) - sizeof(uoffset_t); + return flatbuf.data() + insertion_point + root_offset; +} + +void CopyInline(FlatBufferBuilder &fbb, const reflection::Field &fielddef, + const Table &table, size_t align, size_t size) { + fbb.Align(align); + fbb.PushBytes(table.GetStruct(fielddef.offset()), size); + fbb.TrackField(fielddef.offset(), fbb.GetSize()); +} + +Offset CopyTable(FlatBufferBuilder &fbb, + const reflection::Schema &schema, + const reflection::Object &objectdef, + const Table &table) { + // Before we can construct the table, we have to first generate any + // subobjects, and collect their offsets. + std::vector offsets; + auto fielddefs = objectdef.fields(); + for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { + auto &fielddef = **it; + // Skip if field is not present in the source. + if (!table.CheckField(fielddef.offset())) continue; + uoffset_t offset = 0; + switch (fielddef.type()->base_type()) { + case reflection::String: { + offset = fbb.CreateString(GetFieldS(table, fielddef)).o; + break; + } + case reflection::Obj: { + auto &subobjectdef = *schema.objects()->Get(fielddef.type()->index()); + if (!subobjectdef.is_struct()) { + offset = CopyTable(fbb, schema, subobjectdef, + *GetFieldT(table, fielddef)).o; + } + break; + } + case reflection::Union: { + auto &subobjectdef = GetUnionType(schema, objectdef, fielddef, table); + offset = CopyTable(fbb, schema, subobjectdef, + *GetFieldT(table, fielddef)).o; + break; + } + case reflection::Vector: { + auto vec = table.GetPointer> *>( + fielddef.offset()); + auto element_base_type = fielddef.type()->element(); + auto elemobjectdef = element_base_type == reflection::Obj + ? schema.objects()->Get(fielddef.type()->index()) + : nullptr; + switch (element_base_type) { + case reflection::String: { + std::vector> elements(vec->size()); + auto vec_s = reinterpret_cast> *>(vec); + for (uoffset_t i = 0; i < vec_s->size(); i++) { + elements[i] = fbb.CreateString(vec_s->Get(i)).o; + } + offset = fbb.CreateVector(elements).o; + break; + } + case reflection::Obj: { + if (!elemobjectdef->is_struct()) { + std::vector> elements(vec->size()); + for (uoffset_t i = 0; i < vec->size(); i++) { + elements[i] = + CopyTable(fbb, schema, *elemobjectdef, *vec->Get(i)); + } + offset = fbb.CreateVector(elements).o; + break; + } + // FALL-THRU: + } + default: { // Scalars and structs. + auto element_size = GetTypeSize(element_base_type); + if (elemobjectdef && elemobjectdef->is_struct()) + element_size = elemobjectdef->bytesize(); + fbb.StartVector(element_size, vec->size()); + fbb.PushBytes(vec->Data(), element_size * vec->size()); + offset = fbb.EndVector(vec->size()); + break; + } + } + break; + } + default: // Scalars. + break; + } + if (offset) { + offsets.push_back(offset); + } + } + // Now we can build the actual table from either offsets or scalar data. + auto start = objectdef.is_struct() + ? fbb.StartStruct(objectdef.minalign()) + : fbb.StartTable(); + size_t offset_idx = 0; + for (auto it = fielddefs->begin(); it != fielddefs->end(); ++it) { + auto &fielddef = **it; + if (!table.CheckField(fielddef.offset())) continue; + auto base_type = fielddef.type()->base_type(); + switch (base_type) { + case reflection::Obj: { + auto &subobjectdef = *schema.objects()->Get(fielddef.type()->index()); + if (subobjectdef.is_struct()) { + CopyInline(fbb, fielddef, table, subobjectdef.minalign(), + subobjectdef.bytesize()); + break; + } + // else: FALL-THRU: + } + case reflection::Union: + case reflection::String: + case reflection::Vector: + fbb.AddOffset(fielddef.offset(), Offset(offsets[offset_idx++])); + break; + default: { // Scalars. + auto size = GetTypeSize(base_type); + CopyInline(fbb, fielddef, table, size, size); + break; + } + } + } + assert(offset_idx == offsets.size()); + if (objectdef.is_struct()) { + fbb.ClearOffsets(); + return fbb.EndStruct(); + } else { + return fbb.EndTable(start, static_cast(fielddefs->size())); + } +} + +} // namespace flatbuffers diff --git a/tests/test.cpp b/tests/test.cpp index eb7c487b226..984e1c620cf 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -324,7 +324,7 @@ void ReflectionTest(uint8_t *flatbuf, size_t length) { TEST_EQ(hp_int64, 80); auto hp_double = flatbuffers::GetAnyFieldF(root, hp_field); TEST_EQ(hp_double, 80.0); - auto hp_string = flatbuffers::GetAnyFieldS(root, hp_field, schema); + auto hp_string = flatbuffers::GetAnyFieldS(root, hp_field, &schema); TEST_EQ_STR(hp_string.c_str(), "80"); // We can also modify it.