1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <folly/dynamic.h>
18
19 #include <numeric>
20
21 #include <glog/logging.h>
22
23 #include <folly/Format.h>
24 #include <folly/container/Enumerate.h>
25 #include <folly/hash/Hash.h>
26 #include <folly/lang/Assume.h>
27 #include <folly/lang/Exception.h>
28
29 namespace folly {
30
31 //////////////////////////////////////////////////////////////////////
32
33 #define FOLLY_DYNAMIC_DEF_TYPEINFO(T, str) \
34 const char* const dynamic::TypeInfo<T>::name = str; \
35 //
36
37 FOLLY_DYNAMIC_DEF_TYPEINFO(std::nullptr_t, "null")
38 FOLLY_DYNAMIC_DEF_TYPEINFO(bool, "boolean")
39 FOLLY_DYNAMIC_DEF_TYPEINFO(std::string, "string")
40 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array, "array")
41 FOLLY_DYNAMIC_DEF_TYPEINFO(double, "double")
42 FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t, "int64")
43 FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl, "object")
44
45 #undef FOLLY_DYNAMIC_DEF_TYPEINFO
46
typeName() const47 const char* dynamic::typeName() const {
48 return typeName(type_);
49 }
50
TypeError(const std::string & expected,dynamic::Type actual)51 TypeError::TypeError(const std::string& expected, dynamic::Type actual)
52 : std::runtime_error(sformat(
53 "TypeError: expected dynamic type `{}', but had type `{}'",
54 expected,
55 dynamic::typeName(actual))) {}
56
TypeError(const std::string & expected,dynamic::Type actual1,dynamic::Type actual2)57 TypeError::TypeError(
58 const std::string& expected, dynamic::Type actual1, dynamic::Type actual2)
59 : std::runtime_error(sformat(
60 "TypeError: expected dynamic types `{}, but had types `{}' and `{}'",
61 expected,
62 dynamic::typeName(actual1),
63 dynamic::typeName(actual2))) {}
64
65 // This is a higher-order preprocessor macro to aid going from runtime
66 // types to the compile time type system.
67 #define FB_DYNAMIC_APPLY(type, apply) \
68 do { \
69 switch ((type)) { \
70 case dynamic::NULLT: \
71 apply(std::nullptr_t); \
72 break; \
73 case dynamic::ARRAY: \
74 apply(dynamic::Array); \
75 break; \
76 case dynamic::BOOL: \
77 apply(bool); \
78 break; \
79 case dynamic::DOUBLE: \
80 apply(double); \
81 break; \
82 case dynamic::INT64: \
83 apply(int64_t); \
84 break; \
85 case dynamic::OBJECT: \
86 apply(dynamic::ObjectImpl); \
87 break; \
88 case dynamic::STRING: \
89 apply(std::string); \
90 break; \
91 default: \
92 CHECK(0); \
93 abort(); \
94 } \
95 } while (0)
96
operator <(dynamic const & a,dynamic const & b)97 bool operator<(dynamic const& a, dynamic const& b) {
98 constexpr auto obj = dynamic::OBJECT;
99 if (UNLIKELY(a.type_ == obj || b.type_ == obj)) {
100 auto type = a.type_ == obj ? b.type_ : b.type_ == obj ? a.type_ : obj;
101 throw_exception<TypeError>("object", type);
102 }
103 if (a.type_ != b.type_) {
104 return a.type_ < b.type_;
105 }
106
107 #define FB_X(T) \
108 return dynamic::CompareOp<T>::comp(*a.getAddress<T>(), *b.getAddress<T>())
109 FB_DYNAMIC_APPLY(a.type_, FB_X);
110 #undef FB_X
111 }
112
operator ==(dynamic const & a,dynamic const & b)113 bool operator==(dynamic const& a, dynamic const& b) {
114 if (a.type() != b.type()) {
115 if (a.isNumber() && b.isNumber()) {
116 auto& integ = a.isInt() ? a : b;
117 auto& doubl = a.isInt() ? b : a;
118 return integ.asInt() == doubl.asDouble();
119 }
120 return false;
121 }
122
123 #define FB_X(T) return *a.getAddress<T>() == *b.getAddress<T>();
124 FB_DYNAMIC_APPLY(a.type_, FB_X);
125 #undef FB_X
126 }
127
operator =(dynamic const & o)128 dynamic& dynamic::operator=(dynamic const& o) {
129 if (&o != this) {
130 if (type_ == o.type_) {
131 #define FB_X(T) *getAddress<T>() = *o.getAddress<T>()
132 FB_DYNAMIC_APPLY(type_, FB_X);
133 #undef FB_X
134 } else {
135 destroy();
136 #define FB_X(T) new (getAddress<T>()) T(*o.getAddress<T>())
137 FB_DYNAMIC_APPLY(o.type_, FB_X);
138 #undef FB_X
139 type_ = o.type_;
140 }
141 }
142 return *this;
143 }
144
operator =(dynamic && o)145 dynamic& dynamic::operator=(dynamic&& o) noexcept {
146 if (&o != this) {
147 if (type_ == o.type_) {
148 #define FB_X(T) *getAddress<T>() = std::move(*o.getAddress<T>())
149 FB_DYNAMIC_APPLY(type_, FB_X);
150 #undef FB_X
151 } else {
152 destroy();
153 #define FB_X(T) new (getAddress<T>()) T(std::move(*o.getAddress<T>()))
154 FB_DYNAMIC_APPLY(o.type_, FB_X);
155 #undef FB_X
156 type_ = o.type_;
157 }
158 }
159 return *this;
160 }
161
atImpl(dynamic const & idx) const162 dynamic const& dynamic::atImpl(dynamic const& idx) const& {
163 if (auto* parray = get_nothrow<Array>()) {
164 if (!idx.isInt()) {
165 throw_exception<TypeError>("int64", idx.type());
166 }
167 if (idx < 0 || idx >= parray->size()) {
168 throw_exception<std::out_of_range>("out of range in dynamic array");
169 }
170 return (*parray)[size_t(idx.asInt())];
171 } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
172 auto it = pobject->find(idx);
173 if (it == pobject->end()) {
174 throw_exception<std::out_of_range>(
175 sformat("couldn't find key {} in dynamic object", idx.asString()));
176 }
177 return it->second;
178 } else {
179 throw_exception<TypeError>("object/array", type());
180 }
181 }
182
at(StringPiece idx) const183 dynamic const& dynamic::at(StringPiece idx) const& {
184 auto* pobject = get_nothrow<ObjectImpl>();
185 if (!pobject) {
186 throw_exception<TypeError>("object", type());
187 }
188 auto it = pobject->find(idx);
189 if (it == pobject->end()) {
190 throw_exception<std::out_of_range>(
191 sformat("couldn't find key {} in dynamic object", idx));
192 }
193 return it->second;
194 }
195
operator [](StringPiece k)196 dynamic& dynamic::operator[](StringPiece k) & {
197 auto& obj = get<ObjectImpl>();
198 auto ret = obj.emplace(k, nullptr);
199 return ret.first->second;
200 }
201
getDefault(StringPiece k,const dynamic & v) const202 dynamic dynamic::getDefault(StringPiece k, const dynamic& v) const& {
203 auto& obj = get<ObjectImpl>();
204 auto it = obj.find(k);
205 return it == obj.end() ? v : it->second;
206 }
207
getDefault(StringPiece k,dynamic && v) const208 dynamic dynamic::getDefault(StringPiece k, dynamic&& v) const& {
209 auto& obj = get<ObjectImpl>();
210 auto it = obj.find(k);
211 // Avoid clang bug with ternary
212 if (it == obj.end()) {
213 return std::move(v);
214 } else {
215 return it->second;
216 }
217 }
218
getDefault(StringPiece k,const dynamic & v)219 dynamic dynamic::getDefault(StringPiece k, const dynamic& v) && {
220 auto& obj = get<ObjectImpl>();
221 auto it = obj.find(k);
222 // Avoid clang bug with ternary
223 if (it == obj.end()) {
224 return v;
225 } else {
226 return std::move(it->second);
227 }
228 }
229
getDefault(StringPiece k,dynamic && v)230 dynamic dynamic::getDefault(StringPiece k, dynamic&& v) && {
231 auto& obj = get<ObjectImpl>();
232 auto it = obj.find(k);
233 return std::move(it == obj.end() ? v : it->second);
234 }
235
get_ptrImpl(dynamic const & idx) const236 const dynamic* dynamic::get_ptrImpl(dynamic const& idx) const& {
237 if (auto* parray = get_nothrow<Array>()) {
238 if (!idx.isInt()) {
239 throw_exception<TypeError>("int64", idx.type());
240 }
241 if (idx < 0 || idx >= parray->size()) {
242 return nullptr;
243 }
244 return &(*parray)[size_t(idx.asInt())];
245 } else if (auto* pobject = get_nothrow<ObjectImpl>()) {
246 auto it = pobject->find(idx);
247 if (it == pobject->end()) {
248 return nullptr;
249 }
250 return &it->second;
251 } else {
252 throw_exception<TypeError>("object/array", type());
253 }
254 }
255
get_ptr(StringPiece idx) const256 const dynamic* dynamic::get_ptr(StringPiece idx) const& {
257 auto* pobject = get_nothrow<ObjectImpl>();
258 if (!pobject) {
259 throw_exception<TypeError>("object", type());
260 }
261 auto it = pobject->find(idx);
262 if (it == pobject->end()) {
263 return nullptr;
264 }
265 return &it->second;
266 }
267
size() const268 std::size_t dynamic::size() const {
269 if (auto* ar = get_nothrow<Array>()) {
270 return ar->size();
271 }
272 if (auto* obj = get_nothrow<ObjectImpl>()) {
273 return obj->size();
274 }
275 if (auto* str = get_nothrow<std::string>()) {
276 return str->size();
277 }
278 throw_exception<TypeError>("array/object/string", type());
279 }
280
erase(const_iterator first,const_iterator last)281 dynamic::iterator dynamic::erase(const_iterator first, const_iterator last) {
282 auto& arr = get<Array>();
283 return get<Array>().erase(
284 arr.begin() + (first - arr.begin()), arr.begin() + (last - arr.begin()));
285 }
286
hash() const287 std::size_t dynamic::hash() const {
288 switch (type()) {
289 case NULLT:
290 return 0xBAAAAAAD;
291 case OBJECT: {
292 // Accumulate using addition instead of using hash_range (as in the ARRAY
293 // case), as we need a commutative hash operation since unordered_map's
294 // iteration order is unspecified.
295 auto h = std::hash<std::pair<dynamic const, dynamic>>{};
296 return std::accumulate(
297 items().begin(),
298 items().end(),
299 size_t{0x0B1EC7},
300 [&](auto acc, auto const& item) { return acc + h(item); });
301 }
302 case ARRAY:
303 return static_cast<std::size_t>(folly::hash::hash_range(begin(), end()));
304 case INT64:
305 return std::hash<int64_t>()(getInt());
306 case DOUBLE:
307 return std::hash<double>()(getDouble());
308 case BOOL:
309 return std::hash<bool>()(getBool());
310 case STRING:
311 // keep consistent with detail::DynamicHasher
312 return Hash()(getString());
313 }
314 assume_unreachable();
315 }
316
typeName(Type t)317 char const* dynamic::typeName(Type t) {
318 #define FB_X(T) return TypeInfo<T>::name
319 FB_DYNAMIC_APPLY(t, FB_X);
320 #undef FB_X
321 }
322
destroy()323 void dynamic::destroy() noexcept {
324 // This short-circuit speeds up some microbenchmarks.
325 if (type_ == NULLT) {
326 return;
327 }
328
329 #define FB_X(T) detail::Destroy::destroy(getAddress<T>())
330 FB_DYNAMIC_APPLY(type_, FB_X);
331 #undef FB_X
332 type_ = NULLT;
333 u_.nul = nullptr;
334 }
335
merge_diff(const dynamic & source,const dynamic & target)336 dynamic dynamic::merge_diff(const dynamic& source, const dynamic& target) {
337 if (!source.isObject() || !target.isObject()) {
338 return target;
339 }
340
341 dynamic diff = object;
342
343 // added/modified keys
344 for (const auto& pair : target.items()) {
345 auto it = source.find(pair.first);
346 if (it == source.items().end()) {
347 diff[pair.first] = pair.second;
348 } else {
349 const auto& ssource = it->second;
350 const auto& starget = pair.second;
351 if (ssource.isObject() && starget.isObject()) {
352 auto sdiff = merge_diff(ssource, starget);
353 if (!sdiff.empty()) {
354 diff[pair.first] = std::move(sdiff);
355 }
356 } else if (ssource != starget) {
357 diff[pair.first] = merge_diff(ssource, starget);
358 }
359 }
360 }
361
362 // removed keys
363 for (const auto& pair : source.items()) {
364 auto it = target.find(pair.first);
365 if (it == target.items().end()) {
366 diff[pair.first] = nullptr;
367 }
368 }
369
370 return diff;
371 }
372
373 // clang-format off
374 dynamic::resolved_json_pointer<dynamic const>
375 // clang-format on
try_get_ptr(json_pointer const & jsonPtr) const376 dynamic::try_get_ptr(json_pointer const& jsonPtr) const& {
377 using err_code = json_pointer_resolution_error_code;
378 using error = json_pointer_resolution_error<dynamic const>;
379
380 auto const& tokens = jsonPtr.tokens();
381 if (tokens.empty()) {
382 return json_pointer_resolved_value<dynamic const>{
383 nullptr, this, {nullptr, nullptr}, 0};
384 }
385
386 dynamic const* curr = this;
387 dynamic const* prev = nullptr;
388
389 size_t curr_idx{0};
390 StringPiece curr_key{};
391
392 for (auto it : enumerate(tokens)) {
393 // hit bottom but pointer not exhausted yet
394 if (!curr) {
395 return makeUnexpected(
396 error{err_code::json_pointer_out_of_bounds, it.index, prev});
397 }
398 prev = curr;
399 // handle lookup in array
400 if (auto const* parray = curr->get_nothrow<dynamic::Array>()) {
401 if (it->size() > 1 && it->at(0) == '0') {
402 return makeUnexpected(
403 error{err_code::index_has_leading_zero, it.index, prev});
404 }
405 // if last element of pointer is '-', this is an append operation
406 if (it->size() == 1 && it->at(0) == '-') {
407 // was '-' the last token in pointer?
408 if (it.index == tokens.size() - 1) {
409 return makeUnexpected(
410 error{err_code::append_requested, it.index, prev});
411 }
412 // Cannot resolve past '-' in an array
413 curr = nullptr;
414 continue;
415 }
416 auto const idx = tryTo<size_t>(*it);
417 if (!idx.hasValue()) {
418 return makeUnexpected(
419 error{err_code::index_not_numeric, it.index, prev});
420 }
421 if (idx.value() < parray->size()) {
422 curr = &(*parray)[idx.value()];
423 curr_idx = idx.value();
424 } else {
425 return makeUnexpected(
426 error{err_code::index_out_of_bounds, it.index, prev});
427 }
428 continue;
429 }
430 // handle lookup in object
431 if (auto const* pobject = curr->get_nothrow<dynamic::ObjectImpl>()) {
432 auto const sub_it = pobject->find(*it);
433 if (sub_it == pobject->end()) {
434 return makeUnexpected(error{err_code::key_not_found, it.index, prev});
435 }
436 curr = &sub_it->second;
437 curr_key = *it;
438 continue;
439 }
440 return makeUnexpected(
441 error{err_code::element_not_object_or_array, it.index, prev});
442 }
443 return json_pointer_resolved_value<dynamic const>{
444 prev, curr, curr_key, curr_idx};
445 }
446
get_ptr(json_pointer const & jsonPtr) const447 const dynamic* dynamic::get_ptr(json_pointer const& jsonPtr) const& {
448 using err_code = json_pointer_resolution_error_code;
449
450 auto ret = try_get_ptr(jsonPtr);
451 if (ret.hasValue()) {
452 return ret.value().value;
453 }
454
455 auto const ctx = ret.error().context;
456 auto const objType = ctx ? ctx->type() : Type::NULLT;
457
458 switch (ret.error().error_code) {
459 case err_code::key_not_found:
460 return nullptr;
461 case err_code::index_out_of_bounds:
462 return nullptr;
463 case err_code::append_requested:
464 return nullptr;
465 case err_code::index_not_numeric:
466 throw std::invalid_argument("array index is not numeric");
467 case err_code::index_has_leading_zero:
468 throw std::invalid_argument(
469 "leading zero not allowed when indexing arrays");
470 case err_code::element_not_object_or_array:
471 throw_exception<TypeError>("object/array", objType);
472 case err_code::json_pointer_out_of_bounds:
473 return nullptr;
474 case err_code::other:
475 default:
476 return nullptr;
477 }
478 assume_unreachable();
479 }
480
481 //////////////////////////////////////////////////////////////////////
482
483 } // namespace folly
484