/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 #include #include #include #include #include #include #include namespace folly { ////////////////////////////////////////////////////////////////////// #define FOLLY_DYNAMIC_DEF_TYPEINFO(T, str) \ const char* const dynamic::TypeInfo::name = str; \ // FOLLY_DYNAMIC_DEF_TYPEINFO(std::nullptr_t, "null") FOLLY_DYNAMIC_DEF_TYPEINFO(bool, "boolean") FOLLY_DYNAMIC_DEF_TYPEINFO(std::string, "string") FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::Array, "array") FOLLY_DYNAMIC_DEF_TYPEINFO(double, "double") FOLLY_DYNAMIC_DEF_TYPEINFO(int64_t, "int64") FOLLY_DYNAMIC_DEF_TYPEINFO(dynamic::ObjectImpl, "object") #undef FOLLY_DYNAMIC_DEF_TYPEINFO const char* dynamic::typeName() const { return typeName(type_); } TypeError::TypeError(const std::string& expected, dynamic::Type actual) : std::runtime_error(sformat( "TypeError: expected dynamic type `{}', but had type `{}'", expected, dynamic::typeName(actual))) {} TypeError::TypeError( const std::string& expected, dynamic::Type actual1, dynamic::Type actual2) : std::runtime_error(sformat( "TypeError: expected dynamic types `{}, but had types `{}' and `{}'", expected, dynamic::typeName(actual1), dynamic::typeName(actual2))) {} // This is a higher-order preprocessor macro to aid going from runtime // types to the compile time type system. #define FB_DYNAMIC_APPLY(type, apply) \ do { \ switch ((type)) { \ case NULLT: \ apply(std::nullptr_t); \ break; \ case ARRAY: \ apply(Array); \ break; \ case BOOL: \ apply(bool); \ break; \ case DOUBLE: \ apply(double); \ break; \ case INT64: \ apply(int64_t); \ break; \ case OBJECT: \ apply(ObjectImpl); \ break; \ case STRING: \ apply(std::string); \ break; \ default: \ CHECK(0); \ abort(); \ } \ } while (0) bool dynamic::operator<(dynamic const& o) const { if (UNLIKELY(type_ == OBJECT || o.type_ == OBJECT)) { throw_exception("object", type_); } if (type_ != o.type_) { return type_ < o.type_; } #define FB_X(T) return CompareOp::comp(*getAddress(), *o.getAddress()) FB_DYNAMIC_APPLY(type_, FB_X); #undef FB_X } bool dynamic::operator==(dynamic const& o) const { if (type() != o.type()) { if (isNumber() && o.isNumber()) { auto& integ = isInt() ? *this : o; auto& doubl = isInt() ? o : *this; return integ.asInt() == doubl.asDouble(); } return false; } #define FB_X(T) return *getAddress() == *o.getAddress(); FB_DYNAMIC_APPLY(type_, FB_X); #undef FB_X } dynamic& dynamic::operator=(dynamic const& o) { if (&o != this) { if (type_ == o.type_) { #define FB_X(T) *getAddress() = *o.getAddress() FB_DYNAMIC_APPLY(type_, FB_X); #undef FB_X } else { destroy(); #define FB_X(T) new (getAddress()) T(*o.getAddress()) FB_DYNAMIC_APPLY(o.type_, FB_X); #undef FB_X type_ = o.type_; } } return *this; } dynamic& dynamic::operator=(dynamic&& o) noexcept { if (&o != this) { if (type_ == o.type_) { #define FB_X(T) *getAddress() = std::move(*o.getAddress()) FB_DYNAMIC_APPLY(type_, FB_X); #undef FB_X } else { destroy(); #define FB_X(T) new (getAddress()) T(std::move(*o.getAddress())) FB_DYNAMIC_APPLY(o.type_, FB_X); #undef FB_X type_ = o.type_; } } return *this; } dynamic const& dynamic::atImpl(dynamic const& idx) const& { if (auto* parray = get_nothrow()) { if (!idx.isInt()) { throw_exception("int64", idx.type()); } if (idx < 0 || idx >= parray->size()) { throw_exception("out of range in dynamic array"); } return (*parray)[size_t(idx.asInt())]; } else if (auto* pobject = get_nothrow()) { auto it = pobject->find(idx); if (it == pobject->end()) { throw_exception( sformat("couldn't find key {} in dynamic object", idx.asString())); } return it->second; } else { throw_exception("object/array", type()); } } dynamic const& dynamic::at(StringPiece idx) const& { auto* pobject = get_nothrow(); if (!pobject) { throw_exception("object", type()); } auto it = pobject->find(idx); if (it == pobject->end()) { throw_exception( sformat("couldn't find key {} in dynamic object", idx)); } return it->second; } dynamic& dynamic::operator[](StringPiece k) & { auto& obj = get(); auto ret = obj.emplace(k, nullptr); return ret.first->second; } dynamic dynamic::getDefault(StringPiece k, const dynamic& v) const& { auto& obj = get(); auto it = obj.find(k); return it == obj.end() ? v : it->second; } dynamic dynamic::getDefault(StringPiece k, dynamic&& v) const& { auto& obj = get(); auto it = obj.find(k); // Avoid clang bug with ternary if (it == obj.end()) { return std::move(v); } else { return it->second; } } dynamic dynamic::getDefault(StringPiece k, const dynamic& v) && { auto& obj = get(); auto it = obj.find(k); // Avoid clang bug with ternary if (it == obj.end()) { return v; } else { return std::move(it->second); } } dynamic dynamic::getDefault(StringPiece k, dynamic&& v) && { auto& obj = get(); auto it = obj.find(k); return std::move(it == obj.end() ? v : it->second); } const dynamic* dynamic::get_ptrImpl(dynamic const& idx) const& { if (auto* parray = get_nothrow()) { if (!idx.isInt()) { throw_exception("int64", idx.type()); } if (idx < 0 || idx >= parray->size()) { return nullptr; } return &(*parray)[size_t(idx.asInt())]; } else if (auto* pobject = get_nothrow()) { auto it = pobject->find(idx); if (it == pobject->end()) { return nullptr; } return &it->second; } else { throw_exception("object/array", type()); } } const dynamic* dynamic::get_ptr(StringPiece idx) const& { auto* pobject = get_nothrow(); if (!pobject) { throw_exception("object", type()); } auto it = pobject->find(idx); if (it == pobject->end()) { return nullptr; } return &it->second; } std::size_t dynamic::size() const { if (auto* ar = get_nothrow()) { return ar->size(); } if (auto* obj = get_nothrow()) { return obj->size(); } if (auto* str = get_nothrow()) { return str->size(); } throw_exception("array/object/string", type()); } dynamic::iterator dynamic::erase(const_iterator first, const_iterator last) { auto& arr = get(); return get().erase( arr.begin() + (first - arr.begin()), arr.begin() + (last - arr.begin())); } std::size_t dynamic::hash() const { switch (type()) { case NULLT: return 0xBAAAAAAD; case OBJECT: { // Accumulate using addition instead of using hash_range (as in the ARRAY // case), as we need a commutative hash operation since unordered_map's // iteration order is unspecified. auto h = std::hash>{}; return std::accumulate( items().begin(), items().end(), size_t{0x0B1EC7}, [&](auto acc, auto const& item) { return acc + h(item); }); } case ARRAY: return folly::hash::hash_range(begin(), end()); case INT64: return std::hash()(getInt()); case DOUBLE: return std::hash()(getDouble()); case BOOL: return std::hash()(getBool()); case STRING: // keep consistent with detail::DynamicHasher return Hash()(getString()); } assume_unreachable(); } char const* dynamic::typeName(Type t) { #define FB_X(T) return TypeInfo::name FB_DYNAMIC_APPLY(t, FB_X); #undef FB_X } void dynamic::destroy() noexcept { // This short-circuit speeds up some microbenchmarks. if (type_ == NULLT) { return; } #define FB_X(T) detail::Destroy::destroy(getAddress()) FB_DYNAMIC_APPLY(type_, FB_X); #undef FB_X type_ = NULLT; u_.nul = nullptr; } dynamic dynamic::merge_diff(const dynamic& source, const dynamic& target) { if (!source.isObject() || source.type() != target.type()) { return target; } dynamic diff = object; // added/modified keys for (const auto& pair : target.items()) { auto it = source.find(pair.first); if (it == source.items().end()) { diff[pair.first] = pair.second; } else { diff[pair.first] = merge_diff(source[pair.first], target[pair.first]); } } // removed keys for (const auto& pair : source.items()) { auto it = target.find(pair.first); if (it == target.items().end()) { diff[pair.first] = nullptr; } } return diff; } // clang-format off dynamic::resolved_json_pointer // clang-format on dynamic::try_get_ptr(json_pointer const& jsonPtr) const& { using err_code = json_pointer_resolution_error_code; using error = json_pointer_resolution_error; auto const& tokens = jsonPtr.tokens(); if (tokens.empty()) { return json_pointer_resolved_value{ nullptr, this, {nullptr, nullptr}, 0}; } dynamic const* curr = this; dynamic const* prev = nullptr; size_t curr_idx{0}; StringPiece curr_key{}; for (auto it : enumerate(tokens)) { // hit bottom but pointer not exhausted yet if (!curr) { return makeUnexpected( error{err_code::json_pointer_out_of_bounds, it.index, prev}); } prev = curr; // handle lookup in array if (auto const* parray = curr->get_nothrow()) { if (it->size() > 1 && it->at(0) == '0') { return makeUnexpected( error{err_code::index_has_leading_zero, it.index, prev}); } // if last element of pointer is '-', this is an append operation if (it->size() == 1 && it->at(0) == '-') { // was '-' the last token in pointer? if (it.index == tokens.size() - 1) { return makeUnexpected( error{err_code::append_requested, it.index, prev}); } // Cannot resolve past '-' in an array curr = nullptr; continue; } auto const idx = tryTo(*it); if (!idx.hasValue()) { return makeUnexpected( error{err_code::index_not_numeric, it.index, prev}); } if (idx.value() < parray->size()) { curr = &(*parray)[idx.value()]; curr_idx = idx.value(); } else { return makeUnexpected( error{err_code::index_out_of_bounds, it.index, prev}); } continue; } // handle lookup in object if (auto const* pobject = curr->get_nothrow()) { auto const sub_it = pobject->find(*it); if (sub_it == pobject->end()) { return makeUnexpected(error{err_code::key_not_found, it.index, prev}); } curr = &sub_it->second; curr_key = *it; continue; } return makeUnexpected( error{err_code::element_not_object_or_array, it.index, prev}); } return json_pointer_resolved_value{ prev, curr, curr_key, curr_idx}; } const dynamic* dynamic::get_ptr(json_pointer const& jsonPtr) const& { using err_code = json_pointer_resolution_error_code; auto ret = try_get_ptr(jsonPtr); if (ret.hasValue()) { return ret.value().value; } auto const ctx = ret.error().context; auto const objType = ctx ? ctx->type() : Type::NULLT; switch (ret.error().error_code) { case err_code::key_not_found: return nullptr; case err_code::index_out_of_bounds: return nullptr; case err_code::append_requested: return nullptr; case err_code::index_not_numeric: throw std::invalid_argument("array index is not numeric"); case err_code::index_has_leading_zero: throw std::invalid_argument( "leading zero not allowed when indexing arrays"); case err_code::element_not_object_or_array: throw_exception("object/array", objType); case err_code::json_pointer_out_of_bounds: return nullptr; case err_code::other: default: return nullptr; } assume_unreachable(); } ////////////////////////////////////////////////////////////////////// } // namespace folly