// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #pragma once #include // size_t #include // uint32_t, uint64_t #include #include #include #include // Hash indexes of the keys of large objects, so that a lookup does not compare thousands // of keys (as Boost.JSON switches from a linear search to a hash table for // large objects). An object with document_data::index_min_members members or // more gets an open-addressing table after parsing; its node stores the // number of the table (1-based) in `extra`. A slot holds the offset of a key // node from its object node (0: empty). Of duplicate keys, the first is kept, // as for the linear search. // // The hash is not seeded, so keys chosen to collide could make the build // quadratic. A key therefore sits at most index_max_displacement slots away // from its home slot; if a key would sit further away, the table is dropped // and the object is searched linearly (like a small one). For the same // reason, a lookup visits at most index_max_displacement + 1 slots. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { namespace view { /// the farthest a key may sit from its home slot (a table with at most half of /// its slots in use gives random keys a distance of about 50 for millions of /// members; and every member costs at most this many steps while building) constexpr std::size_t index_max_displacement = 64; // a table has at least 2 * len slots (len >= index_min_members), so the probe // window of index_max_displacement + 1 slots stays far smaller than the table // and a probe never wraps around onto its own start static_assert(document_data::index_min_members >= 2 * index_max_displacement, "a table must be much larger than the probe window of a lookup"); /// hash of a key: its bytes, eight at a time (in a fixed byte order), where the /// last word may overlap the one before (so that no loop over single bytes is /// needed); a key of fewer than eight bytes is read as two overlapping words of /// four or two bytes (in the native byte order: the hash is not stored, and the /// same on both sides of a lookup) inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept { std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1); const auto* p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::uint64_t w = 0; if (n >= 8) { const unsigned char* const last = p + n - 8; while (p < last) { h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u; h ^= h >> 29u; p += 8; } w = read_eight_bytes(last); } else if (n >= 4) { std::uint32_t head = 0; std::uint32_t tail = 0; std::memcpy(&head, p, 4); std::memcpy(&tail, p + n - 4, 4); w = static_cast(head) | (static_cast(tail) << 32u); } else if (n >= 2) { std::uint16_t head = 0; std::uint16_t tail = 0; std::memcpy(&head, p, 2); std::memcpy(&tail, p + n - 2, 2); w = static_cast(head) | (static_cast(tail) << 16u); } else if (n == 1) { w = p[0]; } // (a product only carries bits upwards, and the slot is taken from the low // bits: the high bits of the last word are folded down before the multiplication) h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu; return h ^ (h >> 31u); } /// whether the n <= 16 bytes at a and b are equal, as two overlapping words /// (the first and the last 8, 4, or 2 bytes) and without a call NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept { if (n >= 8) { std::uint64_t x0 = 0; std::uint64_t x1 = 0; std::uint64_t y0 = 0; std::uint64_t y1 = 0; std::memcpy(&x0, a, 8); std::memcpy(&x1, a + n - 8, 8); std::memcpy(&y0, b, 8); std::memcpy(&y1, b + n - 8, 8); return x0 == y0 && x1 == y1; } if (n >= 4) { std::uint32_t x0 = 0; std::uint32_t x1 = 0; std::uint32_t y0 = 0; std::uint32_t y1 = 0; std::memcpy(&x0, a, 4); std::memcpy(&x1, a + n - 4, 4); std::memcpy(&y0, b, 4); std::memcpy(&y1, b + n - 4, 4); return x0 == y0 && x1 == y1; } if (n >= 2) { std::uint16_t x0 = 0; std::uint16_t x1 = 0; std::uint16_t y0 = 0; std::uint16_t y1 = 0; std::memcpy(&x0, a, 2); std::memcpy(&x1, a + n - 2, 2); std::memcpy(&y0, b, 2); std::memcpy(&y1, b + n - 2, 2); return x0 == y0 && x1 == y1; } return n == 0 || *a == *b; } /// build the table of a large object inline void build_object_index(document_data& d, node* obj) { if (d.indexes.size() >= 0xFFFFu) { return; // LCOV_EXCL_LINE (the number must fit `extra`; more large objects are searched linearly) } std::size_t cap = 16; while (cap < 2 * static_cast(obj->len)) { cap *= 2; } const std::size_t start = d.index_slots.size(); d.index_slots.resize(start + cap, 0); std::uint32_t* const table = d.index_slots.data() + start; const std::size_t mask = cap - 1; for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1)) { const char* const key = d.str(*k); std::size_t slot = conditional_static_cast(key_hash(key, k->len)) & mask; bool duplicate = false; std::size_t distance = 0; while (table[slot] != 0) { const node* const other = obj + table[slot]; if (d.key_equals(*other, key, k->len)) { duplicate = true; // keep the first break; } if (distance == index_max_displacement) { // too many keys share a home region d.index_slots.resize(start); // no table: the object is searched linearly return; } ++distance; slot = (slot + 1) & mask; } if (!duplicate) { table[slot] = static_cast(k - obj); } } d.indexes.push_back(document_data::object_index{start, static_cast(mask)}); obj->extra = static_cast(d.indexes.size()); } /// build the tables of the large objects the parser noted inline void build_object_indexes(document_data& d) { for (const std::uint32_t i : d.large_objects) { build_object_index(d, d.nodes + i); } } /// the key node of the first member with this key of an indexed object, or /// nullptr inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept { const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const std::uint32_t* const table = d.index_slots.data() + ix.start; std::size_t slot = conditional_static_cast(key_hash(key, n)) & ix.mask; const auto* const k_bytes = reinterpret_cast(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) // (the loop for longer keys comes first, as the code is laid out; it is not the more common case) if (NLOHMANN_VIEW_UNLIKELY(n <= 16)) { // (up to 16 bytes, the bytes are compared as two words, without a call) for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) { const std::uint32_t s = table[slot]; if (s == 0) { return nullptr; } const node* const k = obj + s; if (k->len == n && same_short_bytes(reinterpret_cast(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) { return k; } slot = (slot + 1) & ix.mask; } return nullptr; } for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) { const std::uint32_t s = table[slot]; if (s == 0) { return nullptr; } const node* const k = obj + s; if (d.key_equals(*k, key, n)) { return k; } slot = (slot + 1) & ix.mask; } return nullptr; // (no key sits further from its home slot) } } // namespace view } // namespace detail NLOHMANN_JSON_NAMESPACE_END