• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "reflection_test.h"
2 
3 #include "tests/arrays_test_generated.h"
4 #include "flatbuffers/minireflect.h"
5 #include "flatbuffers/reflection.h"
6 #include "flatbuffers/reflection_generated.h"
7 #include "flatbuffers/verifier.h"
8 #include "monster_test.h"
9 #include "monster_test_generated.h"
10 #include "test_assert.h"
11 
12 namespace flatbuffers {
13 namespace tests {
14 
15 using namespace MyGame::Example;
16 
ReflectionTest(const std::string & tests_data_path,uint8_t * flatbuf,size_t length)17 void ReflectionTest(const std::string &tests_data_path, uint8_t *flatbuf,
18                     size_t length) {
19   // Load a binary schema.
20   std::string bfbsfile;
21   TEST_EQ(flatbuffers::LoadFile((tests_data_path + "monster_test.bfbs").c_str(),
22                                 true, &bfbsfile),
23           true);
24 
25   // Verify it, just in case:
26   flatbuffers::Verifier verifier(
27       reinterpret_cast<const uint8_t *>(bfbsfile.c_str()), bfbsfile.length());
28   TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
29 
30   // Make sure the schema is what we expect it to be.
31   auto &schema = *reflection::GetSchema(bfbsfile.c_str());
32   auto root_table = schema.root_table();
33 
34   // Check the declaration files.
35   TEST_EQ_STR(root_table->name()->c_str(), "MyGame.Example.Monster");
36   TEST_EQ_STR(root_table->declaration_file()->c_str(), "//monster_test.fbs");
37   TEST_EQ_STR(
38       schema.objects()->LookupByKey("TableA")->declaration_file()->c_str(),
39       "//include_test/include_test1.fbs");
40   TEST_EQ_STR(schema.objects()
41                   ->LookupByKey("MyGame.OtherNameSpace.Unused")
42                   ->declaration_file()
43                   ->c_str(),
44               "//include_test/sub/include_test2.fbs");
45   TEST_EQ_STR(schema.enums()
46                   ->LookupByKey("MyGame.OtherNameSpace.FromInclude")
47                   ->declaration_file()
48                   ->c_str(),
49               "//include_test/sub/include_test2.fbs");
50 
51   // Check scheam filenames and their includes.
52   TEST_EQ(schema.fbs_files()->size(), 3);
53 
54   const auto fbs0 = schema.fbs_files()->Get(0);
55   TEST_EQ_STR(fbs0->filename()->c_str(), "//include_test/include_test1.fbs");
56   const auto fbs0_includes = fbs0->included_filenames();
57   TEST_EQ(fbs0_includes->size(), 2);
58 
59   // TODO(caspern): Should we force or disallow inclusion of self?
60   TEST_EQ_STR(fbs0_includes->Get(0)->c_str(),
61               "//include_test/include_test1.fbs");
62   TEST_EQ_STR(fbs0_includes->Get(1)->c_str(),
63               "//include_test/sub/include_test2.fbs");
64 
65   const auto fbs1 = schema.fbs_files()->Get(1);
66   TEST_EQ_STR(fbs1->filename()->c_str(),
67               "//include_test/sub/include_test2.fbs");
68   const auto fbs1_includes = fbs1->included_filenames();
69   TEST_EQ(fbs1_includes->size(), 2);
70   TEST_EQ_STR(fbs1_includes->Get(0)->c_str(),
71               "//include_test/include_test1.fbs");
72   TEST_EQ_STR(fbs1_includes->Get(1)->c_str(),
73               "//include_test/sub/include_test2.fbs");
74 
75   const auto fbs2 = schema.fbs_files()->Get(2);
76   TEST_EQ_STR(fbs2->filename()->c_str(), "//monster_test.fbs");
77   const auto fbs2_includes = fbs2->included_filenames();
78   TEST_EQ(fbs2_includes->size(), 1);
79   TEST_EQ_STR(fbs2_includes->Get(0)->c_str(),
80               "//include_test/include_test1.fbs");
81 
82   // Check Root table fields
83   auto fields = root_table->fields();
84   auto hp_field_ptr = fields->LookupByKey("hp");
85   TEST_NOTNULL(hp_field_ptr);
86   auto &hp_field = *hp_field_ptr;
87   TEST_EQ_STR(hp_field.name()->c_str(), "hp");
88   TEST_EQ(hp_field.id(), 2);
89   TEST_EQ(hp_field.type()->base_type(), reflection::Short);
90 
91   auto friendly_field_ptr = fields->LookupByKey("friendly");
92   TEST_NOTNULL(friendly_field_ptr);
93   TEST_NOTNULL(friendly_field_ptr->attributes());
94   TEST_NOTNULL(friendly_field_ptr->attributes()->LookupByKey("priority"));
95 
96   // Make sure the table index is what we expect it to be.
97   auto pos_field_ptr = fields->LookupByKey("pos");
98   TEST_NOTNULL(pos_field_ptr);
99   TEST_EQ(pos_field_ptr->type()->base_type(), reflection::Obj);
100   auto pos_table_ptr = schema.objects()->Get(pos_field_ptr->type()->index());
101   TEST_NOTNULL(pos_table_ptr);
102   TEST_EQ_STR(pos_table_ptr->name()->c_str(), "MyGame.Example.Vec3");
103 
104   // Test nullability of fields: hp is a 0-default scalar, pos is a struct =>
105   // optional, and name is a required string => not optional.
106   TEST_EQ(hp_field.optional(), false);
107   TEST_EQ(pos_field_ptr->optional(), true);
108   TEST_EQ(fields->LookupByKey("name")->optional(), false);
109 
110   // Now use it to dynamically access a buffer.
111   auto &root = *flatbuffers::GetAnyRoot(flatbuf);
112 
113   // Verify the buffer first using reflection based verification
114   TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
115           true);
116 
117   auto hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
118   TEST_EQ(hp, 80);
119 
120   // Rather than needing to know the type, we can also get the value of
121   // any field as an int64_t/double/string, regardless of what it actually is.
122   auto hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
123   TEST_EQ(hp_int64, 80);
124   auto hp_double = flatbuffers::GetAnyFieldF(root, hp_field);
125   TEST_EQ(hp_double, 80.0);
126   auto hp_string = flatbuffers::GetAnyFieldS(root, hp_field, &schema);
127   TEST_EQ_STR(hp_string.c_str(), "80");
128 
129   // Get struct field through reflection
130   auto pos_struct = flatbuffers::GetFieldStruct(root, *pos_field_ptr);
131   TEST_NOTNULL(pos_struct);
132   TEST_EQ(flatbuffers::GetAnyFieldF(*pos_struct,
133                                     *pos_table_ptr->fields()->LookupByKey("z")),
134           3.0f);
135 
136   auto test3_field = pos_table_ptr->fields()->LookupByKey("test3");
137   auto test3_struct = flatbuffers::GetFieldStruct(*pos_struct, *test3_field);
138   TEST_NOTNULL(test3_struct);
139   auto test3_object = schema.objects()->Get(test3_field->type()->index());
140 
141   TEST_EQ(flatbuffers::GetAnyFieldF(*test3_struct,
142                                     *test3_object->fields()->LookupByKey("a")),
143           10);
144 
145   // We can also modify it.
146   flatbuffers::SetField<uint16_t>(&root, hp_field, 200);
147   hp = flatbuffers::GetFieldI<uint16_t>(root, hp_field);
148   TEST_EQ(hp, 200);
149 
150   // We can also set fields generically:
151   flatbuffers::SetAnyFieldI(&root, hp_field, 300);
152   hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
153   TEST_EQ(hp_int64, 300);
154   flatbuffers::SetAnyFieldF(&root, hp_field, 300.5);
155   hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
156   TEST_EQ(hp_int64, 300);
157   flatbuffers::SetAnyFieldS(&root, hp_field, "300");
158   hp_int64 = flatbuffers::GetAnyFieldI(root, hp_field);
159   TEST_EQ(hp_int64, 300);
160 
161   // Test buffer is valid after the modifications
162   TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), flatbuf, length),
163           true);
164 
165   // Reset it, for further tests.
166   flatbuffers::SetField<uint16_t>(&root, hp_field, 80);
167 
168   // More advanced functionality: changing the size of items in-line!
169   // First we put the FlatBuffer inside an std::vector.
170   std::vector<uint8_t> resizingbuf(flatbuf, flatbuf + length);
171   // Find the field we want to modify.
172   auto &name_field = *fields->LookupByKey("name");
173   // Get the root.
174   // This time we wrap the result from GetAnyRoot in a smartpointer that
175   // will keep rroot valid as resizingbuf resizes.
176   auto rroot = flatbuffers::piv(flatbuffers::GetAnyRoot(resizingbuf.data()),
177                                 resizingbuf);
178   SetString(schema, "totally new string", GetFieldS(**rroot, name_field),
179             &resizingbuf);
180   // Here resizingbuf has changed, but rroot is still valid.
181   TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "totally new string");
182   // Now lets extend a vector by 100 elements (10 -> 110).
183   auto &inventory_field = *fields->LookupByKey("inventory");
184   auto rinventory = flatbuffers::piv(
185       flatbuffers::GetFieldV<uint8_t>(**rroot, inventory_field), resizingbuf);
186   flatbuffers::ResizeVector<uint8_t>(schema, 110, 50, *rinventory,
187                                      &resizingbuf);
188   // rinventory still valid, so lets read from it.
189   TEST_EQ(rinventory->Get(10), 50);
190 
191   // For reflection uses not covered already, there is a more powerful way:
192   // we can simply generate whatever object we want to add/modify in a
193   // FlatBuffer of its own, then add that to an existing FlatBuffer:
194   // As an example, let's add a string to an array of strings.
195   // First, find our field:
196   auto &testarrayofstring_field = *fields->LookupByKey("testarrayofstring");
197   // Find the vector value:
198   auto rtestarrayofstring = flatbuffers::piv(
199       flatbuffers::GetFieldV<flatbuffers::Offset<flatbuffers::String>>(
200           **rroot, testarrayofstring_field),
201       resizingbuf);
202   // It's a vector of 2 strings, to which we add one more, initialized to
203   // offset 0.
204   flatbuffers::ResizeVector<flatbuffers::Offset<flatbuffers::String>>(
205       schema, 3, 0, *rtestarrayofstring, &resizingbuf);
206   // Here we just create a buffer that contans a single string, but this
207   // could also be any complex set of tables and other values.
208   flatbuffers::FlatBufferBuilder stringfbb;
209   stringfbb.Finish(stringfbb.CreateString("hank"));
210   // Add the contents of it to our existing FlatBuffer.
211   // We do this last, so the pointer doesn't get invalidated (since it is
212   // at the end of the buffer):
213   auto string_ptr = flatbuffers::AddFlatBuffer(
214       resizingbuf, stringfbb.GetBufferPointer(), stringfbb.GetSize());
215   // Finally, set the new value in the vector.
216   rtestarrayofstring->MutateOffset(2, string_ptr);
217   TEST_EQ_STR(rtestarrayofstring->Get(0)->c_str(), "bob");
218   TEST_EQ_STR(rtestarrayofstring->Get(2)->c_str(), "hank");
219   // Test integrity of all resize operations above.
220   flatbuffers::Verifier resize_verifier(
221       reinterpret_cast<const uint8_t *>(resizingbuf.data()),
222       resizingbuf.size());
223   TEST_EQ(VerifyMonsterBuffer(resize_verifier), true);
224 
225   // Test buffer is valid using reflection as well
226   TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(), resizingbuf.data(),
227                               resizingbuf.size()),
228           true);
229 
230   // As an additional test, also set it on the name field.
231   // Note: unlike the name change above, this just overwrites the offset,
232   // rather than changing the string in-place.
233   SetFieldT(*rroot, name_field, string_ptr);
234   TEST_EQ_STR(GetFieldS(**rroot, name_field)->c_str(), "hank");
235 
236   // Using reflection, rather than mutating binary FlatBuffers, we can also copy
237   // tables and other things out of other FlatBuffers into a FlatBufferBuilder,
238   // either part or whole.
239   flatbuffers::FlatBufferBuilder fbb;
240   auto root_offset = flatbuffers::CopyTable(
241       fbb, schema, *root_table, *flatbuffers::GetAnyRoot(flatbuf), true);
242   fbb.Finish(root_offset, MonsterIdentifier());
243   // Test that it was copied correctly:
244   AccessFlatBufferTest(fbb.GetBufferPointer(), fbb.GetSize());
245 
246   // Test buffer is valid using reflection as well
247   TEST_EQ(flatbuffers::Verify(schema, *schema.root_table(),
248                               fbb.GetBufferPointer(), fbb.GetSize()),
249           true);
250 }
251 
MiniReflectFlatBuffersTest(uint8_t * flatbuf)252 void MiniReflectFlatBuffersTest(uint8_t *flatbuf) {
253   auto s =
254       flatbuffers::FlatBufferToString(flatbuf, Monster::MiniReflectTypeTable());
255   TEST_EQ_STR(
256       s.c_str(),
257       "{ "
258       "pos: { x: 1.0, y: 2.0, z: 3.0, test1: 0.0, test2: Red, test3: "
259       "{ a: 10, b: 20 } }, "
260       "hp: 80, "
261       "name: \"MyMonster\", "
262       "inventory: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], "
263       "test_type: Monster, "
264       "test: { name: \"Fred\" }, "
265       "test4: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
266       "testarrayofstring: [ \"bob\", \"fred\", \"bob\", \"fred\" ], "
267       "testarrayoftables: [ { hp: 1000, name: \"Barney\" }, { name: \"Fred\" "
268       "}, "
269       "{ name: \"Wilma\" } ], "
270       // TODO(wvo): should really print this nested buffer correctly.
271       "testnestedflatbuffer: [ 124, 0, 0, 0, 77, 79, 78, 83, 0, 0, 114, 0, 16, "
272       "0, 0, 0, 4, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
273       "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
274       "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
275       "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "
276       "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 114, 0, 0, 0, 0, 0, 0, 0, "
277       "8, 0, 0, 0, 0, 0, 192, 127, 13, 0, 0, 0, 78, 101, 115, 116, 101, 100, "
278       "77, 111, 110, 115, 116, 101, 114, 0, 0, 0 ], "
279       "testarrayofstring2: [ \"jane\", \"mary\" ], "
280       "testarrayofsortedstruct: [ { id: 0, distance: 0 }, "
281       "{ id: 2, distance: 20 }, { id: 3, distance: 30 }, "
282       "{ id: 4, distance: 40 } ], "
283       "flex: [ 210, 4, 5, 2 ], "
284       "test5: [ { a: 10, b: 20 }, { a: 30, b: 40 } ], "
285       "vector_of_enums: [ Blue, Green ], "
286       "scalar_key_sorted_tables: [ { id: \"miss\" } ], "
287       "nan_default: nan "
288       "}");
289 
290   Test test(16, 32);
291   Vec3 vec(1, 2, 3, 1.5, Color_Red, test);
292   flatbuffers::FlatBufferBuilder vec_builder;
293   vec_builder.Finish(vec_builder.CreateStruct(vec));
294   auto vec_buffer = vec_builder.Release();
295   auto vec_str = flatbuffers::FlatBufferToString(vec_buffer.data(),
296                                                  Vec3::MiniReflectTypeTable());
297   TEST_EQ_STR(vec_str.c_str(),
298               "{ x: 1.0, y: 2.0, z: 3.0, test1: 1.5, test2: Red, test3: { a: "
299               "16, b: 32 } }");
300 }
301 
MiniReflectFixedLengthArrayTest()302 void MiniReflectFixedLengthArrayTest() {
303   // VS10 does not support typed enums, exclude from tests
304 #if !defined(_MSC_VER) || _MSC_VER >= 1700
305   flatbuffers::FlatBufferBuilder fbb;
306   MyGame::Example::ArrayStruct aStruct(2, 12, 1);
307   auto aTable = MyGame::Example::CreateArrayTable(fbb, &aStruct);
308   fbb.Finish(aTable);
309 
310   auto flatbuf = fbb.Release();
311   auto s = flatbuffers::FlatBufferToString(
312       flatbuf.data(), MyGame::Example::ArrayTableTypeTable());
313   TEST_EQ_STR(
314       "{ "
315       "a: { a: 2.0, "
316       "b: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "
317       "c: 12, "
318       "d: [ { a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] }, "
319       "{ a: [ 0, 0 ], b: A, c: [ A, A ], d: [ 0, 0 ] } ], "
320       "e: 1, f: [ 0, 0 ] } "
321       "}",
322       s.c_str());
323 #endif
324 }
325 
326 }  // namespace tests
327 }  // namespace flatbuffers
328