blob: 1b34f85820e2c183da1a36a31846fd30f32f9a04 [file] [log] [blame]
Austin Schuh2dd86a92022-09-14 21:19:23 -07001#include "flexbuffers_test.h"
2
3#include "flatbuffers/flexbuffers.h"
4#include "flatbuffers/idl.h"
5#include "is_quiet_nan.h"
6#include "test_assert.h"
7
8namespace flatbuffers {
9namespace tests {
10
11// Shortcuts for the infinity.
12static const auto infinity_d = std::numeric_limits<double>::infinity();
13
14void FlexBuffersTest() {
15 flexbuffers::Builder slb(512,
16 flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS);
17
18 // Write the equivalent of:
19 // { vec: [ -100, "Fred", 4.0, false ], bar: [ 1, 2, 3 ], bar3: [ 1, 2, 3 ],
20 // foo: 100, bool: true, mymap: { foo: "Fred" } }
21
22 // It's possible to do this without std::function support as well.
23 slb.Map([&]() {
24 slb.Vector("vec", [&]() {
25 slb += -100; // Equivalent to slb.Add(-100) or slb.Int(-100);
26 slb += "Fred";
27 slb.IndirectFloat(4.0f);
28 auto i_f = slb.LastValue();
29 uint8_t blob[] = { 77 };
30 slb.Blob(blob, 1);
31 slb += false;
32 slb.ReuseValue(i_f);
33 });
34 int ints[] = { 1, 2, 3 };
35 slb.Vector("bar", ints, 3);
36 slb.FixedTypedVector("bar3", ints, 3);
37 bool bools[] = { true, false, true, false };
38 slb.Vector("bools", bools, 4);
39 slb.Bool("bool", true);
40 slb.Double("foo", 100);
41 slb.Map("mymap", [&]() {
42 slb.String("foo", "Fred"); // Testing key and string reuse.
43 });
44 });
45 slb.Finish();
46
47 // clang-format off
48 #ifdef FLATBUFFERS_TEST_VERBOSE
49 for (size_t i = 0; i < slb.GetBuffer().size(); i++)
50 printf("%d ", slb.GetBuffer().data()[i]);
51 printf("\n");
52 #endif
53 // clang-format on
54
55 std::vector<uint8_t> reuse_tracker;
56 TEST_EQ(flexbuffers::VerifyBuffer(slb.GetBuffer().data(),
57 slb.GetBuffer().size(), &reuse_tracker),
58 true);
59
60 auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap();
61 TEST_EQ(map.size(), 7);
62 auto vec = map["vec"].AsVector();
63 TEST_EQ(vec.size(), 6);
64 TEST_EQ(vec[0].AsInt64(), -100);
65 TEST_EQ_STR(vec[1].AsString().c_str(), "Fred");
66 TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed.
67 TEST_EQ(vec[2].AsDouble(), 4.0);
68 TEST_EQ(vec[2].AsString().IsTheEmptyString(), true); // Wrong Type.
69 TEST_EQ_STR(vec[2].AsString().c_str(), ""); // This still works though.
70 TEST_EQ_STR(vec[2].ToString().c_str(), "4.0"); // Or have it converted.
71 // Few tests for templated version of As.
72 TEST_EQ(vec[0].As<int64_t>(), -100);
73 TEST_EQ_STR(vec[1].As<std::string>().c_str(), "Fred");
74 TEST_EQ(vec[1].As<int64_t>(), 0); // Number parsing failed.
75 TEST_EQ(vec[2].As<double>(), 4.0);
76 // Test that the blob can be accessed.
77 TEST_EQ(vec[3].IsBlob(), true);
78 auto blob = vec[3].AsBlob();
79 TEST_EQ(blob.size(), 1);
80 TEST_EQ(blob.data()[0], 77);
81 TEST_EQ(vec[4].IsBool(), true); // Check if type is a bool
82 TEST_EQ(vec[4].AsBool(), false); // Check if value is false
83 TEST_EQ(vec[5].AsDouble(), 4.0); // This is shared with vec[2] !
84 auto tvec = map["bar"].AsTypedVector();
85 TEST_EQ(tvec.size(), 3);
86 TEST_EQ(tvec[2].AsInt8(), 3);
87 auto tvec3 = map["bar3"].AsFixedTypedVector();
88 TEST_EQ(tvec3.size(), 3);
89 TEST_EQ(tvec3[2].AsInt8(), 3);
90 TEST_EQ(map["bool"].AsBool(), true);
91 auto tvecb = map["bools"].AsTypedVector();
92 TEST_EQ(tvecb.ElementType(), flexbuffers::FBT_BOOL);
93 TEST_EQ(map["foo"].AsUInt8(), 100);
94 TEST_EQ(map["unknown"].IsNull(), true);
95 auto mymap = map["mymap"].AsMap();
96 // These should be equal by pointer equality, since key and value are shared.
97 TEST_EQ(mymap.Keys()[0].AsKey(), map.Keys()[4].AsKey());
98 TEST_EQ(mymap.Values()[0].AsString().c_str(), vec[1].AsString().c_str());
99 // We can mutate values in the buffer.
100 TEST_EQ(vec[0].MutateInt(-99), true);
101 TEST_EQ(vec[0].AsInt64(), -99);
102 TEST_EQ(vec[1].MutateString("John"), true); // Size must match.
103 TEST_EQ_STR(vec[1].AsString().c_str(), "John");
104 TEST_EQ(vec[1].MutateString("Alfred"), false); // Too long.
105 TEST_EQ(vec[2].MutateFloat(2.0f), true);
106 TEST_EQ(vec[2].AsFloat(), 2.0f);
107 TEST_EQ(vec[2].MutateFloat(3.14159), false); // Double does not fit in float.
108 TEST_EQ(vec[4].AsBool(), false); // Is false before change
109 TEST_EQ(vec[4].MutateBool(true), true); // Can change a bool
110 TEST_EQ(vec[4].AsBool(), true); // Changed bool is now true
111
112 // Parse from JSON:
113 flatbuffers::Parser parser;
114 slb.Clear();
115 auto jsontest = "{ a: [ 123, 456.0 ], b: \"hello\", c: true, d: false }";
116 TEST_EQ(parser.ParseFlexBuffer(jsontest, nullptr, &slb), true);
117 TEST_EQ(flexbuffers::VerifyBuffer(slb.GetBuffer().data(),
118 slb.GetBuffer().size(), &reuse_tracker),
119 true);
120 auto jroot = flexbuffers::GetRoot(slb.GetBuffer());
121 auto jmap = jroot.AsMap();
122 auto jvec = jmap["a"].AsVector();
123 TEST_EQ(jvec[0].AsInt64(), 123);
124 TEST_EQ(jvec[1].AsDouble(), 456.0);
125 TEST_EQ_STR(jmap["b"].AsString().c_str(), "hello");
126 TEST_EQ(jmap["c"].IsBool(), true); // Parsed correctly to a bool
127 TEST_EQ(jmap["c"].AsBool(), true); // Parsed correctly to true
128 TEST_EQ(jmap["d"].IsBool(), true); // Parsed correctly to a bool
129 TEST_EQ(jmap["d"].AsBool(), false); // Parsed correctly to false
130 // And from FlexBuffer back to JSON:
131 auto jsonback = jroot.ToString();
132 TEST_EQ_STR(jsontest, jsonback.c_str());
133
134 slb.Clear();
135 slb.Vector([&]() {
136 for (int i = 0; i < 130; ++i) slb.Add(static_cast<uint8_t>(255));
137 slb.Vector([&]() {
138 for (int i = 0; i < 130; ++i) slb.Add(static_cast<uint8_t>(255));
139 slb.Vector([] {});
140 });
141 });
142 slb.Finish();
143 TEST_EQ(slb.GetSize(), 664);
144}
145
146void FlexBuffersReuseBugTest() {
147 flexbuffers::Builder slb;
148 slb.Map([&]() {
149 slb.Vector("vec", [&]() {});
150 slb.Bool("bool", true);
151 });
152 slb.Finish();
153 std::vector<uint8_t> reuse_tracker;
154 // This would fail before, since the reuse_tracker would use the address of
155 // the vector reference to check for reuse, but in this case we have an empty
156 // vector, and since the size field is before the pointer, its address is the
157 // same as thing after it, the key "bool".
158 // We fix this by using the address of the size field for tracking reuse.
159 TEST_EQ(flexbuffers::VerifyBuffer(slb.GetBuffer().data(),
160 slb.GetBuffer().size(), &reuse_tracker),
161 true);
162}
163
164void FlexBuffersFloatingPointTest() {
165#if defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0)
166 flexbuffers::Builder slb(512,
167 flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS);
168 // Parse floating-point values from JSON:
169 flatbuffers::Parser parser;
170 slb.Clear();
171 auto jsontest =
172 "{ a: [1.0, nan, inf, infinity, -inf, +inf, -infinity, 8.0] }";
173 TEST_EQ(parser.ParseFlexBuffer(jsontest, nullptr, &slb), true);
174 auto jroot = flexbuffers::GetRoot(slb.GetBuffer());
175 TEST_EQ(flexbuffers::VerifyBuffer(slb.GetBuffer().data(),
176 slb.GetBuffer().size(), nullptr),
177 true);
178 auto jmap = jroot.AsMap();
179 auto jvec = jmap["a"].AsVector();
180 TEST_EQ(8, jvec.size());
181 TEST_EQ(1.0, jvec[0].AsDouble());
182 TEST_ASSERT(is_quiet_nan(jvec[1].AsDouble()));
183 TEST_EQ(infinity_d, jvec[2].AsDouble());
184 TEST_EQ(infinity_d, jvec[3].AsDouble());
185 TEST_EQ(-infinity_d, jvec[4].AsDouble());
186 TEST_EQ(+infinity_d, jvec[5].AsDouble());
187 TEST_EQ(-infinity_d, jvec[6].AsDouble());
188 TEST_EQ(8.0, jvec[7].AsDouble());
189#endif
190}
191
192void FlexBuffersDeprecatedTest() {
193 // FlexBuffers as originally designed had a flaw involving the
194 // FBT_VECTOR_STRING datatype, and this test documents/tests the fix for it.
195 // Discussion: https://github.com/google/flatbuffers/issues/5627
196 flexbuffers::Builder slb;
197 // FBT_VECTOR_* are "typed vectors" where all elements are of the same type.
198 // Problem is, when storing FBT_STRING elements, it relies on that type to
199 // get the bit-width for the size field of the string, which in this case
200 // isn't present, and instead defaults to 8-bit. This means that any strings
201 // stored inside such a vector, when accessed thru the old API that returns
202 // a String reference, will appear to be truncated if the string stored is
203 // actually >=256 bytes.
204 std::string test_data(300, 'A');
205 auto start = slb.StartVector();
206 // This one will have a 16-bit size field.
207 slb.String(test_data);
208 // This one will have an 8-bit size field.
209 slb.String("hello");
210 // We're asking this to be serialized as a typed vector (true), but not
211 // fixed size (false). The type will be FBT_VECTOR_STRING with a bit-width
212 // of whatever the offsets in the vector need, the bit-widths of the strings
213 // are not stored(!) <- the actual design flaw.
214 // Note that even in the fixed code, we continue to serialize the elements of
215 // FBT_VECTOR_STRING as FBT_STRING, since there may be old code out there
216 // reading new data that we want to continue to function.
217 // Thus, FBT_VECTOR_STRING, while deprecated, will always be represented the
218 // same way, the fix lies on the reading side.
219 slb.EndVector(start, true, false);
220 slb.Finish();
221 // Verify because why not.
222 TEST_EQ(flexbuffers::VerifyBuffer(slb.GetBuffer().data(),
223 slb.GetBuffer().size(), nullptr),
224 true);
225 // So now lets read this data back.
226 // For existing data, since we have no way of knowing what the actual
227 // bit-width of the size field of the string is, we are going to ignore this
228 // field, and instead treat these strings as FBT_KEY (null-terminated), so we
229 // can deal with strings of arbitrary length. This of course truncates strings
230 // with embedded nulls, but we think that that is preferrable over truncating
231 // strings >= 256 bytes.
232 auto vec = flexbuffers::GetRoot(slb.GetBuffer()).AsTypedVector();
233 // Even though this was serialized as FBT_VECTOR_STRING, it is read as
234 // FBT_VECTOR_KEY:
235 TEST_EQ(vec.ElementType(), flexbuffers::FBT_KEY);
236 // Access the long string. Previously, this would return a string of size 1,
237 // since it would read the high-byte of the 16-bit length.
238 // This should now correctly test the full 300 bytes, using AsKey():
239 TEST_EQ_STR(vec[0].AsKey(), test_data.c_str());
240 // Old code that called AsString will continue to work, as the String
241 // accessor objects now use a cached size that can come from a key as well.
242 TEST_EQ_STR(vec[0].AsString().c_str(), test_data.c_str());
243 // Short strings work as before:
244 TEST_EQ_STR(vec[1].AsKey(), "hello");
245 TEST_EQ_STR(vec[1].AsString().c_str(), "hello");
246 // So, while existing code and data mostly "just work" with the fixes applied
247 // to AsTypedVector and AsString, what do you do going forward?
248 // Code accessing existing data doesn't necessarily need to change, though
249 // you could consider using AsKey instead of AsString for a) documenting
250 // that you are accessing keys, or b) a speedup if you don't actually use
251 // the string size.
252 // For new data, or data that doesn't need to be backwards compatible,
253 // instead serialize as FBT_VECTOR (call EndVector with typed = false, then
254 // read elements with AsString), or, for maximum compactness, use
255 // FBT_VECTOR_KEY (call slb.Key above instead, read with AsKey or AsString).
256}
257
258void ParseFlexbuffersFromJsonWithNullTest() {
259 // Test nulls are handled appropriately through flexbuffers to exercise other
260 // code paths of ParseSingleValue in the optional scalars change.
261 // TODO(cneo): Json -> Flatbuffers test once some language can generate code
262 // with optional scalars.
263 {
264 char json[] = "{\"opt_field\": 123 }";
265 flatbuffers::Parser parser;
266 flexbuffers::Builder flexbuild;
267 parser.ParseFlexBuffer(json, nullptr, &flexbuild);
268 auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
269 TEST_EQ(root.AsMap()["opt_field"].AsInt64(), 123);
270 }
271 {
272 char json[] = "{\"opt_field\": 123.4 }";
273 flatbuffers::Parser parser;
274 flexbuffers::Builder flexbuild;
275 parser.ParseFlexBuffer(json, nullptr, &flexbuild);
276 auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
277 TEST_EQ(root.AsMap()["opt_field"].AsDouble(), 123.4);
278 }
279 {
280 char json[] = "{\"opt_field\": null }";
281 flatbuffers::Parser parser;
282 flexbuffers::Builder flexbuild;
283 parser.ParseFlexBuffer(json, nullptr, &flexbuild);
284 auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
285 TEST_ASSERT(!root.AsMap().IsTheEmptyMap());
286 TEST_ASSERT(root.AsMap()["opt_field"].IsNull());
287 TEST_EQ(root.ToString(), std::string("{ opt_field: null }"));
288 }
289}
290
291} // namespace tests
292} // namespace flatbuffers