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