| /* Dictionary object implementation using a hash table */ | |
| /* The distribution includes a separate file, Objects/dictnotes.txt, | |
| describing explorations into dictionary design and optimization. | |
| It covers typical dictionary use patterns, the parameters for | |
| tuning dictionaries, and several ideas for possible optimizations. | |
| */ | |
| /* PyDictKeysObject | |
| This implements the dictionary's hashtable. | |
| As of Python 3.6, this is compact and ordered. Basic idea is described here: | |
| * https://mail.python.org/pipermail/python-dev/2012-December/123028.html | |
| * https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html | |
| layout: | |
| +---------------------+ | |
| | dk_refcnt | | |
| | dk_log2_size | | |
| | dk_log2_index_bytes | | |
| | dk_kind | | |
| | dk_version | | |
| | dk_usable | | |
| | dk_nentries | | |
| +---------------------+ | |
| | dk_indices[] | | |
| | | | |
| +---------------------+ | |
| | dk_entries[] | | |
| | | | |
| +---------------------+ | |
| dk_indices is actual hashtable. It holds index in entries, or DKIX_EMPTY(-1) | |
| or DKIX_DUMMY(-2). | |
| Size of indices is dk_size. Type of each index in indices varies with dk_size: | |
| * int8 for dk_size <= 128 | |
| * int16 for 256 <= dk_size <= 2**15 | |
| * int32 for 2**16 <= dk_size <= 2**31 | |
| * int64 for 2**32 <= dk_size | |
| dk_entries is array of PyDictKeyEntry when dk_kind == DICT_KEYS_GENERAL or | |
| PyDictUnicodeEntry otherwise. Its length is USABLE_FRACTION(dk_size). | |
| NOTE: Since negative value is used for DKIX_EMPTY and DKIX_DUMMY, type of | |
| dk_indices entry is signed integer and int16 is used for table which | |
| dk_size == 256. | |
| */ | |
| /* | |
| The DictObject can be in one of two forms. | |
| Either: | |
| A combined table: | |
| ma_values == NULL, dk_refcnt == 1. | |
| Values are stored in the me_value field of the PyDictKeyEntry. | |
| Or: | |
| A split table: | |
| ma_values != NULL, dk_refcnt >= 1 | |
| Values are stored in the ma_values array. | |
| Only string (unicode) keys are allowed. | |
| There are four kinds of slots in the table (slot is index, and | |
| DK_ENTRIES(keys)[index] if index >= 0): | |
| 1. Unused. index == DKIX_EMPTY | |
| Does not hold an active (key, value) pair now and never did. Unused can | |
| transition to Active upon key insertion. This is each slot's initial state. | |
| 2. Active. index >= 0, me_key != NULL and me_value != NULL | |
| Holds an active (key, value) pair. Active can transition to Dummy or | |
| Pending upon key deletion (for combined and split tables respectively). | |
| This is the only case in which me_value != NULL. | |
| 3. Dummy. index == DKIX_DUMMY (combined only) | |
| Previously held an active (key, value) pair, but that was deleted and an | |
| active pair has not yet overwritten the slot. Dummy can transition to | |
| Active upon key insertion. Dummy slots cannot be made Unused again | |
| else the probe sequence in case of collision would have no way to know | |
| they were once active. | |
| In free-threaded builds dummy slots are not re-used to allow lock-free | |
| lookups to proceed safely. | |
| 4. Pending. index >= 0, key != NULL, and value == NULL (split only) | |
| Not yet inserted in split-table. | |
| */ | |
| /* | |
| Preserving insertion order | |
| It's simple for combined table. Since dk_entries is mostly append only, we can | |
| get insertion order by just iterating dk_entries. | |
| One exception is .popitem(). It removes last item in dk_entries and decrement | |
| dk_nentries to achieve amortized O(1). Since there are DKIX_DUMMY remains in | |
| dk_indices, we can't increment dk_usable even though dk_nentries is | |
| decremented. | |
| To preserve the order in a split table, a bit vector is used to record the | |
| insertion order. When a key is inserted the bit vector is shifted up by 4 bits | |
| and the index of the key is stored in the low 4 bits. | |
| As a consequence of this, split keys have a maximum size of 16. | |
| */ | |
| /* PyDict_MINSIZE is the starting size for any new dict. | |
| * 8 allows dicts with no more than 5 active entries; experiments suggested | |
| * this suffices for the majority of dicts (consisting mostly of usually-small | |
| * dicts created to pass keyword arguments). | |
| * Making this 8, rather than 4 reduces the number of resizes for most | |
| * dictionaries, without any significant extra memory use. | |
| */ | |
| #define PyDict_LOG_MINSIZE 3 | |
| #define PyDict_MINSIZE 8 | |
| #include "Python.h" | |
| #include "pycore_bitutils.h" // _Py_bit_length | |
| #include "pycore_call.h" // _PyObject_CallNoArgs() | |
| #include "pycore_ceval.h" // _PyEval_GetBuiltin() | |
| #include "pycore_code.h" // stats | |
| #include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION | |
| #include "pycore_dict.h" // export _PyDict_SizeOf() | |
| #include "pycore_freelist.h" // _PyFreeListState_GET() | |
| #include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() | |
| #include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() | |
| #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED | |
| #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() | |
| #include "pycore_pystate.h" // _PyThreadState_GET() | |
| #include "pycore_setobject.h" // _PySet_NextEntry() | |
| #include "pycore_tuple.h" // _PyTuple_Recycle() | |
| #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal() | |
| #include "stringlib/eq.h" // unicode_eq() | |
| #include <stdbool.h> | |
| // Forward declarations | |
| static PyObject* frozendict_new(PyTypeObject *type, PyObject *args, | |
| PyObject *kwds); | |
| static PyObject* frozendict_new_untracked(PyTypeObject *type); | |
| static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds); | |
| static PyObject* dict_new_untracked(PyTypeObject *type); | |
| static int dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey); | |
| static int dict_contains(PyObject *op, PyObject *key); | |
| static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override); | |
| /*[clinic input] | |
| class dict "PyDictObject *" "&PyDict_Type" | |
| class frozendict "PyFrozenDictObject *" "&PyFrozenDict_Type" | |
| [clinic start generated code]*/ | |
| /*[clinic end generated code: output=da39a3ee5e6b4b0d input=5dfa93bac68e7c54]*/ | |
| /* | |
| To ensure the lookup algorithm terminates, there must be at least one Unused | |
| slot (NULL key) in the table. | |
| To avoid slowing down lookups on a near-full table, we resize the table when | |
| it's USABLE_FRACTION (currently two-thirds) full. | |
| */ | |
| #ifdef Py_GIL_DISABLED | |
| static inline void | |
| ASSERT_DICT_LOCKED(PyObject *op) | |
| { | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); | |
| } | |
| #define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op)) | |
| #define ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op) \ | |
| if (!_PyInterpreterState_GET()->stoptheworld.world_stopped) { \ | |
| ASSERT_DICT_LOCKED(op); \ | |
| } | |
| #define ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(op) \ | |
| if (!_PyInterpreterState_GET()->stoptheworld.world_stopped) { \ | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); \ | |
| } | |
| #define IS_DICT_SHARED(mp) _PyObject_GC_IS_SHARED(mp) | |
| #define SET_DICT_SHARED(mp) _PyObject_GC_SET_SHARED(mp) | |
| #define LOAD_INDEX(keys, size, idx) _Py_atomic_load_int##size##_relaxed(&((const int##size##_t*)keys->dk_indices)[idx]); | |
| #define STORE_INDEX(keys, size, idx, value) _Py_atomic_store_int##size##_relaxed(&((int##size##_t*)keys->dk_indices)[idx], (int##size##_t)value); | |
| #define ASSERT_OWNED_OR_SHARED(mp) \ | |
| assert(_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)); | |
| #define LOCK_KEYS_IF_SPLIT(keys, kind) \ | |
| if (kind == DICT_KEYS_SPLIT) { \ | |
| LOCK_KEYS(keys); \ | |
| } | |
| #define UNLOCK_KEYS_IF_SPLIT(keys, kind) \ | |
| if (kind == DICT_KEYS_SPLIT) { \ | |
| UNLOCK_KEYS(keys); \ | |
| } | |
| static inline Py_ssize_t | |
| load_keys_nentries(PyDictObject *mp) | |
| { | |
| PyDictKeysObject *keys = _Py_atomic_load_ptr(&mp->ma_keys); | |
| return _Py_atomic_load_ssize(&keys->dk_nentries); | |
| } | |
| static inline void | |
| set_keys(PyDictObject *mp, PyDictKeysObject *keys) | |
| { | |
| ASSERT_OWNED_OR_SHARED(mp); | |
| _Py_atomic_store_ptr_release(&mp->ma_keys, keys); | |
| } | |
| static inline void | |
| set_values(PyDictObject *mp, PyDictValues *values) | |
| { | |
| ASSERT_OWNED_OR_SHARED(mp); | |
| _Py_atomic_store_ptr_release(&mp->ma_values, values); | |
| } | |
| // gh-151593: The _Py_LOCK_DONT_DETACH flag ensures that the outer critical | |
| // section is not dropped if there is some contention on the keys lock. | |
| // It also means that it will be important that LOCK_KEYS() is essentially the | |
| // "inner-most" code and that we don't call Py_DECREF() or similar while | |
| // holding the keys lock. | |
| // | |
| // We are not allowed to acquire other locks within LOCK_KEYS(). For example, | |
| // PyType_Modified() must not be called within LOCK_KEYS() since it acquires | |
| // the type lock. | |
| #define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH) | |
| #define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex) | |
| #define ASSERT_KEYS_LOCKED(keys) assert(PyMutex_IsLocked(&keys->dk_mutex)) | |
| #define LOAD_SHARED_KEY(key) _Py_atomic_load_ptr_acquire(&key) | |
| #define STORE_SHARED_KEY(key, value) _Py_atomic_store_ptr_release(&key, value) | |
| // Inc refs the keys object, giving the previous value | |
| #define INCREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, 1) | |
| // Dec refs the keys object, giving the previous value | |
| #define DECREF_KEYS(dk) _Py_atomic_add_ssize(&dk->dk_refcnt, -1) | |
| #define LOAD_KEYS_NENTRIES(keys) _Py_atomic_load_ssize_relaxed(&keys->dk_nentries) | |
| #define INCREF_KEYS_FT(dk) dictkeys_incref(dk) | |
| #define DECREF_KEYS_FT(dk, shared) dictkeys_decref(dk, shared) | |
| static inline void split_keys_entry_added(PyDictKeysObject *keys) | |
| { | |
| ASSERT_KEYS_LOCKED(keys); | |
| // We increase before we decrease so we never get too small of a value | |
| // when we're racing with reads | |
| _Py_atomic_store_ssize_relaxed(&keys->dk_nentries, keys->dk_nentries + 1); | |
| _Py_atomic_store_ssize_release(&keys->dk_usable, keys->dk_usable - 1); | |
| } | |
| #else /* Py_GIL_DISABLED */ | |
| #define ASSERT_DICT_LOCKED(op) | |
| #define ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op) | |
| #define ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(op) | |
| #define LOCK_KEYS(keys) | |
| #define UNLOCK_KEYS(keys) | |
| #define ASSERT_KEYS_LOCKED(keys) | |
| #define LOAD_SHARED_KEY(key) key | |
| #define STORE_SHARED_KEY(key, value) key = value | |
| #define INCREF_KEYS(dk) dk->dk_refcnt++ | |
| #define DECREF_KEYS(dk) dk->dk_refcnt-- | |
| #define LOAD_KEYS_NENTRIES(keys) keys->dk_nentries | |
| #define INCREF_KEYS_FT(dk) | |
| #define DECREF_KEYS_FT(dk, shared) | |
| #define LOCK_KEYS_IF_SPLIT(keys, kind) | |
| #define UNLOCK_KEYS_IF_SPLIT(keys, kind) | |
| #define IS_DICT_SHARED(mp) (false) | |
| #define SET_DICT_SHARED(mp) | |
| #define LOAD_INDEX(keys, size, idx) ((const int##size##_t*)(keys->dk_indices))[idx] | |
| #define STORE_INDEX(keys, size, idx, value) ((int##size##_t*)(keys->dk_indices))[idx] = (int##size##_t)value | |
| static inline void split_keys_entry_added(PyDictKeysObject *keys) | |
| { | |
| keys->dk_usable--; | |
| keys->dk_nentries++; | |
| } | |
| static inline void | |
| set_keys(PyDictObject *mp, PyDictKeysObject *keys) | |
| { | |
| mp->ma_keys = keys; | |
| } | |
| static inline void | |
| set_values(PyDictObject *mp, PyDictValues *values) | |
| { | |
| mp->ma_values = values; | |
| } | |
| static inline Py_ssize_t | |
| load_keys_nentries(PyDictObject *mp) | |
| { | |
| return mp->ma_keys->dk_nentries; | |
| } | |
| #endif | |
| #ifndef NDEBUG | |
| // Check if it's possible to modify a dictionary. | |
| // Usage: assert(can_modify_dict(mp)). | |
| static inline int | |
| can_modify_dict(PyDictObject *mp) | |
| { | |
| if (PyFrozenDict_Check(mp)) { | |
| // gh-151722: A frozendict must not be tracked by the GC | |
| // when it's being modified. | |
| assert(!_PyObject_GC_IS_TRACKED(mp)); | |
| // No locking required to modify a newly created frozendict | |
| // since it's only accessible from the current thread. | |
| assert(PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp))); | |
| } | |
| else { | |
| // Locking is only required if the dictionary is not | |
| // uniquely referenced. | |
| ASSERT_DICT_LOCKED(mp); | |
| } | |
| return 1; | |
| } | |
| #endif | |
| #define _PyAnyDict_CAST(op) \ | |
| (assert(PyAnyDict_Check(op)), _Py_CAST(PyDictObject*, op)) | |
| #define GET_USED(ep) FT_ATOMIC_LOAD_SSIZE_RELAXED((ep)->ma_used) | |
| #define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELEASE((ep)->me_key, key) | |
| #define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELEASE((ep)->me_value, value) | |
| #define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_values->values[idx], value) | |
| #define STORE_HASH(ep, hash) FT_ATOMIC_STORE_SSIZE_RELAXED((ep)->me_hash, hash) | |
| #define STORE_KEYS_USABLE(keys, usable) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_usable, usable) | |
| #define STORE_KEYS_NENTRIES(keys, nentries) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_nentries, nentries) | |
| #define STORE_USED(mp, used) FT_ATOMIC_STORE_SSIZE_RELAXED(mp->ma_used, used) | |
| #define PERTURB_SHIFT 5 | |
| /* | |
| Major subtleties ahead: Most hash schemes depend on having a "good" hash | |
| function, in the sense of simulating randomness. Python doesn't: its most | |
| important hash functions (for ints) are very regular in common | |
| cases: | |
| >>>[hash(i) for i in range(4)] | |
| [0, 1, 2, 3] | |
| This isn't necessarily bad! To the contrary, in a table of size 2**i, taking | |
| the low-order i bits as the initial table index is extremely fast, and there | |
| are no collisions at all for dicts indexed by a contiguous range of ints. So | |
| this gives better-than-random behavior in common cases, and that's very | |
| desirable. | |
| OTOH, when collisions occur, the tendency to fill contiguous slices of the | |
| hash table makes a good collision resolution strategy crucial. Taking only | |
| the last i bits of the hash code is also vulnerable: for example, consider | |
| the list [i << 16 for i in range(20000)] as a set of keys. Since ints are | |
| their own hash codes, and this fits in a dict of size 2**15, the last 15 bits | |
| of every hash code are all 0: they *all* map to the same table index. | |
| But catering to unusual cases should not slow the usual ones, so we just take | |
| the last i bits anyway. It's up to collision resolution to do the rest. If | |
| we *usually* find the key we're looking for on the first try (and, it turns | |
| out, we usually do -- the table load factor is kept under 2/3, so the odds | |
| are solidly in our favor), then it makes best sense to keep the initial index | |
| computation dirt cheap. | |
| The first half of collision resolution is to visit table indices via this | |
| recurrence: | |
| j = ((5*j) + 1) mod 2**i | |
| For any initial j in range(2**i), repeating that 2**i times generates each | |
| int in range(2**i) exactly once (see any text on random-number generation for | |
| proof). By itself, this doesn't help much: like linear probing (setting | |
| j += 1, or j -= 1, on each loop trip), it scans the table entries in a fixed | |
| order. This would be bad, except that's not the only thing we do, and it's | |
| actually *good* in the common cases where hash keys are consecutive. In an | |
| example that's really too small to make this entirely clear, for a table of | |
| size 2**3 the order of indices is: | |
| 0 -> 1 -> 6 -> 7 -> 4 -> 5 -> 2 -> 3 -> 0 [and here it's repeating] | |
| If two things come in at index 5, the first place we look after is index 2, | |
| not 6, so if another comes in at index 6 the collision at 5 didn't hurt it. | |
| Linear probing is deadly in this case because there the fixed probe order | |
| is the *same* as the order consecutive keys are likely to arrive. But it's | |
| extremely unlikely hash codes will follow a 5*j+1 recurrence by accident, | |
| and certain that consecutive hash codes do not. | |
| The other half of the strategy is to get the other bits of the hash code | |
| into play. This is done by initializing a (unsigned) vrbl "perturb" to the | |
| full hash code, and changing the recurrence to: | |
| perturb >>= PERTURB_SHIFT; | |
| j = (5*j) + 1 + perturb; | |
| use j % 2**i as the next table index; | |
| Now the probe sequence depends (eventually) on every bit in the hash code, | |
| and the pseudo-scrambling property of recurring on 5*j+1 is more valuable, | |
| because it quickly magnifies small differences in the bits that didn't affect | |
| the initial index. Note that because perturb is unsigned, if the recurrence | |
| is executed often enough perturb eventually becomes and remains 0. At that | |
| point (very rarely reached) the recurrence is on (just) 5*j+1 again, and | |
| that's certain to find an empty slot eventually (since it generates every int | |
| in range(2**i), and we make sure there's always at least one empty slot). | |
| Selecting a good value for PERTURB_SHIFT is a balancing act. You want it | |
| small so that the high bits of the hash code continue to affect the probe | |
| sequence across iterations; but you want it large so that in really bad cases | |
| the high-order hash bits have an effect on early iterations. 5 was "the | |
| best" in minimizing total collisions across experiments Tim Peters ran (on | |
| both normal and pathological cases), but 4 and 6 weren't significantly worse. | |
| Historical: Reimer Behrends contributed the idea of using a polynomial-based | |
| approach, using repeated multiplication by x in GF(2**n) where an irreducible | |
| polynomial for each table size was chosen such that x was a primitive root. | |
| Christian Tismer later extended that to use division by x instead, as an | |
| efficient way to get the high bits of the hash code into play. This scheme | |
| also gave excellent collision statistics, but was more expensive: two | |
| if-tests were required inside the loop; computing "the next" index took about | |
| the same number of operations but without as much potential parallelism | |
| (e.g., computing 5*j can go on at the same time as computing 1+perturb in the | |
| above, and then shifting perturb can be done while the table index is being | |
| masked); and the PyDictObject struct required a member to hold the table's | |
| polynomial. In Tim's experiments the current scheme ran faster, produced | |
| equally good collision statistics, needed less code & used less memory. | |
| */ | |
| static int dictresize(PyDictObject *mp, uint8_t log_newsize, int unicode); | |
| static PyObject* dict_iter(PyObject *dict); | |
| static int | |
| setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value); | |
| static int | |
| dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, | |
| PyObject **result, int incref_result); | |
| #ifndef NDEBUG | |
| static int _PyObject_InlineValuesConsistencyCheck(PyObject *obj); | |
| #endif | |
| #include "clinic/dictobject.c.h" | |
| static inline Py_hash_t | |
| unicode_get_hash(PyObject *o) | |
| { | |
| return PyUnstable_Unicode_GET_CACHED_HASH(o); | |
| } | |
| /* Print summary info about the state of the optimized allocator */ | |
| void | |
| _PyDict_DebugMallocStats(FILE *out) | |
| { | |
| _PyDebugAllocatorStats(out, "free PyDictObject", | |
| _Py_FREELIST_SIZE(dicts), | |
| _PyType_PreHeaderSize(&PyDict_Type) + sizeof(PyDictObject)); | |
| _PyDebugAllocatorStats(out, "free PyDictKeysObject", | |
| _Py_FREELIST_SIZE(dictkeys), | |
| sizeof(PyDictKeysObject)); | |
| } | |
| #define DK_MASK(dk) (DK_SIZE(dk)-1) | |
| #define _Py_DICT_IMMORTAL_INITIAL_REFCNT PY_SSIZE_T_MIN | |
| static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr); | |
| /* PyDictKeysObject has refcounts like PyObject does, so we have the | |
| following two functions to mirror what Py_INCREF() and Py_DECREF() do. | |
| (Keep in mind that PyDictKeysObject isn't actually a PyObject.) | |
| Likewise a PyDictKeysObject can be immortal (e.g. Py_EMPTY_KEYS), | |
| so we apply a naive version of what Py_INCREF() and Py_DECREF() do | |
| for immortal objects. */ | |
| static inline void | |
| dictkeys_incref(PyDictKeysObject *dk) | |
| { | |
| if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) { | |
| assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT); | |
| return; | |
| } | |
| #ifdef Py_REF_DEBUG | |
| _Py_IncRefTotal(_PyThreadState_GET()); | |
| #endif | |
| INCREF_KEYS(dk); | |
| } | |
| static inline void | |
| dictkeys_decref(PyDictKeysObject *dk, bool use_qsbr) | |
| { | |
| if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) { | |
| assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT); | |
| return; | |
| } | |
| assert(FT_ATOMIC_LOAD_SSIZE(dk->dk_refcnt) > 0); | |
| #ifdef Py_REF_DEBUG | |
| _Py_DecRefTotal(_PyThreadState_GET()); | |
| #endif | |
| if (DECREF_KEYS(dk) == 1) { | |
| if (DK_IS_UNICODE(dk)) { | |
| PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dk); | |
| Py_ssize_t i, n; | |
| for (i = 0, n = dk->dk_nentries; i < n; i++) { | |
| Py_XDECREF(entries[i].me_key); | |
| Py_XDECREF(entries[i].me_value); | |
| } | |
| } | |
| else { | |
| PyDictKeyEntry *entries = DK_ENTRIES(dk); | |
| Py_ssize_t i, n; | |
| for (i = 0, n = dk->dk_nentries; i < n; i++) { | |
| Py_XDECREF(entries[i].me_key); | |
| Py_XDECREF(entries[i].me_value); | |
| } | |
| } | |
| free_keys_object(dk, use_qsbr); | |
| } | |
| } | |
| /* lookup indices. returns DKIX_EMPTY, DKIX_DUMMY, or ix >=0 */ | |
| static inline Py_ssize_t | |
| dictkeys_get_index(const PyDictKeysObject *keys, Py_ssize_t i) | |
| { | |
| int log2size = DK_LOG_SIZE(keys); | |
| Py_ssize_t ix; | |
| if (log2size < 8) { | |
| ix = LOAD_INDEX(keys, 8, i); | |
| } | |
| else if (log2size < 16) { | |
| ix = LOAD_INDEX(keys, 16, i); | |
| } | |
| #if SIZEOF_VOID_P > 4 | |
| else if (log2size >= 32) { | |
| ix = LOAD_INDEX(keys, 64, i); | |
| } | |
| #endif | |
| else { | |
| ix = LOAD_INDEX(keys, 32, i); | |
| } | |
| assert(ix >= DKIX_DUMMY); | |
| return ix; | |
| } | |
| /* write to indices. */ | |
| static inline void | |
| dictkeys_set_index(PyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix) | |
| { | |
| int log2size = DK_LOG_SIZE(keys); | |
| assert(ix >= DKIX_DUMMY); | |
| assert(keys->dk_version == 0); | |
| if (log2size < 8) { | |
| assert(ix <= 0x7f); | |
| STORE_INDEX(keys, 8, i, ix); | |
| } | |
| else if (log2size < 16) { | |
| assert(ix <= 0x7fff); | |
| STORE_INDEX(keys, 16, i, ix); | |
| } | |
| #if SIZEOF_VOID_P > 4 | |
| else if (log2size >= 32) { | |
| STORE_INDEX(keys, 64, i, ix); | |
| } | |
| #endif | |
| else { | |
| assert(ix <= 0x7fffffff); | |
| STORE_INDEX(keys, 32, i, ix); | |
| } | |
| } | |
| /* USABLE_FRACTION is the maximum dictionary load. | |
| * Increasing this ratio makes dictionaries more dense resulting in more | |
| * collisions. Decreasing it improves sparseness at the expense of spreading | |
| * indices over more cache lines and at the cost of total memory consumed. | |
| * | |
| * USABLE_FRACTION must obey the following: | |
| * (0 < USABLE_FRACTION(n) < n) for all n >= 2 | |
| * | |
| * USABLE_FRACTION should be quick to calculate. | |
| * Fractions around 1/2 to 2/3 seem to work well in practice. | |
| */ | |
| #define USABLE_FRACTION(n) (((n) << 1)/3) | |
| /* Find the smallest dk_size >= minsize. */ | |
| static inline uint8_t | |
| calculate_log2_keysize(Py_ssize_t minsize) | |
| { | |
| #if SIZEOF_LONG == SIZEOF_SIZE_T | |
| minsize = Py_MAX(minsize, PyDict_MINSIZE); | |
| return _Py_bit_length(minsize - 1); | |
| #elif defined(_MSC_VER) | |
| // On 64bit Windows, sizeof(long) == 4. We cannot use _Py_bit_length. | |
| minsize = Py_MAX(minsize, PyDict_MINSIZE); | |
| unsigned long msb; | |
| _BitScanReverse64(&msb, (uint64_t)minsize - 1); | |
| return (uint8_t)(msb + 1); | |
| #else | |
| uint8_t log2_size; | |
| for (log2_size = PyDict_LOG_MINSIZE; | |
| (((Py_ssize_t)1) << log2_size) < minsize; | |
| log2_size++) | |
| ; | |
| return log2_size; | |
| #endif | |
| } | |
| /* estimate_keysize is reverse function of USABLE_FRACTION. | |
| * | |
| * This can be used to reserve enough size to insert n entries without | |
| * resizing. | |
| */ | |
| static inline uint8_t | |
| estimate_log2_keysize(Py_ssize_t n) | |
| { | |
| return calculate_log2_keysize((n*3 + 1) / 2); | |
| } | |
| /* GROWTH_RATE. Growth rate upon hitting maximum load. | |
| * Currently set to used*3. | |
| * This means that dicts double in size when growing without deletions, | |
| * but have more head room when the number of deletions is on a par with the | |
| * number of insertions. See also bpo-17563 and bpo-33205. | |
| * | |
| * GROWTH_RATE was set to used*4 up to version 3.2. | |
| * GROWTH_RATE was set to used*2 in version 3.3.0 | |
| * GROWTH_RATE was set to used*2 + capacity/2 in 3.4.0-3.6.0. | |
| */ | |
| #define GROWTH_RATE(d) ((d)->ma_used*3) | |
| /* This immutable, empty PyDictKeysObject is used for PyDict_Clear() | |
| * (which cannot fail and thus can do no allocation). | |
| * | |
| * See https://github.com/python/cpython/pull/127568#discussion_r1868070614 | |
| * for the rationale of using dk_log2_index_bytes=3 instead of 0. | |
| */ | |
| static PyDictKeysObject empty_keys_struct = { | |
| _Py_DICT_IMMORTAL_INITIAL_REFCNT, /* dk_refcnt */ | |
| 0, /* dk_log2_size */ | |
| 3, /* dk_log2_index_bytes */ | |
| DICT_KEYS_UNICODE, /* dk_kind */ | |
| #ifdef Py_GIL_DISABLED | |
| {0}, /* dk_mutex */ | |
| #endif | |
| 1, /* dk_version */ | |
| 0, /* dk_usable (immutable) */ | |
| 0, /* dk_nentries */ | |
| {DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, | |
| DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY}, /* dk_indices */ | |
| }; | |
| #define Py_EMPTY_KEYS &empty_keys_struct | |
| /* Uncomment to check the dict content in _PyDict_CheckConsistency() */ | |
| // #define DEBUG_PYDICT | |
| #ifdef DEBUG_PYDICT | |
| # define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 1)) | |
| #else | |
| # define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 0)) | |
| #endif | |
| static inline int | |
| get_index_from_order(PyDictObject *mp, Py_ssize_t i) | |
| { | |
| assert(mp->ma_used <= SHARED_KEYS_MAX_SIZE); | |
| assert(i < mp->ma_values->size); | |
| uint8_t *array = get_insertion_order_array(mp->ma_values); | |
| return array[i]; | |
| } | |
| #ifdef DEBUG_PYDICT | |
| static void | |
| dump_entries(PyDictKeysObject *dk) | |
| { | |
| for (Py_ssize_t i = 0; i < dk->dk_nentries; i++) { | |
| if (DK_IS_UNICODE(dk)) { | |
| PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(dk)[i]; | |
| printf("key=%p value=%p\n", ep->me_key, ep->me_value); | |
| } | |
| else { | |
| PyDictKeyEntry *ep = &DK_ENTRIES(dk)[i]; | |
| printf("key=%p hash=%lx value=%p\n", ep->me_key, ep->me_hash, ep->me_value); | |
| } | |
| } | |
| } | |
| #endif | |
| int | |
| _PyDict_CheckConsistency(PyObject *op, int check_content) | |
| { | |
| ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op); | |
| #define CHECK(expr) \ | |
| do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0) | |
| assert(op != NULL); | |
| CHECK(PyAnyDict_Check(op)); | |
| PyDictObject *mp = (PyDictObject *)op; | |
| PyDictKeysObject *keys = mp->ma_keys; | |
| int splitted = _PyDict_HasSplitTable(mp); | |
| Py_ssize_t usable = USABLE_FRACTION(DK_SIZE(keys)); | |
| // In the free-threaded build, shared keys may be concurrently modified, | |
| // so use atomic loads. | |
| Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable); | |
| Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries); | |
| CHECK(0 <= mp->ma_used && mp->ma_used <= usable); | |
| CHECK(0 <= dk_usable && dk_usable <= usable); | |
| CHECK(0 <= dk_nentries && dk_nentries <= usable); | |
| CHECK(dk_usable + dk_nentries <= usable); | |
| if (!splitted) { | |
| /* combined table */ | |
| CHECK(keys->dk_kind != DICT_KEYS_SPLIT); | |
| CHECK(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); | |
| } | |
| else { | |
| CHECK(keys->dk_kind == DICT_KEYS_SPLIT); | |
| CHECK(mp->ma_used <= SHARED_KEYS_MAX_SIZE); | |
| if (mp->ma_values->embedded) { | |
| CHECK(mp->ma_values->embedded == 1); | |
| CHECK(mp->ma_values->valid == 1); | |
| } | |
| } | |
| if (check_content) { | |
| LOCK_KEYS_IF_SPLIT(keys, keys->dk_kind); | |
| for (Py_ssize_t i=0; i < DK_SIZE(keys); i++) { | |
| Py_ssize_t ix = dictkeys_get_index(keys, i); | |
| CHECK(DKIX_DUMMY <= ix && ix <= usable); | |
| } | |
| if (keys->dk_kind == DICT_KEYS_GENERAL) { | |
| PyDictKeyEntry *entries = DK_ENTRIES(keys); | |
| for (Py_ssize_t i=0; i < usable; i++) { | |
| PyDictKeyEntry *entry = &entries[i]; | |
| PyObject *key = entry->me_key; | |
| if (key != NULL) { | |
| /* test_dict fails if PyObject_Hash() is called again */ | |
| CHECK(entry->me_hash != -1); | |
| CHECK(entry->me_value != NULL); | |
| if (PyUnicode_CheckExact(key)) { | |
| Py_hash_t hash = unicode_get_hash(key); | |
| CHECK(entry->me_hash == hash); | |
| } | |
| } | |
| } | |
| } | |
| else { | |
| PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(keys); | |
| for (Py_ssize_t i=0; i < usable; i++) { | |
| PyDictUnicodeEntry *entry = &entries[i]; | |
| PyObject *key = entry->me_key; | |
| if (key != NULL) { | |
| CHECK(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = unicode_get_hash(key); | |
| CHECK(hash != -1); | |
| if (!splitted) { | |
| CHECK(entry->me_value != NULL); | |
| } | |
| } | |
| if (splitted) { | |
| CHECK(entry->me_value == NULL); | |
| } | |
| } | |
| } | |
| if (splitted) { | |
| CHECK(mp->ma_used <= SHARED_KEYS_MAX_SIZE); | |
| /* splitted table */ | |
| int duplicate_check = 0; | |
| for (Py_ssize_t i=0; i < mp->ma_used; i++) { | |
| int index = get_index_from_order(mp, i); | |
| CHECK((duplicate_check & (1<<index)) == 0); | |
| duplicate_check |= (1<<index); | |
| CHECK(mp->ma_values->values[index] != NULL); | |
| } | |
| } | |
| UNLOCK_KEYS_IF_SPLIT(keys, keys->dk_kind); | |
| } | |
| return 1; | |
| #undef CHECK | |
| } | |
| static inline int | |
| get_log2_bytes(uint8_t log2_size) | |
| { | |
| int log2_bytes; | |
| assert(log2_size >= PyDict_LOG_MINSIZE); | |
| if (log2_size < 8) { | |
| log2_bytes = log2_size; | |
| } | |
| else if (log2_size < 16) { | |
| log2_bytes = log2_size + 1; | |
| } | |
| #if SIZEOF_VOID_P > 4 | |
| else if (log2_size >= 32) { | |
| log2_bytes = log2_size + 3; | |
| } | |
| #endif | |
| else { | |
| log2_bytes = log2_size + 2; | |
| } | |
| return log2_bytes; | |
| } | |
| static inline void | |
| init_keys_object(PyDictKeysObject* dk, uint8_t log2_size, int log2_bytes, int kind, | |
| Py_ssize_t usable, Py_ssize_t entry_size) | |
| { | |
| #ifdef Py_REF_DEBUG | |
| _Py_IncRefTotal(_PyThreadState_GET()); | |
| #endif | |
| dk->dk_refcnt = 1; | |
| dk->dk_log2_size = log2_size; | |
| dk->dk_log2_index_bytes = log2_bytes; | |
| dk->dk_kind = kind; | |
| #ifdef Py_GIL_DISABLED | |
| dk->dk_mutex = (PyMutex){0}; | |
| #endif | |
| dk->dk_nentries = 0; | |
| dk->dk_usable = usable; | |
| dk->dk_version = 0; | |
| memset(&dk->dk_indices[0], 0xff, ((size_t)1 << log2_bytes)); | |
| memset(&dk->dk_indices[(size_t)1 << log2_bytes], 0, entry_size * usable); | |
| } | |
| static PyDictKeysObject* | |
| new_keys_object(uint8_t log2_size, bool unicode) | |
| { | |
| Py_ssize_t usable = USABLE_FRACTION((size_t)1<<log2_size); | |
| size_t entry_size = unicode ? sizeof(PyDictUnicodeEntry) : sizeof(PyDictKeyEntry); | |
| int log2_bytes = get_log2_bytes(log2_size); | |
| PyDictKeysObject *dk = NULL; | |
| if (log2_size == PyDict_LOG_MINSIZE && unicode) { | |
| dk = _Py_FREELIST_POP_MEM(dictkeys); | |
| } | |
| if (dk == NULL) { | |
| dk = PyMem_Malloc(sizeof(PyDictKeysObject) | |
| + ((size_t)1 << log2_bytes) | |
| + entry_size * usable); | |
| if (dk == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| } | |
| init_keys_object(dk, log2_size, log2_bytes, | |
| unicode ? DICT_KEYS_UNICODE : DICT_KEYS_GENERAL, | |
| usable, entry_size); | |
| return dk; | |
| } | |
| static void | |
| free_keys_object(PyDictKeysObject *keys, bool use_qsbr) | |
| { | |
| void *ptr = keys; | |
| #ifdef Py_GIL_DISABLED | |
| size_t size = _PyDict_KeysSize(keys); | |
| #endif | |
| if (keys->dk_kind == DICT_KEYS_SPLIT) { | |
| ptr = _PyDictKeys_AsSharedKeys(keys); | |
| #ifdef Py_GIL_DISABLED | |
| size += offsetof(struct _instancekeysobject, dsk_keys); | |
| #endif | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| if (use_qsbr) { | |
| _PyMem_FreeDelayed(ptr, size); | |
| return; | |
| } | |
| #endif | |
| if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE && keys->dk_kind == DICT_KEYS_UNICODE) { | |
| _Py_FREELIST_FREE(dictkeys, keys, PyMem_Free); | |
| } | |
| else { | |
| PyMem_Free(ptr); | |
| } | |
| } | |
| static size_t | |
| values_size_from_count(size_t count) | |
| { | |
| assert(count >= 1); | |
| size_t suffix_size = _Py_SIZE_ROUND_UP(count, sizeof(PyObject *)); | |
| assert(suffix_size < 128); | |
| assert(suffix_size % sizeof(PyObject *) == 0); | |
| return (count + 1) * sizeof(PyObject *) + suffix_size; | |
| } | |
| #define CACHED_KEYS(tp) (((PyHeapTypeObject*)tp)->ht_cached_keys) | |
| static inline PyDictValues* | |
| new_values(size_t size) | |
| { | |
| size_t n = values_size_from_count(size); | |
| PyDictValues *res = (PyDictValues *)PyMem_Malloc(n); | |
| if (res == NULL) { | |
| return NULL; | |
| } | |
| res->embedded = 0; | |
| res->size = 0; | |
| assert(size < 256); | |
| res->capacity = (uint8_t)size; | |
| return res; | |
| } | |
| static inline void | |
| free_values(PyDictValues *values, bool use_qsbr) | |
| { | |
| assert(values->embedded == 0); | |
| #ifdef Py_GIL_DISABLED | |
| if (use_qsbr) { | |
| _PyMem_FreeDelayed(values, values_size_from_count(values->capacity)); | |
| return; | |
| } | |
| #endif | |
| PyMem_Free(values); | |
| } | |
| static inline PyObject * | |
| new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys, | |
| PyDictValues *values, Py_ssize_t used, | |
| int free_values_on_failure, int frozendict, int gc_track) | |
| { | |
| assert(keys != NULL); | |
| if (mp == NULL) { | |
| dictkeys_decref(keys, false); | |
| if (free_values_on_failure) { | |
| free_values(values, false); | |
| } | |
| return NULL; | |
| } | |
| mp->ma_keys = keys; | |
| mp->ma_values = values; | |
| mp->ma_used = used; | |
| mp->_ma_watcher_tag = 0; | |
| if (frozendict) { | |
| ((PyFrozenDictObject *)mp)->ma_hash = -1; | |
| } | |
| ASSERT_CONSISTENT(mp); | |
| if (gc_track) { | |
| _PyObject_GC_TRACK(mp); | |
| } | |
| return (PyObject *)mp; | |
| } | |
| /* Consumes a reference to the keys object */ | |
| static PyObject* | |
| new_dict(PyDictKeysObject *keys, PyDictValues *values, | |
| Py_ssize_t used, int free_values_on_failure) | |
| { | |
| PyDictObject *mp = _Py_FREELIST_POP(PyDictObject, dicts); | |
| if (mp == NULL) { | |
| mp = PyObject_GC_New(PyDictObject, &PyDict_Type); | |
| } | |
| assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type)); | |
| return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 1); | |
| } | |
| /* Consumes a reference to the keys object */ | |
| static PyObject* | |
| new_dict_untracked(PyDictKeysObject *keys, PyDictValues *values, | |
| Py_ssize_t used, int free_values_on_failure) | |
| { | |
| PyDictObject *mp = _Py_FREELIST_POP(PyDictObject, dicts); | |
| if (mp == NULL) { | |
| mp = PyObject_GC_New(PyDictObject, &PyDict_Type); | |
| } | |
| assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type)); | |
| return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 0); | |
| } | |
| /* Consumes a reference to the keys object */ | |
| static PyObject* | |
| new_frozendict_untracked(PyDictKeysObject *keys, PyDictValues *values, | |
| Py_ssize_t used, int free_values_on_failure) | |
| { | |
| PyDictObject *mp = PyObject_GC_New(PyDictObject, &PyFrozenDict_Type); | |
| return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1, 0); | |
| } | |
| static PyObject * | |
| new_dict_with_shared_keys(PyDictKeysObject *keys) | |
| { | |
| size_t size = shared_keys_usable_size(keys); | |
| PyDictValues *values = new_values(size); | |
| if (values == NULL) { | |
| return PyErr_NoMemory(); | |
| } | |
| dictkeys_incref(keys); | |
| for (size_t i = 0; i < size; i++) { | |
| values->values[i] = NULL; | |
| } | |
| return new_dict(keys, values, 0, 1); | |
| } | |
| static PyDictKeysObject * | |
| clone_combined_dict_keys(PyDictObject *orig) | |
| { | |
| assert(PyAnyDict_Check(orig)); | |
| assert(Py_TYPE(orig)->tp_iter == dict_iter); | |
| assert(orig->ma_values == NULL); | |
| assert(orig->ma_keys != Py_EMPTY_KEYS); | |
| assert(orig->ma_keys->dk_refcnt == 1); | |
| if (!PyFrozenDict_Check(orig)) { | |
| ASSERT_DICT_LOCKED(orig); | |
| } | |
| size_t keys_size = _PyDict_KeysSize(orig->ma_keys); | |
| PyDictKeysObject *keys = PyMem_Malloc(keys_size); | |
| if (keys == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| memcpy(keys, orig->ma_keys, keys_size); | |
| /* The keys version must be unique per keys object: the specializer | |
| and the JIT optimizer rely on it to identify a dict's keys. */ | |
| keys->dk_version = 0; | |
| /* After copying key/value pairs, we need to incref all | |
| keys and values and they are about to be co-owned by a | |
| new dict object. */ | |
| PyObject **pkey, **pvalue; | |
| size_t offs; | |
| if (DK_IS_UNICODE(orig->ma_keys)) { | |
| PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(keys); | |
| pkey = &ep0->me_key; | |
| pvalue = &ep0->me_value; | |
| offs = sizeof(PyDictUnicodeEntry) / sizeof(PyObject*); | |
| } | |
| else { | |
| PyDictKeyEntry *ep0 = DK_ENTRIES(keys); | |
| pkey = &ep0->me_key; | |
| pvalue = &ep0->me_value; | |
| offs = sizeof(PyDictKeyEntry) / sizeof(PyObject*); | |
| } | |
| Py_ssize_t n = keys->dk_nentries; | |
| for (Py_ssize_t i = 0; i < n; i++) { | |
| PyObject *value = *pvalue; | |
| if (value != NULL) { | |
| Py_INCREF(value); | |
| Py_INCREF(*pkey); | |
| } | |
| pvalue += offs; | |
| pkey += offs; | |
| } | |
| /* Since we copied the keys table we now have an extra reference | |
| in the system. Manually call increment _Py_RefTotal to signal that | |
| we have it now; calling dictkeys_incref would be an error as | |
| keys->dk_refcnt is already set to 1 (after memcpy). */ | |
| #ifdef Py_REF_DEBUG | |
| _Py_IncRefTotal(_PyThreadState_GET()); | |
| #endif | |
| return keys; | |
| } | |
| PyObject * | |
| PyDict_New(void) | |
| { | |
| /* We don't incref Py_EMPTY_KEYS here because it is immortal. */ | |
| return new_dict(Py_EMPTY_KEYS, NULL, 0, 0); | |
| } | |
| /* Search index of hash table from offset of entry table */ | |
| static Py_ssize_t | |
| lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index) | |
| { | |
| size_t mask = DK_MASK(k); | |
| size_t perturb = (size_t)hash; | |
| size_t i = (size_t)hash & mask; | |
| for (;;) { | |
| Py_ssize_t ix = dictkeys_get_index(k, i); | |
| if (ix == index) { | |
| return i; | |
| } | |
| if (ix == DKIX_EMPTY) { | |
| return DKIX_EMPTY; | |
| } | |
| perturb >>= PERTURB_SHIFT; | |
| i = mask & (i*5 + perturb + 1); | |
| } | |
| Py_UNREACHABLE(); | |
| } | |
| static inline Py_ALWAYS_INLINE Py_ssize_t | |
| do_lookup(PyDictObject *mp, PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, | |
| int (*check_lookup)(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t ix, PyObject *key, Py_hash_t)) | |
| { | |
| void *ep0 = _DK_ENTRIES(dk); | |
| size_t mask = DK_MASK(dk); | |
| size_t perturb = hash; | |
| size_t i = (size_t)hash & mask; | |
| Py_ssize_t ix; | |
| for (;;) { | |
| ix = dictkeys_get_index(dk, i); | |
| if (ix >= 0) { | |
| int cmp = check_lookup(mp, dk, ep0, ix, key, hash); | |
| if (cmp < 0) { | |
| return cmp; | |
| } else if (cmp) { | |
| return ix; | |
| } | |
| } | |
| else if (ix == DKIX_EMPTY) { | |
| return DKIX_EMPTY; | |
| } | |
| perturb >>= PERTURB_SHIFT; | |
| i = mask & (i*5 + perturb + 1); | |
| // Manual loop unrolling | |
| ix = dictkeys_get_index(dk, i); | |
| if (ix >= 0) { | |
| int cmp = check_lookup(mp, dk, ep0, ix, key, hash); | |
| if (cmp < 0) { | |
| return cmp; | |
| } else if (cmp) { | |
| return ix; | |
| } | |
| } | |
| else if (ix == DKIX_EMPTY) { | |
| return DKIX_EMPTY; | |
| } | |
| perturb >>= PERTURB_SHIFT; | |
| i = mask & (i*5 + perturb + 1); | |
| } | |
| Py_UNREACHABLE(); | |
| } | |
| static inline int | |
| compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; | |
| assert(ep->me_key != NULL); | |
| assert(PyUnicode_CheckExact(ep->me_key)); | |
| assert(!PyUnicode_CheckExact(key)); | |
| if (unicode_get_hash(ep->me_key) == hash) { | |
| PyObject *startkey = ep->me_key; | |
| Py_INCREF(startkey); | |
| int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); | |
| Py_DECREF(startkey); | |
| if (cmp < 0) { | |
| return DKIX_ERROR; | |
| } | |
| if (dk == mp->ma_keys && ep->me_key == startkey) { | |
| return cmp; | |
| } | |
| else { | |
| /* The dict was mutated, restart */ | |
| return DKIX_KEY_CHANGED; | |
| } | |
| } | |
| return 0; | |
| } | |
| // Search non-Unicode key from Unicode table | |
| static Py_ssize_t | |
| unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(mp, dk, key, hash, compare_unicode_generic); | |
| } | |
| static inline int | |
| compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; | |
| PyObject *ep_key = FT_ATOMIC_LOAD_PTR_CONSUME(ep->me_key); | |
| assert(ep_key != NULL); | |
| assert(PyUnicode_CheckExact(ep_key)); | |
| if (ep_key == key || | |
| (unicode_get_hash(ep_key) == hash && unicode_eq(ep_key, key))) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static Py_ssize_t _Py_HOT_FUNCTION | |
| unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(NULL, dk, key, hash, compare_unicode_unicode); | |
| } | |
| static inline int | |
| compare_generic(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix]; | |
| assert(ep->me_key != NULL); | |
| if (ep->me_key == key) { | |
| return 1; | |
| } | |
| if (ep->me_hash == hash) { | |
| PyObject *startkey = ep->me_key; | |
| Py_INCREF(startkey); | |
| int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); | |
| Py_DECREF(startkey); | |
| if (cmp < 0) { | |
| return DKIX_ERROR; | |
| } | |
| if (dk == mp->ma_keys && ep->me_key == startkey) { | |
| return cmp; | |
| } | |
| else { | |
| /* The dict was mutated, restart */ | |
| return DKIX_KEY_CHANGED; | |
| } | |
| } | |
| return 0; | |
| } | |
| static Py_ssize_t | |
| dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(mp, dk, key, hash, compare_generic); | |
| } | |
| static bool | |
| check_keys_unicode(PyDictKeysObject *dk, PyObject *key) | |
| { | |
| return PyUnicode_CheckExact(key) && (dk->dk_kind != DICT_KEYS_GENERAL); | |
| } | |
| static Py_ssize_t | |
| hash_unicode_key(PyObject *key) | |
| { | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = unicode_get_hash(key); | |
| if (hash == -1) { | |
| hash = PyUnicode_Type.tp_hash(key); | |
| assert(hash != -1); | |
| } | |
| return hash; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| static Py_ssize_t | |
| unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key, | |
| Py_hash_t hash); | |
| #endif | |
| static Py_ssize_t | |
| unicodekeys_lookup_split(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| Py_ssize_t ix; | |
| assert(dk->dk_kind == DICT_KEYS_SPLIT); | |
| assert(PyUnicode_CheckExact(key)); | |
| #ifdef Py_GIL_DISABLED | |
| // A split dictionaries keys can be mutated by other dictionaries | |
| // but if we have a unicode key we can avoid locking the shared | |
| // keys. | |
| ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); | |
| if (ix == DKIX_KEY_CHANGED) { | |
| LOCK_KEYS(dk); | |
| ix = unicodekeys_lookup_unicode(dk, key, hash); | |
| UNLOCK_KEYS(dk); | |
| } | |
| #else | |
| ix = unicodekeys_lookup_unicode(dk, key, hash); | |
| #endif | |
| return ix; | |
| } | |
| /* Lookup a string in a (all unicode) dict keys. | |
| * Returns DKIX_ERROR if key is not a string, | |
| * or if the dict keys is not all strings. | |
| * If the keys is present then return the index of key. | |
| * If the key is not present then return DKIX_EMPTY. | |
| */ | |
| Py_ssize_t | |
| _PyDictKeys_StringLookup(PyDictKeysObject* dk, PyObject *key) | |
| { | |
| if (!check_keys_unicode(dk, key)) { | |
| return DKIX_ERROR; | |
| } | |
| Py_hash_t hash = hash_unicode_key(key); | |
| return unicodekeys_lookup_unicode(dk, key, hash); | |
| } | |
| Py_ssize_t | |
| _PyDictKeys_StringLookupAndVersion(PyDictKeysObject *dk, PyObject *key, uint32_t *version) | |
| { | |
| if (!check_keys_unicode(dk, key)) { | |
| return DKIX_ERROR; | |
| } | |
| Py_ssize_t ix; | |
| Py_hash_t hash = hash_unicode_key(key); | |
| LOCK_KEYS(dk); | |
| ix = unicodekeys_lookup_unicode(dk, key, hash); | |
| *version = _PyDictKeys_GetVersionForCurrentState(_PyInterpreterState_GET(), dk); | |
| UNLOCK_KEYS(dk); | |
| return ix; | |
| } | |
| /* Like _PyDictKeys_StringLookup() but only works on split keys. Note | |
| * that in free-threaded builds this locks the keys object as required. | |
| */ | |
| Py_ssize_t | |
| _PyDictKeys_StringLookupSplit(PyDictKeysObject* dk, PyObject *key) | |
| { | |
| assert(dk->dk_kind == DICT_KEYS_SPLIT); | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = unicode_get_hash(key); | |
| if (hash == -1) { | |
| hash = PyUnicode_Type.tp_hash(key); | |
| if (hash == -1) { | |
| PyErr_Clear(); | |
| return DKIX_ERROR; | |
| } | |
| } | |
| return unicodekeys_lookup_split(dk, key, hash); | |
| } | |
| /* | |
| The basic lookup function used by all operations. | |
| This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4. | |
| Open addressing is preferred over chaining since the link overhead for | |
| chaining would be substantial (100% with typical malloc overhead). | |
| The initial probe index is computed as hash mod the table size. Subsequent | |
| probe indices are computed as explained earlier. | |
| All arithmetic on hash should ignore overflow. | |
| _Py_dict_lookup() is general-purpose, and may return DKIX_ERROR if (and only if) a | |
| comparison raises an exception. | |
| When the key isn't found a DKIX_EMPTY is returned. | |
| */ | |
| Py_ssize_t | |
| _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr) | |
| { | |
| PyDictKeysObject *dk; | |
| DictKeysKind kind; | |
| Py_ssize_t ix; | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp); | |
| start: | |
| dk = mp->ma_keys; | |
| kind = dk->dk_kind; | |
| if (kind != DICT_KEYS_GENERAL) { | |
| if (PyUnicode_CheckExact(key)) { | |
| #ifdef Py_GIL_DISABLED | |
| if (kind == DICT_KEYS_SPLIT) { | |
| ix = unicodekeys_lookup_split(dk, key, hash); | |
| } | |
| else { | |
| ix = unicodekeys_lookup_unicode(dk, key, hash); | |
| } | |
| #else | |
| ix = unicodekeys_lookup_unicode(dk, key, hash); | |
| #endif | |
| } | |
| else { | |
| INCREF_KEYS_FT(dk); | |
| LOCK_KEYS_IF_SPLIT(dk, kind); | |
| ix = unicodekeys_lookup_generic(mp, dk, key, hash); | |
| UNLOCK_KEYS_IF_SPLIT(dk, kind); | |
| DECREF_KEYS_FT(dk, IS_DICT_SHARED(mp)); | |
| if (ix == DKIX_KEY_CHANGED) { | |
| goto start; | |
| } | |
| } | |
| if (ix >= 0) { | |
| if (kind == DICT_KEYS_SPLIT) { | |
| *value_addr = mp->ma_values->values[ix]; | |
| } | |
| else { | |
| *value_addr = DK_UNICODE_ENTRIES(dk)[ix].me_value; | |
| } | |
| } | |
| else { | |
| *value_addr = NULL; | |
| } | |
| } | |
| else { | |
| ix = dictkeys_generic_lookup(mp, dk, key, hash); | |
| if (ix == DKIX_KEY_CHANGED) { | |
| goto start; | |
| } | |
| if (ix >= 0) { | |
| *value_addr = DK_ENTRIES(dk)[ix].me_value; | |
| } | |
| else { | |
| *value_addr = NULL; | |
| } | |
| } | |
| return ix; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| static inline void | |
| ensure_shared_on_read(PyDictObject *mp) | |
| { | |
| if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { | |
| // The first time we access a dict from a non-owning thread we mark it | |
| // as shared. This ensures that a concurrent resize operation will | |
| // delay freeing the old keys or values using QSBR, which is necessary | |
| // to safely allow concurrent reads without locking... | |
| Py_BEGIN_CRITICAL_SECTION(mp); | |
| if (!IS_DICT_SHARED(mp)) { | |
| SET_DICT_SHARED(mp); | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| } | |
| void | |
| _PyDict_EnsureSharedOnRead(PyDictObject *mp) | |
| { | |
| ensure_shared_on_read(mp); | |
| } | |
| #endif | |
| static inline void | |
| ensure_shared_on_resize(PyDictObject *mp) | |
| { | |
| #ifdef Py_GIL_DISABLED | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp); | |
| if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { | |
| // We are writing to the dict from another thread that owns | |
| // it and we haven't marked it as shared which will ensure | |
| // that when we re-size ma_keys or ma_values that we will | |
| // free using QSBR. We need to lock the dictionary to | |
| // contend with writes from the owning thread, mark it as | |
| // shared, and then we can continue with lock-free reads. | |
| // Technically this is a little heavy handed, we could just | |
| // free the individual old keys / old-values using qsbr | |
| SET_DICT_SHARED(mp); | |
| } | |
| #endif | |
| } | |
| static inline void | |
| ensure_shared_on_keys_version_assignment(PyDictObject *mp) | |
| { | |
| ASSERT_DICT_LOCKED((PyObject *) mp); | |
| #ifdef Py_GIL_DISABLED | |
| if (!IS_DICT_SHARED(mp)) { | |
| // This ensures that a concurrent resize operation will delay | |
| // freeing the old keys or values using QSBR, which is necessary to | |
| // safely allow concurrent reads without locking. | |
| SET_DICT_SHARED(mp); | |
| } | |
| #endif | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| static inline Py_ALWAYS_INLINE int | |
| compare_unicode_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; | |
| PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key); | |
| assert(startkey == NULL || PyUnicode_CheckExact(ep->me_key)); | |
| assert(!PyUnicode_CheckExact(key)); | |
| if (startkey != NULL) { | |
| if (!_Py_TryIncrefCompare(&ep->me_key, startkey)) { | |
| return DKIX_KEY_CHANGED; | |
| } | |
| if (unicode_get_hash(startkey) == hash) { | |
| int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); | |
| Py_DECREF(startkey); | |
| if (cmp < 0) { | |
| return DKIX_ERROR; | |
| } | |
| if (dk == _Py_atomic_load_ptr_relaxed(&mp->ma_keys) && | |
| startkey == _Py_atomic_load_ptr_relaxed(&ep->me_key)) { | |
| return cmp; | |
| } | |
| else { | |
| /* The dict was mutated, restart */ | |
| return DKIX_KEY_CHANGED; | |
| } | |
| } | |
| else { | |
| Py_DECREF(startkey); | |
| } | |
| } | |
| return 0; | |
| } | |
| // Search non-Unicode key from Unicode table | |
| static Py_ssize_t | |
| unicodekeys_lookup_generic_threadsafe(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(mp, dk, key, hash, compare_unicode_generic_threadsafe); | |
| } | |
| static inline Py_ALWAYS_INLINE int | |
| compare_unicode_unicode_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; | |
| PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key); | |
| if (startkey == key) { | |
| assert(PyUnicode_CheckExact(startkey)); | |
| return 1; | |
| } | |
| if (startkey != NULL) { | |
| if (_Py_IsImmortal(startkey)) { | |
| assert(PyUnicode_CheckExact(startkey)); | |
| return unicode_get_hash(startkey) == hash && unicode_eq(startkey, key); | |
| } | |
| else { | |
| if (!_Py_TryIncrefCompare(&ep->me_key, startkey)) { | |
| return DKIX_KEY_CHANGED; | |
| } | |
| assert(PyUnicode_CheckExact(startkey)); | |
| if (unicode_get_hash(startkey) == hash && unicode_eq(startkey, key)) { | |
| Py_DECREF(startkey); | |
| return 1; | |
| } | |
| Py_DECREF(startkey); | |
| } | |
| } | |
| return 0; | |
| } | |
| static Py_ssize_t _Py_HOT_FUNCTION | |
| unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(NULL, dk, key, hash, compare_unicode_unicode_threadsafe); | |
| } | |
| static inline Py_ALWAYS_INLINE int | |
| compare_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk, | |
| void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix]; | |
| PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key); | |
| if (startkey == key) { | |
| return 1; | |
| } | |
| Py_ssize_t ep_hash = _Py_atomic_load_ssize_relaxed(&ep->me_hash); | |
| if (ep_hash == hash) { | |
| if (startkey == NULL || !_Py_TryIncrefCompare(&ep->me_key, startkey)) { | |
| return DKIX_KEY_CHANGED; | |
| } | |
| int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); | |
| Py_DECREF(startkey); | |
| if (cmp < 0) { | |
| return DKIX_ERROR; | |
| } | |
| if (dk == _Py_atomic_load_ptr_relaxed(&mp->ma_keys) && | |
| startkey == _Py_atomic_load_ptr_relaxed(&ep->me_key)) { | |
| return cmp; | |
| } | |
| else { | |
| /* The dict was mutated, restart */ | |
| return DKIX_KEY_CHANGED; | |
| } | |
| } | |
| return 0; | |
| } | |
| static Py_ssize_t | |
| dictkeys_generic_lookup_threadsafe(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) | |
| { | |
| return do_lookup(mp, dk, key, hash, compare_generic_threadsafe); | |
| } | |
| Py_ssize_t | |
| _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr) | |
| { | |
| PyDictKeysObject *dk; | |
| DictKeysKind kind; | |
| Py_ssize_t ix; | |
| PyObject *value; | |
| ensure_shared_on_read(mp); | |
| dk = _Py_atomic_load_ptr(&mp->ma_keys); | |
| kind = dk->dk_kind; | |
| if (kind != DICT_KEYS_GENERAL) { | |
| if (PyUnicode_CheckExact(key)) { | |
| ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); | |
| } | |
| else { | |
| ix = unicodekeys_lookup_generic_threadsafe(mp, dk, key, hash); | |
| } | |
| if (ix == DKIX_KEY_CHANGED) { | |
| goto read_failed; | |
| } | |
| if (ix >= 0) { | |
| if (kind == DICT_KEYS_SPLIT) { | |
| PyDictValues *values = _Py_atomic_load_ptr(&mp->ma_values); | |
| if (values == NULL) | |
| goto read_failed; | |
| uint8_t capacity = _Py_atomic_load_uint8_relaxed(&values->capacity); | |
| if (ix >= (Py_ssize_t)capacity) | |
| goto read_failed; | |
| value = _Py_TryXGetRef(&values->values[ix]); | |
| if (value == NULL) | |
| goto read_failed; | |
| if (values != _Py_atomic_load_ptr(&mp->ma_values)) { | |
| Py_DECREF(value); | |
| goto read_failed; | |
| } | |
| } | |
| else { | |
| value = _Py_TryXGetRef(&DK_UNICODE_ENTRIES(dk)[ix].me_value); | |
| if (value == NULL) { | |
| goto read_failed; | |
| } | |
| if (dk != _Py_atomic_load_ptr(&mp->ma_keys)) { | |
| Py_DECREF(value); | |
| goto read_failed; | |
| } | |
| } | |
| } | |
| else { | |
| value = NULL; | |
| } | |
| } | |
| else { | |
| ix = dictkeys_generic_lookup_threadsafe(mp, dk, key, hash); | |
| if (ix == DKIX_KEY_CHANGED) { | |
| goto read_failed; | |
| } | |
| if (ix >= 0) { | |
| value = _Py_TryXGetRef(&DK_ENTRIES(dk)[ix].me_value); | |
| if (value == NULL) | |
| goto read_failed; | |
| if (dk != _Py_atomic_load_ptr(&mp->ma_keys)) { | |
| Py_DECREF(value); | |
| goto read_failed; | |
| } | |
| } | |
| else { | |
| value = NULL; | |
| } | |
| } | |
| *value_addr = value; | |
| return ix; | |
| read_failed: | |
| // In addition to the normal races of the dict being modified the _Py_TryXGetRef | |
| // can all fail if they don't yet have a shared ref count. That can happen here | |
| // or in the *_lookup_* helper. In that case we need to take the lock to avoid | |
| // mutation and do a normal incref which will make them shared. | |
| Py_BEGIN_CRITICAL_SECTION(mp); | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| *value_addr = value; | |
| if (value != NULL) { | |
| assert(ix >= 0); | |
| _Py_NewRefWithLock(value); | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| return ix; | |
| } | |
| static Py_ssize_t | |
| lookup_threadsafe_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) | |
| { | |
| assert(dk->dk_kind == DICT_KEYS_UNICODE); | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); | |
| if (ix == DKIX_EMPTY) { | |
| *value_addr = PyStackRef_NULL; | |
| return ix; | |
| } | |
| else if (ix >= 0) { | |
| PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value; | |
| PyObject *value = _Py_atomic_load_ptr(addr_of_value); | |
| if (value == NULL) { | |
| *value_addr = PyStackRef_NULL; | |
| return DKIX_EMPTY; | |
| } | |
| if (_PyObject_HasDeferredRefcount(value)) { | |
| *value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_REFCNT }; | |
| return ix; | |
| } | |
| if (_Py_TryIncrefCompare(addr_of_value, value)) { | |
| *value_addr = PyStackRef_FromPyObjectSteal(value); | |
| return ix; | |
| } | |
| return DKIX_KEY_CHANGED; | |
| } | |
| assert(ix == DKIX_KEY_CHANGED); | |
| return ix; | |
| } | |
| Py_ssize_t | |
| _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) | |
| { | |
| ensure_shared_on_read(mp); | |
| PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys); | |
| if (dk->dk_kind == DICT_KEYS_UNICODE && PyUnicode_CheckExact(key)) { | |
| Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, value_addr); | |
| if (ix != DKIX_KEY_CHANGED) { | |
| return ix; | |
| } | |
| } | |
| PyObject *obj; | |
| Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj); | |
| if (ix >= 0 && obj != NULL) { | |
| *value_addr = PyStackRef_FromPyObjectSteal(obj); | |
| } | |
| else { | |
| *value_addr = PyStackRef_NULL; | |
| } | |
| return ix; | |
| } | |
| #else // Py_GIL_DISABLED | |
| Py_ssize_t | |
| _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr) | |
| { | |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, value_addr); | |
| Py_XNewRef(*value_addr); | |
| return ix; | |
| } | |
| Py_ssize_t | |
| _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) | |
| { | |
| PyObject *val; | |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &val); | |
| if (val == NULL) { | |
| *value_addr = PyStackRef_NULL; | |
| } | |
| else { | |
| *value_addr = PyStackRef_FromPyObjectNew(val); | |
| } | |
| return ix; | |
| } | |
| #endif | |
| // Looks up the unicode key `key` in the dictionary. Note that `*method` may | |
| // already contain a valid value! See _PyObject_GetMethodStackRef(). | |
| int | |
| _PyDict_GetMethodStackRef(PyDictObject *mp, PyObject *key, _PyStackRef *method) | |
| { | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = hash_unicode_key(key); | |
| #ifdef Py_GIL_DISABLED | |
| // NOTE: We can only do the fast-path lookup if we are on the owning | |
| // thread or if the dict is already marked as shared so that the load | |
| // of ma_keys is safe without a lock. We cannot call ensure_shared_on_read() | |
| // in this code path without incref'ing the dict because the dict is a | |
| // borrowed reference protected by QSBR, and acquiring the lock could lead | |
| // to a quiescent state (allowing the dict to be freed). | |
| if (_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)) { | |
| PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys); | |
| if (dk->dk_kind == DICT_KEYS_UNICODE) { | |
| _PyStackRef ref; | |
| Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, &ref); | |
| if (ix >= 0) { | |
| assert(!PyStackRef_IsNull(ref)); | |
| PyStackRef_XSETREF(*method, ref); | |
| return 1; | |
| } | |
| else if (ix == DKIX_EMPTY) { | |
| return 0; | |
| } | |
| assert(ix == DKIX_KEY_CHANGED); | |
| } | |
| } | |
| #endif | |
| PyObject *obj; | |
| Py_INCREF(mp); | |
| Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj); | |
| Py_DECREF(mp); | |
| if (ix == DKIX_ERROR) { | |
| PyStackRef_CLEAR(*method); | |
| return -1; | |
| } | |
| else if (ix >= 0 && obj != NULL) { | |
| PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(obj)); | |
| return 1; | |
| } | |
| return 0; // not found | |
| } | |
| int | |
| _PyDict_HasOnlyStringKeys(PyObject *dict) | |
| { | |
| Py_ssize_t pos = 0; | |
| PyObject *key, *value; | |
| assert(PyDict_Check(dict)); | |
| /* Shortcut */ | |
| if (((PyDictObject *)dict)->ma_keys->dk_kind != DICT_KEYS_GENERAL) | |
| return 1; | |
| while (PyDict_Next(dict, &pos, &key, &value)) | |
| if (!PyUnicode_Check(key)) | |
| return 0; | |
| return 1; | |
| } | |
| void | |
| _PyDict_EnablePerThreadRefcounting(PyObject *op) | |
| { | |
| assert(PyDict_Check(op)); | |
| #ifdef Py_GIL_DISABLED | |
| Py_ssize_t id = _PyObject_AssignUniqueId(op); | |
| if (id == _Py_INVALID_UNIQUE_ID) { | |
| return; | |
| } | |
| if ((uint64_t)id >= (uint64_t)DICT_UNIQUE_ID_MAX) { | |
| _PyObject_ReleaseUniqueId(id); | |
| return; | |
| } | |
| PyDictObject *mp = (PyDictObject *)op; | |
| assert((mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) == 0); | |
| mp->_ma_watcher_tag += (uint64_t)id << DICT_UNIQUE_ID_SHIFT; | |
| #endif | |
| } | |
| static inline int | |
| is_unusable_slot(Py_ssize_t ix) | |
| { | |
| #ifdef Py_GIL_DISABLED | |
| return ix >= 0 || ix == DKIX_DUMMY; | |
| #else | |
| return ix >= 0; | |
| #endif | |
| } | |
| /* Internal function to find slot for an item from its hash | |
| when it is known that the key is not present in the dict. | |
| */ | |
| static Py_ssize_t | |
| find_empty_slot(PyDictKeysObject *keys, Py_hash_t hash) | |
| { | |
| assert(keys != NULL); | |
| const size_t mask = DK_MASK(keys); | |
| size_t i = hash & mask; | |
| Py_ssize_t ix = dictkeys_get_index(keys, i); | |
| for (size_t perturb = hash; is_unusable_slot(ix);) { | |
| perturb >>= PERTURB_SHIFT; | |
| i = (i*5 + perturb + 1) & mask; | |
| ix = dictkeys_get_index(keys, i); | |
| } | |
| return i; | |
| } | |
| static int | |
| insertion_resize(PyDictObject *mp, int unicode) | |
| { | |
| return dictresize(mp, calculate_log2_keysize(GROWTH_RATE(mp)), unicode); | |
| } | |
| static inline int | |
| insert_combined_dict(PyDictObject *mp, | |
| Py_hash_t hash, PyObject *key, PyObject *value) | |
| { | |
| // gh-140551: If dict was cleared in _Py_dict_lookup, | |
| // we have to resize one more time to force general key kind. | |
| if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { | |
| if (insertion_resize(mp, 0) < 0) | |
| return -1; | |
| assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL); | |
| } | |
| if (mp->ma_keys->dk_usable <= 0) { | |
| /* Need to resize. */ | |
| if (insertion_resize(mp, 1) < 0) { | |
| return -1; | |
| } | |
| } | |
| _PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value); | |
| FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0); | |
| Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); | |
| dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); | |
| if (DK_IS_UNICODE(mp->ma_keys)) { | |
| PyDictUnicodeEntry *ep; | |
| ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; | |
| STORE_KEY(ep, key); | |
| STORE_VALUE(ep, value); | |
| } | |
| else { | |
| PyDictKeyEntry *ep; | |
| ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; | |
| STORE_KEY(ep, key); | |
| STORE_VALUE(ep, value); | |
| STORE_HASH(ep, hash); | |
| } | |
| STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - 1); | |
| STORE_KEYS_NENTRIES(mp->ma_keys, mp->ma_keys->dk_nentries + 1); | |
| assert(mp->ma_keys->dk_usable >= 0); | |
| return 0; | |
| } | |
| static Py_ssize_t | |
| insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash) | |
| { | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_ssize_t ix; | |
| #ifdef Py_GIL_DISABLED | |
| ix = unicodekeys_lookup_unicode_threadsafe(keys, key, hash); | |
| if (ix >= 0) { | |
| return ix; | |
| } | |
| // We need to acquire the type lock before the keys mutex. Another lock | |
| // is never acquired below the keys mutex but a keys mutex can be acquired | |
| // elsewhere while we hold the types lock. To avoid deadlocks we must always | |
| // acquire the type lock first. | |
| Py_BEGIN_CRITICAL_SECTION_MUTEX(&_PyInterpreterState_GET()->types.mutex); | |
| #endif | |
| LOCK_KEYS(keys); | |
| ix = unicodekeys_lookup_unicode(keys, key, hash); | |
| if (ix == DKIX_EMPTY && keys->dk_usable > 0) { | |
| // Insert into new slot | |
| FT_ATOMIC_STORE_UINT32_RELAXED(keys->dk_version, 0); | |
| struct _instancekeysobject *shared_keys = _PyDictKeys_AsSharedKeys(keys); | |
| PyTypeObject *type = FT_ATOMIC_LOAD_PTR_ACQUIRE(shared_keys->dsk_owning_type); | |
| if (type) { | |
| // we acquired the type lock above | |
| _PyType_Modified_Unlocked(type); | |
| } | |
| Py_ssize_t hashpos = find_empty_slot(keys, hash); | |
| ix = keys->dk_nentries; | |
| dictkeys_set_index(keys, hashpos, ix); | |
| PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(keys)[ix]; | |
| STORE_SHARED_KEY(ep->me_key, Py_NewRef(key)); | |
| split_keys_entry_added(keys); | |
| } | |
| assert (ix < SHARED_KEYS_MAX_SIZE); | |
| UNLOCK_KEYS(keys); | |
| #ifdef Py_GIL_DISABLED | |
| Py_END_CRITICAL_SECTION(); | |
| #endif | |
| return ix; | |
| } | |
| void | |
| _PyDict_InsertSplitValue(PyDictObject *mp, PyObject *key, PyObject *value, Py_ssize_t ix) | |
| { | |
| assert(can_modify_dict(mp)); | |
| assert(PyUnicode_CheckExact(key)); | |
| PyObject *old_value = mp->ma_values->values[ix]; | |
| if (old_value == NULL) { | |
| _PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value); | |
| STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value)); | |
| _PyDictValues_AddToInsertionOrder(mp->ma_values, ix); | |
| STORE_USED(mp, mp->ma_used + 1); | |
| } | |
| else { | |
| _PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, mp, key, value); | |
| STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value)); | |
| // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault, | |
| // when dict only holds the strong reference to value in ep->me_value. | |
| Py_DECREF(old_value); | |
| } | |
| ASSERT_CONSISTENT(mp); | |
| } | |
| // Replace a value at an existing entry. Steals the new value reference. | |
| static void | |
| replace_value(PyDictObject *mp, PyObject *key, Py_ssize_t ix, | |
| PyObject *old_value, PyObject *value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| assert(old_value != NULL); | |
| if (old_value != value) { | |
| _PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, mp, key, value); | |
| if (DK_IS_UNICODE(mp->ma_keys)) { | |
| if (_PyDict_HasSplitTable(mp)) { | |
| STORE_SPLIT_VALUE(mp, ix, value); | |
| } | |
| else { | |
| PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[ix]; | |
| STORE_VALUE(ep, value); | |
| } | |
| } | |
| else { | |
| PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix]; | |
| STORE_VALUE(ep, value); | |
| } | |
| } | |
| Py_DECREF(old_value); /* which **CAN** re-enter (see gh-66843) */ | |
| ASSERT_CONSISTENT(mp); | |
| } | |
| /* | |
| Internal routine to insert a new item into the table. | |
| Used both by the internal resize routine and by the public insert routine. | |
| Returns -1 if an error occurred, or 0 on success. | |
| Consumes key and value references. | |
| */ | |
| static int | |
| insertdict(PyDictObject *mp, | |
| PyObject *key, Py_hash_t hash, PyObject *value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| PyObject *old_value = NULL; | |
| Py_ssize_t ix; | |
| if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) { | |
| ix = insert_split_key(mp->ma_keys, key, hash); | |
| if (ix != DKIX_EMPTY) { | |
| _PyDict_InsertSplitValue(mp, key, value, ix); | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| return 0; | |
| } | |
| // No space in shared keys. Go to insert_combined_dict() below. | |
| } | |
| else { | |
| ix = _Py_dict_lookup(mp, key, hash, &old_value); | |
| if (ix == DKIX_ERROR) | |
| goto Fail; | |
| } | |
| if (old_value == NULL) { | |
| // insert_combined_dict() will convert from non DICT_KEYS_GENERAL table | |
| // into DICT_KEYS_GENERAL table if key is not Unicode. | |
| // We don't convert it before _Py_dict_lookup because non-Unicode key | |
| // may change generic table into Unicode table. | |
| // | |
| // NOTE: ix may not be DKIX_EMPTY because split table may have key | |
| // without value. | |
| if (insert_combined_dict(mp, hash, key, value) < 0) { | |
| goto Fail; | |
| } | |
| STORE_USED(mp, mp->ma_used + 1); | |
| ASSERT_CONSISTENT(mp); | |
| return 0; | |
| } | |
| replace_value(mp, key, ix, old_value, value); | |
| Py_DECREF(key); | |
| return 0; | |
| Fail: | |
| Py_DECREF(value); | |
| Py_DECREF(key); | |
| return -1; | |
| } | |
| // Same as insertdict but specialized for ma_keys == Py_EMPTY_KEYS. | |
| // Consumes key and value references. | |
| static int | |
| insert_to_emptydict(PyDictObject *mp, | |
| PyObject *key, Py_hash_t hash, PyObject *value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| assert(mp->ma_keys == Py_EMPTY_KEYS); | |
| int unicode = PyUnicode_CheckExact(key); | |
| PyDictKeysObject *newkeys = new_keys_object(PyDict_LOG_MINSIZE, unicode); | |
| if (newkeys == NULL) { | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| return -1; | |
| } | |
| _PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value); | |
| /* We don't decref Py_EMPTY_KEYS here because it is immortal. */ | |
| assert(mp->ma_values == NULL); | |
| size_t hashpos = (size_t)hash & (PyDict_MINSIZE-1); | |
| dictkeys_set_index(newkeys, hashpos, 0); | |
| if (unicode) { | |
| PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(newkeys); | |
| ep->me_key = key; | |
| STORE_VALUE(ep, value); | |
| } | |
| else { | |
| PyDictKeyEntry *ep = DK_ENTRIES(newkeys); | |
| ep->me_key = key; | |
| ep->me_hash = hash; | |
| STORE_VALUE(ep, value); | |
| } | |
| STORE_USED(mp, mp->ma_used + 1); | |
| newkeys->dk_usable--; | |
| newkeys->dk_nentries++; | |
| // We store the keys last so no one can see them in a partially inconsistent | |
| // state so that we don't need to switch the keys to being shared yet for | |
| // the case where we're inserting from the non-owner thread. We don't use | |
| // set_keys here because the transition from empty to non-empty is safe | |
| // as the empty keys will never be freed. | |
| FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_keys, newkeys); | |
| return 0; | |
| } | |
| /* | |
| Internal routine used by dictresize() to build a hashtable of entries. | |
| */ | |
| static void | |
| build_indices_generic(PyDictKeysObject *keys, PyDictKeyEntry *ep, Py_ssize_t n) | |
| { | |
| size_t mask = DK_MASK(keys); | |
| for (Py_ssize_t ix = 0; ix != n; ix++, ep++) { | |
| Py_hash_t hash = ep->me_hash; | |
| size_t i = hash & mask; | |
| for (size_t perturb = hash; dictkeys_get_index(keys, i) != DKIX_EMPTY;) { | |
| perturb >>= PERTURB_SHIFT; | |
| i = mask & (i*5 + perturb + 1); | |
| } | |
| dictkeys_set_index(keys, i, ix); | |
| } | |
| } | |
| static void | |
| build_indices_unicode(PyDictKeysObject *keys, PyDictUnicodeEntry *ep, Py_ssize_t n) | |
| { | |
| size_t mask = DK_MASK(keys); | |
| for (Py_ssize_t ix = 0; ix != n; ix++, ep++) { | |
| Py_hash_t hash = unicode_get_hash(ep->me_key); | |
| assert(hash != -1); | |
| size_t i = hash & mask; | |
| for (size_t perturb = hash; dictkeys_get_index(keys, i) != DKIX_EMPTY;) { | |
| perturb >>= PERTURB_SHIFT; | |
| i = mask & (i*5 + perturb + 1); | |
| } | |
| dictkeys_set_index(keys, i, ix); | |
| } | |
| } | |
| static void | |
| invalidate_and_clear_inline_values(PyDictValues *values) | |
| { | |
| assert(values->embedded); | |
| FT_ATOMIC_STORE_UINT8(values->valid, 0); | |
| for (int i = 0; i < values->capacity; i++) { | |
| FT_ATOMIC_STORE_PTR_RELEASE(values->values[i], NULL); | |
| } | |
| } | |
| /* | |
| Restructure the table by allocating a new table and reinserting all | |
| items again. When entries have been deleted, the new table may | |
| actually be smaller than the old one. | |
| If a table is split (its keys and hashes are shared, its values are not), | |
| then the values are temporarily copied into the table, it is resized as | |
| a combined table, then the me_value slots in the old table are NULLed out. | |
| After resizing, a table is always combined. | |
| This function supports: | |
| - Unicode split -> Unicode combined or Generic | |
| - Unicode combined -> Unicode combined or Generic | |
| - Generic -> Generic | |
| */ | |
| static int | |
| dictresize(PyDictObject *mp, | |
| uint8_t log2_newsize, int unicode) | |
| { | |
| assert(can_modify_dict(mp)); | |
| PyDictKeysObject *oldkeys, *newkeys; | |
| PyDictValues *oldvalues; | |
| if (log2_newsize >= SIZEOF_SIZE_T*8) { | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| assert(log2_newsize >= PyDict_LOG_MINSIZE); | |
| oldkeys = mp->ma_keys; | |
| oldvalues = mp->ma_values; | |
| if (!DK_IS_UNICODE(oldkeys)) { | |
| unicode = 0; | |
| } | |
| ensure_shared_on_resize(mp); | |
| /* NOTE: Current odict checks mp->ma_keys to detect resize happen. | |
| * So we can't reuse oldkeys even if oldkeys->dk_size == newsize. | |
| * TODO: Try reusing oldkeys when reimplement odict. | |
| */ | |
| /* Allocate a new table. */ | |
| newkeys = new_keys_object(log2_newsize, unicode); | |
| if (newkeys == NULL) { | |
| return -1; | |
| } | |
| // New table must be large enough. | |
| assert(newkeys->dk_usable >= mp->ma_used); | |
| Py_ssize_t numentries = mp->ma_used; | |
| if (oldvalues != NULL) { | |
| LOCK_KEYS(oldkeys); | |
| PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys); | |
| /* Convert split table into new combined table. | |
| * We must incref keys; we can transfer values. | |
| */ | |
| if (newkeys->dk_kind == DICT_KEYS_GENERAL) { | |
| // split -> generic | |
| PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); | |
| for (Py_ssize_t i = 0; i < numentries; i++) { | |
| int index = get_index_from_order(mp, i); | |
| PyDictUnicodeEntry *ep = &oldentries[index]; | |
| assert(oldvalues->values[index] != NULL); | |
| newentries[i].me_key = Py_NewRef(ep->me_key); | |
| newentries[i].me_hash = unicode_get_hash(ep->me_key); | |
| newentries[i].me_value = oldvalues->values[index]; | |
| } | |
| build_indices_generic(newkeys, newentries, numentries); | |
| } | |
| else { // split -> combined unicode | |
| PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys); | |
| for (Py_ssize_t i = 0; i < numentries; i++) { | |
| int index = get_index_from_order(mp, i); | |
| PyDictUnicodeEntry *ep = &oldentries[index]; | |
| assert(oldvalues->values[index] != NULL); | |
| newentries[i].me_key = Py_NewRef(ep->me_key); | |
| newentries[i].me_value = oldvalues->values[index]; | |
| } | |
| build_indices_unicode(newkeys, newentries, numentries); | |
| } | |
| UNLOCK_KEYS(oldkeys); | |
| set_keys(mp, newkeys); | |
| dictkeys_decref(oldkeys, IS_DICT_SHARED(mp)); | |
| set_values(mp, NULL); | |
| if (oldvalues->embedded) { | |
| assert(oldvalues->embedded == 1); | |
| assert(oldvalues->valid == 1); | |
| invalidate_and_clear_inline_values(oldvalues); | |
| } | |
| else { | |
| free_values(oldvalues, IS_DICT_SHARED(mp)); | |
| } | |
| } | |
| else { // oldkeys is combined. | |
| if (oldkeys->dk_kind == DICT_KEYS_GENERAL) { | |
| // generic -> generic | |
| assert(newkeys->dk_kind == DICT_KEYS_GENERAL); | |
| PyDictKeyEntry *oldentries = DK_ENTRIES(oldkeys); | |
| PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); | |
| if (oldkeys->dk_nentries == numentries) { | |
| memcpy(newentries, oldentries, numentries * sizeof(PyDictKeyEntry)); | |
| } | |
| else { | |
| PyDictKeyEntry *ep = oldentries; | |
| for (Py_ssize_t i = 0; i < numentries; i++) { | |
| while (ep->me_value == NULL) | |
| ep++; | |
| newentries[i] = *ep++; | |
| } | |
| } | |
| build_indices_generic(newkeys, newentries, numentries); | |
| } | |
| else { // oldkeys is combined unicode | |
| PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys); | |
| if (unicode) { // combined unicode -> combined unicode | |
| PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys); | |
| if (oldkeys->dk_nentries == numentries && mp->ma_keys->dk_kind == DICT_KEYS_UNICODE) { | |
| memcpy(newentries, oldentries, numentries * sizeof(PyDictUnicodeEntry)); | |
| } | |
| else { | |
| PyDictUnicodeEntry *ep = oldentries; | |
| for (Py_ssize_t i = 0; i < numentries; i++) { | |
| while (ep->me_value == NULL) | |
| ep++; | |
| newentries[i] = *ep++; | |
| } | |
| } | |
| build_indices_unicode(newkeys, newentries, numentries); | |
| } | |
| else { // combined unicode -> generic | |
| PyDictKeyEntry *newentries = DK_ENTRIES(newkeys); | |
| PyDictUnicodeEntry *ep = oldentries; | |
| for (Py_ssize_t i = 0; i < numentries; i++) { | |
| while (ep->me_value == NULL) | |
| ep++; | |
| newentries[i].me_key = ep->me_key; | |
| newentries[i].me_hash = unicode_get_hash(ep->me_key); | |
| newentries[i].me_value = ep->me_value; | |
| ep++; | |
| } | |
| build_indices_generic(newkeys, newentries, numentries); | |
| } | |
| } | |
| set_keys(mp, newkeys); | |
| if (oldkeys != Py_EMPTY_KEYS) { | |
| #ifdef Py_REF_DEBUG | |
| _Py_DecRefTotal(_PyThreadState_GET()); | |
| #endif | |
| assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); | |
| assert(oldkeys->dk_refcnt == 1); | |
| free_keys_object(oldkeys, IS_DICT_SHARED(mp)); | |
| } | |
| } | |
| STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - numentries); | |
| STORE_KEYS_NENTRIES(mp->ma_keys, numentries); | |
| ASSERT_CONSISTENT(mp); | |
| return 0; | |
| } | |
| static PyObject * | |
| dict_new_presized(Py_ssize_t minused, bool unicode) | |
| { | |
| const uint8_t log2_max_presize = 17; | |
| const Py_ssize_t max_presize = ((Py_ssize_t)1) << log2_max_presize; | |
| uint8_t log2_newsize; | |
| PyDictKeysObject *new_keys; | |
| if (minused <= USABLE_FRACTION(PyDict_MINSIZE)) { | |
| return PyDict_New(); | |
| } | |
| /* There are no strict guarantee that returned dict can contain minused | |
| * items without resize. So we create medium size dict instead of very | |
| * large dict or MemoryError. | |
| */ | |
| if (minused > USABLE_FRACTION(max_presize)) { | |
| log2_newsize = log2_max_presize; | |
| } | |
| else { | |
| log2_newsize = estimate_log2_keysize(minused); | |
| } | |
| new_keys = new_keys_object(log2_newsize, unicode); | |
| if (new_keys == NULL) | |
| return NULL; | |
| return new_dict(new_keys, NULL, 0, 0); | |
| } | |
| PyObject * | |
| _PyDict_NewPresized(Py_ssize_t minused) | |
| { | |
| return dict_new_presized(minused, false); | |
| } | |
| PyObject * | |
| _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, | |
| PyObject *const *values, Py_ssize_t values_offset, | |
| Py_ssize_t length) | |
| { | |
| bool unicode = true; | |
| PyObject *const *ks = keys; | |
| for (Py_ssize_t i = 0; i < length; i++) { | |
| if (!PyUnicode_CheckExact(*ks)) { | |
| unicode = false; | |
| break; | |
| } | |
| ks += keys_offset; | |
| } | |
| PyObject *dict = dict_new_presized(length, unicode); | |
| if (dict == NULL) { | |
| return NULL; | |
| } | |
| ks = keys; | |
| PyObject *const *vs = values; | |
| for (Py_ssize_t i = 0; i < length; i++) { | |
| PyObject *key = *ks; | |
| PyObject *value = *vs; | |
| if (setitem_lock_held((PyDictObject *)dict, key, value) < 0) { | |
| Py_DECREF(dict); | |
| return NULL; | |
| } | |
| ks += keys_offset; | |
| vs += values_offset; | |
| } | |
| return dict; | |
| } | |
| /* Note that, for historical reasons, PyDict_GetItem() suppresses all errors | |
| * that may occur (originally dicts supported only string keys, and exceptions | |
| * weren't possible). So, while the original intent was that a NULL return | |
| * meant the key wasn't present, in reality it can mean that, or that an error | |
| * (suppressed) occurred while computing the key's hash, or that some error | |
| * (suppressed) occurred when comparing keys in the dict's internal probe | |
| * sequence. A nasty example of the latter is when a Python-coded comparison | |
| * function hits a stack-depth error, which can cause this to return NULL | |
| * even if the key is present. | |
| */ | |
| static PyObject * | |
| dict_getitem(PyObject *op, PyObject *key, const char *warnmsg) | |
| { | |
| if (!PyAnyDict_Check(op)) { | |
| return NULL; | |
| } | |
| PyDictObject *mp = (PyDictObject *)op; | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| PyErr_FormatUnraisable(warnmsg); | |
| return NULL; | |
| } | |
| PyThreadState *tstate = _PyThreadState_GET(); | |
| #ifdef Py_DEBUG | |
| // bpo-40839: Before Python 3.10, it was possible to call PyDict_GetItem() | |
| // with the GIL released. | |
| _Py_EnsureTstateNotNULL(tstate); | |
| #endif | |
| /* Preserve the existing exception */ | |
| PyObject *value; | |
| Py_ssize_t ix; (void)ix; | |
| PyObject *exc = _PyErr_GetRaisedException(tstate); | |
| #ifdef Py_GIL_DISABLED | |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | |
| Py_XDECREF(value); | |
| #else | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| #endif | |
| /* Ignore any exception raised by the lookup */ | |
| PyObject *exc2 = _PyErr_Occurred(tstate); | |
| if (exc2 && !PyErr_GivenExceptionMatches(exc2, PyExc_KeyError)) { | |
| PyErr_FormatUnraisable(warnmsg); | |
| } | |
| _PyErr_SetRaisedException(tstate, exc); | |
| assert(ix >= 0 || value == NULL); | |
| return value; // borrowed reference | |
| } | |
| PyObject * | |
| PyDict_GetItem(PyObject *op, PyObject *key) | |
| { | |
| return dict_getitem(op, key, | |
| "Exception ignored in PyDict_GetItem(); consider using " | |
| "PyDict_GetItemRef() or PyDict_GetItemWithError()"); | |
| } | |
| static void | |
| dict_unhashable_type(PyObject *op, PyObject *key) | |
| { | |
| PyObject *exc = PyErr_GetRaisedException(); | |
| assert(exc != NULL); | |
| if (!Py_IS_TYPE(exc, (PyTypeObject*)PyExc_TypeError)) { | |
| PyErr_SetRaisedException(exc); | |
| return; | |
| } | |
| const char *errmsg; | |
| if (PyFrozenDict_Check(op)) { | |
| errmsg = "cannot use '%T' as a frozendict key (%S)"; | |
| } | |
| else { | |
| errmsg = "cannot use '%T' as a dict key (%S)"; | |
| } | |
| PyErr_Format(PyExc_TypeError, errmsg, key, exc); | |
| Py_DECREF(exc); | |
| } | |
| Py_ssize_t | |
| _PyDict_LookupIndexAndValue(PyDictObject *mp, PyObject *key, PyObject **value) | |
| { | |
| // TODO: Thread safety | |
| assert(PyDict_CheckExact((PyObject*)mp)); | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type((PyObject*)mp, key); | |
| return -1; | |
| } | |
| return _Py_dict_lookup(mp, key, hash, value); | |
| } | |
| Py_ssize_t | |
| _PyDict_LookupIndex(PyDictObject *mp, PyObject *key) | |
| { | |
| PyObject *value; // discarded | |
| return _PyDict_LookupIndexAndValue(mp, key, &value); | |
| } | |
| /* Same as PyDict_GetItemWithError() but with hash supplied by caller. | |
| This returns NULL *with* an exception set if an exception occurred. | |
| It returns NULL *without* an exception set if the key wasn't present. | |
| */ | |
| PyObject * | |
| _PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) | |
| { | |
| Py_ssize_t ix; (void)ix; | |
| PyDictObject *mp = (PyDictObject *)op; | |
| PyObject *value; | |
| if (!PyAnyDict_Check(op)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | |
| Py_XDECREF(value); | |
| #else | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| #endif | |
| assert(ix >= 0 || value == NULL); | |
| return value; // borrowed reference | |
| } | |
| /* Gets an item and provides a new reference if the value is present. | |
| * Returns 1 if the key is present, 0 if the key is missing, and -1 if an | |
| * exception occurred. | |
| */ | |
| int | |
| _PyDict_GetItemRef_KnownHash_LockHeld(PyDictObject *op, PyObject *key, | |
| Py_hash_t hash, PyObject **result) | |
| { | |
| PyObject *value; | |
| Py_ssize_t ix = _Py_dict_lookup(op, key, hash, &value); | |
| assert(ix >= 0 || value == NULL); | |
| if (ix == DKIX_ERROR) { | |
| *result = NULL; | |
| return -1; | |
| } | |
| if (value == NULL) { | |
| *result = NULL; | |
| return 0; // missing key | |
| } | |
| *result = Py_NewRef(value); | |
| return 1; // key is present | |
| } | |
| /* Gets an item and provides a new reference if the value is present. | |
| * Returns 1 if the key is present, 0 if the key is missing, and -1 if an | |
| * exception occurred. | |
| */ | |
| int | |
| _PyDict_GetItemRef_KnownHash(PyDictObject *op, PyObject *key, Py_hash_t hash, PyObject **result) | |
| { | |
| PyObject *value; | |
| #ifdef Py_GIL_DISABLED | |
| Py_ssize_t ix = _Py_dict_lookup_threadsafe(op, key, hash, &value); | |
| #else | |
| Py_ssize_t ix = _Py_dict_lookup(op, key, hash, &value); | |
| #endif | |
| assert(ix >= 0 || value == NULL); | |
| if (ix == DKIX_ERROR) { | |
| *result = NULL; | |
| return -1; | |
| } | |
| if (value == NULL) { | |
| *result = NULL; | |
| return 0; // missing key | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| *result = value; | |
| #else | |
| *result = Py_NewRef(value); | |
| #endif | |
| return 1; // key is present | |
| } | |
| int | |
| PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) | |
| { | |
| if (!PyAnyDict_Check(op)) { | |
| PyErr_BadInternalCall(); | |
| *result = NULL; | |
| return -1; | |
| } | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(op, key); | |
| *result = NULL; | |
| return -1; | |
| } | |
| return _PyDict_GetItemRef_KnownHash((PyDictObject *)op, key, hash, result); | |
| } | |
| int | |
| _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result) | |
| { | |
| ASSERT_DICT_LOCKED(op); | |
| assert(PyUnicode_CheckExact(key)); | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type((PyObject*)op, key); | |
| *result = NULL; | |
| return -1; | |
| } | |
| PyObject *value; | |
| Py_ssize_t ix = _Py_dict_lookup(op, key, hash, &value); | |
| assert(ix >= 0 || value == NULL); | |
| if (ix == DKIX_ERROR) { | |
| *result = NULL; | |
| return -1; | |
| } | |
| if (value == NULL) { | |
| *result = NULL; | |
| return 0; // missing key | |
| } | |
| *result = Py_NewRef(value); | |
| return 1; // key is present | |
| } | |
| /* Variant of PyDict_GetItem() that doesn't suppress exceptions. | |
| This returns NULL *with* an exception set if an exception occurred. | |
| It returns NULL *without* an exception set if the key wasn't present. | |
| */ | |
| PyObject * | |
| PyDict_GetItemWithError(PyObject *op, PyObject *key) | |
| { | |
| Py_ssize_t ix; (void)ix; | |
| Py_hash_t hash; | |
| PyDictObject*mp = (PyDictObject *)op; | |
| PyObject *value; | |
| if (!PyAnyDict_Check(op)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(op, key); | |
| return NULL; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | |
| Py_XDECREF(value); | |
| #else | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| #endif | |
| assert(ix >= 0 || value == NULL); | |
| return value; // borrowed reference | |
| } | |
| PyObject * | |
| _PyDict_GetItemWithError(PyObject *dp, PyObject *kv) | |
| { | |
| assert(PyUnicode_CheckExact(kv)); | |
| Py_hash_t hash = Py_TYPE(kv)->tp_hash(kv); | |
| if (hash == -1) { | |
| return NULL; | |
| } | |
| return _PyDict_GetItem_KnownHash(dp, kv, hash); // borrowed reference | |
| } | |
| PyObject * | |
| _PyDict_GetItemStringWithError(PyObject *v, const char *key) | |
| { | |
| PyObject *kv, *rv; | |
| kv = PyUnicode_FromString(key); | |
| if (kv == NULL) { | |
| return NULL; | |
| } | |
| rv = PyDict_GetItemWithError(v, kv); | |
| Py_DECREF(kv); | |
| return rv; | |
| } | |
| PyObject * | |
| _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObject *key, _PyStackRef *res) | |
| { | |
| Py_ssize_t ix; | |
| Py_hash_t hash; | |
| hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| *res = PyStackRef_NULL; | |
| return NULL; | |
| } | |
| /* namespace 1: globals */ | |
| ix = _Py_dict_lookup_threadsafe_stackref(globals, key, hash, res); | |
| if (ix == DKIX_ERROR) { | |
| return NULL; | |
| } | |
| if (ix != DKIX_EMPTY && !PyStackRef_IsNull(*res)) { | |
| return (PyObject *)globals; | |
| } | |
| /* namespace 2: builtins */ | |
| ix = _Py_dict_lookup_threadsafe_stackref(builtins, key, hash, res); | |
| assert(ix >= 0 || PyStackRef_IsNull(*res)); | |
| return PyStackRef_IsNull(*res) ? NULL : (PyObject *)builtins; | |
| } | |
| PyObject * | |
| _PyDict_LoadBuiltinsFromGlobals(PyObject *globals) | |
| { | |
| if (!PyAnyDict_Check(globals)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| PyDictObject *mp = (PyDictObject *)globals; | |
| PyObject *key = &_Py_ID(__builtins__); | |
| Py_hash_t hash = unicode_get_hash(key); | |
| // Use the stackref variant to avoid reference count contention on the | |
| // builtins module in the free threading build. It's important not to | |
| // make any escaping calls between the lookup and the `PyStackRef_CLOSE()` | |
| // because the `ref` is not visible to the GC. | |
| _PyStackRef ref; | |
| Py_ssize_t ix = _Py_dict_lookup_threadsafe_stackref(mp, key, hash, &ref); | |
| if (ix == DKIX_ERROR) { | |
| return NULL; | |
| } | |
| if (PyStackRef_IsNull(ref)) { | |
| return Py_NewRef(PyEval_GetBuiltins()); | |
| } | |
| PyObject *builtins = PyStackRef_AsPyObjectBorrow(ref); | |
| if (PyModule_Check(builtins)) { | |
| builtins = _PyModule_GetDict(builtins); | |
| assert(builtins != NULL); | |
| } | |
| _Py_INCREF_BUILTINS(builtins); | |
| PyStackRef_CLOSE(ref); | |
| return builtins; | |
| } | |
| #define frozendict_does_not_support(WHAT) \ | |
| PyErr_SetString(PyExc_TypeError, "frozendict object does " \ | |
| "not support item " WHAT) | |
| /* Consumes references to key and value */ | |
| static int | |
| setitem_take2_lock_held_known_hash(PyDictObject *mp, PyObject *key, PyObject *value, Py_hash_t hash) | |
| { | |
| assert(PyAnyDict_Check(mp)); | |
| assert(can_modify_dict(mp)); | |
| assert(key); | |
| assert(value); | |
| if (mp->ma_keys == Py_EMPTY_KEYS) { | |
| return insert_to_emptydict(mp, key, hash, value); | |
| } | |
| /* insertdict() handles any resizing that might be necessary */ | |
| return insertdict(mp, key, hash, value); | |
| } | |
| static int | |
| setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) | |
| { | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type((PyObject*)mp, key); | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| return -1; | |
| } | |
| return setitem_take2_lock_held_known_hash(mp, key, value, hash); | |
| } | |
| int | |
| _PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(mp); | |
| res = setitem_take2_lock_held(mp, key, value); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| int | |
| _PyDict_SetItem_Take2_KnownHash(PyDictObject *mp, PyObject *key, PyObject *value, Py_hash_t hash) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(mp); | |
| res = setitem_take2_lock_held_known_hash(mp, key, value, hash); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| /* CAUTION: PyDict_SetItem() must guarantee that it won't resize the | |
| * dictionary if it's merely replacing the value for an existing key. | |
| * This means that it's safe to loop over a dictionary with PyDict_Next() | |
| * and occasionally replace a value -- but you can't insert new keys or | |
| * remove them. | |
| */ | |
| int | |
| PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) | |
| { | |
| assert(key); | |
| assert(value); | |
| if (!PyDict_Check(op)) { | |
| if (PyFrozenDict_Check(op)) { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| return _PyDict_SetItem_Take2((PyDictObject *)op, | |
| Py_NewRef(key), Py_NewRef(value)); | |
| } | |
| static int | |
| _PyAnyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) | |
| { | |
| assert(PyAnyDict_Check(op)); | |
| assert(key); | |
| assert(value); | |
| return _PyDict_SetItem_Take2((PyDictObject *)op, | |
| Py_NewRef(key), Py_NewRef(value)); | |
| } | |
| static int | |
| setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) | |
| { | |
| assert(key); | |
| assert(value); | |
| return setitem_take2_lock_held(mp, | |
| Py_NewRef(key), Py_NewRef(value)); | |
| } | |
| int | |
| _PyDict_SetItem_KnownHash_LockHeld(PyDictObject *mp, PyObject *key, PyObject *value, | |
| Py_hash_t hash) | |
| { | |
| if (mp->ma_keys == Py_EMPTY_KEYS) { | |
| return insert_to_emptydict(mp, Py_NewRef(key), hash, Py_NewRef(value)); | |
| } | |
| /* insertdict() handles any resizing that might be necessary */ | |
| return insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value)); | |
| } | |
| int | |
| _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, | |
| Py_hash_t hash) | |
| { | |
| assert(key); | |
| assert(value); | |
| assert(hash != -1); | |
| if (!PyDict_Check(op)) { | |
| if (PyFrozenDict_Check(op)) { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| res = _PyDict_SetItem_KnownHash_LockHeld((PyDictObject *)op, key, value, hash); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| static void | |
| delete_index_from_values(PyDictValues *values, Py_ssize_t ix) | |
| { | |
| uint8_t *array = get_insertion_order_array(values); | |
| int size = values->size; | |
| assert(size <= values->capacity); | |
| int i; | |
| for (i = 0; array[i] != ix; i++) { | |
| assert(i < size); | |
| } | |
| assert(i < size); | |
| size--; | |
| for (; i < size; i++) { | |
| array[i] = array[i+1]; | |
| } | |
| values->size = size; | |
| } | |
| static void | |
| delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, | |
| PyObject *old_value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| PyObject *old_key; | |
| Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); | |
| assert(hashpos >= 0); | |
| STORE_USED(mp, mp->ma_used - 1); | |
| if (_PyDict_HasSplitTable(mp)) { | |
| assert(old_value == mp->ma_values->values[ix]); | |
| STORE_SPLIT_VALUE(mp, ix, NULL); | |
| assert(ix < SHARED_KEYS_MAX_SIZE); | |
| /* Update order */ | |
| delete_index_from_values(mp->ma_values, ix); | |
| ASSERT_CONSISTENT(mp); | |
| } | |
| else { | |
| FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0); | |
| dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY); | |
| if (DK_IS_UNICODE(mp->ma_keys)) { | |
| PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[ix]; | |
| old_key = ep->me_key; | |
| STORE_KEY(ep, NULL); | |
| STORE_VALUE(ep, NULL); | |
| } | |
| else { | |
| PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix]; | |
| old_key = ep->me_key; | |
| STORE_KEY(ep, NULL); | |
| STORE_VALUE(ep, NULL); | |
| STORE_HASH(ep, 0); | |
| } | |
| Py_DECREF(old_key); | |
| } | |
| Py_DECREF(old_value); | |
| ASSERT_CONSISTENT(mp); | |
| } | |
| int | |
| PyDict_DelItem(PyObject *op, PyObject *key) | |
| { | |
| assert(key); | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(op, key); | |
| return -1; | |
| } | |
| return _PyDict_DelItem_KnownHash(op, key, hash); | |
| } | |
| int | |
| _PyDict_DelItem_KnownHash_LockHeld(PyObject *op, PyObject *key, Py_hash_t hash) | |
| { | |
| if (!PyDict_Check(op)) { | |
| if (PyFrozenDict_Check(op)) { | |
| frozendict_does_not_support("deletion"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| Py_ssize_t ix; | |
| PyObject *old_value; | |
| PyDictObject *mp = (PyDictObject *)op; | |
| assert(can_modify_dict(mp)); | |
| assert(key); | |
| assert(hash != -1); | |
| ix = _Py_dict_lookup(mp, key, hash, &old_value); | |
| if (ix == DKIX_ERROR) | |
| return -1; | |
| if (ix == DKIX_EMPTY || old_value == NULL) { | |
| _PyErr_SetKeyError(key); | |
| return -1; | |
| } | |
| _PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL); | |
| delitem_common(mp, hash, ix, old_value); | |
| return 0; | |
| } | |
| int | |
| _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| res = _PyDict_DelItem_KnownHash_LockHeld(op, key, hash); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| static int | |
| delitemif_lock_held(PyObject *op, PyObject *key, | |
| int (*predicate)(PyObject *value, void *arg), | |
| void *arg) | |
| { | |
| PyDictObject *mp = _PyAnyDict_CAST(op); | |
| assert(can_modify_dict(mp)); | |
| Py_ssize_t ix; | |
| Py_hash_t hash; | |
| PyObject *old_value; | |
| int res; | |
| assert(key); | |
| hash = PyObject_Hash(key); | |
| if (hash == -1) | |
| return -1; | |
| ix = _Py_dict_lookup(mp, key, hash, &old_value); | |
| if (ix == DKIX_ERROR) { | |
| return -1; | |
| } | |
| if (ix == DKIX_EMPTY || old_value == NULL) { | |
| return 0; | |
| } | |
| res = predicate(old_value, arg); | |
| if (res == -1) | |
| return -1; | |
| if (res > 0) { | |
| _PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL); | |
| delitem_common(mp, hash, ix, old_value); | |
| return 1; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| /* This function promises that the predicate -> deletion sequence is atomic | |
| * (i.e. protected by the GIL or the per-dict mutex in free threaded builds), | |
| * assuming the predicate itself doesn't release the GIL (or cause re-entrancy | |
| * which would release the per-dict mutex) | |
| */ | |
| int | |
| _PyDict_DelItemIf(PyObject *op, PyObject *key, | |
| int (*predicate)(PyObject *value, void *arg), | |
| void *arg) | |
| { | |
| assert(PyDict_Check(op)); | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| res = delitemif_lock_held(op, key, predicate, arg); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| int | |
| _PyDict_ReplaceItemIf(PyObject *op, PyObject *key, | |
| PyObject *expected, PyObject *replacement) | |
| { | |
| assert(PyDict_Check(op)); | |
| assert(expected != NULL); | |
| assert(replacement != NULL); | |
| Py_hash_t hash = PyObject_Hash(key); | |
| if (hash == -1) { | |
| return -1; | |
| } | |
| int result = 0; | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| PyDictObject *mp = (PyDictObject *)op; | |
| PyObject *current; | |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, ¤t); | |
| if (ix == DKIX_ERROR) { | |
| result = -1; | |
| } | |
| else if (current == expected) { | |
| // Do not look up the key again: equality can execute Python code. | |
| replace_value(mp, key, ix, current, Py_NewRef(replacement)); | |
| result = 1; | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| return result; | |
| } | |
| static void | |
| clear_embedded_values(PyDictValues *values, Py_ssize_t nentries) | |
| { | |
| PyObject *refs[SHARED_KEYS_MAX_SIZE]; | |
| assert(nentries <= SHARED_KEYS_MAX_SIZE); | |
| for (Py_ssize_t i = 0; i < nentries; i++) { | |
| refs[i] = values->values[i]; | |
| FT_ATOMIC_STORE_PTR_RELEASE(values->values[i], NULL); | |
| } | |
| values->size = 0; | |
| for (Py_ssize_t i = 0; i < nentries; i++) { | |
| Py_XDECREF(refs[i]); | |
| } | |
| } | |
| static void | |
| clear_lock_held(PyObject *op) | |
| { | |
| if (!PyDict_Check(op)) { | |
| return; | |
| } | |
| PyDictObject *mp = (PyDictObject *)op; | |
| assert(can_modify_dict(mp)); | |
| PyDictKeysObject *oldkeys; | |
| PyDictValues *oldvalues; | |
| Py_ssize_t i, n; | |
| oldkeys = mp->ma_keys; | |
| oldvalues = mp->ma_values; | |
| if (oldkeys == Py_EMPTY_KEYS) { | |
| return; | |
| } | |
| /* Empty the dict... */ | |
| _PyDict_NotifyEvent(PyDict_EVENT_CLEARED, mp, NULL, NULL); | |
| // We don't inc ref empty keys because they're immortal | |
| ensure_shared_on_resize(mp); | |
| STORE_USED(mp, 0); | |
| if (oldvalues == NULL) { | |
| set_keys(mp, Py_EMPTY_KEYS); | |
| assert(oldkeys->dk_refcnt == 1); | |
| dictkeys_decref(oldkeys, IS_DICT_SHARED(mp)); | |
| } | |
| else if (oldvalues->embedded) { | |
| clear_embedded_values(oldvalues, oldkeys->dk_nentries); | |
| } | |
| else { | |
| set_values(mp, NULL); | |
| set_keys(mp, Py_EMPTY_KEYS); | |
| n = oldkeys->dk_nentries; | |
| for (i = 0; i < n; i++) { | |
| PyObject *tmp = oldvalues->values[i]; | |
| FT_ATOMIC_STORE_PTR_RELEASE(oldvalues->values[i], NULL); | |
| Py_XDECREF(tmp); | |
| } | |
| free_values(oldvalues, IS_DICT_SHARED(mp)); | |
| dictkeys_decref(oldkeys, IS_DICT_SHARED(mp)); | |
| } | |
| ASSERT_CONSISTENT(mp); | |
| } | |
| void | |
| _PyDict_Clear_LockHeld(PyObject *op) { | |
| clear_lock_held(op); | |
| } | |
| void | |
| PyDict_Clear(PyObject *op) | |
| { | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| clear_lock_held(op); | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| /* Internal version of PyDict_Next that returns a hash value in addition | |
| * to the key and value. | |
| * Return 1 on success, return 0 when the reached the end of the dictionary | |
| * (or if op is not a dictionary) | |
| */ | |
| int | |
| _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, | |
| PyObject **pvalue, Py_hash_t *phash) | |
| { | |
| Py_ssize_t i; | |
| PyDictObject *mp; | |
| PyObject *key, *value; | |
| Py_hash_t hash; | |
| if (!PyAnyDict_Check(op)) | |
| return 0; | |
| mp = (PyDictObject *)op; | |
| i = *ppos; | |
| if (_PyDict_HasSplitTable(mp)) { | |
| assert(mp->ma_used <= SHARED_KEYS_MAX_SIZE); | |
| if (i < 0 || i >= mp->ma_used) | |
| return 0; | |
| int index = get_index_from_order(mp, i); | |
| value = mp->ma_values->values[index]; | |
| key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(mp->ma_keys)[index].me_key); | |
| hash = unicode_get_hash(key); | |
| assert(value != NULL); | |
| } | |
| else { | |
| Py_ssize_t n = mp->ma_keys->dk_nentries; | |
| if (i < 0 || i >= n) | |
| return 0; | |
| if (DK_IS_UNICODE(mp->ma_keys)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(mp->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| return 0; | |
| key = entry_ptr->me_key; | |
| hash = unicode_get_hash(entry_ptr->me_key); | |
| value = entry_ptr->me_value; | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(mp->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| return 0; | |
| key = entry_ptr->me_key; | |
| hash = entry_ptr->me_hash; | |
| value = entry_ptr->me_value; | |
| } | |
| } | |
| *ppos = i+1; | |
| if (pkey) | |
| *pkey = key; | |
| if (pvalue) | |
| *pvalue = value; | |
| if (phash) | |
| *phash = hash; | |
| return 1; | |
| } | |
| /* | |
| * Iterate over a dict. Use like so: | |
| * | |
| * Py_ssize_t i; | |
| * PyObject *key, *value; | |
| * i = 0; # important! i should not otherwise be changed by you | |
| * while (PyDict_Next(yourdict, &i, &key, &value)) { | |
| * Refer to borrowed references in key and value. | |
| * } | |
| * | |
| * Return 1 on success, return 0 when the reached the end of the dictionary | |
| * (or if op is not a dictionary) | |
| * | |
| * CAUTION: In general, it isn't safe to use PyDict_Next in a loop that | |
| * mutates the dict. One exception: it is safe if the loop merely changes | |
| * the values associated with the keys (but doesn't insert new keys or | |
| * delete keys), via PyDict_SetItem(). | |
| */ | |
| int | |
| PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue) | |
| { | |
| return _PyDict_Next(op, ppos, pkey, pvalue, NULL); | |
| } | |
| /* Internal version of dict.pop(). */ | |
| int | |
| _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, | |
| PyObject **result) | |
| { | |
| assert(PyDict_Check(mp)); | |
| assert(can_modify_dict(mp)); | |
| if (mp->ma_used == 0) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return 0; | |
| } | |
| PyObject *old_value; | |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value); | |
| if (ix == DKIX_ERROR) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| if (ix == DKIX_EMPTY || old_value == NULL) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return 0; | |
| } | |
| assert(old_value != NULL); | |
| _PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL); | |
| delitem_common(mp, hash, ix, Py_NewRef(old_value)); | |
| ASSERT_CONSISTENT(mp); | |
| if (result) { | |
| *result = old_value; | |
| } | |
| else { | |
| Py_DECREF(old_value); | |
| } | |
| return 1; | |
| } | |
| static int | |
| pop_lock_held(PyObject *op, PyObject *key, PyObject **result) | |
| { | |
| if (!PyDict_Check(op)) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| if (PyFrozenDict_Check(op)) { | |
| frozendict_does_not_support("deletion"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| PyDictObject *dict = (PyDictObject *)op; | |
| assert(can_modify_dict(dict)); | |
| if (dict->ma_used == 0) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return 0; | |
| } | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(op, key); | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| return _PyDict_Pop_KnownHash(dict, key, hash, result); | |
| } | |
| int | |
| PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) | |
| { | |
| int err; | |
| Py_BEGIN_CRITICAL_SECTION(op); | |
| err = pop_lock_held(op, key, result); | |
| Py_END_CRITICAL_SECTION(); | |
| return err; | |
| } | |
| int | |
| PyDict_PopString(PyObject *op, const char *key, PyObject **result) | |
| { | |
| PyObject *key_obj = PyUnicode_FromString(key); | |
| if (key_obj == NULL) { | |
| if (result != NULL) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| int res = PyDict_Pop(op, key_obj, result); | |
| Py_DECREF(key_obj); | |
| return res; | |
| } | |
| static PyObject * | |
| dict_pop_default(PyObject *dict, PyObject *key, PyObject *default_value) | |
| { | |
| PyObject *result; | |
| if (PyDict_Pop(dict, key, &result) == 0) { | |
| if (default_value != NULL) { | |
| return Py_NewRef(default_value); | |
| } | |
| _PyErr_SetKeyError(key); | |
| return NULL; | |
| } | |
| return result; | |
| } | |
| PyObject * | |
| _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value) | |
| { | |
| return dict_pop_default(dict, key, default_value); | |
| } | |
| static PyDictObject * | |
| dict_dict_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| PyObject *oldvalue; | |
| Py_ssize_t pos = 0; | |
| PyObject *key; | |
| Py_hash_t hash; | |
| int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); | |
| uint8_t log2_keysize = estimate_log2_keysize(PyDict_GET_SIZE(iterable)); | |
| uint8_t new_size = Py_MAX(log2_keysize, DK_LOG_SIZE(mp->ma_keys)); | |
| if (dictresize(mp, new_size, unicode)) { | |
| Py_DECREF(mp); | |
| return NULL; | |
| } | |
| while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { | |
| if (insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value))) { | |
| Py_DECREF(mp); | |
| return NULL; | |
| } | |
| } | |
| return mp; | |
| } | |
| static PyDictObject * | |
| dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value) | |
| { | |
| assert(can_modify_dict(mp)); | |
| Py_ssize_t pos = 0; | |
| PyObject *key; | |
| Py_hash_t hash; | |
| uint8_t log2_keysize = estimate_log2_keysize(PySet_GET_SIZE(iterable)); | |
| uint8_t new_size = Py_MAX(log2_keysize, DK_LOG_SIZE(mp->ma_keys)); | |
| if (dictresize(mp, new_size, 0)) { | |
| Py_DECREF(mp); | |
| return NULL; | |
| } | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(iterable); | |
| while (_PySet_NextEntryRef(iterable, &pos, &key, &hash)) { | |
| if (insertdict(mp, key, hash, Py_NewRef(value))) { | |
| Py_DECREF(mp); | |
| return NULL; | |
| } | |
| } | |
| return mp; | |
| } | |
| /* Internal version of dict.from_keys(). It is subclass-friendly. */ | |
| PyObject * | |
| _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) | |
| { | |
| PyObject *it = NULL; /* iter(iterable) */ | |
| PyObject *d; | |
| int need_copy = 0; | |
| if (cls == (PyObject*)&PyFrozenDict_Type) { | |
| // gh-151722: Create a frozendict which is not tracked by the GC. | |
| d = frozendict_new_untracked(&PyFrozenDict_Type); | |
| } | |
| else { | |
| // Dict subclass, or frozendict subclass which overrides | |
| // the constructor. | |
| d = _PyObject_CallNoArgs(cls); | |
| } | |
| if (d == NULL) { | |
| return NULL; | |
| } | |
| // gh-151722: If cls constructor returns a frozendict which is tracked by | |
| // the GC, create a frozendict copy which is not tracked by the GC. | |
| // | |
| // At the function exit, return cls(fd) where fd is a frozendict. | |
| // | |
| // Untracking the frozendict requires tracking again the frozendict on | |
| // error which is more complicated. It's easier to work on a copy. | |
| if (PyFrozenDict_Check(d) && _PyObject_GC_IS_TRACKED(d)) { | |
| need_copy = 1; | |
| PyObject *copy = frozendict_new_untracked(&PyFrozenDict_Type); | |
| if (copy == NULL) { | |
| goto Fail; | |
| } | |
| if (dict_merge(copy, d, 1, NULL) < 0) { | |
| Py_DECREF(copy); | |
| goto Fail; | |
| } | |
| Py_SETREF(d, copy); | |
| } | |
| if (PyFrozenDict_Check(d)) { | |
| assert(can_modify_dict((PyDictObject*)d)); | |
| } | |
| if (PyDict_CheckExact(d)) { | |
| if (PyDict_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| Py_BEGIN_CRITICAL_SECTION2(d, iterable); | |
| d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); | |
| Py_END_CRITICAL_SECTION2(); | |
| goto Done; | |
| } | |
| else if (PyFrozenDict_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); | |
| Py_END_CRITICAL_SECTION(); | |
| goto Done; | |
| } | |
| else if (PyAnySet_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| Py_BEGIN_CRITICAL_SECTION2(d, iterable); | |
| d = (PyObject *)dict_set_fromkeys(mp, iterable, value); | |
| Py_END_CRITICAL_SECTION2(); | |
| goto Done; | |
| } | |
| } | |
| else if (PyFrozenDict_CheckExact(d)) { | |
| if (PyDict_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| Py_BEGIN_CRITICAL_SECTION(iterable); | |
| d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); | |
| Py_END_CRITICAL_SECTION(); | |
| goto Done; | |
| } | |
| else if (PyFrozenDict_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); | |
| goto Done; | |
| } | |
| else if (PyAnySet_CheckExact(iterable)) { | |
| PyDictObject *mp = (PyDictObject *)d; | |
| Py_BEGIN_CRITICAL_SECTION(iterable); | |
| d = (PyObject *)dict_set_fromkeys(mp, iterable, value); | |
| Py_END_CRITICAL_SECTION(); | |
| goto Done; | |
| } | |
| } | |
| it = PyObject_GetIter(iterable); | |
| if (it == NULL){ | |
| goto Fail; | |
| } | |
| if (PyDict_CheckExact(d)) { | |
| int status = 0; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| while (1) { | |
| PyObject *key; | |
| status = PyIter_NextItem(it, &key); | |
| if (status <= 0) { | |
| break; | |
| } | |
| status = setitem_lock_held((PyDictObject *)d, key, value); | |
| Py_DECREF(key); | |
| if (status < 0) { | |
| break; | |
| } | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| if (status < 0) { | |
| goto Fail; | |
| } | |
| } | |
| else if (PyFrozenDict_Check(d)) { | |
| while (1) { | |
| PyObject *key; | |
| int status = PyIter_NextItem(it, &key); | |
| if (status < 0) { | |
| goto Fail; | |
| } | |
| if (status == 0) { | |
| break; | |
| } | |
| // setitem_take2_lock_held consumes a reference to key | |
| status = setitem_take2_lock_held((PyDictObject *)d, | |
| key, Py_NewRef(value)); | |
| if (status < 0) { | |
| goto Fail; | |
| } | |
| } | |
| } | |
| else { | |
| while (1) { | |
| PyObject *key; | |
| int status = PyIter_NextItem(it, &key); | |
| if (status < 0) { | |
| goto Fail; | |
| } | |
| if (status == 0) { | |
| break; | |
| } | |
| status = PyObject_SetItem(d, key, value); | |
| Py_DECREF(key); | |
| if (status < 0) { | |
| goto Fail; | |
| } | |
| } | |
| } | |
| assert(!PyErr_Occurred()); | |
| Py_DECREF(it); | |
| goto Done; | |
| Fail: | |
| assert(PyErr_Occurred()); | |
| Py_XDECREF(it); | |
| Py_DECREF(d); | |
| return NULL; | |
| Done: | |
| if (d == NULL) { | |
| return NULL; | |
| } | |
| if (need_copy) { | |
| PyObject *copy = _PyObject_CallOneArg(cls, d); | |
| Py_SETREF(d, copy); | |
| } | |
| else if (!_PyObject_GC_IS_TRACKED(d)) { | |
| _PyObject_GC_TRACK(d); | |
| } | |
| return d; | |
| } | |
| /* Methods */ | |
| static void | |
| dict_dealloc(PyObject *self) | |
| { | |
| PyDictObject *mp = (PyDictObject *)self; | |
| _PyObject_ResurrectStart(self); | |
| _PyDict_NotifyEvent(PyDict_EVENT_DEALLOCATED, mp, NULL, NULL); | |
| if (_PyObject_ResurrectEnd(self)) { | |
| return; | |
| } | |
| PyDictValues *values = mp->ma_values; | |
| PyDictKeysObject *keys = mp->ma_keys; | |
| Py_ssize_t i, n; | |
| /* bpo-31095: UnTrack is needed before calling any callbacks */ | |
| PyObject_GC_UnTrack(mp); | |
| if (values != NULL) { | |
| if (values->embedded == 0) { | |
| for (i = 0, n = values->capacity; i < n; i++) { | |
| Py_XDECREF(values->values[i]); | |
| } | |
| free_values(values, false); | |
| } | |
| dictkeys_decref(keys, false); | |
| } | |
| else if (keys != NULL) { | |
| assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); | |
| dictkeys_decref(keys, false); | |
| } | |
| if (Py_IS_TYPE(mp, &PyDict_Type)) { | |
| _Py_FREELIST_FREE(dicts, mp, Py_TYPE(mp)->tp_free); | |
| } | |
| else { | |
| Py_TYPE(mp)->tp_free((PyObject *)mp); | |
| } | |
| } | |
| static PyObject * | |
| anydict_repr_impl(PyObject *self) | |
| { | |
| PyDictObject *mp = (PyDictObject *)self; | |
| PyObject *key = NULL, *value = NULL; | |
| int res = Py_ReprEnter(self); | |
| if (res != 0) { | |
| return (res > 0 ? PyUnicode_FromString("{...}") : NULL); | |
| } | |
| if (mp->ma_used == 0) { | |
| Py_ReprLeave(self); | |
| return PyUnicode_FromString("{}"); | |
| } | |
| // "{" + "1: 2" + ", 3: 4" * (len - 1) + "}" | |
| Py_ssize_t prealloc = 1 + 4 + 6 * (mp->ma_used - 1) + 1; | |
| PyUnicodeWriter *writer = PyUnicodeWriter_Create(prealloc); | |
| if (writer == NULL) { | |
| goto error; | |
| } | |
| if (PyUnicodeWriter_WriteChar(writer, '{') < 0) { | |
| goto error; | |
| } | |
| /* Do repr() on each key+value pair, and insert ": " between them. | |
| Note that repr may mutate the dict. */ | |
| Py_ssize_t i = 0; | |
| int first = 1; | |
| while (_PyDict_Next(self, &i, &key, &value, NULL)) { | |
| // Prevent repr from deleting key or value during key format. | |
| Py_INCREF(key); | |
| Py_INCREF(value); | |
| if (!first) { | |
| // Write ", " | |
| if (PyUnicodeWriter_WriteChar(writer, ',') < 0) { | |
| goto error; | |
| } | |
| if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) { | |
| goto error; | |
| } | |
| } | |
| first = 0; | |
| // Write repr(key) | |
| if (PyUnicodeWriter_WriteRepr(writer, key) < 0) { | |
| goto error; | |
| } | |
| // Write ": " | |
| if (PyUnicodeWriter_WriteChar(writer, ':') < 0) { | |
| goto error; | |
| } | |
| if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) { | |
| goto error; | |
| } | |
| // Write repr(value) | |
| if (PyUnicodeWriter_WriteRepr(writer, value) < 0) { | |
| goto error; | |
| } | |
| Py_CLEAR(key); | |
| Py_CLEAR(value); | |
| } | |
| if (PyUnicodeWriter_WriteChar(writer, '}') < 0) { | |
| goto error; | |
| } | |
| Py_ReprLeave(self); | |
| return PyUnicodeWriter_Finish(writer); | |
| error: | |
| Py_ReprLeave(self); | |
| PyUnicodeWriter_Discard(writer); | |
| Py_XDECREF(key); | |
| Py_XDECREF(value); | |
| return NULL; | |
| } | |
| static PyObject * | |
| dict_repr_lock_held(PyObject *self) | |
| { | |
| ASSERT_DICT_LOCKED((PyDictObject *)self); | |
| return anydict_repr_impl(self); | |
| } | |
| static PyObject * | |
| dict_repr(PyObject *self) | |
| { | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION(self); | |
| res = dict_repr_lock_held(self); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| static Py_ssize_t | |
| dict_length(PyObject *self) | |
| { | |
| return GET_USED(_PyAnyDict_CAST(self)); | |
| } | |
| static Py_ssize_t | |
| frozendict_length(PyObject *self) | |
| { | |
| return _PyAnyDict_CAST(self)->ma_used; | |
| } | |
| PyObject * | |
| _PyDict_SubscriptKnownHash(PyObject *self, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictObject *mp = (PyDictObject *)self; | |
| Py_ssize_t ix; | |
| PyObject *value; | |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | |
| if (ix == DKIX_ERROR) | |
| return NULL; | |
| if (ix == DKIX_EMPTY || value == NULL) { | |
| if (!PyAnyDict_CheckExact(mp)) { | |
| /* Look up __missing__ method if we're a subclass. */ | |
| PyObject *missing, *res; | |
| missing = _PyObject_LookupSpecial( | |
| (PyObject *)mp, &_Py_ID(__missing__)); | |
| if (missing != NULL) { | |
| res = PyObject_CallOneArg(missing, key); | |
| Py_DECREF(missing); | |
| return res; | |
| } | |
| else if (PyErr_Occurred()) | |
| return NULL; | |
| } | |
| _PyErr_SetKeyError(key); | |
| return NULL; | |
| } | |
| return value; | |
| } | |
| PyObject * | |
| _PyDict_Subscript(PyObject *self, PyObject *key) | |
| { | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(self, key); | |
| return NULL; | |
| } | |
| return _PyDict_SubscriptKnownHash(self, key, hash); | |
| } | |
| int | |
| _PyDict_StoreSubscript(PyObject *mp, PyObject *v, PyObject *w) | |
| { | |
| if (w == NULL) | |
| return PyDict_DelItem(mp, v); | |
| else | |
| return PyDict_SetItem(mp, v, w); | |
| } | |
| static PyMappingMethods dict_as_mapping = { | |
| dict_length, /*mp_length*/ | |
| _PyDict_Subscript, /*mp_subscript*/ | |
| _PyDict_StoreSubscript, /*mp_ass_subscript*/ | |
| }; | |
| static PyObject * | |
| keys_lock_held(PyObject *dict) | |
| { | |
| ASSERT_DICT_LOCKED(dict); | |
| if (dict == NULL || !PyAnyDict_Check(dict)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| PyDictObject *mp = (PyDictObject *)dict; | |
| PyObject *v; | |
| Py_ssize_t n; | |
| again: | |
| n = mp->ma_used; | |
| v = PyList_New(n); | |
| if (v == NULL) | |
| return NULL; | |
| if (n != mp->ma_used) { | |
| /* Durnit. The allocations caused the dict to resize. | |
| * Just start over, this shouldn't normally happen. | |
| */ | |
| Py_DECREF(v); | |
| goto again; | |
| } | |
| /* Nothing we do below makes any function calls. */ | |
| Py_ssize_t j = 0, pos = 0; | |
| PyObject *key; | |
| while (_PyDict_Next((PyObject*)mp, &pos, &key, NULL, NULL)) { | |
| assert(j < n); | |
| PyList_SET_ITEM(v, j, Py_NewRef(key)); | |
| j++; | |
| } | |
| assert(j == n); | |
| return v; | |
| } | |
| PyObject * | |
| PyDict_Keys(PyObject *dict) | |
| { | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| res = keys_lock_held(dict); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| static PyObject * | |
| values_lock_held(PyObject *dict) | |
| { | |
| ASSERT_DICT_LOCKED(dict); | |
| if (dict == NULL || !PyAnyDict_Check(dict)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| PyDictObject *mp = (PyDictObject *)dict; | |
| PyObject *v; | |
| Py_ssize_t n; | |
| again: | |
| n = mp->ma_used; | |
| v = PyList_New(n); | |
| if (v == NULL) | |
| return NULL; | |
| if (n != mp->ma_used) { | |
| /* Durnit. The allocations caused the dict to resize. | |
| * Just start over, this shouldn't normally happen. | |
| */ | |
| Py_DECREF(v); | |
| goto again; | |
| } | |
| /* Nothing we do below makes any function calls. */ | |
| Py_ssize_t j = 0, pos = 0; | |
| PyObject *value; | |
| while (_PyDict_Next((PyObject*)mp, &pos, NULL, &value, NULL)) { | |
| assert(j < n); | |
| PyList_SET_ITEM(v, j, Py_NewRef(value)); | |
| j++; | |
| } | |
| assert(j == n); | |
| return v; | |
| } | |
| PyObject * | |
| PyDict_Values(PyObject *dict) | |
| { | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| res = values_lock_held(dict); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| static PyObject * | |
| items_lock_held(PyObject *dict) | |
| { | |
| ASSERT_DICT_LOCKED(dict); | |
| if (dict == NULL || !PyAnyDict_Check(dict)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| PyDictObject *mp = (PyDictObject *)dict; | |
| PyObject *v; | |
| Py_ssize_t i, n; | |
| PyObject *item; | |
| /* Preallocate the list of tuples, to avoid allocations during | |
| * the loop over the items, which could trigger GC, which | |
| * could resize the dict. :-( | |
| */ | |
| again: | |
| n = mp->ma_used; | |
| v = PyList_New(n); | |
| if (v == NULL) | |
| return NULL; | |
| for (i = 0; i < n; i++) { | |
| item = PyTuple_New(2); | |
| if (item == NULL) { | |
| Py_DECREF(v); | |
| return NULL; | |
| } | |
| PyList_SET_ITEM(v, i, item); | |
| } | |
| if (n != mp->ma_used) { | |
| /* Durnit. The allocations caused the dict to resize. | |
| * Just start over, this shouldn't normally happen. | |
| */ | |
| Py_DECREF(v); | |
| goto again; | |
| } | |
| /* Nothing we do below makes any function calls. */ | |
| Py_ssize_t j = 0, pos = 0; | |
| PyObject *key, *value; | |
| while (_PyDict_Next((PyObject*)mp, &pos, &key, &value, NULL)) { | |
| assert(j < n); | |
| PyObject *item = PyList_GET_ITEM(v, j); | |
| PyTuple_SET_ITEM(item, 0, Py_NewRef(key)); | |
| PyTuple_SET_ITEM(item, 1, Py_NewRef(value)); | |
| j++; | |
| } | |
| assert(j == n); | |
| return v; | |
| } | |
| PyObject * | |
| PyDict_Items(PyObject *dict) | |
| { | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| res = items_lock_held(dict); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| /*[clinic input] | |
| @permit_long_summary | |
| @classmethod | |
| dict.fromkeys | |
| iterable: object | |
| value: object=None | |
| / | |
| Create a new dictionary with keys from iterable and values set to value. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value) | |
| /*[clinic end generated code: output=8fb98e4b10384999 input=3903715eb48b287e]*/ | |
| { | |
| return _PyDict_FromKeys((PyObject *)type, iterable, value); | |
| } | |
| /* Single-arg dict update; used by dict_update_common and operators. */ | |
| static int | |
| dict_update_arg(PyObject *self, PyObject *arg) | |
| { | |
| if (PyAnyDict_CheckExact(arg)) { | |
| return dict_merge(self, arg, 1, NULL); | |
| } | |
| int has_keys = PyObject_HasAttrWithError(arg, &_Py_ID(keys)); | |
| if (has_keys < 0) { | |
| return -1; | |
| } | |
| if (has_keys) { | |
| return dict_merge(self, arg, 1, NULL); | |
| } | |
| return dict_merge_from_seq2(self, arg, 1); | |
| } | |
| static int | |
| dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, | |
| const char *methname) | |
| { | |
| PyObject *arg = NULL; | |
| int result = 0; | |
| if (!PyArg_UnpackTuple(args, methname, 0, 1, &arg)) { | |
| result = -1; | |
| } | |
| else if (arg != NULL) { | |
| result = dict_update_arg(self, arg); | |
| } | |
| if (result == 0 && kwds != NULL) { | |
| if (PyArg_ValidateKeywordArguments(kwds)) | |
| result = dict_merge(self, kwds, 1, NULL); | |
| else | |
| result = -1; | |
| } | |
| return result; | |
| } | |
| /* Note: dict.update() uses the METH_VARARGS|METH_KEYWORDS calling convention. | |
| Using METH_FASTCALL|METH_KEYWORDS would make dict.update(**dict2) calls | |
| slower, see the issue #29312. */ | |
| static PyObject * | |
| dict_update(PyObject *self, PyObject *args, PyObject *kwds) | |
| { | |
| if (dict_update_common(self, args, kwds, "update") != -1) | |
| Py_RETURN_NONE; | |
| return NULL; | |
| } | |
| /* Update unconditionally replaces existing items. | |
| Merge has a 3rd argument 'override'; if set, it acts like Update, | |
| otherwise it leaves existing items unchanged. | |
| PyDict_{Update,Merge} update/merge from a mapping object. | |
| PyDict_MergeFromSeq2 updates/merges from any iterable object | |
| producing iterable objects of length 2. | |
| */ | |
| static int | |
| merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override) | |
| { | |
| PyObject *it; /* iter(seq2) */ | |
| Py_ssize_t i; /* index into seq2 of current element */ | |
| PyObject *item; /* seq2[i] */ | |
| PyObject *fast; /* item as a 2-tuple or 2-list */ | |
| assert(d != NULL); | |
| assert(PyAnyDict_Check(d)); | |
| assert(seq2 != NULL); | |
| assert(can_modify_dict((PyDictObject*)d)); | |
| it = PyObject_GetIter(seq2); | |
| if (it == NULL) | |
| return -1; | |
| for (i = 0; ; ++i) { | |
| PyObject *key, *value; | |
| Py_ssize_t n; | |
| fast = NULL; | |
| item = PyIter_Next(it); | |
| if (item == NULL) { | |
| if (PyErr_Occurred()) | |
| goto Fail; | |
| break; | |
| } | |
| /* Convert item to sequence, and verify length 2. */ | |
| fast = PySequence_Fast(item, "object is not iterable"); | |
| if (fast == NULL) { | |
| if (PyErr_ExceptionMatches(PyExc_TypeError)) { | |
| _PyErr_FormatNote( | |
| "Cannot convert dictionary update " | |
| "sequence element #%zd to a sequence", | |
| i); | |
| } | |
| goto Fail; | |
| } | |
| n = PySequence_Fast_GET_SIZE(fast); | |
| if (n != 2) { | |
| PyErr_Format(PyExc_ValueError, | |
| "dictionary update sequence element #%zd " | |
| "has length %zd; 2 is required", | |
| i, n); | |
| goto Fail; | |
| } | |
| /* Update/merge with this (key, value) pair. */ | |
| key = PySequence_Fast_GET_ITEM(fast, 0); | |
| value = PySequence_Fast_GET_ITEM(fast, 1); | |
| Py_INCREF(key); | |
| Py_INCREF(value); | |
| if (override) { | |
| if (setitem_lock_held((PyDictObject *)d, key, value) < 0) { | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| goto Fail; | |
| } | |
| } | |
| else { | |
| if (dict_setdefault_ref_lock_held(d, key, value, NULL, 0) < 0) { | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| goto Fail; | |
| } | |
| } | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| Py_DECREF(fast); | |
| Py_DECREF(item); | |
| } | |
| i = 0; | |
| ASSERT_CONSISTENT(d); | |
| goto Return; | |
| Fail: | |
| Py_XDECREF(item); | |
| Py_XDECREF(fast); | |
| i = -1; | |
| Return: | |
| Py_DECREF(it); | |
| return Py_SAFE_DOWNCAST(i, Py_ssize_t, int); | |
| } | |
| static int | |
| dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| res = merge_from_seq2_lock_held(d, seq2, override); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| int | |
| PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) | |
| { | |
| assert(d != NULL); | |
| assert(seq2 != NULL); | |
| if (!PyDict_Check(d)) { | |
| if (PyFrozenDict_Check(d)) { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| return dict_merge_from_seq2(d, seq2, override); | |
| } | |
| static int | |
| dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject **dupkey) | |
| { | |
| assert(can_modify_dict(mp)); | |
| ASSERT_DICT_LOCKED(other); | |
| if (other == mp || other->ma_used == 0) | |
| /* a.update(a) or a.update({}); nothing to do */ | |
| return 0; | |
| if (mp->ma_used == 0) { | |
| /* Since the target dict is empty, _PyDict_Contains_KnownHash() | |
| * always returns 0. Setting override to 1 | |
| * skips the unnecessary test. | |
| */ | |
| override = 1; | |
| PyDictKeysObject *okeys = other->ma_keys; | |
| // If other is clean, combined, and just allocated, just clone it. | |
| if (mp->ma_values == NULL && | |
| other->ma_values == NULL && | |
| other->ma_used == okeys->dk_nentries && | |
| (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE || | |
| USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used) | |
| ) { | |
| _PyDict_NotifyEvent(PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL); | |
| PyDictKeysObject *keys = clone_combined_dict_keys(other); | |
| if (keys == NULL) | |
| return -1; | |
| ensure_shared_on_resize(mp); | |
| dictkeys_decref(mp->ma_keys, IS_DICT_SHARED(mp)); | |
| set_keys(mp, keys); | |
| STORE_USED(mp, other->ma_used); | |
| ASSERT_CONSISTENT(mp); | |
| return 0; | |
| } | |
| } | |
| /* Do one big resize at the start, rather than | |
| * incrementally resizing as we insert new items. Expect | |
| * that there will be no (or few) overlapping keys. | |
| */ | |
| if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) { | |
| int unicode = DK_IS_UNICODE(other->ma_keys); | |
| if (dictresize(mp, estimate_log2_keysize(mp->ma_used + other->ma_used), | |
| unicode)) { | |
| return -1; | |
| } | |
| } | |
| Py_ssize_t orig_size = other->ma_used; | |
| Py_ssize_t pos = 0; | |
| Py_hash_t hash; | |
| PyObject *key, *value; | |
| while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { | |
| int err = 0; | |
| if (override == 1) { | |
| err = insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value)); | |
| } | |
| else { | |
| Py_INCREF(key); | |
| Py_INCREF(value); | |
| err = _PyDict_Contains_KnownHash((PyObject *)mp, key, hash); | |
| if (err == 0) { | |
| err = insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value)); | |
| } | |
| else if (err > 0) { | |
| if (dupkey != NULL) { | |
| *dupkey = key; | |
| Py_DECREF(value); | |
| return -2; | |
| } | |
| err = 0; | |
| } | |
| Py_DECREF(value); | |
| Py_DECREF(key); | |
| } | |
| if (err != 0) | |
| return -1; | |
| if (orig_size != other->ma_used) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dict mutated during update"); | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int | |
| dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey) | |
| { | |
| assert(a != NULL); | |
| assert(b != NULL); | |
| assert(0 <= override && override <= 2); | |
| PyDictObject *mp = _PyAnyDict_CAST(a); | |
| int res = 0; | |
| if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) { | |
| PyDictObject *other = (PyDictObject*)b; | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION2(a, b); | |
| assert(can_modify_dict(mp)); | |
| res = dict_dict_merge((PyDictObject *)a, other, override, dupkey); | |
| ASSERT_CONSISTENT(a); | |
| Py_END_CRITICAL_SECTION2(); | |
| return res; | |
| } | |
| else { | |
| /* Do it the generic, slower way */ | |
| Py_BEGIN_CRITICAL_SECTION(a); | |
| assert(can_modify_dict(mp)); | |
| PyObject *keys = PyMapping_Keys(b); | |
| PyObject *iter; | |
| PyObject *key, *value; | |
| int status; | |
| if (keys == NULL) { | |
| /* Docstring says this is equivalent to E.keys() so | |
| * if E doesn't have a .keys() method we want | |
| * AttributeError to percolate up. Might as well | |
| * do the same for any other error. | |
| */ | |
| res = -1; | |
| goto slow_exit; | |
| } | |
| iter = PyObject_GetIter(keys); | |
| Py_DECREF(keys); | |
| if (iter == NULL) { | |
| res = -1; | |
| goto slow_exit; | |
| } | |
| for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) { | |
| if (override != 1) { | |
| status = dict_contains(a, key); | |
| if (status != 0) { | |
| if (status > 0) { | |
| if (dupkey == NULL) { | |
| Py_DECREF(key); | |
| continue; | |
| } | |
| *dupkey = key; | |
| res = -2; | |
| } | |
| else { | |
| Py_DECREF(key); | |
| res = -1; | |
| } | |
| Py_DECREF(iter); | |
| goto slow_exit; | |
| } | |
| } | |
| value = PyObject_GetItem(b, key); | |
| if (value == NULL) { | |
| Py_DECREF(iter); | |
| Py_DECREF(key); | |
| res = -1; | |
| goto slow_exit; | |
| } | |
| status = setitem_lock_held(mp, key, value); | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| if (status < 0) { | |
| Py_DECREF(iter); | |
| res = -1; | |
| goto slow_exit; | |
| return -1; | |
| } | |
| } | |
| Py_DECREF(iter); | |
| if (PyErr_Occurred()) { | |
| /* Iterator completed, via error */ | |
| res = -1; | |
| goto slow_exit; | |
| } | |
| slow_exit: | |
| ASSERT_CONSISTENT(a); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| } | |
| static int | |
| dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey) | |
| { | |
| /* We accept for the argument either a concrete dictionary object, | |
| * or an abstract "mapping" object. For the former, we can do | |
| * things quite efficiently. For the latter, we only require that | |
| * PyMapping_Keys() and PyObject_GetItem() be supported. | |
| */ | |
| if (a == NULL || !PyDict_Check(a) || b == NULL) { | |
| if (a != NULL && PyFrozenDict_Check(a)) { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| int res = dict_merge(a, b, override, dupkey); | |
| assert(_PyObject_GC_IS_TRACKED(a)); | |
| return res; | |
| } | |
| int | |
| PyDict_Update(PyObject *a, PyObject *b) | |
| { | |
| return dict_merge_api(a, b, 1, NULL); | |
| } | |
| int | |
| PyDict_Merge(PyObject *a, PyObject *b, int override) | |
| { | |
| /* XXX Deprecate override not in (0, 1). */ | |
| return dict_merge_api(a, b, override != 0, NULL); | |
| } | |
| int | |
| _PyDict_MergeUniq(PyObject *a, PyObject *b, PyObject **dupkey) | |
| { | |
| return dict_merge_api(a, b, 2, dupkey); | |
| } | |
| /*[clinic input] | |
| dict.copy | |
| Return a shallow copy of the dict. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_copy_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=ffb782cf970a5c39 input=73935f042b639de4]*/ | |
| { | |
| return PyDict_Copy((PyObject *)self); | |
| } | |
| /* Copies the values, but does not change the reference | |
| * counts of the objects in the array. | |
| * Return NULL, but does *not* set an exception on failure */ | |
| static PyDictValues * | |
| copy_values(PyDictValues *values) | |
| { | |
| PyDictValues *newvalues = new_values(values->capacity); | |
| if (newvalues == NULL) { | |
| return NULL; | |
| } | |
| newvalues->size = values->size; | |
| uint8_t *values_order = get_insertion_order_array(values); | |
| uint8_t *new_values_order = get_insertion_order_array(newvalues); | |
| memcpy(new_values_order, values_order, values->capacity); | |
| for (int i = 0; i < values->capacity; i++) { | |
| newvalues->values[i] = values->values[i]; | |
| } | |
| assert(newvalues->embedded == 0); | |
| return newvalues; | |
| } | |
| static PyObject * | |
| copy_lock_held_untracked(PyObject *o, int as_frozendict) | |
| { | |
| PyObject *copy; | |
| PyDictObject *mp; | |
| // frozendict is immutable and so doesn't need critical section | |
| if (!PyFrozenDict_Check(o)) { | |
| ASSERT_DICT_LOCKED(o); | |
| } | |
| mp = (PyDictObject *)o; | |
| if (mp->ma_used == 0) { | |
| /* The dict is empty; just return a new dict. */ | |
| PyObject *d; | |
| if (as_frozendict) { | |
| d = frozendict_new_untracked(&PyFrozenDict_Type); | |
| } | |
| else { | |
| d = dict_new_untracked(&PyDict_Type); | |
| } | |
| assert(d == NULL || !_PyObject_GC_IS_TRACKED(d)); | |
| return d; | |
| } | |
| if (_PyDict_HasSplitTable(mp)) { | |
| PyDictObject *split_copy; | |
| PyDictValues *newvalues = copy_values(mp->ma_values); | |
| if (newvalues == NULL) { | |
| return PyErr_NoMemory(); | |
| } | |
| if (as_frozendict) { | |
| split_copy = (PyDictObject *)PyObject_GC_New(PyFrozenDictObject, | |
| &PyFrozenDict_Type); | |
| } | |
| else { | |
| split_copy = PyObject_GC_New(PyDictObject, &PyDict_Type); | |
| } | |
| if (split_copy == NULL) { | |
| free_values(newvalues, false); | |
| return NULL; | |
| } | |
| for (size_t i = 0; i < newvalues->capacity; i++) { | |
| Py_XINCREF(newvalues->values[i]); | |
| } | |
| split_copy->ma_values = newvalues; | |
| split_copy->ma_keys = mp->ma_keys; | |
| split_copy->ma_used = mp->ma_used; | |
| split_copy->_ma_watcher_tag = 0; | |
| dictkeys_incref(mp->ma_keys); | |
| if (as_frozendict) { | |
| PyFrozenDictObject *frozen = (PyFrozenDictObject *)split_copy; | |
| frozen->ma_hash = -1; | |
| } | |
| assert(!_PyObject_GC_IS_TRACKED(split_copy)); | |
| return (PyObject *)split_copy; | |
| } | |
| if (Py_TYPE(mp)->tp_iter == dict_iter && | |
| mp->ma_values == NULL && | |
| (mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3)) | |
| { | |
| /* Use fast-copy if: | |
| (1) type(mp) doesn't override tp_iter; and | |
| (2) 'mp' is not a split-dict; and | |
| (3) if 'mp' is non-compact ('del' operation does not resize dicts), | |
| do fast-copy only if it has at most 1/3 non-used keys. | |
| The last condition (3) is important to guard against a pathological | |
| case when a large dict is almost emptied with multiple del/pop | |
| operations and copied after that. In cases like this, we defer to | |
| PyDict_Merge, which produces a compacted copy. | |
| */ | |
| PyDictKeysObject *keys = clone_combined_dict_keys(mp); | |
| if (keys == NULL) { | |
| return NULL; | |
| } | |
| PyDictObject *new; | |
| if (as_frozendict) { | |
| new = (PyDictObject *)new_frozendict_untracked(keys, NULL, 0, 0); | |
| } | |
| else { | |
| new = (PyDictObject *)new_dict_untracked(keys, NULL, 0, 0); | |
| } | |
| if (new == NULL) { | |
| /* In case of an error, new_dict()/new_frozendict() takes care of | |
| cleaning up `keys`. */ | |
| return NULL; | |
| } | |
| new->ma_used = mp->ma_used; | |
| ASSERT_CONSISTENT(new); | |
| assert(!_PyObject_GC_IS_TRACKED(new)); | |
| return (PyObject *)new; | |
| } | |
| if (as_frozendict) { | |
| copy = frozendict_new_untracked(&PyFrozenDict_Type); | |
| } | |
| else { | |
| copy = dict_new_untracked(&PyDict_Type); | |
| } | |
| if (copy == NULL) | |
| return NULL; | |
| if (dict_merge(copy, o, 1, NULL) < 0) { | |
| Py_DECREF(copy); | |
| return NULL; | |
| } | |
| assert(!_PyObject_GC_IS_TRACKED(copy)); | |
| return copy; | |
| } | |
| PyObject * | |
| PyDict_Copy(PyObject *o) | |
| { | |
| if (o == NULL || !PyDict_Check(o)) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION(o); | |
| res = copy_lock_held_untracked(o, 0); | |
| Py_END_CRITICAL_SECTION(); | |
| if (res != NULL) { | |
| _PyObject_GC_TRACK(res); | |
| } | |
| return res; | |
| } | |
| // Similar to PyDict_Copy(), but return a frozendict if the argument | |
| // is a frozendict. | |
| static PyObject * | |
| anydict_copy_untracked(PyObject *o) | |
| { | |
| assert(PyAnyDict_Check(o)); | |
| PyObject *res; | |
| if (PyFrozenDict_Check(o)) { | |
| res = copy_lock_held_untracked(o, 1); | |
| } | |
| else { | |
| Py_BEGIN_CRITICAL_SECTION(o); | |
| res = copy_lock_held_untracked(o, 0); | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| return res; | |
| } | |
| // Similar to PyDict_Copy(), but accept also frozendict: | |
| // convert frozendict to a new dict. | |
| PyObject* | |
| _PyDict_CopyAsDict(PyObject *o) | |
| { | |
| assert(PyAnyDict_Check(o)); | |
| PyObject *res; | |
| if (PyFrozenDict_Check(o)) { | |
| res = copy_lock_held_untracked(o, 0); | |
| } | |
| else { | |
| Py_BEGIN_CRITICAL_SECTION(o); | |
| res = copy_lock_held_untracked(o, 0); | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| if (res != NULL) { | |
| _PyObject_GC_TRACK(res); | |
| } | |
| return res; | |
| } | |
| Py_ssize_t | |
| PyDict_Size(PyObject *mp) | |
| { | |
| if (mp == NULL || !PyAnyDict_Check(mp)) { | |
| PyErr_BadInternalCall(); | |
| return -1; | |
| } | |
| return GET_USED((PyDictObject *)mp); | |
| } | |
| /* Return 1 if dicts equal, 0 if not, -1 if error. | |
| * Gets out as soon as any difference is detected. | |
| * Uses only Py_EQ comparison. | |
| */ | |
| static int | |
| dict_equal_lock_held(PyDictObject *a, PyDictObject *b) | |
| { | |
| Py_ssize_t i; | |
| ASSERT_DICT_LOCKED(a); | |
| ASSERT_DICT_LOCKED(b); | |
| if (a->ma_used != b->ma_used) | |
| /* can't be equal if # of entries differ */ | |
| return 0; | |
| /* Same # of entries -- check all of 'em. Exit early on any diff. */ | |
| for (i = 0; i < LOAD_KEYS_NENTRIES(a->ma_keys); i++) { | |
| PyObject *key, *aval; | |
| Py_hash_t hash; | |
| if (DK_IS_UNICODE(a->ma_keys)) { | |
| PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(a->ma_keys)[i]; | |
| key = ep->me_key; | |
| if (key == NULL) { | |
| continue; | |
| } | |
| hash = unicode_get_hash(key); | |
| if (_PyDict_HasSplitTable(a)) | |
| aval = a->ma_values->values[i]; | |
| else | |
| aval = ep->me_value; | |
| } | |
| else { | |
| PyDictKeyEntry *ep = &DK_ENTRIES(a->ma_keys)[i]; | |
| key = ep->me_key; | |
| aval = ep->me_value; | |
| hash = ep->me_hash; | |
| } | |
| if (aval != NULL) { | |
| int cmp; | |
| PyObject *bval; | |
| /* temporarily bump aval's refcount to ensure it stays | |
| alive until we're done with it */ | |
| Py_INCREF(aval); | |
| /* ditto for key */ | |
| Py_INCREF(key); | |
| /* reuse the known hash value */ | |
| _Py_dict_lookup(b, key, hash, &bval); | |
| if (bval == NULL) { | |
| Py_DECREF(key); | |
| Py_DECREF(aval); | |
| if (PyErr_Occurred()) | |
| return -1; | |
| return 0; | |
| } | |
| Py_INCREF(bval); | |
| cmp = PyObject_RichCompareBool(aval, bval, Py_EQ); | |
| Py_DECREF(key); | |
| Py_DECREF(aval); | |
| Py_DECREF(bval); | |
| if (cmp <= 0) /* error or not equal */ | |
| return cmp; | |
| } | |
| } | |
| return 1; | |
| } | |
| static int | |
| dict_equal(PyDictObject *a, PyDictObject *b) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION2(a, b); | |
| res = dict_equal_lock_held(a, b); | |
| Py_END_CRITICAL_SECTION2(); | |
| return res; | |
| } | |
| static PyObject * | |
| dict_richcompare(PyObject *v, PyObject *w, int op) | |
| { | |
| int cmp; | |
| PyObject *res; | |
| if (!PyAnyDict_Check(v) || !PyAnyDict_Check(w)) { | |
| res = Py_NotImplemented; | |
| } | |
| else if (op == Py_EQ || op == Py_NE) { | |
| cmp = dict_equal((PyDictObject *)v, (PyDictObject *)w); | |
| if (cmp < 0) | |
| return NULL; | |
| res = (cmp == (op == Py_EQ)) ? Py_True : Py_False; | |
| } | |
| else | |
| res = Py_NotImplemented; | |
| return Py_NewRef(res); | |
| } | |
| /*[clinic input] | |
| @coexist | |
| dict.__contains__ | |
| key: object | |
| / | |
| True if the dictionary has the specified key, else False. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict___contains___impl(PyDictObject *self, PyObject *key) | |
| /*[clinic end generated code: output=1b314e6da7687dae input=fe1cb42ad831e820]*/ | |
| { | |
| int contains = dict_contains((PyObject *)self, key); | |
| if (contains < 0) { | |
| return NULL; | |
| } | |
| if (contains) { | |
| Py_RETURN_TRUE; | |
| } | |
| Py_RETURN_FALSE; | |
| } | |
| /*[clinic input] | |
| dict.get | |
| key: object | |
| default: object = None | |
| / | |
| Return the value for key if key is in the dictionary, else default. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) | |
| /*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/ | |
| { | |
| PyObject *val = NULL; | |
| Py_hash_t hash; | |
| Py_ssize_t ix; | |
| hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type((PyObject*)self, key); | |
| return NULL; | |
| } | |
| ix = _Py_dict_lookup_threadsafe(self, key, hash, &val); | |
| if (ix == DKIX_ERROR) | |
| return NULL; | |
| if (ix == DKIX_EMPTY || val == NULL) { | |
| val = Py_NewRef(default_value); | |
| } | |
| return val; | |
| } | |
| static int | |
| dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, | |
| PyObject **result, int incref_result) | |
| { | |
| if (!PyDict_Check(d)) { | |
| if (PyFrozenDict_Check(d)) { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| assert(can_modify_dict((PyDictObject*)d)); | |
| PyDictObject *mp = (PyDictObject *)d; | |
| PyObject *value; | |
| Py_hash_t hash; | |
| Py_ssize_t ix; | |
| hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(d, key); | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| if (mp->ma_keys == Py_EMPTY_KEYS) { | |
| if (insert_to_emptydict(mp, Py_NewRef(key), hash, | |
| Py_NewRef(default_value)) < 0) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| if (result) { | |
| *result = incref_result ? Py_NewRef(default_value) : default_value; | |
| } | |
| return 0; | |
| } | |
| if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) { | |
| ix = insert_split_key(mp->ma_keys, key, hash); | |
| if (ix != DKIX_EMPTY) { | |
| PyObject *value = mp->ma_values->values[ix]; | |
| int already_present = value != NULL; | |
| if (!already_present) { | |
| _PyDict_InsertSplitValue(mp, key, default_value, ix); | |
| value = default_value; | |
| } | |
| if (result) { | |
| *result = incref_result ? Py_NewRef(value) : value; | |
| } | |
| return already_present; | |
| } | |
| // No space in shared keys. Go to insert_combined_dict() below. | |
| } | |
| else { | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| if (ix == DKIX_ERROR) { | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| } | |
| if (ix == DKIX_EMPTY) { | |
| value = default_value; | |
| // See comment to this function in insertdict. | |
| if (insert_combined_dict(mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) { | |
| Py_DECREF(key); | |
| Py_DECREF(value); | |
| if (result) { | |
| *result = NULL; | |
| } | |
| return -1; | |
| } | |
| STORE_USED(mp, mp->ma_used + 1); | |
| assert(mp->ma_keys->dk_usable >= 0); | |
| ASSERT_CONSISTENT(mp); | |
| if (result) { | |
| *result = incref_result ? Py_NewRef(value) : value; | |
| } | |
| return 0; | |
| } | |
| assert(value != NULL); | |
| ASSERT_CONSISTENT(mp); | |
| if (result) { | |
| *result = incref_result ? Py_NewRef(value) : value; | |
| } | |
| return 1; | |
| } | |
| int | |
| PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, | |
| PyObject **result) | |
| { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| res = dict_setdefault_ref_lock_held(d, key, default_value, result, 1); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| PyObject * | |
| PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) | |
| { | |
| PyObject *result; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| dict_setdefault_ref_lock_held(d, key, defaultobj, &result, 0); | |
| Py_END_CRITICAL_SECTION(); | |
| return result; | |
| } | |
| /*[clinic input] | |
| @critical_section | |
| dict.setdefault | |
| key: object | |
| default: object = None | |
| / | |
| Insert key with a value of default if key is not in the dictionary. | |
| Return the value for key if key is in the dictionary, else default. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_setdefault_impl(PyDictObject *self, PyObject *key, | |
| PyObject *default_value) | |
| /*[clinic end generated code: output=f8c1101ebf69e220 input=9237af9a0a224302]*/ | |
| { | |
| PyObject *val; | |
| dict_setdefault_ref_lock_held((PyObject *)self, key, default_value, &val, 1); | |
| return val; | |
| } | |
| /*[clinic input] | |
| dict.clear | |
| Remove all items from the dict. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_clear_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=5139a830df00830a input=0bf729baba97a4c2]*/ | |
| { | |
| PyDict_Clear((PyObject *)self); | |
| Py_RETURN_NONE; | |
| } | |
| /*[clinic input] | |
| @permit_long_summary | |
| dict.pop | |
| key: object | |
| default: object = NULL | |
| / | |
| D.pop(k[,d]) -> v, remove specified key and return the corresponding value. | |
| If the key is not found, return the default if given; otherwise, | |
| raise a KeyError. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value) | |
| /*[clinic end generated code: output=3abb47b89f24c21c input=d409c7eb2de67e38]*/ | |
| { | |
| return dict_pop_default((PyObject*)self, key, default_value); | |
| } | |
| /*[clinic input] | |
| @critical_section | |
| dict.popitem | |
| Remove and return a (key, value) pair as a 2-tuple. | |
| Pairs are returned in LIFO (last-in, first-out) order. | |
| Raises KeyError if the dict is empty. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_popitem_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/ | |
| { | |
| assert(can_modify_dict(self)); | |
| Py_ssize_t i, j; | |
| PyObject *res; | |
| /* Allocate the result tuple before checking the size. Believe it | |
| * or not, this allocation could trigger a garbage collection which | |
| * could empty the dict, so if we checked the size first and that | |
| * happened, the result would be an infinite loop (searching for an | |
| * entry that no longer exists). Note that the usual popitem() | |
| * idiom is "while d: k, v = d.popitem()". so needing to throw the | |
| * tuple away if the dict *is* empty isn't a significant | |
| * inefficiency -- possible, but unlikely in practice. | |
| */ | |
| res = PyTuple_New(2); | |
| if (res == NULL) | |
| return NULL; | |
| if (self->ma_used == 0) { | |
| Py_DECREF(res); | |
| PyErr_SetString(PyExc_KeyError, "popitem(): dictionary is empty"); | |
| return NULL; | |
| } | |
| /* Convert split table to combined table */ | |
| if (_PyDict_HasSplitTable(self)) { | |
| if (dictresize(self, DK_LOG_SIZE(self->ma_keys), 1) < 0) { | |
| Py_DECREF(res); | |
| return NULL; | |
| } | |
| } | |
| FT_ATOMIC_STORE_UINT32_RELAXED(self->ma_keys->dk_version, 0); | |
| /* Pop last item */ | |
| PyObject *key, *value; | |
| Py_hash_t hash; | |
| if (DK_IS_UNICODE(self->ma_keys)) { | |
| PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(self->ma_keys); | |
| i = self->ma_keys->dk_nentries - 1; | |
| while (i >= 0 && ep0[i].me_value == NULL) { | |
| i--; | |
| } | |
| assert(i >= 0); | |
| key = ep0[i].me_key; | |
| _PyDict_NotifyEvent(PyDict_EVENT_DELETED, self, key, NULL); | |
| hash = unicode_get_hash(key); | |
| value = ep0[i].me_value; | |
| STORE_KEY(&ep0[i], NULL); | |
| STORE_VALUE(&ep0[i], NULL); | |
| } | |
| else { | |
| PyDictKeyEntry *ep0 = DK_ENTRIES(self->ma_keys); | |
| i = self->ma_keys->dk_nentries - 1; | |
| while (i >= 0 && ep0[i].me_value == NULL) { | |
| i--; | |
| } | |
| assert(i >= 0); | |
| key = ep0[i].me_key; | |
| _PyDict_NotifyEvent(PyDict_EVENT_DELETED, self, key, NULL); | |
| hash = ep0[i].me_hash; | |
| value = ep0[i].me_value; | |
| STORE_KEY(&ep0[i], NULL); | |
| STORE_HASH(&ep0[i], -1); | |
| STORE_VALUE(&ep0[i], NULL); | |
| } | |
| j = lookdict_index(self->ma_keys, hash, i); | |
| assert(j >= 0); | |
| assert(dictkeys_get_index(self->ma_keys, j) == i); | |
| dictkeys_set_index(self->ma_keys, j, DKIX_DUMMY); | |
| PyTuple_SET_ITEM(res, 0, key); | |
| PyTuple_SET_ITEM(res, 1, value); | |
| /* We can't dk_usable++ since there is DKIX_DUMMY in indices */ | |
| STORE_KEYS_NENTRIES(self->ma_keys, i); | |
| STORE_USED(self, self->ma_used - 1); | |
| ASSERT_CONSISTENT(self); | |
| return res; | |
| } | |
| static int | |
| dict_traverse(PyObject *op, visitproc visit, void *arg) | |
| { | |
| PyDictObject *mp = (PyDictObject *)op; | |
| PyDictKeysObject *keys = mp->ma_keys; | |
| Py_ssize_t i, n = keys->dk_nentries; | |
| if (DK_IS_UNICODE(keys)) { | |
| if (_PyDict_HasSplitTable(mp)) { | |
| for (i = 0; i < n; i++) { | |
| Py_VISIT(mp->ma_values->values[i]); | |
| } | |
| } | |
| else { | |
| PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(keys); | |
| for (i = 0; i < n; i++) { | |
| Py_VISIT(entries[i].me_value); | |
| } | |
| } | |
| } | |
| else { | |
| PyDictKeyEntry *entries = DK_ENTRIES(keys); | |
| for (i = 0; i < n; i++) { | |
| if (entries[i].me_value != NULL) { | |
| Py_VISIT(entries[i].me_value); | |
| Py_VISIT(entries[i].me_key); | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static int | |
| dict_tp_clear(PyObject *op) | |
| { | |
| PyDict_Clear(op); | |
| return 0; | |
| } | |
| static PyObject *dictiter_new(PyDictObject *, PyTypeObject *); | |
| Py_ssize_t | |
| _PyDict_SizeOf_LockHeld(PyDictObject *mp) | |
| { | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp); | |
| size_t res = _PyObject_SIZE(Py_TYPE(mp)); | |
| if (_PyDict_HasSplitTable(mp)) { | |
| res += shared_keys_usable_size(mp->ma_keys) * sizeof(PyObject*); | |
| } | |
| /* If the dictionary is split, the keys portion is accounted-for | |
| in the type object. */ | |
| if (mp->ma_keys->dk_refcnt == 1) { | |
| res += _PyDict_KeysSize(mp->ma_keys); | |
| } | |
| assert(res <= (size_t)PY_SSIZE_T_MAX); | |
| return (Py_ssize_t)res; | |
| } | |
| void | |
| _PyDict_ClearKeysVersionLockHeld(PyObject *op) | |
| { | |
| PyDictObject *mp = _PyAnyDict_CAST(op); | |
| assert(can_modify_dict(mp)); | |
| FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0); | |
| } | |
| Py_ssize_t | |
| _PyDict_SizeOf(PyDictObject *mp) | |
| { | |
| Py_ssize_t res; | |
| Py_BEGIN_CRITICAL_SECTION(mp); | |
| res = _PyDict_SizeOf_LockHeld(mp); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| size_t | |
| _PyDict_KeysSize(PyDictKeysObject *keys) | |
| { | |
| size_t es = (keys->dk_kind == DICT_KEYS_GENERAL | |
| ? sizeof(PyDictKeyEntry) : sizeof(PyDictUnicodeEntry)); | |
| size_t size = sizeof(PyDictKeysObject); | |
| size += (size_t)1 << keys->dk_log2_index_bytes; | |
| size += USABLE_FRACTION((size_t)DK_SIZE(keys)) * es; | |
| return size; | |
| } | |
| /*[clinic input] | |
| dict.__sizeof__ | |
| Return the size of the dict in memory, in bytes. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict___sizeof___impl(PyDictObject *self) | |
| /*[clinic end generated code: output=44279379b3824bda input=4fec4ddfc44a4d1a]*/ | |
| { | |
| return PyLong_FromSsize_t(_PyDict_SizeOf(self)); | |
| } | |
| PyObject * | |
| _PyDict_Or(PyObject *self, PyObject *other) | |
| { | |
| if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) { | |
| Py_RETURN_NOTIMPLEMENTED; | |
| } | |
| PyObject *new = anydict_copy_untracked(self); | |
| if (new == NULL) { | |
| return NULL; | |
| } | |
| if (dict_update_arg(new, other)) { | |
| Py_DECREF(new); | |
| return NULL; | |
| } | |
| _PyObject_GC_TRACK(new); | |
| return new; | |
| } | |
| static PyObject * | |
| frozendict_or(PyObject *self, PyObject *other) | |
| { | |
| if (PyFrozenDict_CheckExact(self)) { | |
| // frozendict() | frozendict(...) => frozendict(...) | |
| if (GET_USED((PyDictObject *)self) == 0 | |
| && PyFrozenDict_CheckExact(other)) | |
| { | |
| return Py_NewRef(other); | |
| } | |
| // frozendict(...) | frozendict() => frozendict(...) | |
| if (PyAnyDict_CheckExact(other) | |
| && GET_USED((PyDictObject *)other) == 0) | |
| { | |
| return Py_NewRef(self); | |
| } | |
| } | |
| return _PyDict_Or(self, other); | |
| } | |
| PyObject * | |
| _PyDict_IOr(PyObject *self, PyObject *other) | |
| { | |
| if (dict_update_arg(self, other)) { | |
| return NULL; | |
| } | |
| return Py_NewRef(self); | |
| } | |
| PyDoc_STRVAR(getitem__doc__, | |
| "__getitem__($self, key, /)\n--\n\nReturn self[key]."); | |
| PyDoc_STRVAR(update__doc__, | |
| "D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.\n\ | |
| If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k]\n\ | |
| If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v\n\ | |
| In either case, this is followed by: for k in F: D[k] = F[k]"); | |
| /* Forward */ | |
| static PyMethodDef mapp_methods[] = { | |
| DICT___CONTAINS___METHODDEF | |
| {"__getitem__", _PyDict_Subscript, METH_O | METH_COEXIST, | |
| getitem__doc__}, | |
| DICT___SIZEOF___METHODDEF | |
| DICT_GET_METHODDEF | |
| DICT_SETDEFAULT_METHODDEF | |
| DICT_POP_METHODDEF | |
| DICT_POPITEM_METHODDEF | |
| DICT_KEYS_METHODDEF | |
| DICT_ITEMS_METHODDEF | |
| DICT_VALUES_METHODDEF | |
| {"update", _PyCFunction_CAST(dict_update), METH_VARARGS | METH_KEYWORDS, | |
| update__doc__}, | |
| DICT_FROMKEYS_METHODDEF | |
| DICT_CLEAR_METHODDEF | |
| DICT_COPY_METHODDEF | |
| DICT___REVERSED___METHODDEF | |
| {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, | |
| PyDoc_STR("dicts are generic over two types, signifying (respectively) the types of their keys and values")}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| static int | |
| dict_contains(PyObject *op, PyObject *key) | |
| { | |
| Py_hash_t hash = _PyObject_HashDictKey(key); | |
| if (hash == -1) { | |
| dict_unhashable_type(op, key); | |
| return -1; | |
| } | |
| return _PyDict_Contains_KnownHash(op, key, hash); | |
| } | |
| /* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ | |
| int | |
| PyDict_Contains(PyObject *op, PyObject *key) | |
| { | |
| if (!PyAnyDict_Check(op)) { | |
| PyErr_BadInternalCall(); | |
| return -1; | |
| } | |
| return dict_contains(op, key); | |
| } | |
| int | |
| PyDict_ContainsString(PyObject *op, const char *key) | |
| { | |
| PyObject *key_obj = PyUnicode_FromString(key); | |
| if (key_obj == NULL) { | |
| return -1; | |
| } | |
| int res = PyDict_Contains(op, key_obj); | |
| Py_DECREF(key_obj); | |
| return res; | |
| } | |
| /* Internal version of PyDict_Contains used when the hash value is already known */ | |
| int | |
| _PyDict_Contains_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) | |
| { | |
| PyDictObject *mp = _PyAnyDict_CAST(op); | |
| PyObject *value; | |
| Py_ssize_t ix; | |
| #ifdef Py_GIL_DISABLED | |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | |
| #else | |
| ix = _Py_dict_lookup(mp, key, hash, &value); | |
| #endif | |
| if (ix == DKIX_ERROR) | |
| return -1; | |
| if (ix != DKIX_EMPTY && value != NULL) { | |
| #ifdef Py_GIL_DISABLED | |
| Py_DECREF(value); | |
| #endif | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /* Hack to implement "key in dict" */ | |
| static PySequenceMethods dict_as_sequence = { | |
| 0, /* sq_length */ | |
| 0, /* sq_concat */ | |
| 0, /* sq_repeat */ | |
| 0, /* sq_item */ | |
| 0, /* sq_slice */ | |
| 0, /* sq_ass_item */ | |
| 0, /* sq_ass_slice */ | |
| dict_contains, /* sq_contains */ | |
| 0, /* sq_inplace_concat */ | |
| 0, /* sq_inplace_repeat */ | |
| }; | |
| static PyNumberMethods dict_as_number = { | |
| .nb_or = _PyDict_Or, | |
| .nb_inplace_or = _PyDict_IOr, | |
| }; | |
| static PyObject* | |
| anydict_new_untracked(PyTypeObject *type) | |
| { | |
| assert(type != NULL); | |
| // dict and frozendict subclasses must implement the GC protocol | |
| assert(_PyType_IS_GC(type)); | |
| PyObject *self = _PyType_AllocNoTrack(type, 0); | |
| if (self == NULL) { | |
| return NULL; | |
| } | |
| PyDictObject *d = (PyDictObject *)self; | |
| d->ma_used = 0; | |
| d->_ma_watcher_tag = 0; | |
| // We don't inc ref empty keys because they're immortal | |
| assert((Py_EMPTY_KEYS)->dk_refcnt == _Py_DICT_IMMORTAL_INITIAL_REFCNT); | |
| d->ma_keys = Py_EMPTY_KEYS; | |
| d->ma_values = NULL; | |
| ASSERT_CONSISTENT(d); | |
| return self; | |
| } | |
| static PyObject* | |
| dict_new_untracked(PyTypeObject *type) | |
| { | |
| assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyDict_Type)); | |
| return anydict_new_untracked(type); | |
| } | |
| static PyObject * | |
| dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds)) | |
| { | |
| /* tp_new ignores args/kwds; args/kwds are consumed by dict_init (tp_init). */ | |
| PyObject *self = dict_new_untracked(type); | |
| if (self == NULL) { | |
| return NULL; | |
| } | |
| _PyObject_GC_TRACK(self); | |
| return self; | |
| } | |
| static int | |
| dict_init(PyObject *self, PyObject *args, PyObject *kwds) | |
| { | |
| return dict_update_common(self, args, kwds, "dict"); | |
| } | |
| static PyObject * | |
| dict_vectorcall(PyObject *type, PyObject * const*args, | |
| size_t nargsf, PyObject *kwnames) | |
| { | |
| Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); | |
| if (!_PyArg_CheckPositional("dict", nargs, 0, 1)) { | |
| return NULL; | |
| } | |
| PyObject *self = dict_new(_PyType_CAST(type), NULL, NULL); | |
| if (self == NULL) { | |
| return NULL; | |
| } | |
| if (nargs == 1) { | |
| if (dict_update_arg(self, args[0]) < 0) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| args++; | |
| } | |
| if (kwnames != NULL) { | |
| for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) { | |
| PyObject *key = PyTuple_GET_ITEM(kwnames, i); // borrowed | |
| if (PyDict_SetItem(self, key, args[i]) < 0) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| } | |
| } | |
| return self; | |
| } | |
| static PyObject * | |
| frozendict_vectorcall(PyObject *type, PyObject * const*args, | |
| size_t nargsf, PyObject *kwnames) | |
| { | |
| Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); | |
| if (!_PyArg_CheckPositional("frozendict", nargs, 0, 1)) { | |
| return NULL; | |
| } | |
| if (nargs == 1 && kwnames == NULL | |
| && PyFrozenDict_CheckExact(args[0]) | |
| && Py_Is((PyTypeObject*)type, &PyFrozenDict_Type)) | |
| { | |
| // frozendict(frozendict) returns the same object unmodified | |
| return Py_NewRef(args[0]); | |
| } | |
| /* gh-151722: Keep the frozendict untracked until it is fully built, | |
| so a half-built object is never reachable from another thread (using the gc module). */ | |
| PyObject *self = frozendict_new_untracked(_PyType_CAST(type)); | |
| if (self == NULL) { | |
| return NULL; | |
| } | |
| if (nargs == 1) { | |
| if (dict_update_arg(self, args[0]) < 0) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| args++; | |
| } | |
| if (kwnames != NULL) { | |
| for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwnames); i++) { | |
| PyObject *key = PyTuple_GET_ITEM(kwnames, i); // borrowed | |
| if (_PyAnyDict_SetItem(self, key, args[i]) < 0) { | |
| Py_DECREF(self); | |
| return NULL; | |
| } | |
| } | |
| } | |
| _PyObject_GC_TRACK(self); | |
| return self; | |
| } | |
| static PyObject * | |
| dict_iter(PyObject *self) | |
| { | |
| PyDictObject *dict = (PyDictObject *)self; | |
| return dictiter_new(dict, &PyDictIterKey_Type); | |
| } | |
| PyDoc_STRVAR(dictionary_doc, | |
| "dict() -> new empty dictionary\n" | |
| "dict(mapping) -> new dictionary initialized from a mapping object's\n" | |
| " (key, value) pairs\n" | |
| "dict(iterable) -> new dictionary initialized as if via:\n" | |
| " d = {}\n" | |
| " for k, v in iterable:\n" | |
| " d[k] = v\n" | |
| "dict(**kwargs) -> new dictionary initialized with the name=value pairs\n" | |
| " in the keyword argument list. For example: dict(one=1, two=2)"); | |
| PyTypeObject PyDict_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict", | |
| sizeof(PyDictObject), | |
| 0, | |
| dict_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| dict_repr, /* tp_repr */ | |
| &dict_as_number, /* tp_as_number */ | |
| &dict_as_sequence, /* tp_as_sequence */ | |
| &dict_as_mapping, /* tp_as_mapping */ | |
| PyObject_HashNotImplemented, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | | |
| Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DICT_SUBCLASS | | |
| _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_MAPPING, /* tp_flags */ | |
| dictionary_doc, /* tp_doc */ | |
| dict_traverse, /* tp_traverse */ | |
| dict_tp_clear, /* tp_clear */ | |
| dict_richcompare, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| dict_iter, /* tp_iter */ | |
| 0, /* tp_iternext */ | |
| mapp_methods, /* tp_methods */ | |
| 0, /* tp_members */ | |
| 0, /* tp_getset */ | |
| 0, /* tp_base */ | |
| 0, /* tp_dict */ | |
| 0, /* tp_descr_get */ | |
| 0, /* tp_descr_set */ | |
| 0, /* tp_dictoffset */ | |
| dict_init, /* tp_init */ | |
| _PyType_AllocNoTrack, /* tp_alloc */ | |
| dict_new, /* tp_new */ | |
| PyObject_GC_Del, /* tp_free */ | |
| .tp_vectorcall = dict_vectorcall, | |
| .tp_version_tag = _Py_TYPE_VERSION_DICT, | |
| }; | |
| /* For backward compatibility with old dictionary interface */ | |
| PyObject * | |
| PyDict_GetItemString(PyObject *v, const char *key) | |
| { | |
| PyObject *kv, *rv; | |
| kv = PyUnicode_FromString(key); | |
| if (kv == NULL) { | |
| PyErr_FormatUnraisable( | |
| "Exception ignored in PyDict_GetItemString(); consider using " | |
| "PyDict_GetItemStringRef()"); | |
| return NULL; | |
| } | |
| rv = dict_getitem(v, kv, | |
| "Exception ignored in PyDict_GetItemString(); consider using " | |
| "PyDict_GetItemStringRef()"); | |
| Py_DECREF(kv); | |
| return rv; // borrowed reference | |
| } | |
| int | |
| PyDict_GetItemStringRef(PyObject *v, const char *key, PyObject **result) | |
| { | |
| PyObject *key_obj = PyUnicode_FromString(key); | |
| if (key_obj == NULL) { | |
| *result = NULL; | |
| return -1; | |
| } | |
| int res = PyDict_GetItemRef(v, key_obj, result); | |
| Py_DECREF(key_obj); | |
| return res; | |
| } | |
| int | |
| PyDict_SetItemString(PyObject *v, const char *key, PyObject *item) | |
| { | |
| PyObject *kv; | |
| int err; | |
| kv = PyUnicode_FromString(key); | |
| if (kv == NULL) | |
| return -1; | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| _PyUnicode_InternImmortal(interp, &kv); /* XXX Should we really? */ | |
| err = PyDict_SetItem(v, kv, item); | |
| Py_DECREF(kv); | |
| return err; | |
| } | |
| int | |
| PyDict_DelItemString(PyObject *v, const char *key) | |
| { | |
| PyObject *kv; | |
| int err; | |
| kv = PyUnicode_FromString(key); | |
| if (kv == NULL) | |
| return -1; | |
| err = PyDict_DelItem(v, kv); | |
| Py_DECREF(kv); | |
| return err; | |
| } | |
| /* Dictionary iterator types */ | |
| typedef struct { | |
| PyObject_HEAD | |
| PyDictObject *di_dict; /* Set to NULL when iterator is exhausted */ | |
| Py_ssize_t di_used; | |
| Py_ssize_t di_pos; | |
| PyObject* di_result; /* reusable result tuple for iteritems */ | |
| Py_ssize_t len; | |
| } dictiterobject; | |
| static PyObject * | |
| dictiter_new(PyDictObject *dict, PyTypeObject *itertype) | |
| { | |
| Py_ssize_t used; | |
| dictiterobject *di; | |
| di = PyObject_GC_New(dictiterobject, itertype); | |
| if (di == NULL) { | |
| return NULL; | |
| } | |
| di->di_dict = (PyDictObject*)Py_NewRef(dict); | |
| used = GET_USED(dict); | |
| di->di_used = used; | |
| di->len = used; | |
| di->di_result = NULL; | |
| if (itertype == &PyDictRevIterKey_Type || | |
| itertype == &PyDictRevIterItem_Type || | |
| itertype == &PyDictRevIterValue_Type) { | |
| if (_PyDict_HasSplitTable(dict)) { | |
| di->di_pos = used - 1; | |
| } | |
| else { | |
| di->di_pos = load_keys_nentries(dict) - 1; | |
| } | |
| } | |
| else { | |
| di->di_pos = 0; | |
| } | |
| /* gh-152107: track before allocating di_result. A dictiter with a NULL | |
| di_result is a valid state for dictiter_traverse()/dictiter_dealloc(), | |
| so a failure of the allocation below can safely DECREF a tracked di. */ | |
| _PyObject_GC_TRACK(di); | |
| if (itertype == &PyDictIterItem_Type || | |
| itertype == &PyDictRevIterItem_Type) { | |
| di->di_result = _PyTuple_FromPairSteal(Py_None, Py_None); | |
| if (di->di_result == NULL) { | |
| Py_DECREF(di); | |
| return NULL; | |
| } | |
| } | |
| return (PyObject *)di; | |
| } | |
| static void | |
| dictiter_dealloc(PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| /* bpo-31095: UnTrack is needed before calling any callbacks */ | |
| _PyObject_GC_UNTRACK(di); | |
| Py_XDECREF(di->di_dict); | |
| Py_XDECREF(di->di_result); | |
| PyObject_GC_Del(di); | |
| } | |
| static int | |
| dictiter_traverse(PyObject *self, visitproc visit, void *arg) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| Py_VISIT(di->di_dict); | |
| Py_VISIT(di->di_result); | |
| return 0; | |
| } | |
| static PyObject * | |
| dictiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| Py_ssize_t len = 0; | |
| if (di->di_dict != NULL && di->di_used == GET_USED(di->di_dict)) | |
| len = FT_ATOMIC_LOAD_SSIZE_RELAXED(di->len); | |
| return PyLong_FromSize_t(len); | |
| } | |
| PyDoc_STRVAR(length_hint_doc, | |
| "Private method returning an estimate of len(list(it))."); | |
| static PyObject * | |
| dictiter_reduce(PyObject *di, PyObject *Py_UNUSED(ignored)); | |
| PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); | |
| static PyMethodDef dictiter_methods[] = { | |
| {"__length_hint__", dictiter_len, METH_NOARGS, | |
| length_hint_doc}, | |
| {"__reduce__", dictiter_reduce, METH_NOARGS, | |
| reduce_doc}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| #ifdef Py_GIL_DISABLED | |
| static int | |
| dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, | |
| PyObject **out_key, PyObject **out_value); | |
| #else /* Py_GIL_DISABLED */ | |
| static PyObject* | |
| dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyObject *key; | |
| Py_ssize_t i; | |
| PyDictKeysObject *k; | |
| assert (PyAnyDict_Check(d)); | |
| ASSERT_DICT_LOCKED(d); | |
| if (di->di_used != d->ma_used) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary changed size during iteration"); | |
| di->di_used = -1; /* Make this state sticky */ | |
| return NULL; | |
| } | |
| i = di->di_pos; | |
| k = d->ma_keys; | |
| assert(i >= 0); | |
| if (_PyDict_HasSplitTable(d)) { | |
| if (i >= d->ma_used) | |
| goto fail; | |
| int index = get_index_from_order(d, i); | |
| key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(k)[index].me_key); | |
| assert(d->ma_values->values[index] != NULL); | |
| } | |
| else { | |
| Py_ssize_t n = k->dk_nentries; | |
| if (DK_IS_UNICODE(k)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(k)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| key = entry_ptr->me_key; | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(k)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| key = entry_ptr->me_key; | |
| } | |
| } | |
| // We found an element (key), but did not expect it | |
| if (di->len == 0) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary keys changed during iteration"); | |
| goto fail; | |
| } | |
| di->di_pos = i+1; | |
| di->len--; | |
| return Py_NewRef(key); | |
| fail: | |
| di->di_dict = NULL; | |
| Py_DECREF(d); | |
| return NULL; | |
| } | |
| #endif /* Py_GIL_DISABLED */ | |
| static PyObject* | |
| dictiter_iternextkey(PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyDictObject *d = di->di_dict; | |
| if (d == NULL) | |
| return NULL; | |
| PyObject *value; | |
| #ifdef Py_GIL_DISABLED | |
| if (dictiter_iternext_threadsafe(d, self, &value, NULL) < 0) { | |
| value = NULL; | |
| } | |
| #else | |
| value = dictiter_iternextkey_lock_held(d, self); | |
| #endif | |
| return value; | |
| } | |
| PyTypeObject PyDictIterKey_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_keyiterator", /* tp_name */ | |
| sizeof(dictiterobject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictiter_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| 0, /* tp_repr */ | |
| 0, /* tp_as_number */ | |
| 0, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictiter_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| 0, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| PyObject_SelfIter, /* tp_iter */ | |
| dictiter_iternextkey, /* tp_iternext */ | |
| dictiter_methods, /* tp_methods */ | |
| 0, | |
| }; | |
| #ifndef Py_GIL_DISABLED | |
| static PyObject * | |
| dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyObject *value; | |
| Py_ssize_t i; | |
| assert (PyAnyDict_Check(d)); | |
| ASSERT_DICT_LOCKED(d); | |
| if (di->di_used != d->ma_used) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary changed size during iteration"); | |
| di->di_used = -1; /* Make this state sticky */ | |
| return NULL; | |
| } | |
| i = di->di_pos; | |
| assert(i >= 0); | |
| if (_PyDict_HasSplitTable(d)) { | |
| if (i >= d->ma_used) | |
| goto fail; | |
| int index = get_index_from_order(d, i); | |
| value = d->ma_values->values[index]; | |
| assert(value != NULL); | |
| } | |
| else { | |
| Py_ssize_t n = d->ma_keys->dk_nentries; | |
| if (DK_IS_UNICODE(d->ma_keys)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(d->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| value = entry_ptr->me_value; | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(d->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| value = entry_ptr->me_value; | |
| } | |
| } | |
| // We found an element, but did not expect it | |
| if (di->len == 0) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary keys changed during iteration"); | |
| goto fail; | |
| } | |
| di->di_pos = i+1; | |
| di->len--; | |
| return Py_NewRef(value); | |
| fail: | |
| di->di_dict = NULL; | |
| Py_DECREF(d); | |
| return NULL; | |
| } | |
| #endif /* Py_GIL_DISABLED */ | |
| static PyObject * | |
| dictiter_iternextvalue(PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyDictObject *d = di->di_dict; | |
| if (d == NULL) | |
| return NULL; | |
| PyObject *value; | |
| #ifdef Py_GIL_DISABLED | |
| if (dictiter_iternext_threadsafe(d, self, NULL, &value) < 0) { | |
| value = NULL; | |
| } | |
| #else | |
| value = dictiter_iternextvalue_lock_held(d, self); | |
| #endif | |
| return value; | |
| } | |
| PyTypeObject PyDictIterValue_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_valueiterator", /* tp_name */ | |
| sizeof(dictiterobject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictiter_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| 0, /* tp_repr */ | |
| 0, /* tp_as_number */ | |
| 0, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictiter_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| 0, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| PyObject_SelfIter, /* tp_iter */ | |
| dictiter_iternextvalue, /* tp_iternext */ | |
| dictiter_methods, /* tp_methods */ | |
| 0, | |
| }; | |
| static int | |
| dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self, | |
| PyObject **out_key, PyObject **out_value) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyObject *key, *value; | |
| Py_ssize_t i; | |
| assert (PyAnyDict_Check(d)); | |
| ASSERT_DICT_LOCKED(d); | |
| if (di->di_used != d->ma_used) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary changed size during iteration"); | |
| di->di_used = -1; /* Make this state sticky */ | |
| return -1; | |
| } | |
| i = FT_ATOMIC_LOAD_SSIZE_RELAXED(di->di_pos); | |
| assert(i >= 0); | |
| if (_PyDict_HasSplitTable(d)) { | |
| if (i >= d->ma_used) | |
| goto fail; | |
| int index = get_index_from_order(d, i); | |
| key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(d->ma_keys)[index].me_key); | |
| value = d->ma_values->values[index]; | |
| assert(value != NULL); | |
| } | |
| else { | |
| Py_ssize_t n = d->ma_keys->dk_nentries; | |
| if (DK_IS_UNICODE(d->ma_keys)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(d->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| key = entry_ptr->me_key; | |
| value = entry_ptr->me_value; | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(d->ma_keys)[i]; | |
| while (i < n && entry_ptr->me_value == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| key = entry_ptr->me_key; | |
| value = entry_ptr->me_value; | |
| } | |
| } | |
| // We found an element, but did not expect it | |
| if (di->len == 0) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary keys changed during iteration"); | |
| goto fail; | |
| } | |
| di->di_pos = i+1; | |
| di->len--; | |
| if (out_key != NULL) { | |
| *out_key = Py_NewRef(key); | |
| } | |
| if (out_value != NULL) { | |
| *out_value = Py_NewRef(value); | |
| } | |
| return 0; | |
| fail: | |
| di->di_dict = NULL; | |
| Py_DECREF(d); | |
| return -1; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| // Grabs the key and/or value from the provided locations and if successful | |
| // returns them with an increased reference count. If either one is unsuccessful | |
| // nothing is incref'd and returns -1. | |
| static int | |
| acquire_key_value(PyObject **key_loc, PyObject *value, PyObject **value_loc, | |
| PyObject **out_key, PyObject **out_value) | |
| { | |
| if (out_key) { | |
| *out_key = _Py_TryXGetRef(key_loc); | |
| if (*out_key == NULL) { | |
| return -1; | |
| } | |
| } | |
| if (out_value) { | |
| if (!_Py_TryIncrefCompare(value_loc, value)) { | |
| if (out_key) { | |
| Py_DECREF(*out_key); | |
| } | |
| return -1; | |
| } | |
| *out_value = value; | |
| } | |
| return 0; | |
| } | |
| static int | |
| dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self, | |
| PyObject **out_key, PyObject **out_value) | |
| { | |
| int res; | |
| dictiterobject *di = (dictiterobject *)self; | |
| Py_ssize_t i; | |
| PyDictKeysObject *k; | |
| assert (PyAnyDict_Check(d)); | |
| if (di->di_used != _Py_atomic_load_ssize_relaxed(&d->ma_used)) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary changed size during iteration"); | |
| di->di_used = -1; /* Make this state sticky */ | |
| return -1; | |
| } | |
| ensure_shared_on_read(d); | |
| i = _Py_atomic_load_ssize_relaxed(&di->di_pos); | |
| k = _Py_atomic_load_ptr_acquire(&d->ma_keys); | |
| assert(i >= 0); | |
| if (_PyDict_HasSplitTable(d)) { | |
| PyDictValues *values = _Py_atomic_load_ptr_consume(&d->ma_values); | |
| if (values == NULL) { | |
| goto concurrent_modification; | |
| } | |
| Py_ssize_t used = (Py_ssize_t)_Py_atomic_load_uint8(&values->size); | |
| if (i >= used) { | |
| goto fail; | |
| } | |
| // We're racing against writes to the order from delete_index_from_values, but | |
| // single threaded can suffer from concurrent modification to those as well and | |
| // can have either duplicated or skipped attributes, so we strive to do no better | |
| // here. | |
| int index = get_index_from_order(d, i); | |
| PyObject *value = _Py_atomic_load_ptr(&values->values[index]); | |
| if (acquire_key_value(&DK_UNICODE_ENTRIES(k)[index].me_key, value, | |
| &values->values[index], out_key, out_value) < 0) { | |
| goto try_locked; | |
| } | |
| } | |
| else { | |
| Py_ssize_t n = _Py_atomic_load_ssize_relaxed(&k->dk_nentries); | |
| if (DK_IS_UNICODE(k)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(k)[i]; | |
| PyObject *value; | |
| while (i < n && | |
| (value = _Py_atomic_load_ptr(&entry_ptr->me_value)) == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| if (acquire_key_value(&entry_ptr->me_key, value, | |
| &entry_ptr->me_value, out_key, out_value) < 0) { | |
| goto try_locked; | |
| } | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(k)[i]; | |
| PyObject *value; | |
| while (i < n && | |
| (value = _Py_atomic_load_ptr(&entry_ptr->me_value)) == NULL) { | |
| entry_ptr++; | |
| i++; | |
| } | |
| if (i >= n) | |
| goto fail; | |
| if (acquire_key_value(&entry_ptr->me_key, value, | |
| &entry_ptr->me_value, out_key, out_value) < 0) { | |
| goto try_locked; | |
| } | |
| } | |
| } | |
| // We found an element (key), but did not expect it | |
| Py_ssize_t len; | |
| if ((len = _Py_atomic_load_ssize_relaxed(&di->len)) == 0) { | |
| goto concurrent_modification; | |
| } | |
| _Py_atomic_store_ssize_relaxed(&di->di_pos, i + 1); | |
| _Py_atomic_store_ssize_relaxed(&di->len, len - 1); | |
| return 0; | |
| concurrent_modification: | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary keys changed during iteration"); | |
| fail: | |
| di->di_dict = NULL; | |
| Py_DECREF(d); | |
| return -1; | |
| try_locked: | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| res = dictiter_iternextitem_lock_held(d, self, out_key, out_value); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| #endif | |
| static bool | |
| acquire_iter_result(PyObject *result) | |
| { | |
| if (_PyObject_IsUniquelyReferenced(result)) { | |
| Py_INCREF(result); | |
| return true; | |
| } | |
| return false; | |
| } | |
| static PyObject * | |
| dictiter_iternextitem(PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyDictObject *d = di->di_dict; | |
| if (d == NULL) | |
| return NULL; | |
| PyObject *key, *value; | |
| #ifdef Py_GIL_DISABLED | |
| if (dictiter_iternext_threadsafe(d, self, &key, &value) == 0) { | |
| #else | |
| if (dictiter_iternextitem_lock_held(d, self, &key, &value) == 0) { | |
| #endif | |
| PyObject *result = di->di_result; | |
| if (acquire_iter_result(result)) { | |
| PyObject *oldkey = PyTuple_GET_ITEM(result, 0); | |
| PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); | |
| PyTuple_SET_ITEM(result, 0, key); | |
| PyTuple_SET_ITEM(result, 1, value); | |
| Py_DECREF(oldkey); | |
| Py_DECREF(oldvalue); | |
| // bpo-42536: The GC may have untracked this result tuple. Since we're | |
| // recycling it, make sure it's tracked again: | |
| _PyTuple_Recycle(result); | |
| } | |
| else { | |
| result = _PyTuple_FromPairSteal(key, value); | |
| } | |
| return result; | |
| } | |
| return NULL; | |
| } | |
| PyTypeObject PyDictIterItem_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_itemiterator", /* tp_name */ | |
| sizeof(dictiterobject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictiter_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| 0, /* tp_repr */ | |
| 0, /* tp_as_number */ | |
| 0, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictiter_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| 0, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| PyObject_SelfIter, /* tp_iter */ | |
| dictiter_iternextitem, /* tp_iternext */ | |
| dictiter_methods, /* tp_methods */ | |
| 0, | |
| }; | |
| /* dictreviter */ | |
| static PyObject * | |
| dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| assert (PyAnyDict_Check(d)); | |
| ASSERT_DICT_LOCKED(d); | |
| if (di->di_used != d->ma_used) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary changed size during iteration"); | |
| di->di_used = -1; /* Make this state sticky */ | |
| return NULL; | |
| } | |
| Py_ssize_t i = di->di_pos; | |
| PyDictKeysObject *k = d->ma_keys; | |
| PyObject *key, *value, *result; | |
| if (i < 0) { | |
| goto fail; | |
| } | |
| if (_PyDict_HasSplitTable(d)) { | |
| int index = get_index_from_order(d, i); | |
| key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(k)[index].me_key); | |
| value = d->ma_values->values[index]; | |
| assert(value != NULL); | |
| } | |
| else { | |
| if (i >= k->dk_nentries) { | |
| goto fail; | |
| } | |
| if (DK_IS_UNICODE(k)) { | |
| PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(k)[i]; | |
| while (entry_ptr->me_value == NULL) { | |
| if (--i < 0) { | |
| goto fail; | |
| } | |
| entry_ptr--; | |
| } | |
| key = entry_ptr->me_key; | |
| value = entry_ptr->me_value; | |
| } | |
| else { | |
| PyDictKeyEntry *entry_ptr = &DK_ENTRIES(k)[i]; | |
| while (entry_ptr->me_value == NULL) { | |
| if (--i < 0) { | |
| goto fail; | |
| } | |
| entry_ptr--; | |
| } | |
| key = entry_ptr->me_key; | |
| value = entry_ptr->me_value; | |
| } | |
| } | |
| // We found an element, but did not expect it | |
| if (di->len == 0) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "dictionary keys changed during iteration"); | |
| goto fail; | |
| } | |
| di->di_pos = i-1; | |
| di->len--; | |
| if (Py_IS_TYPE(di, &PyDictRevIterKey_Type)) { | |
| return Py_NewRef(key); | |
| } | |
| else if (Py_IS_TYPE(di, &PyDictRevIterValue_Type)) { | |
| return Py_NewRef(value); | |
| } | |
| else if (Py_IS_TYPE(di, &PyDictRevIterItem_Type)) { | |
| result = di->di_result; | |
| if (_PyObject_IsUniquelyReferenced(result)) { | |
| PyObject *oldkey = PyTuple_GET_ITEM(result, 0); | |
| PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); | |
| PyTuple_SET_ITEM(result, 0, Py_NewRef(key)); | |
| PyTuple_SET_ITEM(result, 1, Py_NewRef(value)); | |
| Py_INCREF(result); | |
| Py_DECREF(oldkey); | |
| Py_DECREF(oldvalue); | |
| // bpo-42536: The GC may have untracked this result tuple. Since | |
| // we're recycling it, make sure it's tracked again: | |
| _PyTuple_Recycle(result); | |
| } | |
| else { | |
| result = _PyTuple_FromPair(key, value); | |
| } | |
| return result; | |
| } | |
| else { | |
| Py_UNREACHABLE(); | |
| } | |
| fail: | |
| di->di_dict = NULL; | |
| Py_DECREF(d); | |
| return NULL; | |
| } | |
| static PyObject * | |
| dictreviter_iternext(PyObject *self) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| PyDictObject *d = di->di_dict; | |
| if (d == NULL) | |
| return NULL; | |
| PyObject *value; | |
| Py_BEGIN_CRITICAL_SECTION(d); | |
| value = dictreviter_iter_lock_held(d, self); | |
| Py_END_CRITICAL_SECTION(); | |
| return value; | |
| } | |
| PyTypeObject PyDictRevIterKey_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_reversekeyiterator", | |
| sizeof(dictiterobject), | |
| .tp_dealloc = dictiter_dealloc, | |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, | |
| .tp_traverse = dictiter_traverse, | |
| .tp_iter = PyObject_SelfIter, | |
| .tp_iternext = dictreviter_iternext, | |
| .tp_methods = dictiter_methods | |
| }; | |
| /*[clinic input] | |
| dict.__reversed__ | |
| Return a reverse iterator over the dict keys. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict___reversed___impl(PyDictObject *self) | |
| /*[clinic end generated code: output=e674483336d1ed51 input=23210ef3477d8c4d]*/ | |
| { | |
| assert (PyAnyDict_Check(self)); | |
| return dictiter_new(self, &PyDictRevIterKey_Type); | |
| } | |
| static PyObject * | |
| dictiter_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| dictiterobject *di = (dictiterobject *)self; | |
| /* copy the iterator state */ | |
| dictiterobject tmp = *di; | |
| Py_XINCREF(tmp.di_dict); | |
| PyObject *list = PySequence_List((PyObject*)&tmp); | |
| Py_XDECREF(tmp.di_dict); | |
| if (list == NULL) { | |
| return NULL; | |
| } | |
| return Py_BuildValue("N(N)", _PyEval_GetBuiltin(&_Py_ID(iter)), list); | |
| } | |
| PyTypeObject PyDictRevIterItem_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_reverseitemiterator", | |
| sizeof(dictiterobject), | |
| .tp_dealloc = dictiter_dealloc, | |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, | |
| .tp_traverse = dictiter_traverse, | |
| .tp_iter = PyObject_SelfIter, | |
| .tp_iternext = dictreviter_iternext, | |
| .tp_methods = dictiter_methods | |
| }; | |
| PyTypeObject PyDictRevIterValue_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_reversevalueiterator", | |
| sizeof(dictiterobject), | |
| .tp_dealloc = dictiter_dealloc, | |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, | |
| .tp_traverse = dictiter_traverse, | |
| .tp_iter = PyObject_SelfIter, | |
| .tp_iternext = dictreviter_iternext, | |
| .tp_methods = dictiter_methods | |
| }; | |
| /***********************************************/ | |
| /* View objects for keys(), items(), values(). */ | |
| /***********************************************/ | |
| /* The instance lay-out is the same for all three; but the type differs. */ | |
| static void | |
| dictview_dealloc(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| /* bpo-31095: UnTrack is needed before calling any callbacks */ | |
| _PyObject_GC_UNTRACK(dv); | |
| Py_XDECREF(dv->dv_dict); | |
| PyObject_GC_Del(dv); | |
| } | |
| static int | |
| dictview_traverse(PyObject *self, visitproc visit, void *arg) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| Py_VISIT(dv->dv_dict); | |
| return 0; | |
| } | |
| static Py_ssize_t | |
| dictview_len(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| Py_ssize_t len = 0; | |
| if (dv->dv_dict != NULL) | |
| len = GET_USED(dv->dv_dict); | |
| return len; | |
| } | |
| PyObject * | |
| _PyDictView_New(PyObject *dict, PyTypeObject *type) | |
| { | |
| _PyDictViewObject *dv; | |
| if (dict == NULL) { | |
| PyErr_BadInternalCall(); | |
| return NULL; | |
| } | |
| if (!PyAnyDict_Check(dict)) { | |
| /* XXX Get rid of this restriction later */ | |
| PyErr_Format(PyExc_TypeError, | |
| "%s() requires a dict argument, not '%s'", | |
| type->tp_name, Py_TYPE(dict)->tp_name); | |
| return NULL; | |
| } | |
| dv = PyObject_GC_New(_PyDictViewObject, type); | |
| if (dv == NULL) | |
| return NULL; | |
| dv->dv_dict = (PyDictObject *)Py_NewRef(dict); | |
| _PyObject_GC_TRACK(dv); | |
| return (PyObject *)dv; | |
| } | |
| static PyObject * | |
| dictview_mapping(PyObject *view, void *Py_UNUSED(ignored)) { | |
| assert(view != NULL); | |
| assert(PyDictKeys_Check(view) | |
| || PyDictValues_Check(view) | |
| || PyDictItems_Check(view)); | |
| PyObject *mapping = (PyObject *)((_PyDictViewObject *)view)->dv_dict; | |
| return PyDictProxy_New(mapping); | |
| } | |
| static PyGetSetDef dictview_getset[] = { | |
| {"mapping", dictview_mapping, NULL, | |
| PyDoc_STR("dictionary that this view refers to"), NULL}, | |
| {0} | |
| }; | |
| /* TODO(guido): The views objects are not complete: | |
| * support more set operations | |
| * support arbitrary mappings? | |
| - either these should be static or exported in dictobject.h | |
| - if public then they should probably be in builtins | |
| */ | |
| /* Return 1 if self is a subset of other, iterating over self; | |
| 0 if not; -1 if an error occurred. */ | |
| static int | |
| all_contained_in(PyObject *self, PyObject *other) | |
| { | |
| PyObject *iter = PyObject_GetIter(self); | |
| int ok = 1; | |
| if (iter == NULL) | |
| return -1; | |
| for (;;) { | |
| PyObject *next = PyIter_Next(iter); | |
| if (next == NULL) { | |
| if (PyErr_Occurred()) | |
| ok = -1; | |
| break; | |
| } | |
| ok = PySequence_Contains(other, next); | |
| Py_DECREF(next); | |
| if (ok <= 0) | |
| break; | |
| } | |
| Py_DECREF(iter); | |
| return ok; | |
| } | |
| static PyObject * | |
| dictview_richcompare(PyObject *self, PyObject *other, int op) | |
| { | |
| Py_ssize_t len_self, len_other; | |
| int ok; | |
| PyObject *result; | |
| assert(self != NULL); | |
| assert(PyDictViewSet_Check(self)); | |
| assert(other != NULL); | |
| if (!PyAnySet_Check(other) && !PyDictViewSet_Check(other)) | |
| Py_RETURN_NOTIMPLEMENTED; | |
| len_self = PyObject_Size(self); | |
| if (len_self < 0) | |
| return NULL; | |
| len_other = PyObject_Size(other); | |
| if (len_other < 0) | |
| return NULL; | |
| ok = 0; | |
| switch(op) { | |
| case Py_NE: | |
| case Py_EQ: | |
| if (len_self == len_other) | |
| ok = all_contained_in(self, other); | |
| if (op == Py_NE && ok >= 0) | |
| ok = !ok; | |
| break; | |
| case Py_LT: | |
| if (len_self < len_other) | |
| ok = all_contained_in(self, other); | |
| break; | |
| case Py_LE: | |
| if (len_self <= len_other) | |
| ok = all_contained_in(self, other); | |
| break; | |
| case Py_GT: | |
| if (len_self > len_other) | |
| ok = all_contained_in(other, self); | |
| break; | |
| case Py_GE: | |
| if (len_self >= len_other) | |
| ok = all_contained_in(other, self); | |
| break; | |
| } | |
| if (ok < 0) | |
| return NULL; | |
| result = ok ? Py_True : Py_False; | |
| return Py_NewRef(result); | |
| } | |
| static PyObject * | |
| dictview_repr(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| PyObject *seq; | |
| PyObject *result = NULL; | |
| Py_ssize_t rc; | |
| rc = Py_ReprEnter((PyObject *)dv); | |
| if (rc != 0) { | |
| return rc > 0 ? PyUnicode_FromString("...") : NULL; | |
| } | |
| seq = PySequence_List((PyObject *)dv); | |
| if (seq == NULL) { | |
| goto Done; | |
| } | |
| result = PyUnicode_FromFormat("%s(%R)", Py_TYPE(dv)->tp_name, seq); | |
| Py_DECREF(seq); | |
| Done: | |
| Py_ReprLeave((PyObject *)dv); | |
| return result; | |
| } | |
| /*** dict_keys ***/ | |
| static PyObject * | |
| dictkeys_iter(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictIterKey_Type); | |
| } | |
| static int | |
| dictkeys_contains(PyObject *self, PyObject *obj) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) | |
| return 0; | |
| return dict_contains((PyObject *)dv->dv_dict, obj); | |
| } | |
| static PySequenceMethods dictkeys_as_sequence = { | |
| dictview_len, /* sq_length */ | |
| 0, /* sq_concat */ | |
| 0, /* sq_repeat */ | |
| 0, /* sq_item */ | |
| 0, /* sq_slice */ | |
| 0, /* sq_ass_item */ | |
| 0, /* sq_ass_slice */ | |
| dictkeys_contains, /* sq_contains */ | |
| }; | |
| // Create a set object from dictviews object. | |
| // Returns a new reference. | |
| // This utility function is used by set operations. | |
| static PyObject* | |
| dictviews_to_set(PyObject *self) | |
| { | |
| PyObject *left = self; | |
| if (PyDictKeys_Check(self)) { | |
| // PySet_New() has fast path for the dict object. | |
| PyObject *dict = (PyObject *)((_PyDictViewObject *)self)->dv_dict; | |
| if (PyAnyDict_CheckExact(dict)) { | |
| left = dict; | |
| } | |
| } | |
| return PySet_New(left); | |
| } | |
| static PyObject* | |
| dictviews_sub(PyObject *self, PyObject *other) | |
| { | |
| PyObject *result = dictviews_to_set(self); | |
| if (result == NULL) { | |
| return NULL; | |
| } | |
| PyObject *tmp = PyObject_CallMethodOneArg( | |
| result, &_Py_ID(difference_update), other); | |
| if (tmp == NULL) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| Py_DECREF(tmp); | |
| return result; | |
| } | |
| static int | |
| dictitems_contains(PyObject *dv, PyObject *obj); | |
| PyObject * | |
| _PyDictView_Intersect(PyObject* self, PyObject *other) | |
| { | |
| PyObject *result; | |
| PyObject *it; | |
| PyObject *key; | |
| Py_ssize_t len_self; | |
| int rv; | |
| objobjproc dict_contains; | |
| /* Python interpreter swaps parameters when dict view | |
| is on right side of & */ | |
| if (!PyDictViewSet_Check(self)) { | |
| PyObject *tmp = other; | |
| other = self; | |
| self = tmp; | |
| } | |
| len_self = dictview_len(self); | |
| /* if other is a set and self is smaller than other, | |
| reuse set intersection logic */ | |
| if (PySet_CheckExact(other) && len_self <= PyObject_Size(other)) { | |
| return PyObject_CallMethodObjArgs( | |
| other, &_Py_ID(intersection), self, NULL); | |
| } | |
| /* if other is another dict view, and it is bigger than self, | |
| swap them */ | |
| if (PyDictViewSet_Check(other)) { | |
| Py_ssize_t len_other = dictview_len(other); | |
| if (len_other > len_self) { | |
| PyObject *tmp = other; | |
| other = self; | |
| self = tmp; | |
| } | |
| } | |
| /* at this point, two things should be true | |
| 1. self is a dictview | |
| 2. if other is a dictview then it is smaller than self */ | |
| result = PySet_New(NULL); | |
| if (result == NULL) | |
| return NULL; | |
| it = PyObject_GetIter(other); | |
| if (it == NULL) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| if (PyDictKeys_Check(self)) { | |
| dict_contains = dictkeys_contains; | |
| } | |
| /* else PyDictItems_Check(self) */ | |
| else { | |
| dict_contains = dictitems_contains; | |
| } | |
| while ((key = PyIter_Next(it)) != NULL) { | |
| rv = dict_contains(self, key); | |
| if (rv < 0) { | |
| goto error; | |
| } | |
| if (rv) { | |
| if (PySet_Add(result, key)) { | |
| goto error; | |
| } | |
| } | |
| Py_DECREF(key); | |
| } | |
| Py_DECREF(it); | |
| if (PyErr_Occurred()) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| return result; | |
| error: | |
| Py_DECREF(it); | |
| Py_DECREF(result); | |
| Py_DECREF(key); | |
| return NULL; | |
| } | |
| static PyObject* | |
| dictviews_or(PyObject* self, PyObject *other) | |
| { | |
| PyObject *result = dictviews_to_set(self); | |
| if (result == NULL) { | |
| return NULL; | |
| } | |
| if (_PySet_Update(result, other) < 0) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| return result; | |
| } | |
| static PyObject * | |
| dictitems_xor_lock_held(PyObject *d1, PyObject *d2) | |
| { | |
| ASSERT_DICT_LOCKED(d1); | |
| ASSERT_DICT_LOCKED(d2); | |
| PyObject *temp_dict = copy_lock_held_untracked(d1, 0); | |
| if (temp_dict == NULL) { | |
| return NULL; | |
| } | |
| _PyObject_GC_TRACK(temp_dict); | |
| PyObject *result_set = PySet_New(NULL); | |
| if (result_set == NULL) { | |
| Py_CLEAR(temp_dict); | |
| return NULL; | |
| } | |
| PyObject *key = NULL, *val1 = NULL, *val2 = NULL; | |
| Py_ssize_t pos = 0; | |
| Py_hash_t hash; | |
| while (_PyDict_Next(d2, &pos, &key, &val2, &hash)) { | |
| Py_INCREF(key); | |
| Py_INCREF(val2); | |
| val1 = _PyDict_GetItem_KnownHash(temp_dict, key, hash); | |
| int to_delete; | |
| if (val1 == NULL) { | |
| if (PyErr_Occurred()) { | |
| goto error; | |
| } | |
| to_delete = 0; | |
| } | |
| else { | |
| Py_INCREF(val1); | |
| to_delete = PyObject_RichCompareBool(val1, val2, Py_EQ); | |
| Py_CLEAR(val1); | |
| if (to_delete < 0) { | |
| goto error; | |
| } | |
| } | |
| if (to_delete) { | |
| Py_CLEAR(val2); | |
| if (_PyDict_DelItem_KnownHash(temp_dict, key, hash) < 0) { | |
| goto error; | |
| } | |
| Py_CLEAR(key); | |
| } | |
| else { | |
| PyObject *pair = _PyTuple_FromPairSteal(key, val2); | |
| key = val2 = NULL; | |
| if (pair == NULL) { | |
| goto error; | |
| } | |
| if (PySet_Add(result_set, pair) < 0) { | |
| Py_DECREF(pair); | |
| goto error; | |
| } | |
| Py_DECREF(pair); | |
| } | |
| } | |
| PyObject *remaining_pairs = PyObject_CallMethodNoArgs( | |
| temp_dict, &_Py_ID(items)); | |
| if (remaining_pairs == NULL) { | |
| goto error; | |
| } | |
| if (_PySet_Update(result_set, remaining_pairs) < 0) { | |
| Py_DECREF(remaining_pairs); | |
| goto error; | |
| } | |
| Py_DECREF(temp_dict); | |
| Py_DECREF(remaining_pairs); | |
| return result_set; | |
| error: | |
| Py_XDECREF(temp_dict); | |
| Py_XDECREF(result_set); | |
| Py_XDECREF(key); | |
| Py_XDECREF(val1); | |
| Py_XDECREF(val2); | |
| return NULL; | |
| } | |
| static PyObject * | |
| dictitems_xor(PyObject *self, PyObject *other) | |
| { | |
| assert(PyDictItems_Check(self)); | |
| assert(PyDictItems_Check(other)); | |
| PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict; | |
| PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict; | |
| PyObject *res; | |
| Py_BEGIN_CRITICAL_SECTION2(d1, d2); | |
| res = dictitems_xor_lock_held(d1, d2); | |
| Py_END_CRITICAL_SECTION2(); | |
| return res; | |
| } | |
| static PyObject* | |
| dictviews_xor(PyObject* self, PyObject *other) | |
| { | |
| if (PyDictItems_Check(self) && PyDictItems_Check(other)) { | |
| return dictitems_xor(self, other); | |
| } | |
| PyObject *result = dictviews_to_set(self); | |
| if (result == NULL) { | |
| return NULL; | |
| } | |
| PyObject *tmp = PyObject_CallMethodOneArg( | |
| result, &_Py_ID(symmetric_difference_update), other); | |
| if (tmp == NULL) { | |
| Py_DECREF(result); | |
| return NULL; | |
| } | |
| Py_DECREF(tmp); | |
| return result; | |
| } | |
| static PyNumberMethods dictviews_as_number = { | |
| 0, /*nb_add*/ | |
| dictviews_sub, /*nb_subtract*/ | |
| 0, /*nb_multiply*/ | |
| 0, /*nb_remainder*/ | |
| 0, /*nb_divmod*/ | |
| 0, /*nb_power*/ | |
| 0, /*nb_negative*/ | |
| 0, /*nb_positive*/ | |
| 0, /*nb_absolute*/ | |
| 0, /*nb_bool*/ | |
| 0, /*nb_invert*/ | |
| 0, /*nb_lshift*/ | |
| 0, /*nb_rshift*/ | |
| _PyDictView_Intersect, /*nb_and*/ | |
| dictviews_xor, /*nb_xor*/ | |
| dictviews_or, /*nb_or*/ | |
| }; | |
| static PyObject* | |
| dictviews_isdisjoint(PyObject *self, PyObject *other) | |
| { | |
| PyObject *it; | |
| PyObject *item = NULL; | |
| if (self == other) { | |
| if (dictview_len(self) == 0) | |
| Py_RETURN_TRUE; | |
| else | |
| Py_RETURN_FALSE; | |
| } | |
| /* Iterate over the shorter object (only if other is a set, | |
| * because PySequence_Contains may be expensive otherwise): */ | |
| if (PyAnySet_Check(other) || PyDictViewSet_Check(other)) { | |
| Py_ssize_t len_self = dictview_len(self); | |
| Py_ssize_t len_other = PyObject_Size(other); | |
| if (len_other == -1) | |
| return NULL; | |
| if ((len_other > len_self)) { | |
| PyObject *tmp = other; | |
| other = self; | |
| self = tmp; | |
| } | |
| } | |
| it = PyObject_GetIter(other); | |
| if (it == NULL) | |
| return NULL; | |
| while ((item = PyIter_Next(it)) != NULL) { | |
| int contains = PySequence_Contains(self, item); | |
| Py_DECREF(item); | |
| if (contains == -1) { | |
| Py_DECREF(it); | |
| return NULL; | |
| } | |
| if (contains) { | |
| Py_DECREF(it); | |
| Py_RETURN_FALSE; | |
| } | |
| } | |
| Py_DECREF(it); | |
| if (PyErr_Occurred()) | |
| return NULL; /* PyIter_Next raised an exception. */ | |
| Py_RETURN_TRUE; | |
| } | |
| PyDoc_STRVAR(isdisjoint_doc, | |
| "Return True if the view and the given iterable have a null intersection."); | |
| static PyObject* dictkeys_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); | |
| PyDoc_STRVAR(reversed_keys_doc, | |
| "Return a reverse iterator over the dict keys."); | |
| static PyMethodDef dictkeys_methods[] = { | |
| {"isdisjoint", dictviews_isdisjoint, METH_O, | |
| isdisjoint_doc}, | |
| {"__reversed__", dictkeys_reversed, METH_NOARGS, | |
| reversed_keys_doc}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| PyTypeObject PyDictKeys_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_keys", /* tp_name */ | |
| sizeof(_PyDictViewObject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictview_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| dictview_repr, /* tp_repr */ | |
| &dictviews_as_number, /* tp_as_number */ | |
| &dictkeys_as_sequence, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictview_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| dictview_richcompare, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| dictkeys_iter, /* tp_iter */ | |
| 0, /* tp_iternext */ | |
| dictkeys_methods, /* tp_methods */ | |
| .tp_getset = dictview_getset, | |
| }; | |
| /*[clinic input] | |
| dict.keys | |
| Return a set-like object providing a view on the dict's keys. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_keys_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=aac2830c62990358 input=42f48a7a771212a7]*/ | |
| { | |
| return _PyDictView_New((PyObject *)self, &PyDictKeys_Type); | |
| } | |
| static PyObject * | |
| dictkeys_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictRevIterKey_Type); | |
| } | |
| /*** dict_items ***/ | |
| static PyObject * | |
| dictitems_iter(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictIterItem_Type); | |
| } | |
| static int | |
| dictitems_contains(PyObject *self, PyObject *obj) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| int result; | |
| PyObject *key, *value, *found; | |
| if (dv->dv_dict == NULL) | |
| return 0; | |
| if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) | |
| return 0; | |
| key = PyTuple_GET_ITEM(obj, 0); | |
| value = PyTuple_GET_ITEM(obj, 1); | |
| result = PyDict_GetItemRef((PyObject *)dv->dv_dict, key, &found); | |
| if (result == 1) { | |
| result = PyObject_RichCompareBool(found, value, Py_EQ); | |
| Py_DECREF(found); | |
| } | |
| return result; | |
| } | |
| static PySequenceMethods dictitems_as_sequence = { | |
| dictview_len, /* sq_length */ | |
| 0, /* sq_concat */ | |
| 0, /* sq_repeat */ | |
| 0, /* sq_item */ | |
| 0, /* sq_slice */ | |
| 0, /* sq_ass_item */ | |
| 0, /* sq_ass_slice */ | |
| dictitems_contains, /* sq_contains */ | |
| }; | |
| static PyObject* dictitems_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); | |
| PyDoc_STRVAR(reversed_items_doc, | |
| "Return a reverse iterator over the dict items."); | |
| static PyMethodDef dictitems_methods[] = { | |
| {"isdisjoint", dictviews_isdisjoint, METH_O, | |
| isdisjoint_doc}, | |
| {"__reversed__", dictitems_reversed, METH_NOARGS, | |
| reversed_items_doc}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| PyTypeObject PyDictItems_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_items", /* tp_name */ | |
| sizeof(_PyDictViewObject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictview_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| dictview_repr, /* tp_repr */ | |
| &dictviews_as_number, /* tp_as_number */ | |
| &dictitems_as_sequence, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictview_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| dictview_richcompare, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| dictitems_iter, /* tp_iter */ | |
| 0, /* tp_iternext */ | |
| dictitems_methods, /* tp_methods */ | |
| .tp_getset = dictview_getset, | |
| }; | |
| /*[clinic input] | |
| dict.items | |
| Return a set-like object providing a view on the dict's items. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_items_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=88c7db7150c7909a input=87c822872eb71f5a]*/ | |
| { | |
| return _PyDictView_New((PyObject *)self, &PyDictItems_Type); | |
| } | |
| static PyObject * | |
| dictitems_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictRevIterItem_Type); | |
| } | |
| /*** dict_values ***/ | |
| static PyObject * | |
| dictvalues_iter(PyObject *self) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictIterValue_Type); | |
| } | |
| static PySequenceMethods dictvalues_as_sequence = { | |
| dictview_len, /* sq_length */ | |
| 0, /* sq_concat */ | |
| 0, /* sq_repeat */ | |
| 0, /* sq_item */ | |
| 0, /* sq_slice */ | |
| 0, /* sq_ass_item */ | |
| 0, /* sq_ass_slice */ | |
| 0, /* sq_contains */ | |
| }; | |
| static PyObject* dictvalues_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); | |
| PyDoc_STRVAR(reversed_values_doc, | |
| "Return a reverse iterator over the dict values."); | |
| static PyMethodDef dictvalues_methods[] = { | |
| {"__reversed__", dictvalues_reversed, METH_NOARGS, | |
| reversed_values_doc}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| PyTypeObject PyDictValues_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| "dict_values", /* tp_name */ | |
| sizeof(_PyDictViewObject), /* tp_basicsize */ | |
| 0, /* tp_itemsize */ | |
| /* methods */ | |
| dictview_dealloc, /* tp_dealloc */ | |
| 0, /* tp_vectorcall_offset */ | |
| 0, /* tp_getattr */ | |
| 0, /* tp_setattr */ | |
| 0, /* tp_as_async */ | |
| dictview_repr, /* tp_repr */ | |
| 0, /* tp_as_number */ | |
| &dictvalues_as_sequence, /* tp_as_sequence */ | |
| 0, /* tp_as_mapping */ | |
| 0, /* tp_hash */ | |
| 0, /* tp_call */ | |
| 0, /* tp_str */ | |
| PyObject_GenericGetAttr, /* tp_getattro */ | |
| 0, /* tp_setattro */ | |
| 0, /* tp_as_buffer */ | |
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 0, /* tp_doc */ | |
| dictview_traverse, /* tp_traverse */ | |
| 0, /* tp_clear */ | |
| 0, /* tp_richcompare */ | |
| 0, /* tp_weaklistoffset */ | |
| dictvalues_iter, /* tp_iter */ | |
| 0, /* tp_iternext */ | |
| dictvalues_methods, /* tp_methods */ | |
| .tp_getset = dictview_getset, | |
| }; | |
| /*[clinic input] | |
| dict.values | |
| Return an object providing a view on the dict's values. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| dict_values_impl(PyDictObject *self) | |
| /*[clinic end generated code: output=ce9f2e9e8a959dd4 input=b46944f85493b230]*/ | |
| { | |
| return _PyDictView_New((PyObject *)self, &PyDictValues_Type); | |
| } | |
| static PyObject * | |
| dictvalues_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| _PyDictViewObject *dv = (_PyDictViewObject *)self; | |
| if (dv->dv_dict == NULL) { | |
| Py_RETURN_NONE; | |
| } | |
| return dictiter_new(dv->dv_dict, &PyDictRevIterValue_Type); | |
| } | |
| /* Returns NULL if cannot allocate a new PyDictKeysObject, | |
| but does not set an error */ | |
| PyDictKeysObject * | |
| _PyDict_NewKeysForClass(PyHeapTypeObject *cls) | |
| { | |
| int log2_bytes = get_log2_bytes(NEXT_LOG2_SHARED_KEYS_MAX_SIZE); | |
| Py_ssize_t usable = USABLE_FRACTION((size_t)1<<NEXT_LOG2_SHARED_KEYS_MAX_SIZE); | |
| struct _instancekeysobject *shared_keys = | |
| PyMem_Malloc(sizeof(struct _instancekeysobject) | |
| + ((size_t)1 << log2_bytes) | |
| + sizeof(PyDictUnicodeEntry) * usable); | |
| if (shared_keys == NULL) { | |
| PyErr_Clear(); | |
| return NULL; | |
| } | |
| shared_keys->dsk_owning_type = (PyTypeObject *)cls; | |
| PyDictKeysObject* keys = &shared_keys->dsk_keys; | |
| init_keys_object(keys, NEXT_LOG2_SHARED_KEYS_MAX_SIZE, log2_bytes, DICT_KEYS_SPLIT, | |
| SHARED_KEYS_MAX_SIZE, sizeof(PyDictUnicodeEntry)); | |
| assert(keys->dk_nentries == 0); | |
| /* Set to max size+1 as it will shrink by one before each new object */ | |
| if (cls->ht_type.tp_dict) { | |
| PyObject *attrs = PyDict_GetItem(cls->ht_type.tp_dict, &_Py_ID(__static_attributes__)); | |
| if (attrs != NULL && PyTuple_Check(attrs)) { | |
| for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(attrs); i++) { | |
| PyObject *key = PyTuple_GET_ITEM(attrs, i); | |
| Py_hash_t hash; | |
| if (PyUnicode_CheckExact(key) && (hash = unicode_get_hash(key)) != -1) { | |
| if (insert_split_key(keys, key, hash) == DKIX_EMPTY) { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return keys; | |
| } | |
| void | |
| _PyDict_RemoveKeysForClass(PyHeapTypeObject *cls) | |
| { | |
| struct _instancekeysobject *shared_keys = _PyDictKeys_AsSharedKeys(cls->ht_cached_keys); | |
| FT_ATOMIC_STORE_PTR_RELEASE(shared_keys->dsk_owning_type, NULL); | |
| _PyDictKeys_DecRef(cls->ht_cached_keys); | |
| } | |
| void | |
| _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp) | |
| { | |
| assert(tp->tp_flags & Py_TPFLAGS_HEAPTYPE); | |
| assert(tp->tp_flags & Py_TPFLAGS_INLINE_VALUES); | |
| assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT); | |
| PyDictKeysObject *keys = CACHED_KEYS(tp); | |
| assert(keys != NULL); | |
| OBJECT_STAT_INC(inline_values); | |
| #ifdef Py_GIL_DISABLED | |
| Py_ssize_t usable = _Py_atomic_load_ssize_relaxed(&keys->dk_usable); | |
| if (usable > 1) { | |
| LOCK_KEYS(keys); | |
| if (keys->dk_usable > 1) { | |
| _Py_atomic_store_ssize(&keys->dk_usable, keys->dk_usable - 1); | |
| } | |
| UNLOCK_KEYS(keys); | |
| } | |
| #else | |
| if (keys->dk_usable > 1) { | |
| keys->dk_usable--; | |
| } | |
| #endif | |
| size_t size = shared_keys_usable_size(keys); | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| assert(size < 256); | |
| values->capacity = (uint8_t)size; | |
| values->size = 0; | |
| values->embedded = 1; | |
| values->valid = 1; | |
| for (size_t i = 0; i < size; i++) { | |
| values->values[i] = NULL; | |
| } | |
| _PyObject_ManagedDictPointer(obj)->dict = NULL; | |
| } | |
| static PyDictObject * | |
| make_dict_from_instance_attributes(PyDictKeysObject *keys, PyDictValues *values) | |
| { | |
| dictkeys_incref(keys); | |
| Py_ssize_t used = 0; | |
| size_t size = shared_keys_usable_size(keys); | |
| for (size_t i = 0; i < size; i++) { | |
| PyObject *val = values->values[i]; | |
| if (val != NULL) { | |
| used += 1; | |
| } | |
| } | |
| PyDictObject *res = (PyDictObject *)new_dict(keys, values, used, 0); | |
| return res; | |
| } | |
| PyDictObject * | |
| _PyObject_MaterializeManagedDict_LockHeld(PyObject *obj) | |
| { | |
| ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); | |
| OBJECT_STAT_INC(dict_materialized_on_request); | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| PyDictObject *dict; | |
| if (values->valid) { | |
| PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); | |
| dict = make_dict_from_instance_attributes(keys, values); | |
| } | |
| else { | |
| dict = (PyDictObject *)PyDict_New(); | |
| } | |
| FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict, | |
| dict); | |
| return dict; | |
| } | |
| PyDictObject * | |
| _PyObject_MaterializeManagedDict(PyObject *obj) | |
| { | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict != NULL) { | |
| return dict; | |
| } | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| #ifdef Py_GIL_DISABLED | |
| dict = _PyObject_GetManagedDict(obj); | |
| if (dict != NULL) { | |
| // We raced with another thread creating the dict | |
| goto exit; | |
| } | |
| #endif | |
| dict = _PyObject_MaterializeManagedDict_LockHeld(obj); | |
| #ifdef Py_GIL_DISABLED | |
| exit: | |
| #endif | |
| Py_END_CRITICAL_SECTION(); | |
| return dict; | |
| } | |
| int | |
| _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value) | |
| { | |
| if (!PyDict_Check(dict)) { | |
| if (PyFrozenDict_Check(dict)) { | |
| if (value == NULL) { | |
| frozendict_does_not_support("deletion"); | |
| } | |
| else { | |
| frozendict_does_not_support("assignment"); | |
| } | |
| } | |
| else { | |
| PyErr_BadInternalCall(); | |
| } | |
| return -1; | |
| } | |
| if (value == NULL) { | |
| Py_hash_t hash = _PyObject_HashDictKey(name); | |
| if (hash == -1) { | |
| dict_unhashable_type((PyObject*)dict, name); | |
| return -1; | |
| } | |
| return _PyDict_DelItem_KnownHash_LockHeld((PyObject *)dict, name, hash); | |
| } else { | |
| return setitem_lock_held(dict, name, value); | |
| } | |
| } | |
| // Called with either the object's lock or the dict's lock held | |
| // depending on whether or not a dict has been materialized for | |
| // the object. | |
| static int | |
| store_instance_attr_lock_held(PyObject *obj, PyDictValues *values, | |
| PyObject *name, PyObject *value) | |
| { | |
| PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); | |
| assert(keys != NULL); | |
| assert(values != NULL); | |
| assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES); | |
| Py_ssize_t ix = DKIX_EMPTY; | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| assert(dict == NULL || ((PyDictObject *)dict)->ma_values == values); | |
| if (PyUnicode_CheckExact(name)) { | |
| Py_hash_t hash = unicode_get_hash(name); | |
| if (hash == -1) { | |
| hash = PyUnicode_Type.tp_hash(name); | |
| assert(hash != -1); | |
| } | |
| ix = insert_split_key(keys, name, hash); | |
| #ifdef Py_STATS | |
| if (ix == DKIX_EMPTY) { | |
| if (PyUnicode_CheckExact(name)) { | |
| if (shared_keys_usable_size(keys) == SHARED_KEYS_MAX_SIZE) { | |
| OBJECT_STAT_INC(dict_materialized_too_big); | |
| } | |
| else { | |
| OBJECT_STAT_INC(dict_materialized_new_key); | |
| } | |
| } | |
| else { | |
| OBJECT_STAT_INC(dict_materialized_str_subclass); | |
| } | |
| } | |
| #endif | |
| } | |
| if (ix == DKIX_EMPTY) { | |
| int res; | |
| if (dict == NULL) { | |
| // Make the dict but don't publish it in the object | |
| // so that no one else will see it. | |
| dict = make_dict_from_instance_attributes(keys, values); | |
| if (dict == NULL || | |
| _PyDict_SetItem_LockHeld(dict, name, value) < 0) { | |
| Py_XDECREF(dict); | |
| return -1; | |
| } | |
| FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict, | |
| (PyDictObject *)dict); | |
| return 0; | |
| } | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(dict); | |
| res = _PyDict_SetItem_LockHeld(dict, name, value); | |
| return res; | |
| } | |
| PyObject *old_value = values->values[ix]; | |
| if (old_value == NULL && value == NULL) { | |
| PyErr_Format(PyExc_AttributeError, | |
| "'%.100s' object has no attribute '%U'", | |
| Py_TYPE(obj)->tp_name, name); | |
| (void)_PyObject_SetAttributeErrorContext(obj, name); | |
| return -1; | |
| } | |
| if (dict) { | |
| PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED : | |
| value == NULL ? PyDict_EVENT_DELETED : | |
| PyDict_EVENT_MODIFIED); | |
| _PyDict_NotifyEvent(event, dict, name, value); | |
| } | |
| FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value)); | |
| if (old_value == NULL) { | |
| _PyDictValues_AddToInsertionOrder(values, ix); | |
| if (dict) { | |
| assert(dict->ma_values == values); | |
| STORE_USED(dict, dict->ma_used + 1); | |
| } | |
| } | |
| else { | |
| if (value == NULL) { | |
| delete_index_from_values(values, ix); | |
| if (dict) { | |
| assert(dict->ma_values == values); | |
| STORE_USED(dict, dict->ma_used - 1); | |
| } | |
| } | |
| Py_DECREF(old_value); | |
| } | |
| return 0; | |
| } | |
| static inline int | |
| store_instance_attr_dict(PyObject *obj, PyDictObject *dict, PyObject *name, PyObject *value) | |
| { | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| if (dict->ma_values == values) { | |
| res = store_instance_attr_lock_held(obj, values, name, value); | |
| } | |
| else { | |
| res = _PyDict_SetItem_LockHeld(dict, name, value); | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| int | |
| _PyObject_StoreInstanceAttribute(PyObject *obj, PyObject *name, PyObject *value) | |
| { | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| if (!FT_ATOMIC_LOAD_UINT8(values->valid)) { | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| dict = (PyDictObject *)PyObject_GenericGetDict(obj, NULL); | |
| if (dict == NULL) { | |
| return -1; | |
| } | |
| int res = store_instance_attr_dict(obj, dict, name, value); | |
| Py_DECREF(dict); | |
| return res; | |
| } | |
| return store_instance_attr_dict(obj, dict, name, value); | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| // We have a valid inline values, at least for now... There are two potential | |
| // races with having the values become invalid. One is the dictionary | |
| // being detached from the object. The other is if someone is inserting | |
| // into the dictionary directly and therefore causing it to resize. | |
| // | |
| // If we haven't materialized the dictionary yet we lock on the object, which | |
| // will also be used to prevent the dictionary from being materialized while | |
| // we're doing the insertion. If we race and the dictionary gets created | |
| // then we'll need to release the object lock and lock the dictionary to | |
| // prevent resizing. | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| int res; | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| res = store_instance_attr_lock_held(obj, values, name, value); | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| if (dict == NULL) { | |
| return res; | |
| } | |
| } | |
| return store_instance_attr_dict(obj, dict, name, value); | |
| #else | |
| return store_instance_attr_lock_held(obj, values, name, value); | |
| #endif | |
| } | |
| /* Sanity check for managed dicts */ | |
| #if 0 | |
| #define CHECK(val) assert(val); if (!(val)) { return 0; } | |
| int | |
| _PyObject_ManagedDictValidityCheck(PyObject *obj) | |
| { | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| CHECK(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT); | |
| PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(obj); | |
| if (_PyManagedDictPointer_IsValues(*managed_dict)) { | |
| PyDictValues *values = _PyManagedDictPointer_GetValues(*managed_dict); | |
| int size = ((uint8_t *)values)[-2]; | |
| int count = 0; | |
| PyDictKeysObject *keys = CACHED_KEYS(tp); | |
| for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { | |
| if (values->values[i] != NULL) { | |
| count++; | |
| } | |
| } | |
| CHECK(size == count); | |
| } | |
| else { | |
| if (managed_dict->dict != NULL) { | |
| CHECK(PyDict_Check(managed_dict->dict)); | |
| } | |
| } | |
| return 1; | |
| } | |
| #endif | |
| // Attempts to get an instance attribute from the inline values. Returns true | |
| // if successful, or false if the caller needs to lookup in the dictionary. | |
| bool | |
| _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr) | |
| { | |
| assert(PyUnicode_CheckExact(name)); | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| if (!FT_ATOMIC_LOAD_UINT8(values->valid)) { | |
| return false; | |
| } | |
| PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); | |
| assert(keys != NULL); | |
| Py_ssize_t ix = _PyDictKeys_StringLookupSplit(keys, name); | |
| if (ix == DKIX_EMPTY) { | |
| *attr = NULL; | |
| return true; | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| PyObject *value = _Py_atomic_load_ptr_acquire(&values->values[ix]); | |
| if (value == NULL) { | |
| if (FT_ATOMIC_LOAD_UINT8(values->valid)) { | |
| *attr = NULL; | |
| return true; | |
| } | |
| } | |
| else if (_Py_TryIncrefCompare(&values->values[ix], value)) { | |
| *attr = value; | |
| return true; | |
| } | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| // No dict, lock the object to prevent one from being | |
| // materialized... | |
| bool success = false; | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| // Still no dict, we can read from the values | |
| assert(values->valid); | |
| value = values->values[ix]; | |
| *attr = _Py_XNewRefWithLock(value); | |
| success = true; | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| if (success) { | |
| return true; | |
| } | |
| } | |
| // We have a dictionary, we'll need to lock it to prevent | |
| // the values from being resized. | |
| assert(dict != NULL); | |
| bool success; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) { | |
| value = _Py_atomic_load_ptr_consume(&values->values[ix]); | |
| *attr = _Py_XNewRefWithLock(value); | |
| success = true; | |
| } else { | |
| // Caller needs to lookup from the dictionary | |
| success = false; | |
| } | |
| Py_END_CRITICAL_SECTION(); | |
| return success; | |
| #else | |
| PyObject *value = values->values[ix]; | |
| *attr = Py_XNewRef(value); | |
| return true; | |
| #endif | |
| } | |
| int | |
| _PyObject_IsInstanceDictEmpty(PyObject *obj) | |
| { | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if (tp->tp_dictoffset == 0) { | |
| return 1; | |
| } | |
| PyDictObject *dict; | |
| if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) { | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| if (FT_ATOMIC_LOAD_UINT8(values->valid)) { | |
| PyDictKeysObject *keys = CACHED_KEYS(tp); | |
| for (Py_ssize_t i = 0; i < LOAD_KEYS_NENTRIES(keys); i++) { | |
| if (FT_ATOMIC_LOAD_PTR_RELAXED(values->values[i]) != NULL) { | |
| return 0; | |
| } | |
| } | |
| return 1; | |
| } | |
| dict = _PyObject_GetManagedDict(obj); | |
| } | |
| else if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) { | |
| dict = _PyObject_GetManagedDict(obj); | |
| } | |
| else { | |
| PyObject **dictptr = _PyObject_ComputedDictPointer(obj); | |
| dict = (PyDictObject *)*dictptr; | |
| } | |
| if (dict == NULL) { | |
| return 1; | |
| } | |
| return GET_USED((PyDictObject *)dict) == 0; | |
| } | |
| int | |
| PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) | |
| { | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) { | |
| return 0; | |
| } | |
| PyDictObject *dict = _PyObject_ManagedDictPointer(obj)->dict; | |
| if (dict != NULL) { | |
| // GH-130327: If there's a managed dictionary available, we should | |
| // *always* traverse it. The dict is responsible for traversing the | |
| // inline values if it points to them. | |
| Py_VISIT(dict); | |
| } | |
| else if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) { | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| if (values->valid) { | |
| for (Py_ssize_t i = 0; i < values->capacity; i++) { | |
| Py_VISIT(values->values[i]); | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static void | |
| clear_inline_values(PyDictValues *values) | |
| { | |
| if (values->valid) { | |
| FT_ATOMIC_STORE_UINT8(values->valid, 0); | |
| for (Py_ssize_t i = 0; i < values->capacity; i++) { | |
| Py_CLEAR(values->values[i]); | |
| } | |
| } | |
| } | |
| static void | |
| set_dict_inline_values(PyObject *obj, PyDictObject *new_dict) | |
| { | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj); | |
| PyDictValues *values = _PyObject_InlineValues(obj); | |
| Py_XINCREF(new_dict); | |
| FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict); | |
| clear_inline_values(values); | |
| } | |
| #ifdef Py_GIL_DISABLED | |
| // Trys and sets the dictionary for an object in the easy case when our current | |
| // dictionary is either completely not materialized or is a dictionary which | |
| // does not point at the inline values. | |
| static bool | |
| try_set_dict_inline_only_or_other_dict(PyObject *obj, PyObject *new_dict, PyDictObject **cur_dict) | |
| { | |
| bool replaced = false; | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| PyDictObject *dict = *cur_dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| // We only have inline values, we can just completely replace them. | |
| set_dict_inline_values(obj, (PyDictObject *)new_dict); | |
| replaced = true; | |
| goto exit_lock; | |
| } | |
| if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) != _PyObject_InlineValues(obj)) { | |
| // We have a materialized dict which doesn't point at the inline values, | |
| // We get to simply swap dictionaries and free the old dictionary. | |
| FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, | |
| (PyDictObject *)Py_XNewRef(new_dict)); | |
| replaced = true; | |
| goto exit_lock; | |
| } | |
| else { | |
| // We have inline values, we need to lock the dict and the object | |
| // at the same time to safely dematerialize them. To do that while releasing | |
| // the object lock we need a strong reference to the current dictionary. | |
| Py_INCREF(dict); | |
| } | |
| exit_lock: | |
| Py_END_CRITICAL_SECTION(); | |
| return replaced; | |
| } | |
| // Replaces a dictionary that is probably the dictionary which has been | |
| // materialized and points at the inline values. We could have raced | |
| // and replaced it with another dictionary though. | |
| static int | |
| replace_dict_probably_inline_materialized(PyObject *obj, PyDictObject *inline_dict, | |
| PyDictObject *cur_dict, PyObject *new_dict) | |
| { | |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj); | |
| if (cur_dict == inline_dict) { | |
| assert(FT_ATOMIC_LOAD_PTR_RELAXED(inline_dict->ma_values) == _PyObject_InlineValues(obj)); | |
| int err = _PyDict_DetachFromObject(inline_dict, obj); | |
| if (err != 0) { | |
| assert(new_dict == NULL); | |
| return err; | |
| } | |
| } | |
| FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, | |
| (PyDictObject *)Py_XNewRef(new_dict)); | |
| return 0; | |
| } | |
| #endif | |
| static void | |
| decref_maybe_delay(PyObject *obj, bool delay) | |
| { | |
| if (delay) { | |
| _PyObject_XDecRefDelayed(obj); | |
| } | |
| else { | |
| Py_XDECREF(obj); | |
| } | |
| } | |
| int | |
| _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict) | |
| { | |
| assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); | |
| #ifndef NDEBUG | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); | |
| Py_END_CRITICAL_SECTION(); | |
| #endif | |
| int err = 0; | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) { | |
| #ifdef Py_GIL_DISABLED | |
| PyDictObject *prev_dict; | |
| if (!try_set_dict_inline_only_or_other_dict(obj, new_dict, &prev_dict)) { | |
| // We had a materialized dictionary which pointed at the inline | |
| // values. We need to lock both the object and the dict at the | |
| // same time to safely replace it. We can't merely lock the dictionary | |
| // while the object is locked because it could suspend the object lock. | |
| PyDictObject *cur_dict; | |
| assert(prev_dict != NULL); | |
| Py_BEGIN_CRITICAL_SECTION2(obj, prev_dict); | |
| // We could have had another thread race in between the call to | |
| // try_set_dict_inline_only_or_other_dict where we locked the object | |
| // and when we unlocked and re-locked the dictionary. | |
| cur_dict = _PyObject_GetManagedDict(obj); | |
| err = replace_dict_probably_inline_materialized(obj, prev_dict, | |
| cur_dict, new_dict); | |
| Py_END_CRITICAL_SECTION2(); | |
| // Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict | |
| // while the object was locked | |
| decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict); | |
| if (err != 0) { | |
| return err; | |
| } | |
| prev_dict = cur_dict; | |
| } | |
| if (prev_dict != NULL) { | |
| // decref for the dictionary that we replaced | |
| decref_maybe_delay((PyObject *)prev_dict, true); | |
| } | |
| return 0; | |
| #else | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| set_dict_inline_values(obj, (PyDictObject *)new_dict); | |
| return 0; | |
| } | |
| if (_PyDict_DetachFromObject(dict, obj) == 0) { | |
| _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)Py_XNewRef(new_dict); | |
| Py_DECREF(dict); | |
| return 0; | |
| } | |
| assert(new_dict == NULL); | |
| return -1; | |
| #endif | |
| } | |
| else { | |
| PyDictObject *dict; | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| dict = _PyObject_ManagedDictPointer(obj)->dict; | |
| FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, | |
| (PyDictObject *)Py_XNewRef(new_dict)); | |
| Py_END_CRITICAL_SECTION(); | |
| decref_maybe_delay((PyObject *)dict, true); | |
| } | |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); | |
| return err; | |
| } | |
| static int | |
| detach_dict_from_object(PyDictObject *mp, PyObject *obj) | |
| { | |
| assert(_PyObject_ManagedDictPointer(obj)->dict == mp); | |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); | |
| if (FT_ATOMIC_LOAD_PTR_RELAXED(mp->ma_values) != _PyObject_InlineValues(obj)) { | |
| return 0; | |
| } | |
| // We could be called with an unlocked dict when the caller knows the | |
| // values are already detached, so we assert after inline values check. | |
| ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(mp); | |
| assert(mp->ma_values->embedded == 1); | |
| assert(mp->ma_values->valid == 1); | |
| assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES); | |
| PyDictValues *values = copy_values(mp->ma_values); | |
| if (values == NULL) { | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| mp->ma_values = values; | |
| invalidate_and_clear_inline_values(_PyObject_InlineValues(obj)); | |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); | |
| ASSERT_CONSISTENT(mp); | |
| return 0; | |
| } | |
| void | |
| PyObject_ClearManagedDict(PyObject *obj) | |
| { | |
| // This is called when the object is being freed or cleared | |
| // by the GC and therefore known to have no references. | |
| if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) { | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| // We have no materialized dictionary and inline values | |
| // that just need to be cleared. | |
| // No dict to clear, we're done | |
| clear_inline_values(_PyObject_InlineValues(obj)); | |
| return; | |
| } | |
| else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) == | |
| _PyObject_InlineValues(obj)) { | |
| // We have a materialized object which points at the inline | |
| // values. We need to materialize the keys. Nothing can modify | |
| // this object, but we need to lock the dictionary. | |
| int err; | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| err = detach_dict_from_object(dict, obj); | |
| Py_END_CRITICAL_SECTION(); | |
| if (err) { | |
| /* Must be out of memory */ | |
| assert(PyErr_Occurred() == PyExc_MemoryError); | |
| PyErr_FormatUnraisable("Exception ignored while " | |
| "clearing an object managed dict"); | |
| /* Clear the dict */ | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| PyDictKeysObject *oldkeys = dict->ma_keys; | |
| set_keys(dict, Py_EMPTY_KEYS); | |
| dict->ma_values = NULL; | |
| dictkeys_decref(oldkeys, IS_DICT_SHARED(dict)); | |
| STORE_USED(dict, 0); | |
| clear_inline_values(_PyObject_InlineValues(obj)); | |
| Py_END_CRITICAL_SECTION(); | |
| } | |
| } | |
| } | |
| Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict); | |
| } | |
| int | |
| _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) | |
| { | |
| ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); | |
| return detach_dict_from_object(mp, obj); | |
| } | |
| static inline PyObject * | |
| ensure_managed_dict(PyObject *obj) | |
| { | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && | |
| FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(obj)->valid)) { | |
| dict = _PyObject_MaterializeManagedDict(obj); | |
| } | |
| else { | |
| #ifdef Py_GIL_DISABLED | |
| // Check again that we're not racing with someone else creating the dict | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| dict = _PyObject_GetManagedDict(obj); | |
| if (dict != NULL) { | |
| goto done; | |
| } | |
| #endif | |
| dict = (PyDictObject *)new_dict_with_shared_keys(CACHED_KEYS(tp)); | |
| FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict, | |
| (PyDictObject *)dict); | |
| #ifdef Py_GIL_DISABLED | |
| done: | |
| Py_END_CRITICAL_SECTION(); | |
| #endif | |
| } | |
| } | |
| return (PyObject *)dict; | |
| } | |
| static inline PyObject * | |
| ensure_nonmanaged_dict(PyObject *obj, PyObject **dictptr) | |
| { | |
| PyDictKeysObject *cached; | |
| PyObject *dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr); | |
| if (dict == NULL) { | |
| #ifdef Py_GIL_DISABLED | |
| Py_BEGIN_CRITICAL_SECTION(obj); | |
| dict = *dictptr; | |
| if (dict != NULL) { | |
| goto done; | |
| } | |
| #endif | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) && (cached = CACHED_KEYS(tp))) { | |
| assert(!_PyType_HasFeature(tp, Py_TPFLAGS_INLINE_VALUES)); | |
| dict = new_dict_with_shared_keys(cached); | |
| } | |
| else { | |
| dict = PyDict_New(); | |
| } | |
| FT_ATOMIC_STORE_PTR_RELEASE(*dictptr, dict); | |
| #ifdef Py_GIL_DISABLED | |
| done: | |
| Py_END_CRITICAL_SECTION(); | |
| #endif | |
| } | |
| return dict; | |
| } | |
| PyObject * | |
| PyObject_GenericGetDict(PyObject *obj, void *context) | |
| { | |
| PyTypeObject *tp = Py_TYPE(obj); | |
| if (_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT)) { | |
| return Py_XNewRef(ensure_managed_dict(obj)); | |
| } | |
| else { | |
| PyObject **dictptr = _PyObject_ComputedDictPointer(obj); | |
| if (dictptr == NULL) { | |
| PyErr_SetString(PyExc_AttributeError, | |
| "This object has no __dict__"); | |
| return NULL; | |
| } | |
| return Py_XNewRef(ensure_nonmanaged_dict(obj, dictptr)); | |
| } | |
| } | |
| int | |
| _PyObjectDict_SetItem(PyTypeObject *tp, PyObject *obj, PyObject **dictptr, | |
| PyObject *key, PyObject *value) | |
| { | |
| PyObject *dict; | |
| int res; | |
| assert(dictptr != NULL); | |
| dict = ensure_nonmanaged_dict(obj, dictptr); | |
| if (dict == NULL) { | |
| return -1; | |
| } | |
| Py_BEGIN_CRITICAL_SECTION(dict); | |
| res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, key, value); | |
| ASSERT_CONSISTENT(dict); | |
| Py_END_CRITICAL_SECTION(); | |
| return res; | |
| } | |
| void | |
| _PyDictKeys_DecRef(PyDictKeysObject *keys) | |
| { | |
| dictkeys_decref(keys, false); | |
| } | |
| static inline uint32_t | |
| get_next_dict_keys_version(PyInterpreterState *interp) | |
| { | |
| #ifdef Py_GIL_DISABLED | |
| uint32_t v; | |
| do { | |
| v = _Py_atomic_load_uint32_relaxed( | |
| &interp->dict_state.next_keys_version); | |
| if (v == 0) { | |
| return 0; | |
| } | |
| } while (!_Py_atomic_compare_exchange_uint32( | |
| &interp->dict_state.next_keys_version, &v, v + 1)); | |
| #else | |
| if (interp->dict_state.next_keys_version == 0) { | |
| return 0; | |
| } | |
| uint32_t v = interp->dict_state.next_keys_version++; | |
| #endif | |
| return v; | |
| } | |
| // In free-threaded builds the caller must ensure that the keys object is not | |
| // being mutated concurrently by another thread. | |
| uint32_t | |
| _PyDictKeys_GetVersionForCurrentState(PyInterpreterState *interp, | |
| PyDictKeysObject *dictkeys) | |
| { | |
| uint32_t dk_version = FT_ATOMIC_LOAD_UINT32_RELAXED(dictkeys->dk_version); | |
| if (dk_version != 0) { | |
| return dk_version; | |
| } | |
| dk_version = get_next_dict_keys_version(interp); | |
| FT_ATOMIC_STORE_UINT32_RELAXED(dictkeys->dk_version, dk_version); | |
| return dk_version; | |
| } | |
| uint32_t | |
| _PyDict_GetKeysVersionForCurrentState(PyInterpreterState *interp, | |
| PyDictObject *dict) | |
| { | |
| ASSERT_DICT_LOCKED((PyObject *) dict); | |
| uint32_t dk_version = | |
| _PyDictKeys_GetVersionForCurrentState(interp, dict->ma_keys); | |
| ensure_shared_on_keys_version_assignment(dict); | |
| return dk_version; | |
| } | |
| static inline int | |
| validate_watcher_id(PyInterpreterState *interp, int watcher_id) | |
| { | |
| if (watcher_id < 0 || watcher_id >= DICT_MAX_WATCHERS) { | |
| PyErr_Format(PyExc_ValueError, "Invalid dict watcher ID %d", watcher_id); | |
| return -1; | |
| } | |
| PyDict_WatchCallback cb = FT_ATOMIC_LOAD_PTR_RELAXED( | |
| interp->dict_state.watchers[watcher_id]); | |
| if (cb == NULL) { | |
| PyErr_Format(PyExc_ValueError, "No dict watcher set for ID %d", watcher_id); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| // In free-threaded builds, Add/Clear serialize on watcher_mutex and publish | |
| // callbacks with release stores. SendEvent reads them lock-free using | |
| // acquire loads. | |
| int | |
| PyDict_Watch(int watcher_id, PyObject* dict) | |
| { | |
| if (!PyDict_Check(dict)) { | |
| PyErr_SetString(PyExc_ValueError, "Cannot watch non-dictionary"); | |
| return -1; | |
| } | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| if (validate_watcher_id(interp, watcher_id)) { | |
| return -1; | |
| } | |
| FT_ATOMIC_OR_UINT64(((PyDictObject*)dict)->_ma_watcher_tag, | |
| 1ULL << watcher_id); | |
| return 0; | |
| } | |
| int | |
| PyDict_Unwatch(int watcher_id, PyObject* dict) | |
| { | |
| if (!PyDict_Check(dict)) { | |
| PyErr_SetString(PyExc_ValueError, "Cannot watch non-dictionary"); | |
| return -1; | |
| } | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| if (validate_watcher_id(interp, watcher_id)) { | |
| return -1; | |
| } | |
| FT_ATOMIC_AND_UINT64(((PyDictObject*)dict)->_ma_watcher_tag, | |
| ~(1ULL << watcher_id)); | |
| return 0; | |
| } | |
| int | |
| PyDict_AddWatcher(PyDict_WatchCallback callback) | |
| { | |
| int watcher_id = -1; | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| FT_MUTEX_LOCK_FLAGS(&interp->dict_state.watcher_mutex, | |
| _Py_LOCK_DONT_DETACH); | |
| /* Some watchers are reserved for CPython, start at the first available one */ | |
| for (int i = FIRST_AVAILABLE_WATCHER; i < DICT_MAX_WATCHERS; i++) { | |
| if (!interp->dict_state.watchers[i]) { | |
| FT_ATOMIC_STORE_PTR_RELEASE(interp->dict_state.watchers[i], callback); | |
| watcher_id = i; | |
| goto done; | |
| } | |
| } | |
| PyErr_SetString(PyExc_RuntimeError, "no more dict watcher IDs available"); | |
| done: | |
| FT_MUTEX_UNLOCK(&interp->dict_state.watcher_mutex); | |
| return watcher_id; | |
| } | |
| int | |
| PyDict_ClearWatcher(int watcher_id) | |
| { | |
| int res = 0; | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| FT_MUTEX_LOCK_FLAGS(&interp->dict_state.watcher_mutex, | |
| _Py_LOCK_DONT_DETACH); | |
| if (validate_watcher_id(interp, watcher_id)) { | |
| res = -1; | |
| goto done; | |
| } | |
| FT_ATOMIC_STORE_PTR_RELEASE(interp->dict_state.watchers[watcher_id], NULL); | |
| done: | |
| FT_MUTEX_UNLOCK(&interp->dict_state.watcher_mutex); | |
| return res; | |
| } | |
| static const char * | |
| dict_event_name(PyDict_WatchEvent event) { | |
| switch (event) { | |
| #define CASE(op) \ | |
| case PyDict_EVENT_##op: \ | |
| return "PyDict_EVENT_" #op; | |
| PY_FOREACH_DICT_EVENT(CASE) | |
| #undef CASE | |
| } | |
| Py_UNREACHABLE(); | |
| } | |
| void | |
| _PyDict_SendEvent(int watcher_bits, | |
| PyDict_WatchEvent event, | |
| PyDictObject *mp, | |
| PyObject *key, | |
| PyObject *value) | |
| { | |
| PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| for (int i = 0; i < DICT_MAX_WATCHERS; i++) { | |
| if (watcher_bits & 1) { | |
| PyDict_WatchCallback cb = FT_ATOMIC_LOAD_PTR_ACQUIRE( | |
| interp->dict_state.watchers[i]); | |
| if (cb && (cb(event, (PyObject*)mp, key, value) < 0)) { | |
| // We don't want to resurrect the dict by potentially having an | |
| // unraisablehook keep a reference to it, so we don't pass the | |
| // dict as context, just an informative string message. Dict | |
| // repr can call arbitrary code, so we invent a simpler version. | |
| PyErr_FormatUnraisable( | |
| "Exception ignored in %s watcher callback for <dict at %p>", | |
| dict_event_name(event), mp); | |
| } | |
| } | |
| watcher_bits >>= 1; | |
| } | |
| } | |
| #ifndef NDEBUG | |
| static int | |
| _PyObject_InlineValuesConsistencyCheck(PyObject *obj) | |
| { | |
| if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) == 0) { | |
| return 1; | |
| } | |
| assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); | |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); | |
| if (dict == NULL) { | |
| return 1; | |
| } | |
| if (dict->ma_values == _PyObject_InlineValues(obj) || | |
| _PyObject_InlineValues(obj)->valid == 0) { | |
| return 1; | |
| } | |
| assert(0); | |
| return 0; | |
| } | |
| #endif | |
| // --- frozendict implementation --------------------------------------------- | |
| static PyObject * | |
| frozendict_getnewargs(PyObject *op, PyObject *Py_UNUSED(dummy)) | |
| { | |
| // Call dict(op): convert 'op' frozendict to a dict | |
| PyObject *arg = PyObject_CallOneArg((PyObject*)&PyDict_Type, op); | |
| if (arg == NULL) { | |
| return NULL; | |
| } | |
| return Py_BuildValue("(N)", arg); | |
| } | |
| static PyNumberMethods frozendict_as_number = { | |
| .nb_or = frozendict_or, | |
| }; | |
| static PyMappingMethods frozendict_as_mapping = { | |
| .mp_length = frozendict_length, | |
| .mp_subscript = _PyDict_Subscript, | |
| }; | |
| static PyMethodDef frozendict_methods[] = { | |
| DICT___CONTAINS___METHODDEF | |
| {"__getitem__", _PyDict_Subscript, METH_O | METH_COEXIST, getitem__doc__}, | |
| DICT___SIZEOF___METHODDEF | |
| DICT_GET_METHODDEF | |
| DICT_KEYS_METHODDEF | |
| DICT_ITEMS_METHODDEF | |
| DICT_VALUES_METHODDEF | |
| DICT_FROMKEYS_METHODDEF | |
| FROZENDICT_COPY_METHODDEF | |
| DICT___REVERSED___METHODDEF | |
| {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, | |
| PyDoc_STR("frozendicts are generic over two types, signifying (respectively) the types of the frozendict's keys and values")}, | |
| {"__getnewargs__", frozendict_getnewargs, METH_NOARGS}, | |
| {NULL, NULL} /* sentinel */ | |
| }; | |
| static PyObject * | |
| frozendict_repr(PyObject *self) | |
| { | |
| PyDictObject *mp = _PyAnyDict_CAST(self); | |
| if (mp->ma_used == 0) { | |
| return PyUnicode_FromFormat("%s()", Py_TYPE(self)->tp_name); | |
| } | |
| PyObject *repr = anydict_repr_impl(self); | |
| if (repr == NULL) { | |
| return NULL; | |
| } | |
| assert(PyUnicode_Check(repr)); | |
| PyObject *res = PyUnicode_FromFormat("%s(%U)", | |
| Py_TYPE(self)->tp_name, | |
| repr); | |
| Py_DECREF(repr); | |
| return res; | |
| } | |
| static Py_uhash_t | |
| _shuffle_bits(Py_uhash_t h) | |
| { | |
| return ((h ^ 89869747UL) ^ (h << 16)) * 3644798167UL; | |
| } | |
| // Compute hash((key, value)). | |
| // Code copied from tuple_hash(). | |
| static Py_hash_t | |
| frozendict_pair_hash(Py_hash_t key_hash, PyObject *value) | |
| { | |
| assert(key_hash != -1); | |
| const Py_ssize_t len = 2; | |
| Py_uhash_t acc = _PyTuple_HASH_XXPRIME_5; | |
| Py_uhash_t lane = key_hash; | |
| acc += lane * _PyTuple_HASH_XXPRIME_2; | |
| acc = _PyTuple_HASH_XXROTATE(acc); | |
| acc *= _PyTuple_HASH_XXPRIME_1; | |
| lane = PyObject_Hash(value); | |
| if (lane == (Py_uhash_t)-1) { | |
| return -1; | |
| } | |
| acc += lane * _PyTuple_HASH_XXPRIME_2; | |
| acc = _PyTuple_HASH_XXROTATE(acc); | |
| acc *= _PyTuple_HASH_XXPRIME_1; | |
| /* Add input length, mangled to keep the historical value of hash(()). */ | |
| acc += len ^ (_PyTuple_HASH_XXPRIME_5 ^ 3527539UL); | |
| if (acc == (Py_uhash_t)-1) { | |
| acc = 1546275796; | |
| } | |
| return acc; | |
| } | |
| // Code copied from frozenset_hash() | |
| static Py_hash_t | |
| frozendict_hash(PyObject *op) | |
| { | |
| PyFrozenDictObject *self = _PyFrozenDictObject_CAST(op); | |
| Py_hash_t shash = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->ma_hash); | |
| if (shash != -1) { | |
| return shash; | |
| } | |
| PyDictObject *mp = _PyAnyDict_CAST(op); | |
| Py_uhash_t hash = 0; | |
| PyObject *value; // borrowed ref | |
| Py_ssize_t pos = 0; | |
| Py_hash_t key_hash; | |
| while (_PyDict_Next(op, &pos, NULL, &value, &key_hash)) { | |
| Py_hash_t pair_hash = frozendict_pair_hash(key_hash, value); | |
| if (pair_hash == -1) { | |
| return -1; | |
| } | |
| hash ^= _shuffle_bits(pair_hash); | |
| } | |
| /* Factor in the number of active entries */ | |
| hash ^= ((Py_uhash_t)mp->ma_used + 1) * 1927868237UL; | |
| /* Disperse patterns arising in nested frozendicts */ | |
| hash ^= (hash >> 11) ^ (hash >> 25); | |
| hash = hash * 69069U + 907133923UL; | |
| /* -1 is reserved as an error code */ | |
| if (hash == (Py_uhash_t)-1) { | |
| hash = 590923713UL; | |
| } | |
| FT_ATOMIC_STORE_SSIZE_RELAXED(self->ma_hash, (Py_hash_t)hash); | |
| return (Py_hash_t)hash; | |
| } | |
| /* Allocate an empty, GC-untracked frozendict; the constructor tracks it once | |
| fully built. */ | |
| static PyObject * | |
| frozendict_new_untracked(PyTypeObject *type) | |
| { | |
| assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyFrozenDict_Type)); | |
| PyObject *d = anydict_new_untracked(type); | |
| if (d == NULL) { | |
| return NULL; | |
| } | |
| assert(can_modify_dict(_PyAnyDict_CAST(d))); | |
| _PyFrozenDictObject_CAST(d)->ma_hash = -1; | |
| return d; | |
| } | |
| static PyObject * | |
| frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| { | |
| PyObject *d = frozendict_new_untracked(type); | |
| if (d == NULL) { | |
| return NULL; | |
| } | |
| if (args != NULL) { | |
| if (dict_update_common(d, args, kwds, "frozendict") < 0) { | |
| Py_DECREF(d); | |
| return NULL; | |
| } | |
| } | |
| else { | |
| assert(kwds == NULL); | |
| } | |
| _PyObject_GC_TRACK(d); | |
| return d; | |
| } | |
| PyObject* | |
| PyFrozenDict_New(PyObject *iterable) | |
| { | |
| if (iterable != NULL) { | |
| if (PyFrozenDict_CheckExact(iterable)) { | |
| // PyFrozenDict_New(frozendict) returns the same object unmodified | |
| return Py_NewRef(iterable); | |
| } | |
| PyObject *args = PyTuple_Pack(1, iterable); | |
| if (args == NULL) { | |
| return NULL; | |
| } | |
| PyObject *frozendict = frozendict_new(&PyFrozenDict_Type, args, NULL); | |
| Py_DECREF(args); | |
| return frozendict; | |
| } | |
| else { | |
| PyObject *args = Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE); | |
| return frozendict_new(&PyFrozenDict_Type, args, NULL); | |
| } | |
| } | |
| /*[clinic input] | |
| frozendict.copy | |
| Return a shallow copy of the frozendict. | |
| [clinic start generated code]*/ | |
| static PyObject * | |
| frozendict_copy_impl(PyFrozenDictObject *self) | |
| /*[clinic end generated code: output=e580fd91d9fc2cf7 input=35f6abeaa08fd4bc]*/ | |
| { | |
| assert(PyFrozenDict_Check(self)); | |
| if (PyFrozenDict_CheckExact(self)) { | |
| return Py_NewRef(self); | |
| } | |
| PyObject *copy = anydict_copy_untracked((PyObject*)self); | |
| if (copy != NULL) { | |
| _PyObject_GC_TRACK(copy); | |
| } | |
| return copy; | |
| } | |
| PyDoc_STRVAR(frozendict_doc, | |
| "frozendict() -> new empty immutable dictionary\n" | |
| "frozendict(mapping) -> new immutable dictionary initialized from a mapping\n" | |
| " object's (key, value) pairs\n" | |
| "frozendict(iterable) -> new immutable dictionary initialized as if via:\n" | |
| " d = {}\n" | |
| " for k, v in iterable:\n" | |
| " d[k] = v\n" | |
| " d = frozendict(d)\n" | |
| "frozendict(**kwargs) -> new immutable dictionary initialized with the name=value\n" | |
| " pairs in the keyword argument list. For example: frozendict(one=1, two=2)"); | |
| PyTypeObject PyFrozenDict_Type = { | |
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
| .tp_name = "frozendict", | |
| .tp_basicsize = sizeof(PyFrozenDictObject), | |
| .tp_dealloc = dict_dealloc, | |
| .tp_repr = frozendict_repr, | |
| .tp_as_number = &frozendict_as_number, | |
| .tp_as_sequence = &dict_as_sequence, | |
| .tp_as_mapping = &frozendict_as_mapping, | |
| .tp_hash = frozendict_hash, | |
| .tp_getattro = PyObject_GenericGetAttr, | |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | |
| | Py_TPFLAGS_BASETYPE | |
| | _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_MAPPING, | |
| .tp_doc = frozendict_doc, | |
| .tp_traverse = dict_traverse, | |
| .tp_clear = dict_tp_clear, | |
| .tp_richcompare = dict_richcompare, | |
| .tp_iter = dict_iter, | |
| .tp_methods = frozendict_methods, | |
| .tp_alloc = _PyType_AllocNoTrack, | |
| .tp_new = frozendict_new, | |
| .tp_free = PyObject_GC_Del, | |
| .tp_vectorcall = frozendict_vectorcall, | |
| .tp_version_tag = _Py_TYPE_VERSION_FROZENDICT, | |
| }; |