| /* 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 is vary on 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 "stringlib/eq.h" // unicode_eq() |
| |
| #include <stdbool.h> |
| |
| /*[clinic input] |
| class dict "PyDictObject *" "&PyDict_Type" |
| [clinic start generated code]*/ |
| /*[clinic end generated code: output=da39a3ee5e6b4b0d input=f157a5a0ce9589d6]*/ |
| |
| |
| /* |
| 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); |
| } |
| |
| #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(_PyInterpreterState_GET(), 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 |
| |
| #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(PyInterpreterState *interp, 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" |
| |
| |
| #ifdef WITH_FREELISTS |
| static struct _Py_dict_freelist * |
| get_dict_freelist(void) |
| { |
| struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); |
| return &freelists->dicts; |
| } |
| |
| static struct _Py_dictkeys_freelist * |
| get_dictkeys_freelist(void) |
| { |
| struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); |
| return &freelists->dictkeys; |
| } |
| #endif |
| |
| |
| void |
| _PyDict_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization) |
| { |
| #ifdef WITH_FREELISTS |
| struct _Py_dict_freelist *freelist = &freelists->dicts; |
| while (freelist->numfree > 0) { |
| PyDictObject *op = freelist->items[--freelist->numfree]; |
| assert(PyDict_CheckExact(op)); |
| PyObject_GC_Del(op); |
| } |
| struct _Py_dictkeys_freelist *keys_freelist = &freelists->dictkeys; |
| while (keys_freelist->numfree > 0) { |
| PyMem_Free(keys_freelist->items[--keys_freelist->numfree]); |
| } |
| if (is_finalization) { |
| freelist->numfree = -1; |
| keys_freelist->numfree = -1; |
| } |
| #endif |
| } |
| |
| static inline Py_hash_t |
| unicode_get_hash(PyObject *o) |
| { |
| assert(PyUnicode_CheckExact(o)); |
| return FT_ATOMIC_LOAD_SSIZE_RELAXED(_PyASCIIObject_CAST(o)->hash); |
| } |
| |
| /* Print summary info about the state of the optimized allocator */ |
| void |
| _PyDict_DebugMallocStats(FILE *out) |
| { |
| #ifdef WITH_FREELISTS |
| struct _Py_dict_freelist *dict_freelist = get_dict_freelist(); |
| _PyDebugAllocatorStats(out, "free PyDictObject", |
| dict_freelist->numfree, sizeof(PyDictObject)); |
| struct _Py_dictkeys_freelist *dictkeys_freelist = get_dictkeys_freelist(); |
| _PyDebugAllocatorStats(out, "free PyDictKeysObject", |
| dictkeys_freelist->numfree, sizeof(PyDictKeysObject)); |
| #endif |
| } |
| |
| #define DK_MASK(dk) (DK_SIZE(dk)-1) |
| |
| 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) == _Py_IMMORTAL_REFCNT) { |
| return; |
| } |
| #ifdef Py_REF_DEBUG |
| _Py_IncRefTotal(_PyThreadState_GET()); |
| #endif |
| INCREF_KEYS(dk); |
| } |
| |
| static inline void |
| dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk, bool use_qsbr) |
| { |
| if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_IMMORTAL_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 = (minsize | PyDict_MINSIZE) - 1; |
| return _Py_bit_length(minsize | (PyDict_MINSIZE-1)); |
| #elif defined(_MSC_VER) |
| // On 64bit Windows, sizeof(long) == 4. |
| minsize = (minsize | PyDict_MINSIZE) - 1; |
| unsigned long msb; |
| _BitScanReverse64(&msb, (uint64_t)minsize); |
| 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). |
| */ |
| static PyDictKeysObject empty_keys_struct = { |
| _Py_IMMORTAL_REFCNT, /* dk_refcnt */ |
| 0, /* dk_log2_size */ |
| 0, /* 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(PyDict_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 PyDictKeysObject* |
| new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) |
| { |
| PyDictKeysObject *dk; |
| Py_ssize_t usable; |
| int log2_bytes; |
| size_t entry_size = unicode ? sizeof(PyDictUnicodeEntry) : sizeof(PyDictKeyEntry); |
| |
| assert(log2_size >= PyDict_LOG_MINSIZE); |
| |
| usable = USABLE_FRACTION((size_t)1<<log2_size); |
| 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; |
| } |
| |
| #ifdef WITH_FREELISTS |
| struct _Py_dictkeys_freelist *freelist = get_dictkeys_freelist(); |
| if (log2_size == PyDict_LOG_MINSIZE && unicode && freelist->numfree > 0) { |
| dk = freelist->items[--freelist->numfree]; |
| OBJECT_STAT_INC(from_freelist); |
| } |
| else |
| #endif |
| { |
| dk = PyMem_Malloc(sizeof(PyDictKeysObject) |
| + ((size_t)1 << log2_bytes) |
| + entry_size * usable); |
| if (dk == NULL) { |
| PyErr_NoMemory(); |
| return NULL; |
| } |
| } |
| #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 = unicode ? DICT_KEYS_UNICODE : DICT_KEYS_GENERAL; |
| #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); |
| return dk; |
| } |
| |
| static void |
| free_keys_object(PyDictKeysObject *keys, bool use_qsbr) |
| { |
| #ifdef Py_GIL_DISABLED |
| if (use_qsbr) { |
| _PyMem_FreeDelayed(keys); |
| return; |
| } |
| #endif |
| #ifdef WITH_FREELISTS |
| struct _Py_dictkeys_freelist *freelist = get_dictkeys_freelist(); |
| if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE |
| && freelist->numfree < PyDict_MAXFREELIST |
| && freelist->numfree >= 0 |
| && DK_IS_UNICODE(keys)) { |
| freelist->items[freelist->numfree++] = keys; |
| OBJECT_STAT_INC(to_freelist); |
| return; |
| } |
| #endif |
| PyMem_Free(keys); |
| } |
| |
| 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); |
| return; |
| } |
| #endif |
| PyMem_Free(values); |
| } |
| |
| /* Consumes a reference to the keys object */ |
| static PyObject * |
| new_dict(PyInterpreterState *interp, |
| PyDictKeysObject *keys, PyDictValues *values, |
| Py_ssize_t used, int free_values_on_failure) |
| { |
| PyDictObject *mp; |
| assert(keys != NULL); |
| #ifdef WITH_FREELISTS |
| struct _Py_dict_freelist *freelist = get_dict_freelist(); |
| if (freelist->numfree > 0) { |
| mp = freelist->items[--freelist->numfree]; |
| assert (mp != NULL); |
| assert (Py_IS_TYPE(mp, &PyDict_Type)); |
| OBJECT_STAT_INC(from_freelist); |
| _Py_NewReference((PyObject *)mp); |
| } |
| else |
| #endif |
| { |
| mp = PyObject_GC_New(PyDictObject, &PyDict_Type); |
| if (mp == NULL) { |
| dictkeys_decref(interp, 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_version_tag = DICT_NEXT_VERSION(interp); |
| ASSERT_CONSISTENT(mp); |
| return (PyObject *)mp; |
| } |
| |
| static PyObject * |
| new_dict_with_shared_keys(PyInterpreterState *interp, 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(interp, keys, values, 0, 1); |
| } |
| |
| |
| static PyDictKeysObject * |
| clone_combined_dict_keys(PyDictObject *orig) |
| { |
| assert(PyDict_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); |
| |
| 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); |
| |
| /* 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) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| /* We don't incref Py_EMPTY_KEYS here because it is immortal. */ |
| return new_dict(interp, 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_RELAXED(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); |
| } |
| |
| /* 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) |
| { |
| DictKeysKind kind = dk->dk_kind; |
| if (!PyUnicode_CheckExact(key) || kind == DICT_KEYS_GENERAL) { |
| return DKIX_ERROR; |
| } |
| 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_unicode(dk, key, hash); |
| } |
| |
| #ifdef Py_GIL_DISABLED |
| |
| static Py_ssize_t |
| unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key, |
| Py_hash_t hash); |
| |
| #endif |
| |
| /* |
| 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) { |
| // 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); |
| } |
| #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(); |
| } |
| } |
| #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 |
| } |
| |
| #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_relaxed(&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_relaxed(&ep->me_key); |
| assert(startkey == NULL || PyUnicode_CheckExact(startkey)); |
| if (startkey == key) { |
| return 1; |
| } |
| if (startkey != NULL) { |
| if (_Py_IsImmortal(startkey)) { |
| return unicode_get_hash(startkey) == hash && unicode_eq(startkey, key); |
| } |
| else { |
| if (!_Py_TryIncrefCompare(&ep->me_key, startkey)) { |
| return DKIX_KEY_CHANGED; |
| } |
| 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_relaxed(&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; |
| } |
| |
| #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; |
| } |
| |
| #endif |
| |
| 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; |
| } |
| |
| #define MAINTAIN_TRACKING(mp, key, value) \ |
| do { \ |
| if (!_PyObject_GC_IS_TRACKED(mp)) { \ |
| if (_PyObject_GC_MAY_BE_TRACKED(key) || \ |
| _PyObject_GC_MAY_BE_TRACKED(value)) { \ |
| _PyObject_GC_TRACK(mp); \ |
| } \ |
| } \ |
| } while(0) |
| |
| void |
| _PyDict_MaybeUntrack(PyObject *op) |
| { |
| PyDictObject *mp; |
| PyObject *value; |
| Py_ssize_t i, numentries; |
| |
| ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op); |
| |
| if (!PyDict_CheckExact(op) || !_PyObject_GC_IS_TRACKED(op)) |
| return; |
| |
| mp = (PyDictObject *) op; |
| ASSERT_CONSISTENT(mp); |
| numentries = mp->ma_keys->dk_nentries; |
| if (_PyDict_HasSplitTable(mp)) { |
| for (i = 0; i < numentries; i++) { |
| if ((value = mp->ma_values->values[i]) == NULL) |
| continue; |
| if (_PyObject_GC_MAY_BE_TRACKED(value)) { |
| return; |
| } |
| } |
| } |
| else { |
| if (DK_IS_UNICODE(mp->ma_keys)) { |
| PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(mp->ma_keys); |
| for (i = 0; i < numentries; i++) { |
| if ((value = ep0[i].me_value) == NULL) |
| continue; |
| if (_PyObject_GC_MAY_BE_TRACKED(value)) |
| return; |
| } |
| } |
| else { |
| PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_keys); |
| for (i = 0; i < numentries; i++) { |
| if ((value = ep0[i].me_value) == NULL) |
| continue; |
| if (_PyObject_GC_MAY_BE_TRACKED(value) || |
| _PyObject_GC_MAY_BE_TRACKED(ep0[i].me_key)) |
| return; |
| } |
| } |
| } |
| _PyObject_GC_UNTRACK(op); |
| } |
| |
| 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(PyInterpreterState *interp, PyDictObject *mp, int unicode) |
| { |
| return dictresize(interp, mp, calculate_log2_keysize(GROWTH_RATE(mp)), unicode); |
| } |
| |
| static inline int |
| insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, |
| Py_hash_t hash, PyObject *key, PyObject *value) |
| { |
| if (mp->ma_keys->dk_usable <= 0) { |
| /* Need to resize. */ |
| if (insertion_resize(interp, mp, 1) < 0) { |
| return -1; |
| } |
| } |
| |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_ADDED, mp, key, value); |
| 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); |
| } |
| mp->ma_version_tag = new_version; |
| 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; |
| } |
| #endif |
| |
| LOCK_KEYS(keys); |
| ix = unicodekeys_lookup_unicode(keys, key, hash); |
| if (ix == DKIX_EMPTY && keys->dk_usable > 0) { |
| // Insert into new slot |
| keys->dk_version = 0; |
| 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); |
| return ix; |
| } |
| |
| static void |
| insert_split_value(PyInterpreterState *interp, PyDictObject *mp, PyObject *key, PyObject *value, Py_ssize_t ix) |
| { |
| assert(PyUnicode_CheckExact(key)); |
| ASSERT_DICT_LOCKED(mp); |
| MAINTAIN_TRACKING(mp, key, value); |
| PyObject *old_value = mp->ma_values->values[ix]; |
| if (old_value == NULL) { |
| uint64_t new_version = _PyDict_NotifyEvent(interp, 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); |
| mp->ma_version_tag = new_version; |
| } |
| else { |
| uint64_t new_version = _PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value); |
| STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value)); |
| mp->ma_version_tag = new_version; |
| // 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); |
| } |
| |
| /* |
| 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(PyInterpreterState *interp, PyDictObject *mp, |
| PyObject *key, Py_hash_t hash, PyObject *value) |
| { |
| PyObject *old_value; |
| |
| ASSERT_DICT_LOCKED(mp); |
| |
| if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { |
| if (insertion_resize(interp, mp, 0) < 0) |
| goto Fail; |
| assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL); |
| } |
| |
| if (_PyDict_HasSplitTable(mp)) { |
| Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash); |
| if (ix != DKIX_EMPTY) { |
| insert_split_value(interp, mp, key, value, ix); |
| Py_DECREF(key); |
| Py_DECREF(value); |
| return 0; |
| } |
| |
| /* No space in shared keys. Resize and continue below. */ |
| if (insertion_resize(interp, mp, 1) < 0) { |
| goto Fail; |
| } |
| } |
| |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value); |
| if (ix == DKIX_ERROR) |
| goto Fail; |
| |
| MAINTAIN_TRACKING(mp, key, value); |
| |
| if (ix == DKIX_EMPTY) { |
| assert(!_PyDict_HasSplitTable(mp)); |
| /* Insert into new slot. */ |
| assert(old_value == NULL); |
| if (insert_combined_dict(interp, mp, hash, key, value) < 0) { |
| goto Fail; |
| } |
| STORE_USED(mp, mp->ma_used + 1); |
| ASSERT_CONSISTENT(mp); |
| return 0; |
| } |
| |
| if (old_value != value) { |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_MODIFIED, mp, key, value); |
| assert(old_value != NULL); |
| assert(!_PyDict_HasSplitTable(mp)); |
| if (DK_IS_UNICODE(mp->ma_keys)) { |
| 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); |
| } |
| mp->ma_version_tag = new_version; |
| } |
| Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ |
| ASSERT_CONSISTENT(mp); |
| 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(PyInterpreterState *interp, PyDictObject *mp, |
| PyObject *key, Py_hash_t hash, PyObject *value) |
| { |
| assert(mp->ma_keys == Py_EMPTY_KEYS); |
| ASSERT_DICT_LOCKED(mp); |
| |
| int unicode = PyUnicode_CheckExact(key); |
| PyDictKeysObject *newkeys = new_keys_object( |
| interp, PyDict_LOG_MINSIZE, unicode); |
| if (newkeys == NULL) { |
| Py_DECREF(key); |
| Py_DECREF(value); |
| return -1; |
| } |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_ADDED, mp, key, value); |
| |
| /* We don't decref Py_EMPTY_KEYS here because it is immortal. */ |
| assert(mp->ma_values == NULL); |
| |
| MAINTAIN_TRACKING(mp, key, value); |
| |
| 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); |
| mp->ma_version_tag = new_version; |
| 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); |
| } |
| } |
| |
| /* |
| 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(PyInterpreterState *interp, PyDictObject *mp, |
| uint8_t log2_newsize, int unicode) |
| { |
| PyDictKeysObject *oldkeys, *newkeys; |
| PyDictValues *oldvalues; |
| |
| ASSERT_DICT_LOCKED(mp); |
| |
| 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(interp, 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(interp, oldkeys, IS_DICT_SHARED(mp)); |
| set_values(mp, NULL); |
| if (oldvalues->embedded) { |
| assert(oldvalues->embedded == 1); |
| assert(oldvalues->valid == 1); |
| FT_ATOMIC_STORE_UINT8(oldvalues->valid, 0); |
| } |
| 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(PyInterpreterState *interp, 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(interp, log2_newsize, unicode); |
| if (new_keys == NULL) |
| return NULL; |
| return new_dict(interp, new_keys, NULL, 0, 0); |
| } |
| |
| PyObject * |
| _PyDict_NewPresized(Py_ssize_t minused) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| return dict_new_presized(interp, 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; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| for (Py_ssize_t i = 0; i < length; i++) { |
| if (!PyUnicode_CheckExact(*ks)) { |
| unicode = false; |
| break; |
| } |
| ks += keys_offset; |
| } |
| |
| PyObject *dict = dict_new_presized(interp, 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 (!PyDict_Check(op)) { |
| return NULL; |
| } |
| PyDictObject *mp = (PyDictObject *)op; |
| |
| Py_hash_t hash = _PyObject_HashFast(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()"); |
| } |
| |
| Py_ssize_t |
| _PyDict_LookupIndex(PyDictObject *mp, PyObject *key) |
| { |
| // TODO: Thread safety |
| PyObject *value; |
| assert(PyDict_CheckExact((PyObject*)mp)); |
| assert(PyUnicode_CheckExact(key)); |
| |
| Py_hash_t hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| return -1; |
| } |
| |
| return _Py_dict_lookup(mp, key, hash, &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 (!PyDict_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 (!PyDict_Check(op)) { |
| PyErr_BadInternalCall(); |
| *result = NULL; |
| return -1; |
| } |
| |
| Py_hash_t hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| *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_HashFast(key); |
| if (hash == -1) { |
| *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 (!PyDict_Check(op)) { |
| PyErr_BadInternalCall(); |
| return NULL; |
| } |
| hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| 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 = kv->ob_type->tp_hash(kv); |
| if (hash == -1) { |
| return NULL; |
| } |
| return _PyDict_GetItem_KnownHash(dp, kv, hash); // borrowed reference |
| } |
| |
| PyObject * |
| _PyDict_GetItemIdWithError(PyObject *dp, _Py_Identifier *key) |
| { |
| PyObject *kv; |
| kv = _PyUnicode_FromId(key); /* borrowed */ |
| if (kv == NULL) |
| return NULL; |
| Py_hash_t hash = unicode_get_hash(kv); |
| assert (hash != -1); /* interned strings have their hash value initialised */ |
| 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; |
| } |
| |
| /* Fast version of global value lookup (LOAD_GLOBAL). |
| * Lookup in globals, then builtins. |
| * |
| * |
| * |
| * |
| * Raise an exception and return NULL if an error occurred (ex: computing the |
| * key hash failed, key comparison failed, ...). Return NULL if the key doesn't |
| * exist. Return the value if the key exists. |
| * |
| * Returns a new reference. |
| */ |
| PyObject * |
| _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) |
| { |
| Py_ssize_t ix; |
| Py_hash_t hash; |
| PyObject *value; |
| |
| hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| return NULL; |
| } |
| |
| /* namespace 1: globals */ |
| ix = _Py_dict_lookup_threadsafe(globals, key, hash, &value); |
| if (ix == DKIX_ERROR) |
| return NULL; |
| if (ix != DKIX_EMPTY && value != NULL) |
| return value; |
| |
| /* namespace 2: builtins */ |
| ix = _Py_dict_lookup_threadsafe(builtins, key, hash, &value); |
| assert(ix >= 0 || value == NULL); |
| return value; |
| } |
| |
| /* Consumes references to key and value */ |
| static int |
| setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) |
| { |
| ASSERT_DICT_LOCKED(mp); |
| |
| assert(key); |
| assert(value); |
| assert(PyDict_Check(mp)); |
| Py_hash_t hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| Py_DECREF(key); |
| Py_DECREF(value); |
| return -1; |
| } |
| |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| if (mp->ma_keys == Py_EMPTY_KEYS) { |
| return insert_to_emptydict(interp, mp, key, hash, value); |
| } |
| /* insertdict() handles any resizing that might be necessary */ |
| return insertdict(interp, mp, key, hash, value); |
| } |
| |
| 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; |
| } |
| |
| /* 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) |
| { |
| if (!PyDict_Check(op)) { |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| 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) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| if (mp->ma_keys == Py_EMPTY_KEYS) { |
| return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); |
| } |
| /* insertdict() handles any resizing that might be necessary */ |
| return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); |
| } |
| |
| int |
| _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, |
| Py_hash_t hash) |
| { |
| if (!PyDict_Check(op)) { |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| assert(key); |
| assert(value); |
| assert(hash != -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, uint64_t new_version) |
| { |
| PyObject *old_key; |
| |
| ASSERT_DICT_LOCKED(mp); |
| |
| Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); |
| assert(hashpos >= 0); |
| |
| STORE_USED(mp, mp->ma_used - 1); |
| mp->ma_version_tag = new_version; |
| 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 { |
| 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_HashFast(key); |
| if (hash == -1) { |
| return -1; |
| } |
| |
| return _PyDict_DelItem_KnownHash(op, key, hash); |
| } |
| |
| static int |
| delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash) |
| { |
| Py_ssize_t ix; |
| PyDictObject *mp; |
| PyObject *old_value; |
| |
| if (!PyDict_Check(op)) { |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| |
| ASSERT_DICT_LOCKED(op); |
| |
| assert(key); |
| assert(hash != -1); |
| mp = (PyDictObject *)op; |
| 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; |
| } |
| |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_DELETED, mp, key, NULL); |
| delitem_common(mp, hash, ix, old_value, new_version); |
| return 0; |
| } |
| |
| int |
| _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) |
| { |
| int res; |
| Py_BEGIN_CRITICAL_SECTION(op); |
| res = delitem_knownhash_lock_held(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) |
| { |
| Py_ssize_t ix; |
| PyDictObject *mp; |
| Py_hash_t hash; |
| PyObject *old_value; |
| int res; |
| |
| ASSERT_DICT_LOCKED(op); |
| |
| assert(key); |
| hash = PyObject_Hash(key); |
| if (hash == -1) |
| return -1; |
| mp = (PyDictObject *)op; |
| 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) { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_DELETED, mp, key, NULL); |
| delitem_common(mp, hash, ix, old_value, new_version); |
| 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; |
| } |
| |
| static void |
| clear_lock_held(PyObject *op) |
| { |
| PyDictObject *mp; |
| PyDictKeysObject *oldkeys; |
| PyDictValues *oldvalues; |
| Py_ssize_t i, n; |
| |
| ASSERT_DICT_LOCKED(op); |
| |
| if (!PyDict_Check(op)) |
| return; |
| mp = ((PyDictObject *)op); |
| oldkeys = mp->ma_keys; |
| oldvalues = mp->ma_values; |
| if (oldkeys == Py_EMPTY_KEYS) { |
| return; |
| } |
| /* Empty the dict... */ |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_CLEARED, mp, NULL, NULL); |
| // We don't inc ref empty keys because they're immortal |
| ensure_shared_on_resize(mp); |
| mp->ma_version_tag = new_version; |
| STORE_USED(mp, 0); |
| if (oldvalues == NULL) { |
| set_keys(mp, Py_EMPTY_KEYS); |
| assert(oldkeys->dk_refcnt == 1); |
| dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); |
| } |
| else { |
| n = oldkeys->dk_nentries; |
| for (i = 0; i < n; i++) { |
| Py_CLEAR(oldvalues->values[i]); |
| } |
| if (oldvalues->embedded) { |
| oldvalues->size = 0; |
| } |
| else { |
| set_values(mp, NULL); |
| set_keys(mp, Py_EMPTY_KEYS); |
| free_values(oldvalues, IS_DICT_SHARED(mp)); |
| dictkeys_decref(interp, oldkeys, false); |
| } |
| } |
| ASSERT_CONSISTENT(mp); |
| } |
| |
| 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 (!PyDict_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_DICT_LOCKED(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); |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_DELETED, mp, key, NULL); |
| delitem_common(mp, hash, ix, Py_NewRef(old_value), new_version); |
| |
| 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) |
| { |
| ASSERT_DICT_LOCKED(op); |
| |
| if (!PyDict_Check(op)) { |
| if (result) { |
| *result = NULL; |
| } |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| PyDictObject *dict = (PyDictObject *)op; |
| |
| if (dict->ma_used == 0) { |
| if (result) { |
| *result = NULL; |
| } |
| return 0; |
| } |
| |
| Py_hash_t hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| 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; |
| } |
| |
| |
| PyObject * |
| _PyDict_Pop(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; |
| } |
| |
| static PyDictObject * |
| dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp, |
| PyObject *iterable, PyObject *value) |
| { |
| PyObject *oldvalue; |
| Py_ssize_t pos = 0; |
| PyObject *key; |
| Py_hash_t hash; |
| int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); |
| uint8_t new_size = Py_MAX( |
| estimate_log2_keysize(PyDict_GET_SIZE(iterable)), |
| DK_LOG_SIZE(mp->ma_keys)); |
| if (dictresize(interp, mp, new_size, unicode)) { |
| Py_DECREF(mp); |
| return NULL; |
| } |
| |
| while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { |
| if (insertdict(interp, mp, |
| Py_NewRef(key), hash, Py_NewRef(value))) { |
| Py_DECREF(mp); |
| return NULL; |
| } |
| } |
| return mp; |
| } |
| |
| static PyDictObject * |
| dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp, |
| PyObject *iterable, PyObject *value) |
| { |
| Py_ssize_t pos = 0; |
| PyObject *key; |
| Py_hash_t hash; |
| |
| if (dictresize(interp, mp, |
| estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) { |
| Py_DECREF(mp); |
| return NULL; |
| } |
| |
| _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(iterable); |
| while (_PySet_NextEntryRef(iterable, &pos, &key, &hash)) { |
| if (insertdict(interp, 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; /* iter(iterable) */ |
| PyObject *key; |
| PyObject *d; |
| int status; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| d = _PyObject_CallNoArgs(cls); |
| if (d == NULL) |
| return NULL; |
| |
| |
| if (PyDict_CheckExact(d)) { |
| if (PyDict_CheckExact(iterable)) { |
| PyDictObject *mp = (PyDictObject *)d; |
| |
| Py_BEGIN_CRITICAL_SECTION2(d, iterable); |
| d = (PyObject *)dict_dict_fromkeys(interp, mp, iterable, value); |
| Py_END_CRITICAL_SECTION2(); |
| return d; |
| } |
| else if (PyAnySet_CheckExact(iterable)) { |
| PyDictObject *mp = (PyDictObject *)d; |
| |
| Py_BEGIN_CRITICAL_SECTION2(d, iterable); |
| d = (PyObject *)dict_set_fromkeys(interp, mp, iterable, value); |
| Py_END_CRITICAL_SECTION2(); |
| return d; |
| } |
| } |
| |
| it = PyObject_GetIter(iterable); |
| if (it == NULL){ |
| Py_DECREF(d); |
| return NULL; |
| } |
| |
| if (PyDict_CheckExact(d)) { |
| Py_BEGIN_CRITICAL_SECTION(d); |
| while ((key = PyIter_Next(it)) != NULL) { |
| status = setitem_lock_held((PyDictObject *)d, key, value); |
| Py_DECREF(key); |
| if (status < 0) { |
| assert(PyErr_Occurred()); |
| goto dict_iter_exit; |
| } |
| } |
| dict_iter_exit:; |
| Py_END_CRITICAL_SECTION(); |
| } else { |
| while ((key = PyIter_Next(it)) != NULL) { |
| status = PyObject_SetItem(d, key, value); |
| Py_DECREF(key); |
| if (status < 0) |
| goto Fail; |
| } |
| } |
| |
| if (PyErr_Occurred()) |
| goto Fail; |
| Py_DECREF(it); |
| return d; |
| |
| Fail: |
| Py_DECREF(it); |
| Py_DECREF(d); |
| return NULL; |
| } |
| |
| /* Methods */ |
| |
| static void |
| dict_dealloc(PyObject *self) |
| { |
| PyDictObject *mp = (PyDictObject *)self; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| assert(Py_REFCNT(mp) == 0); |
| Py_SET_REFCNT(mp, 1); |
| _PyDict_NotifyEvent(interp, PyDict_EVENT_DEALLOCATED, mp, NULL, NULL); |
| if (Py_REFCNT(mp) > 1) { |
| Py_SET_REFCNT(mp, Py_REFCNT(mp) - 1); |
| return; |
| } |
| Py_SET_REFCNT(mp, 0); |
| 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); |
| Py_TRASHCAN_BEGIN(mp, dict_dealloc) |
| if (values != NULL) { |
| if (values->embedded == 0) { |
| for (i = 0, n = mp->ma_keys->dk_nentries; i < n; i++) { |
| Py_XDECREF(values->values[i]); |
| } |
| free_values(values, false); |
| } |
| dictkeys_decref(interp, keys, false); |
| } |
| else if (keys != NULL) { |
| assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); |
| dictkeys_decref(interp, keys, false); |
| } |
| #ifdef WITH_FREELISTS |
| struct _Py_dict_freelist *freelist = get_dict_freelist(); |
| if (freelist->numfree < PyDict_MAXFREELIST && freelist->numfree >=0 && |
| Py_IS_TYPE(mp, &PyDict_Type)) { |
| freelist->items[freelist->numfree++] = mp; |
| OBJECT_STAT_INC(to_freelist); |
| } |
| else |
| #endif |
| { |
| Py_TYPE(mp)->tp_free((PyObject *)mp); |
| } |
| Py_TRASHCAN_END |
| } |
| |
| |
| static PyObject * |
| dict_repr_lock_held(PyObject *self) |
| { |
| PyDictObject *mp = (PyDictObject *)self; |
| Py_ssize_t i; |
| PyObject *key = NULL, *value = NULL; |
| _PyUnicodeWriter writer; |
| int first; |
| |
| ASSERT_DICT_LOCKED(mp); |
| |
| i = Py_ReprEnter((PyObject *)mp); |
| if (i != 0) { |
| return i > 0 ? PyUnicode_FromString("{...}") : NULL; |
| } |
| |
| if (mp->ma_used == 0) { |
| Py_ReprLeave((PyObject *)mp); |
| return PyUnicode_FromString("{}"); |
| } |
| |
| _PyUnicodeWriter_Init(&writer); |
| writer.overallocate = 1; |
| /* "{" + "1: 2" + ", 3: 4" * (len - 1) + "}" */ |
| writer.min_length = 1 + 4 + (2 + 4) * (mp->ma_used - 1) + 1; |
| |
| 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. */ |
| i = 0; |
| first = 1; |
| while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) { |
| PyObject *s; |
| int res; |
| |
| /* Prevent repr from deleting key or value during key format. */ |
| Py_INCREF(key); |
| Py_INCREF(value); |
| |
| if (!first) { |
| if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0) |
| goto error; |
| } |
| first = 0; |
| |
| s = PyObject_Repr(key); |
| if (s == NULL) |
| goto error; |
| res = _PyUnicodeWriter_WriteStr(&writer, s); |
| Py_DECREF(s); |
| if (res < 0) |
| goto error; |
| |
| if (_PyUnicodeWriter_WriteASCIIString(&writer, ": ", 2) < 0) |
| goto error; |
| |
| s = PyObject_Repr(value); |
| if (s == NULL) |
| goto error; |
| res = _PyUnicodeWriter_WriteStr(&writer, s); |
| Py_DECREF(s); |
| if (res < 0) |
| goto error; |
| |
| Py_CLEAR(key); |
| Py_CLEAR(value); |
| } |
| |
| writer.overallocate = 0; |
| if (_PyUnicodeWriter_WriteChar(&writer, '}') < 0) |
| goto error; |
| |
| Py_ReprLeave((PyObject *)mp); |
| |
| return _PyUnicodeWriter_Finish(&writer); |
| |
| error: |
| Py_ReprLeave((PyObject *)mp); |
| _PyUnicodeWriter_Dealloc(&writer); |
| Py_XDECREF(key); |
| Py_XDECREF(value); |
| return NULL; |
| } |
| |
| 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 FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)self)->ma_used); |
| } |
| |
| static PyObject * |
| dict_subscript(PyObject *self, PyObject *key) |
| { |
| PyDictObject *mp = (PyDictObject *)self; |
| Py_ssize_t ix; |
| Py_hash_t hash; |
| PyObject *value; |
| |
| hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| return NULL; |
| } |
| ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); |
| if (ix == DKIX_ERROR) |
| return NULL; |
| if (ix == DKIX_EMPTY || value == NULL) { |
| if (!PyDict_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; |
| } |
| |
| static int |
| dict_ass_sub(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*/ |
| dict_subscript, /*mp_subscript*/ |
| dict_ass_sub, /*mp_ass_subscript*/ |
| }; |
| |
| static PyObject * |
| keys_lock_held(PyObject *dict) |
| { |
| ASSERT_DICT_LOCKED(dict); |
| |
| if (dict == NULL || !PyDict_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 || !PyDict_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 || !PyDict_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] |
| @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=382ba4855d0f74c3]*/ |
| { |
| 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 (PyDict_CheckExact(arg)) { |
| return PyDict_Merge(self, arg, 1); |
| } |
| int has_keys = PyObject_HasAttrWithError(arg, &_Py_ID(keys)); |
| if (has_keys < 0) { |
| return -1; |
| } |
| if (has_keys) { |
| return PyDict_Merge(self, arg, 1); |
| } |
| return PyDict_MergeFromSeq2(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 = PyDict_Merge(self, kwds, 1); |
| 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(PyDict_Check(d)); |
| assert(seq2 != NULL); |
| |
| 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, ""); |
| if (fast == NULL) { |
| if (PyErr_ExceptionMatches(PyExc_TypeError)) |
| PyErr_Format(PyExc_TypeError, |
| "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); |
| } |
| |
| int |
| PyDict_MergeFromSeq2(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; |
| } |
| |
| static int |
| dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *other, int override) |
| { |
| ASSERT_DICT_LOCKED(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_GetItem() |
| * always returns NULL. 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) |
| ) { |
| uint64_t new_version = _PyDict_NotifyEvent( |
| interp, 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(interp, mp->ma_keys, IS_DICT_SHARED(mp)); |
| mp->ma_keys = keys; |
| STORE_USED(mp, other->ma_used); |
| mp->ma_version_tag = new_version; |
| ASSERT_CONSISTENT(mp); |
| |
| if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) { |
| /* Maintain tracking. */ |
| _PyObject_GC_TRACK(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(interp, mp, |
| estimate_log2_keysize(mp->ma_used + other->ma_used), |
| unicode)) { |
| return -1; |
| } |
| } |
| |
| Py_ssize_t orig_size = other->ma_keys->dk_nentries; |
| Py_ssize_t pos = 0; |
| Py_hash_t hash; |
| PyObject *key, *value; |
| |
| while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { |
| int err = 0; |
| Py_INCREF(key); |
| Py_INCREF(value); |
| if (override == 1) { |
| err = insertdict(interp, mp, |
| Py_NewRef(key), hash, Py_NewRef(value)); |
| } |
| else { |
| err = _PyDict_Contains_KnownHash((PyObject *)mp, key, hash); |
| if (err == 0) { |
| err = insertdict(interp, mp, |
| Py_NewRef(key), hash, Py_NewRef(value)); |
| } |
| else if (err > 0) { |
| if (override != 0) { |
| _PyErr_SetKeyError(key); |
| Py_DECREF(value); |
| Py_DECREF(key); |
| return -1; |
| } |
| err = 0; |
| } |
| } |
| Py_DECREF(value); |
| Py_DECREF(key); |
| if (err != 0) |
| return -1; |
| |
| if (orig_size != other->ma_keys->dk_nentries) { |
| PyErr_SetString(PyExc_RuntimeError, |
| "dict mutated during update"); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| static int |
| dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) |
| { |
| PyDictObject *mp, *other; |
| |
| assert(0 <= override && override <= 2); |
| |
| /* 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) { |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| mp = (PyDictObject*)a; |
| int res = 0; |
| if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) { |
| other = (PyDictObject*)b; |
| int res; |
| Py_BEGIN_CRITICAL_SECTION2(a, b); |
| res = dict_dict_merge(interp, (PyDictObject *)a, other, override); |
| ASSERT_CONSISTENT(a); |
| Py_END_CRITICAL_SECTION2(); |
| return res; |
| } |
| else { |
| /* Do it the generic, slower way */ |
| Py_BEGIN_CRITICAL_SECTION(a); |
| 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 = PyDict_Contains(a, key); |
| if (status != 0) { |
| if (status > 0) { |
| if (override == 0) { |
| Py_DECREF(key); |
| continue; |
| } |
| _PyErr_SetKeyError(key); |
| } |
| Py_DECREF(key); |
| Py_DECREF(iter); |
| res = -1; |
| 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; |
| } |
| } |
| |
| int |
| PyDict_Update(PyObject *a, PyObject *b) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| return dict_merge(interp, a, b, 1); |
| } |
| |
| int |
| PyDict_Merge(PyObject *a, PyObject *b, int override) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| /* XXX Deprecate override not in (0, 1). */ |
| return dict_merge(interp, a, b, override != 0); |
| } |
| |
| int |
| _PyDict_MergeEx(PyObject *a, PyObject *b, int override) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| return dict_merge(interp, a, b, override); |
| } |
| |
| /*[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(PyObject *o) |
| { |
| PyObject *copy; |
| PyDictObject *mp; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| ASSERT_DICT_LOCKED(o); |
| |
| mp = (PyDictObject *)o; |
| if (mp->ma_used == 0) { |
| /* The dict is empty; just return a new dict. */ |
| return PyDict_New(); |
| } |
| |
| if (_PyDict_HasSplitTable(mp)) { |
| PyDictObject *split_copy; |
| PyDictValues *newvalues = copy_values(mp->ma_values); |
| if (newvalues == NULL) { |
| return PyErr_NoMemory(); |
| } |
| 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_version_tag = DICT_NEXT_VERSION(interp); |
| dictkeys_incref(mp->ma_keys); |
| if (_PyObject_GC_IS_TRACKED(mp)) |
| _PyObject_GC_TRACK(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 = (PyDictObject *)new_dict(interp, keys, NULL, 0, 0); |
| if (new == NULL) { |
| /* In case of an error, `new_dict()` takes care of |
| cleaning up `keys`. */ |
| return NULL; |
| } |
| |
| new->ma_used = mp->ma_used; |
| ASSERT_CONSISTENT(new); |
| if (_PyObject_GC_IS_TRACKED(mp)) { |
| /* Maintain tracking. */ |
| _PyObject_GC_TRACK(new); |
| } |
| |
| return (PyObject *)new; |
| } |
| |
| copy = PyDict_New(); |
| if (copy == NULL) |
| return NULL; |
| if (dict_merge(interp, copy, o, 1) == 0) |
| return copy; |
| Py_DECREF(copy); |
| return NULL; |
| } |
| |
| 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(o); |
| |
| Py_END_CRITICAL_SECTION(); |
| return res; |
| } |
| |
| Py_ssize_t |
| PyDict_Size(PyObject *mp) |
| { |
| if (mp == NULL || !PyDict_Check(mp)) { |
| PyErr_BadInternalCall(); |
| return -1; |
| } |
| return FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)mp)->ma_used); |
| } |
| |
| /* 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 (!PyDict_Check(v) || !PyDict_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__(PyDictObject *self, PyObject *key) |
| /*[clinic end generated code: output=a3d03db709ed6e6b input=fe1cb42ad831e820]*/ |
| { |
| int contains = PyDict_Contains((PyObject *)self, key); |
| if (contains < 0) { |
| return NULL; |
| } |
| if (contains) { |
| Py_RETURN_TRUE; |
| } |
| Py_RETURN_FALSE; |
| } |
| |
| /*[clinic input] |
| @critical_section |
| 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=a631d3f18f584c60]*/ |
| { |
| PyObject *val = NULL; |
| Py_hash_t hash; |
| Py_ssize_t ix; |
| |
| hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| 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) |
| { |
| PyDictObject *mp = (PyDictObject *)d; |
| PyObject *value; |
| Py_hash_t hash; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| ASSERT_DICT_LOCKED(d); |
| |
| if (!PyDict_Check(d)) { |
| PyErr_BadInternalCall(); |
| if (result) { |
| *result = NULL; |
| } |
| return -1; |
| } |
| |
| hash = _PyObject_HashFast(key); |
| if (hash == -1) { |
| if (result) { |
| *result = NULL; |
| } |
| return -1; |
| } |
| |
| if (mp->ma_keys == Py_EMPTY_KEYS) { |
| if (insert_to_emptydict(interp, 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 (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) { |
| if (insertion_resize(interp, mp, 0) < 0) { |
| if (result) { |
| *result = NULL; |
| } |
| return -1; |
| } |
| } |
| |
| if (_PyDict_HasSplitTable(mp)) { |
| Py_ssize_t 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) { |
| insert_split_value(interp, 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. Resize and continue below. */ |
| if (insertion_resize(interp, mp, 1) < 0) { |
| goto error; |
| } |
| } |
| |
| assert(!_PyDict_HasSplitTable(mp)); |
| |
| Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); |
| if (ix == DKIX_ERROR) { |
| if (result) { |
| *result = NULL; |
| } |
| return -1; |
| } |
| |
| if (ix == DKIX_EMPTY) { |
| assert(!_PyDict_HasSplitTable(mp)); |
| value = default_value; |
| |
| if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) { |
| Py_DECREF(key); |
| Py_DECREF(value); |
| if (result) { |
| *result = NULL; |
| } |
| } |
| |
| MAINTAIN_TRACKING(mp, key, value); |
| 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; |
| |
| error: |
| if (result) { |
| *result = NULL; |
| } |
| 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] |
| 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=e221baa01044c44c]*/ |
| { |
| return _PyDict_Pop((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]*/ |
| { |
| Py_ssize_t i, j; |
| PyObject *res; |
| uint64_t new_version; |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| ASSERT_DICT_LOCKED(self); |
| |
| /* 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(interp, self, DK_LOG_SIZE(self->ma_keys), 1) < 0) { |
| Py_DECREF(res); |
| return NULL; |
| } |
| } |
| 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; |
| new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_DELETED, self, key, NULL); |
| hash = unicode_get_hash(key); |
| value = ep0[i].me_value; |
| ep0[i].me_key = NULL; |
| ep0[i].me_value = 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; |
| new_version = _PyDict_NotifyEvent( |
| interp, PyDict_EVENT_DELETED, self, key, NULL); |
| hash = ep0[i].me_hash; |
| value = ep0[i].me_value; |
| ep0[i].me_key = NULL; |
| ep0[i].me_hash = -1; |
| ep0[i].me_value = 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); |
| self->ma_version_tag = new_version; |
| 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)) { |
| if (!mp->ma_values->embedded) { |
| 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 *); |
| |
| static Py_ssize_t |
| sizeof_lock_held(PyDictObject *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; |
| } |
| |
| Py_ssize_t |
| _PyDict_SizeOf(PyDictObject *mp) |
| { |
| Py_ssize_t res; |
| Py_BEGIN_CRITICAL_SECTION(mp); |
| res = sizeof_lock_held(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)); |
| } |
| |
| static PyObject * |
| dict_or(PyObject *self, PyObject *other) |
| { |
| if (!PyDict_Check(self) || !PyDict_Check(other)) { |
| Py_RETURN_NOTIMPLEMENTED; |
| } |
| PyObject *new = PyDict_Copy(self); |
| if (new == NULL) { |
| return NULL; |
| } |
| if (dict_update_arg(new, other)) { |
| Py_DECREF(new); |
| return NULL; |
| } |
| return new; |
| } |
| |
| static PyObject * |
| dict_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__", dict_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("See PEP 585")}, |
| {NULL, NULL} /* sentinel */ |
| }; |
| |
| /* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ |
| int |
| PyDict_Contains(PyObject *op, PyObject *key) |
| { |
| Py_hash_t hash = _PyObject_HashFast(key); |
| |
| if (hash == -1) { |
| return -1; |
| } |
| |
| return _PyDict_Contains_KnownHash(op, key, hash); |
| } |
| |
| 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 = (PyDictObject *)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; |
| } |
| |
| int |
| _PyDict_ContainsId(PyObject *op, _Py_Identifier *key) |
| { |
| PyObject *kv = _PyUnicode_FromId(key); /* borrowed */ |
| if (kv == NULL) { |
| return -1; |
| } |
| return PyDict_Contains(op, kv); |
| } |
| |
| /* 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 */ |
| PyDict_Contains, /* sq_contains */ |
| 0, /* sq_inplace_concat */ |
| 0, /* sq_inplace_repeat */ |
| }; |
| |
| static PyNumberMethods dict_as_number = { |
| .nb_or = dict_or, |
| .nb_inplace_or = dict_ior, |
| }; |
| |
| static PyObject * |
| dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds) |
| { |
| assert(type != NULL); |
| assert(type->tp_alloc != NULL); |
| // dict subclasses must implement the GC protocol |
| assert(_PyType_IS_GC(type)); |
| |
| PyObject *self = type->tp_alloc(type, 0); |
| if (self == NULL) { |
| return NULL; |
| } |
| PyDictObject *d = (PyDictObject *)self; |
| |
| d->ma_used = 0; |
| d->ma_version_tag = DICT_NEXT_VERSION( |
| _PyInterpreterState_GET()); |
| dictkeys_incref(Py_EMPTY_KEYS); |
| d->ma_keys = Py_EMPTY_KEYS; |
| d->ma_values = NULL; |
| ASSERT_CONSISTENT(d); |
| |
| if (type != &PyDict_Type) { |
| // Don't track if a subclass tp_alloc is PyType_GenericAlloc() |
| if (!_PyObject_GC_IS_TRACKED(d)) { |
| _PyObject_GC_TRACK(d); |
| } |
| } |
| else { |
| // _PyType_AllocNoTrack() does not track the created object |
| assert(!_PyObject_GC_IS_TRACKED(d)); |
| } |
| 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++) { |
| if (PyDict_SetItem(self, PyTuple_GET_ITEM(kwnames, i), args[i]) < 0) { |
| Py_DECREF(self); |
| return NULL; |
| } |
| } |
| } |
| 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, |
| }; |
| |
| /* 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_GetItemRefString()"); |
| return NULL; |
| } |
| rv = dict_getitem(v, kv, |
| "Exception ignored in PyDict_GetItemString(); consider using " |
| "PyDict_GetItemRefString()"); |
| 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_SetItemId(PyObject *v, _Py_Identifier *key, PyObject *item) |
| { |
| PyObject *kv; |
| kv = _PyUnicode_FromId(key); /* borrowed */ |
| if (kv == NULL) |
| return -1; |
| return PyDict_SetItem(v, kv, item); |
| } |
| |
| 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_DelItemId(PyObject *v, _Py_Identifier *key) |
| { |
| PyObject *kv = _PyUnicode_FromId(key); /* borrowed */ |
| if (kv == NULL) |
| return -1; |
| return PyDict_DelItem(v, kv); |
| } |
| |
| 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 = FT_ATOMIC_LOAD_SSIZE_RELAXED(dict->ma_used); |
| di->di_used = used; |
| di->len = used; |
| 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; |
| } |
| if (itertype == &PyDictIterItem_Type || |
| itertype == &PyDictRevIterItem_Type) { |
| di->di_result = PyTuple_Pack(2, Py_None, Py_None); |
| if (di->di_result == NULL) { |
| Py_DECREF(di); |
| return NULL; |
| } |
| } |
| else { |
| di->di_result = NULL; |
| } |
| _PyObject_GC_TRACK(di); |
| 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 == FT_ATOMIC_LOAD_SSIZE_RELAXED(di->di_dict->ma_used)) |
| 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 (PyDict_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 (PyDict_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 (PyDict_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 unsucessful |
| // 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 (PyDict_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_relaxed(&d->ma_keys); |
| assert(i >= 0); |
| if (_PyDict_HasSplitTable(d)) { |
| PyDictValues *values = _Py_atomic_load_ptr_relaxed(&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 |
| has_unique_reference(PyObject *op) |
| { |
| #ifdef Py_GIL_DISABLED |
| return (_Py_IsOwnedByCurrentThread(op) && |
| op->ob_ref_local == 1 && |
| _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); |
| #else |
| return Py_REFCNT(op) == 1; |
| #endif |
| } |
| |
| static bool |
| acquire_iter_result(PyObject *result) |
| { |
| if (has_unique_reference(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: |
| if (!_PyObject_GC_IS_TRACKED(result)) { |
| _PyObject_GC_TRACK(result); |
| } |
| } |
| else { |
| result = PyTuple_New(2); |
| if (result == NULL) |
| return NULL; |
| PyTuple_SET_ITEM(result, 0, key); |
| PyTuple_SET_ITEM(result, 1, 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 (PyDict_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 (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; |
| } |
| } |
| 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 (Py_REFCNT(result) == 1) { |
| 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: |
| if (!_PyObject_GC_IS_TRACKED(result)) { |
| _PyObject_GC_TRACK(result); |
| } |
| } |
| else { |
| result = PyTuple_New(2); |
| if (result == NULL) { |
| return NULL; |
| } |
| PyTuple_SET_ITEM(result, 0, Py_NewRef(key)); |
| PyTuple_SET_ITEM(result, 1, Py_NewRef(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 (PyDict_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 = FT_ATOMIC_LOAD_SSIZE_RELAXED(dv->dv_dict->ma_used); |
| return len; |
| } |
| |
| PyObject * |
| _PyDictView_New(PyObject *dict, PyTypeObject *type) |
| { |
| _PyDictViewObject *dv; |
| if (dict == NULL) { |
| PyErr_BadInternalCall(); |
| return NULL; |
| } |
| if (!PyDict_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, (setter)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 PyDict_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 (PyDict_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(d1); |
| if (temp_dict == NULL) { |
| return NULL; |
| } |
| 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); |
| if (to_delete < 0) { |
| goto error; |
| } |
| } |
| |
| if (to_delete) { |
| if (_PyDict_DelItem_KnownHash(temp_dict, key, hash) < 0) { |
| goto error; |
| } |
| } |
| else { |
| PyObject *pair = PyTuple_Pack(2, key, val2); |
| if (pair == NULL) { |
| goto error; |
| } |
| if (PySet_Add(result_set, pair) < 0) { |
| Py_DECREF(pair); |
| goto error; |
| } |
| Py_DECREF(pair); |
| } |
| Py_DECREF(key); |
| Py_XDECREF(val1); |
| Py_DECREF(val2); |
| } |
| key = val1 = val2 = NULL; |
| |
| 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*/ |
| (binaryfunc)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*/ |
| (binaryfunc)_PyDictView_Intersect, /*nb_and*/ |
| (binaryfunc)dictviews_xor, /*nb_xor*/ |
| (binaryfunc)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 */ |
| (objobjproc)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(void) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| PyDictKeysObject *keys = new_keys_object( |
| interp, NEXT_LOG2_SHARED_KEYS_MAX_SIZE, 1); |
| if (keys == NULL) { |
| PyErr_Clear(); |
| } |
| else { |
| assert(keys->dk_nentries == 0); |
| /* Set to max size+1 as it will shrink by one before each new object */ |
| keys->dk_usable = SHARED_KEYS_MAX_SIZE; |
| keys->dk_kind = DICT_KEYS_SPLIT; |
| } |
| return 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(PyInterpreterState *interp, |
| PyDictKeysObject *keys, PyDictValues *values) |
| { |
| dictkeys_incref(keys); |
| Py_ssize_t used = 0; |
| Py_ssize_t track = 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; |
| track += _PyObject_GC_MAY_BE_TRACKED(val); |
| } |
| } |
| PyDictObject *res = (PyDictObject *)new_dict(interp, keys, values, used, 0); |
| if (track && res) { |
| _PyObject_GC_TRACK(res); |
| } |
| 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) { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj)); |
| dict = make_dict_from_instance_attributes(interp, 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 (value == NULL) { |
| Py_hash_t hash = _PyObject_HashFast(name); |
| if (hash == -1) { |
| return -1; |
| } |
| return delitem_knownhash_lock_held((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(PyInterpreterState_Get(), 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); |
| return -1; |
| } |
| |
| if (dict) { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED : |
| value == NULL ? PyDict_EVENT_DELETED : |
| PyDict_EVENT_MODIFIED); |
| _PyDict_NotifyEvent(interp, 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_StringLookup(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 || _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_relaxed(&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 < keys->dk_nentries; 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 FT_ATOMIC_LOAD_SSIZE_RELAXED(((PyDictObject *)dict)->ma_used) == 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; |
| } |
| 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; |
| } |
| } |
| Py_VISIT(_PyObject_ManagedDictPointer(obj)->dict); |
| return 0; |
| } |
| |
| 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); |
| |
| 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]); |
| } |
| } |
| } |
| |
| int |
| _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict) |
| { |
| assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); |
| int err = 0; |
| PyTypeObject *tp = Py_TYPE(obj); |
| if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) { |
| PyDictObject *dict = _PyObject_GetManagedDict(obj); |
| if (dict == NULL) { |
| #ifdef Py_GIL_DISABLED |
| Py_BEGIN_CRITICAL_SECTION(obj); |
| |
| dict = _PyObject_ManagedDictPointer(obj)->dict; |
| if (dict == NULL) { |
| set_dict_inline_values(obj, (PyDictObject *)new_dict); |
| } |
| |
| Py_END_CRITICAL_SECTION(); |
| |
| if (dict == NULL) { |
| return 0; |
| } |
| #else |
| set_dict_inline_values(obj, (PyDictObject *)new_dict); |
| return 0; |
| #endif |
| } |
| |
| Py_BEGIN_CRITICAL_SECTION2(dict, obj); |
| |
| // We've locked dict, but the actual dict could have changed |
| // since we locked it. |
| dict = _PyObject_ManagedDictPointer(obj)->dict; |
| err = _PyDict_DetachFromObject(dict, obj); |
| if (err == 0) { |
| FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, |
| (PyDictObject *)Py_XNewRef(new_dict)); |
| } |
| Py_END_CRITICAL_SECTION2(); |
| |
| if (err == 0) { |
| Py_XDECREF(dict); |
| } |
| } |
| 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(); |
| |
| Py_XDECREF(dict); |
| } |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); |
| return err; |
| } |
| |
| void |
| PyObject_ClearManagedDict(PyObject *obj) |
| { |
| if (_PyObject_SetManagedDict(obj, NULL) < 0) { |
| PyErr_WriteUnraisable(NULL); |
| } |
| } |
| |
| int |
| _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) |
| { |
| ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(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) { |
| /* Out of memory. Clear the dict */ |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| PyDictKeysObject *oldkeys = mp->ma_keys; |
| set_keys(mp, Py_EMPTY_KEYS); |
| dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp)); |
| STORE_USED(mp, 0); |
| PyErr_NoMemory(); |
| return -1; |
| } |
| mp->ma_values = values; |
| |
| FT_ATOMIC_STORE_UINT8(_PyObject_InlineValues(obj)->valid, 0); |
| |
| assert(_PyObject_InlineValuesConsistencyCheck(obj)); |
| ASSERT_CONSISTENT(mp); |
| return 0; |
| } |
| |
| 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(_PyInterpreterState_GET(), |
| 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))) { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| assert(!_PyType_HasFeature(tp, Py_TPFLAGS_INLINE_VALUES)); |
| dict = new_dict_with_shared_keys(interp, 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) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| dictkeys_decref(interp, keys, false); |
| } |
| |
| uint32_t _PyDictKeys_GetVersionForCurrentState(PyInterpreterState *interp, |
| PyDictKeysObject *dictkeys) |
| { |
| if (dictkeys->dk_version != 0) { |
| return dictkeys->dk_version; |
| } |
| if (interp->dict_state.next_keys_version == 0) { |
| return 0; |
| } |
| uint32_t v = interp->dict_state.next_keys_version++; |
| dictkeys->dk_version = v; |
| return v; |
| } |
| |
| 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; |
| } |
| if (!interp->dict_state.watchers[watcher_id]) { |
| PyErr_Format(PyExc_ValueError, "No dict watcher set for ID %d", watcher_id); |
| return -1; |
| } |
| return 0; |
| } |
| |
| 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; |
| } |
| ((PyDictObject*)dict)->ma_version_tag |= (1LL << 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; |
| } |
| ((PyDictObject*)dict)->ma_version_tag &= ~(1LL << watcher_id); |
| return 0; |
| } |
| |
| int |
| PyDict_AddWatcher(PyDict_WatchCallback callback) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| |
| /* Start at 2, as 0 and 1 are reserved for CPython */ |
| for (int i = 2; i < DICT_MAX_WATCHERS; i++) { |
| if (!interp->dict_state.watchers[i]) { |
| interp->dict_state.watchers[i] = callback; |
| return i; |
| } |
| } |
| |
| PyErr_SetString(PyExc_RuntimeError, "no more dict watcher IDs available"); |
| return -1; |
| } |
| |
| int |
| PyDict_ClearWatcher(int watcher_id) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| if (validate_watcher_id(interp, watcher_id)) { |
| return -1; |
| } |
| interp->dict_state.watchers[watcher_id] = NULL; |
| return 0; |
| } |
| |
| 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 = 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 |