mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	 f27af8ba8e
			
		
	
	
		f27af8ba8e
		
			
		
	
	
	
	
		
			
			Co-authored-by: sobolevn <mail@sobolevn.me> Co-authored-by: Ned Batchelder <ned@nedbatchelder.com> Co-authored-by: Tomas R. <tomas.roun8@gmail.com> Co-authored-by: Petr Viktorin <encukou@gmail.com>
		
			
				
	
	
		
			7802 lines
		
	
	
	
		
			228 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			7802 lines
		
	
	
	
		
			228 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Dictionary object implementation using a hash table */
 | |
| 
 | |
| /* The distribution includes a separate file, Objects/dictnotes.txt,
 | |
|    describing explorations into dictionary design and optimization.
 | |
|    It covers typical dictionary use patterns, the parameters for
 | |
|    tuning dictionaries, and several ideas for possible optimizations.
 | |
| */
 | |
| 
 | |
| /* PyDictKeysObject
 | |
| 
 | |
| This implements the dictionary's hashtable.
 | |
| 
 | |
| As of Python 3.6, this is compact and ordered. Basic idea is described here:
 | |
| * https://mail.python.org/pipermail/python-dev/2012-December/123028.html
 | |
| * https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html
 | |
| 
 | |
| layout:
 | |
| 
 | |
| +---------------------+
 | |
| | dk_refcnt           |
 | |
| | dk_log2_size        |
 | |
| | dk_log2_index_bytes |
 | |
| | dk_kind             |
 | |
| | dk_version          |
 | |
| | dk_usable           |
 | |
| | dk_nentries         |
 | |
| +---------------------+
 | |
| | dk_indices[]        |
 | |
| |                     |
 | |
| +---------------------+
 | |
| | dk_entries[]        |
 | |
| |                     |
 | |
| +---------------------+
 | |
| 
 | |
| dk_indices is actual hashtable.  It holds index in entries, or DKIX_EMPTY(-1)
 | |
| or DKIX_DUMMY(-2).
 | |
| Size of indices is dk_size.  Type of each index in indices varies with dk_size:
 | |
| 
 | |
| * int8  for          dk_size <= 128
 | |
| * int16 for 256   <= dk_size <= 2**15
 | |
| * int32 for 2**16 <= dk_size <= 2**31
 | |
| * int64 for 2**32 <= dk_size
 | |
| 
 | |
| dk_entries is array of PyDictKeyEntry when dk_kind == DICT_KEYS_GENERAL or
 | |
| PyDictUnicodeEntry otherwise. Its length is USABLE_FRACTION(dk_size).
 | |
| 
 | |
| NOTE: Since negative value is used for DKIX_EMPTY and DKIX_DUMMY, type of
 | |
| dk_indices entry is signed integer and int16 is used for table which
 | |
| dk_size == 256.
 | |
| */
 | |
| 
 | |
| 
 | |
| /*
 | |
| The DictObject can be in one of two forms.
 | |
| 
 | |
| Either:
 | |
|   A combined table:
 | |
|     ma_values == NULL, dk_refcnt == 1.
 | |
|     Values are stored in the me_value field of the PyDictKeyEntry.
 | |
| Or:
 | |
|   A split table:
 | |
|     ma_values != NULL, dk_refcnt >= 1
 | |
|     Values are stored in the ma_values array.
 | |
|     Only string (unicode) keys are allowed.
 | |
| 
 | |
| There are four kinds of slots in the table (slot is index, and
 | |
| DK_ENTRIES(keys)[index] if index >= 0):
 | |
| 
 | |
| 1. Unused.  index == DKIX_EMPTY
 | |
|    Does not hold an active (key, value) pair now and never did.  Unused can
 | |
|    transition to Active upon key insertion.  This is each slot's initial state.
 | |
| 
 | |
| 2. Active.  index >= 0, me_key != NULL and me_value != NULL
 | |
|    Holds an active (key, value) pair.  Active can transition to Dummy or
 | |
|    Pending upon key deletion (for combined and split tables respectively).
 | |
|    This is the only case in which me_value != NULL.
 | |
| 
 | |
| 3. Dummy.  index == DKIX_DUMMY  (combined only)
 | |
|    Previously held an active (key, value) pair, but that was deleted and an
 | |
|    active pair has not yet overwritten the slot.  Dummy can transition to
 | |
|    Active upon key insertion.  Dummy slots cannot be made Unused again
 | |
|    else the probe sequence in case of collision would have no way to know
 | |
|    they were once active.
 | |
|    In free-threaded builds dummy slots are not re-used to allow lock-free
 | |
|    lookups to proceed safely.
 | |
| 
 | |
| 4. Pending. index >= 0, key != NULL, and value == NULL  (split only)
 | |
|    Not yet inserted in split-table.
 | |
| */
 | |
| 
 | |
| /*
 | |
| Preserving insertion order
 | |
| 
 | |
| It's simple for combined table.  Since dk_entries is mostly append only, we can
 | |
| get insertion order by just iterating dk_entries.
 | |
| 
 | |
| One exception is .popitem().  It removes last item in dk_entries and decrement
 | |
| dk_nentries to achieve amortized O(1).  Since there are DKIX_DUMMY remains in
 | |
| dk_indices, we can't increment dk_usable even though dk_nentries is
 | |
| decremented.
 | |
| 
 | |
| To preserve the order in a split table, a bit vector is used  to record the
 | |
| insertion order. When a key is inserted the bit vector is shifted up by 4 bits
 | |
| and the index of the key is stored in the low 4 bits.
 | |
| As a consequence of this, split keys have a maximum size of 16.
 | |
| */
 | |
| 
 | |
| /* PyDict_MINSIZE is the starting size for any new dict.
 | |
|  * 8 allows dicts with no more than 5 active entries; experiments suggested
 | |
|  * this suffices for the majority of dicts (consisting mostly of usually-small
 | |
|  * dicts created to pass keyword arguments).
 | |
|  * Making this 8, rather than 4 reduces the number of resizes for most
 | |
|  * dictionaries, without any significant extra memory use.
 | |
|  */
 | |
| #define PyDict_LOG_MINSIZE 3
 | |
| #define PyDict_MINSIZE 8
 | |
| 
 | |
| #include "Python.h"
 | |
| #include "pycore_bitutils.h"      // _Py_bit_length
 | |
| #include "pycore_call.h"          // _PyObject_CallNoArgs()
 | |
| #include "pycore_ceval.h"         // _PyEval_GetBuiltin()
 | |
| #include "pycore_code.h"          // stats
 | |
| #include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
 | |
| #include "pycore_dict.h"          // export _PyDict_SizeOf()
 | |
| #include "pycore_freelist.h"      // _PyFreeListState_GET()
 | |
| #include "pycore_gc.h"            // _PyObject_GC_IS_TRACKED()
 | |
| #include "pycore_object.h"        // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
 | |
| #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED
 | |
| #include "pycore_pyerrors.h"      // _PyErr_GetRaisedException()
 | |
| #include "pycore_pystate.h"       // _PyThreadState_GET()
 | |
| #include "pycore_setobject.h"     // _PySet_NextEntry()
 | |
| #include "pycore_tuple.h"         // _PyTuple_Recycle()
 | |
| #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal()
 | |
| 
 | |
| #include "stringlib/eq.h"                // unicode_eq()
 | |
| #include <stdbool.h>
 | |
| 
 | |
| 
 | |
| /*[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(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(PyDictObject *mp, uint8_t log_newsize, int unicode);
 | |
| 
 | |
| static PyObject* dict_iter(PyObject *dict);
 | |
| 
 | |
| static int
 | |
| setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value);
 | |
| static int
 | |
| dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value,
 | |
|                     PyObject **result, int incref_result);
 | |
| 
 | |
| #ifndef NDEBUG
 | |
| static int _PyObject_InlineValuesConsistencyCheck(PyObject *obj);
 | |
| #endif
 | |
| 
 | |
| #include "clinic/dictobject.c.h"
 | |
| 
 | |
| 
 | |
| static inline Py_hash_t
 | |
| unicode_get_hash(PyObject *o)
 | |
| {
 | |
|     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)
 | |
| {
 | |
|     _PyDebugAllocatorStats(out, "free PyDictObject",
 | |
|                            _Py_FREELIST_SIZE(dicts),
 | |
|                            sizeof(PyDictObject));
 | |
|     _PyDebugAllocatorStats(out, "free PyDictKeysObject",
 | |
|                            _Py_FREELIST_SIZE(dictkeys),
 | |
|                            sizeof(PyDictKeysObject));
 | |
| }
 | |
| 
 | |
| #define DK_MASK(dk) (DK_SIZE(dk)-1)
 | |
| 
 | |
| #define _Py_DICT_IMMORTAL_INITIAL_REFCNT PY_SSIZE_T_MIN
 | |
| 
 | |
| static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr);
 | |
| 
 | |
| /* PyDictKeysObject has refcounts like PyObject does, so we have the
 | |
|    following two functions to mirror what Py_INCREF() and Py_DECREF() do.
 | |
|    (Keep in mind that PyDictKeysObject isn't actually a PyObject.)
 | |
|    Likewise a PyDictKeysObject can be immortal (e.g. Py_EMPTY_KEYS),
 | |
|    so we apply a naive version of what Py_INCREF() and Py_DECREF() do
 | |
|    for immortal objects. */
 | |
| 
 | |
| static inline void
 | |
| dictkeys_incref(PyDictKeysObject *dk)
 | |
| {
 | |
|     if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) {
 | |
|         assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT);
 | |
|         return;
 | |
|     }
 | |
| #ifdef Py_REF_DEBUG
 | |
|     _Py_IncRefTotal(_PyThreadState_GET());
 | |
| #endif
 | |
|     INCREF_KEYS(dk);
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| dictkeys_decref(PyDictKeysObject *dk, bool use_qsbr)
 | |
| {
 | |
|     if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) {
 | |
|         assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT);
 | |
|         return;
 | |
|     }
 | |
|     assert(FT_ATOMIC_LOAD_SSIZE(dk->dk_refcnt) > 0);
 | |
| #ifdef Py_REF_DEBUG
 | |
|     _Py_DecRefTotal(_PyThreadState_GET());
 | |
| #endif
 | |
|     if (DECREF_KEYS(dk) == 1) {
 | |
|         if (DK_IS_UNICODE(dk)) {
 | |
|             PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dk);
 | |
|             Py_ssize_t i, n;
 | |
|             for (i = 0, n = dk->dk_nentries; i < n; i++) {
 | |
|                 Py_XDECREF(entries[i].me_key);
 | |
|                 Py_XDECREF(entries[i].me_value);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             PyDictKeyEntry *entries = DK_ENTRIES(dk);
 | |
|             Py_ssize_t i, n;
 | |
|             for (i = 0, n = dk->dk_nentries; i < n; i++) {
 | |
|                 Py_XDECREF(entries[i].me_key);
 | |
|                 Py_XDECREF(entries[i].me_value);
 | |
|             }
 | |
|         }
 | |
|         free_keys_object(dk, use_qsbr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* lookup indices.  returns DKIX_EMPTY, DKIX_DUMMY, or ix >=0 */
 | |
| static inline Py_ssize_t
 | |
| dictkeys_get_index(const PyDictKeysObject *keys, Py_ssize_t i)
 | |
| {
 | |
|     int log2size = DK_LOG_SIZE(keys);
 | |
|     Py_ssize_t ix;
 | |
| 
 | |
|     if (log2size < 8) {
 | |
|         ix = LOAD_INDEX(keys, 8, i);
 | |
|     }
 | |
|     else if (log2size < 16) {
 | |
|         ix = LOAD_INDEX(keys, 16, i);
 | |
|     }
 | |
| #if SIZEOF_VOID_P > 4
 | |
|     else if (log2size >= 32) {
 | |
|         ix = LOAD_INDEX(keys, 64, i);
 | |
|     }
 | |
| #endif
 | |
|     else {
 | |
|         ix = LOAD_INDEX(keys, 32, i);
 | |
|     }
 | |
|     assert(ix >= DKIX_DUMMY);
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| /* write to indices. */
 | |
| static inline void
 | |
| dictkeys_set_index(PyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix)
 | |
| {
 | |
|     int log2size = DK_LOG_SIZE(keys);
 | |
| 
 | |
|     assert(ix >= DKIX_DUMMY);
 | |
|     assert(keys->dk_version == 0);
 | |
| 
 | |
|     if (log2size < 8) {
 | |
|         assert(ix <= 0x7f);
 | |
|         STORE_INDEX(keys, 8, i, ix);
 | |
|     }
 | |
|     else if (log2size < 16) {
 | |
|         assert(ix <= 0x7fff);
 | |
|         STORE_INDEX(keys, 16, i, ix);
 | |
|     }
 | |
| #if SIZEOF_VOID_P > 4
 | |
|     else if (log2size >= 32) {
 | |
|         STORE_INDEX(keys, 64, i, ix);
 | |
|     }
 | |
| #endif
 | |
|     else {
 | |
|         assert(ix <= 0x7fffffff);
 | |
|         STORE_INDEX(keys, 32, i, ix);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /* USABLE_FRACTION is the maximum dictionary load.
 | |
|  * Increasing this ratio makes dictionaries more dense resulting in more
 | |
|  * collisions.  Decreasing it improves sparseness at the expense of spreading
 | |
|  * indices over more cache lines and at the cost of total memory consumed.
 | |
|  *
 | |
|  * USABLE_FRACTION must obey the following:
 | |
|  *     (0 < USABLE_FRACTION(n) < n) for all n >= 2
 | |
|  *
 | |
|  * USABLE_FRACTION should be quick to calculate.
 | |
|  * Fractions around 1/2 to 2/3 seem to work well in practice.
 | |
|  */
 | |
| #define USABLE_FRACTION(n) (((n) << 1)/3)
 | |
| 
 | |
| /* Find the smallest dk_size >= minsize. */
 | |
| static inline uint8_t
 | |
| calculate_log2_keysize(Py_ssize_t minsize)
 | |
| {
 | |
| #if SIZEOF_LONG == SIZEOF_SIZE_T
 | |
|     minsize = Py_MAX(minsize, PyDict_MINSIZE);
 | |
|     return _Py_bit_length(minsize - 1);
 | |
| #elif defined(_MSC_VER)
 | |
|     // On 64bit Windows, sizeof(long) == 4. We cannot use _Py_bit_length.
 | |
|     minsize = Py_MAX(minsize, PyDict_MINSIZE);
 | |
|     unsigned long msb;
 | |
|     _BitScanReverse64(&msb, (uint64_t)minsize - 1);
 | |
|     return (uint8_t)(msb + 1);
 | |
| #else
 | |
|     uint8_t log2_size;
 | |
|     for (log2_size = PyDict_LOG_MINSIZE;
 | |
|             (((Py_ssize_t)1) << log2_size) < minsize;
 | |
|             log2_size++)
 | |
|         ;
 | |
|     return log2_size;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /* estimate_keysize is reverse function of USABLE_FRACTION.
 | |
|  *
 | |
|  * This can be used to reserve enough size to insert n entries without
 | |
|  * resizing.
 | |
|  */
 | |
| static inline uint8_t
 | |
| estimate_log2_keysize(Py_ssize_t n)
 | |
| {
 | |
|     return calculate_log2_keysize((n*3 + 1) / 2);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* GROWTH_RATE. Growth rate upon hitting maximum load.
 | |
|  * Currently set to used*3.
 | |
|  * This means that dicts double in size when growing without deletions,
 | |
|  * but have more head room when the number of deletions is on a par with the
 | |
|  * number of insertions.  See also bpo-17563 and bpo-33205.
 | |
|  *
 | |
|  * GROWTH_RATE was set to used*4 up to version 3.2.
 | |
|  * GROWTH_RATE was set to used*2 in version 3.3.0
 | |
|  * GROWTH_RATE was set to used*2 + capacity/2 in 3.4.0-3.6.0.
 | |
|  */
 | |
| #define GROWTH_RATE(d) ((d)->ma_used*3)
 | |
| 
 | |
| /* This immutable, empty PyDictKeysObject is used for PyDict_Clear()
 | |
|  * (which cannot fail and thus can do no allocation).
 | |
|  *
 | |
|  * See https://github.com/python/cpython/pull/127568#discussion_r1868070614
 | |
|  * for the rationale of using dk_log2_index_bytes=3 instead of 0.
 | |
|  */
 | |
| static PyDictKeysObject empty_keys_struct = {
 | |
|         _Py_DICT_IMMORTAL_INITIAL_REFCNT, /* dk_refcnt */
 | |
|         0, /* dk_log2_size */
 | |
|         3, /* dk_log2_index_bytes */
 | |
|         DICT_KEYS_UNICODE, /* dk_kind */
 | |
| #ifdef Py_GIL_DISABLED
 | |
|         {0}, /* dk_mutex */
 | |
| #endif
 | |
|         1, /* dk_version */
 | |
|         0, /* dk_usable (immutable) */
 | |
|         0, /* dk_nentries */
 | |
|         {DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY,
 | |
|          DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY, DKIX_EMPTY}, /* dk_indices */
 | |
| };
 | |
| 
 | |
| #define Py_EMPTY_KEYS &empty_keys_struct
 | |
| 
 | |
| /* Uncomment to check the dict content in _PyDict_CheckConsistency() */
 | |
| // #define DEBUG_PYDICT
 | |
| 
 | |
| #ifdef DEBUG_PYDICT
 | |
| #  define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 1))
 | |
| #else
 | |
| #  define ASSERT_CONSISTENT(op) assert(_PyDict_CheckConsistency((PyObject *)(op), 0))
 | |
| #endif
 | |
| 
 | |
| static inline int
 | |
| get_index_from_order(PyDictObject *mp, Py_ssize_t i)
 | |
| {
 | |
|     assert(mp->ma_used <= SHARED_KEYS_MAX_SIZE);
 | |
|     assert(i < mp->ma_values->size);
 | |
|     uint8_t *array = get_insertion_order_array(mp->ma_values);
 | |
|     return array[i];
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG_PYDICT
 | |
| static void
 | |
| dump_entries(PyDictKeysObject *dk)
 | |
| {
 | |
|     for (Py_ssize_t i = 0; i < dk->dk_nentries; i++) {
 | |
|         if (DK_IS_UNICODE(dk)) {
 | |
|             PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(dk)[i];
 | |
|             printf("key=%p value=%p\n", ep->me_key, ep->me_value);
 | |
|         }
 | |
|         else {
 | |
|             PyDictKeyEntry *ep = &DK_ENTRIES(dk)[i];
 | |
|             printf("key=%p hash=%lx value=%p\n", ep->me_key, ep->me_hash, ep->me_value);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| int
 | |
| _PyDict_CheckConsistency(PyObject *op, int check_content)
 | |
| {
 | |
|     ASSERT_WORLD_STOPPED_OR_DICT_LOCKED(op);
 | |
| 
 | |
| #define CHECK(expr) \
 | |
|     do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0)
 | |
| 
 | |
|     assert(op != NULL);
 | |
|     CHECK(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(uint8_t log2_size, bool unicode)
 | |
| {
 | |
|     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;
 | |
|     }
 | |
| 
 | |
|     PyDictKeysObject *dk = NULL;
 | |
|     if (log2_size == PyDict_LOG_MINSIZE && unicode) {
 | |
|         dk = _Py_FREELIST_POP_MEM(dictkeys);
 | |
|     }
 | |
|     if (dk == NULL) {
 | |
|         dk = PyMem_Malloc(sizeof(PyDictKeysObject)
 | |
|                           + ((size_t)1 << log2_bytes)
 | |
|                           + entry_size * usable);
 | |
|         if (dk == NULL) {
 | |
|             PyErr_NoMemory();
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
| #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, _PyDict_KeysSize(keys));
 | |
|         return;
 | |
|     }
 | |
| #endif
 | |
|     if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE && keys->dk_kind == DICT_KEYS_UNICODE) {
 | |
|         _Py_FREELIST_FREE(dictkeys, keys, PyMem_Free);
 | |
|     }
 | |
|     else {
 | |
|         PyMem_Free(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, values_size_from_count(values->capacity));
 | |
|         return;
 | |
|     }
 | |
| #endif
 | |
|     PyMem_Free(values);
 | |
| }
 | |
| 
 | |
| /* Consumes a reference to the keys object */
 | |
| static PyObject *
 | |
| new_dict(PyDictKeysObject *keys, PyDictValues *values,
 | |
|          Py_ssize_t used, int free_values_on_failure)
 | |
| {
 | |
|     assert(keys != NULL);
 | |
|     PyDictObject *mp = _Py_FREELIST_POP(PyDictObject, dicts);
 | |
|     if (mp == NULL) {
 | |
|         mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
 | |
|         if (mp == NULL) {
 | |
|             dictkeys_decref(keys, false);
 | |
|             if (free_values_on_failure) {
 | |
|                 free_values(values, false);
 | |
|             }
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
|     assert(Py_IS_TYPE(mp, &PyDict_Type));
 | |
|     mp->ma_keys = keys;
 | |
|     mp->ma_values = values;
 | |
|     mp->ma_used = used;
 | |
|     mp->_ma_watcher_tag = 0;
 | |
|     ASSERT_CONSISTENT(mp);
 | |
|     _PyObject_GC_TRACK(mp);
 | |
|     return (PyObject *)mp;
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| new_dict_with_shared_keys(PyDictKeysObject *keys)
 | |
| {
 | |
|     size_t size = shared_keys_usable_size(keys);
 | |
|     PyDictValues *values = new_values(size);
 | |
|     if (values == NULL) {
 | |
|         return PyErr_NoMemory();
 | |
|     }
 | |
|     dictkeys_incref(keys);
 | |
|     for (size_t i = 0; i < size; i++) {
 | |
|         values->values[i] = NULL;
 | |
|     }
 | |
|     return new_dict(keys, values, 0, 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| static PyDictKeysObject *
 | |
| clone_combined_dict_keys(PyDictObject *orig)
 | |
| {
 | |
|     assert(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)
 | |
| {
 | |
|     /* We don't incref Py_EMPTY_KEYS here because it is immortal. */
 | |
|     return new_dict(Py_EMPTY_KEYS, NULL, 0, 0);
 | |
| }
 | |
| 
 | |
| /* Search index of hash table from offset of entry table */
 | |
| static Py_ssize_t
 | |
| lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index)
 | |
| {
 | |
|     size_t mask = DK_MASK(k);
 | |
|     size_t perturb = (size_t)hash;
 | |
|     size_t i = (size_t)hash & mask;
 | |
| 
 | |
|     for (;;) {
 | |
|         Py_ssize_t ix = dictkeys_get_index(k, i);
 | |
|         if (ix == index) {
 | |
|             return i;
 | |
|         }
 | |
|         if (ix == DKIX_EMPTY) {
 | |
|             return DKIX_EMPTY;
 | |
|         }
 | |
|         perturb >>= PERTURB_SHIFT;
 | |
|         i = mask & (i*5 + perturb + 1);
 | |
|     }
 | |
|     Py_UNREACHABLE();
 | |
| }
 | |
| 
 | |
| static inline Py_ALWAYS_INLINE Py_ssize_t
 | |
| do_lookup(PyDictObject *mp, PyDictKeysObject *dk, PyObject *key, Py_hash_t hash,
 | |
|           int (*check_lookup)(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t ix, PyObject *key, Py_hash_t))
 | |
| {
 | |
|     void *ep0 = _DK_ENTRIES(dk);
 | |
|     size_t mask = DK_MASK(dk);
 | |
|     size_t perturb = hash;
 | |
|     size_t i = (size_t)hash & mask;
 | |
|     Py_ssize_t ix;
 | |
|     for (;;) {
 | |
|         ix = dictkeys_get_index(dk, i);
 | |
|         if (ix >= 0) {
 | |
|             int cmp = check_lookup(mp, dk, ep0, ix, key, hash);
 | |
|             if (cmp < 0) {
 | |
|                 return cmp;
 | |
|             } else if (cmp) {
 | |
|                 return ix;
 | |
|             }
 | |
|         }
 | |
|         else if (ix == DKIX_EMPTY) {
 | |
|             return DKIX_EMPTY;
 | |
|         }
 | |
|         perturb >>= PERTURB_SHIFT;
 | |
|         i = mask & (i*5 + perturb + 1);
 | |
| 
 | |
|         // Manual loop unrolling
 | |
|         ix = dictkeys_get_index(dk, i);
 | |
|         if (ix >= 0) {
 | |
|             int cmp = check_lookup(mp, dk, ep0, ix, key, hash);
 | |
|             if (cmp < 0) {
 | |
|                 return cmp;
 | |
|             } else if (cmp) {
 | |
|                 return ix;
 | |
|             }
 | |
|         }
 | |
|         else if (ix == DKIX_EMPTY) {
 | |
|             return DKIX_EMPTY;
 | |
|         }
 | |
|         perturb >>= PERTURB_SHIFT;
 | |
|         i = mask & (i*5 + perturb + 1);
 | |
|     }
 | |
|     Py_UNREACHABLE();
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk,
 | |
|                         void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
 | |
|     assert(ep->me_key != NULL);
 | |
|     assert(PyUnicode_CheckExact(ep->me_key));
 | |
|     assert(!PyUnicode_CheckExact(key));
 | |
| 
 | |
|     if (unicode_get_hash(ep->me_key) == hash) {
 | |
|         PyObject *startkey = ep->me_key;
 | |
|         Py_INCREF(startkey);
 | |
|         int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
 | |
|         Py_DECREF(startkey);
 | |
|         if (cmp < 0) {
 | |
|             return DKIX_ERROR;
 | |
|         }
 | |
|         if (dk == mp->ma_keys && ep->me_key == startkey) {
 | |
|             return cmp;
 | |
|         }
 | |
|         else {
 | |
|             /* The dict was mutated, restart */
 | |
|             return DKIX_KEY_CHANGED;
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| // Search non-Unicode key from Unicode table
 | |
| static Py_ssize_t
 | |
| unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     return do_lookup(mp, dk, key, hash, compare_unicode_generic);
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk,
 | |
|                         void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
 | |
|     PyObject *ep_key = FT_ATOMIC_LOAD_PTR_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);
 | |
| }
 | |
| 
 | |
| static bool
 | |
| check_keys_unicode(PyDictKeysObject *dk, PyObject *key)
 | |
| {
 | |
|     return PyUnicode_CheckExact(key) && (dk->dk_kind != DICT_KEYS_GENERAL);
 | |
| }
 | |
| 
 | |
| static Py_ssize_t
 | |
| hash_unicode_key(PyObject *key)
 | |
| {
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
|     Py_hash_t hash = unicode_get_hash(key);
 | |
|     if (hash == -1) {
 | |
|         hash = PyUnicode_Type.tp_hash(key);
 | |
|         assert(hash != -1);
 | |
|     }
 | |
|     return hash;
 | |
| }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| static Py_ssize_t
 | |
| unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key,
 | |
|                                       Py_hash_t hash);
 | |
| #endif
 | |
| 
 | |
| static Py_ssize_t
 | |
| unicodekeys_lookup_split(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     Py_ssize_t ix;
 | |
|     assert(dk->dk_kind == DICT_KEYS_SPLIT);
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     // A split dictionaries keys can be mutated by other dictionaries
 | |
|     // but if we have a unicode key we can avoid locking the shared
 | |
|     // keys.
 | |
|     ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
 | |
|     if (ix == DKIX_KEY_CHANGED) {
 | |
|         LOCK_KEYS(dk);
 | |
|         ix = unicodekeys_lookup_unicode(dk, key, hash);
 | |
|         UNLOCK_KEYS(dk);
 | |
|     }
 | |
| #else
 | |
|     ix = unicodekeys_lookup_unicode(dk, key, hash);
 | |
| #endif
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| /* Lookup a string in a (all unicode) dict keys.
 | |
|  * Returns DKIX_ERROR if key is not a string,
 | |
|  * or if the dict keys is not all strings.
 | |
|  * If the keys is present then return the index of key.
 | |
|  * If the key is not present then return DKIX_EMPTY.
 | |
|  */
 | |
| Py_ssize_t
 | |
| _PyDictKeys_StringLookup(PyDictKeysObject* dk, PyObject *key)
 | |
| {
 | |
|     if (!check_keys_unicode(dk, key)) {
 | |
|         return DKIX_ERROR;
 | |
|     }
 | |
|     Py_hash_t hash = hash_unicode_key(key);
 | |
|     return unicodekeys_lookup_unicode(dk, key, hash);
 | |
| }
 | |
| 
 | |
| Py_ssize_t
 | |
| _PyDictKeys_StringLookupAndVersion(PyDictKeysObject *dk, PyObject *key, uint32_t *version)
 | |
| {
 | |
|     if (!check_keys_unicode(dk, key)) {
 | |
|         return DKIX_ERROR;
 | |
|     }
 | |
|     Py_ssize_t ix;
 | |
|     Py_hash_t hash = hash_unicode_key(key);
 | |
|     LOCK_KEYS(dk);
 | |
|     ix = unicodekeys_lookup_unicode(dk, key, hash);
 | |
|     *version = _PyDictKeys_GetVersionForCurrentState(_PyInterpreterState_GET(), dk);
 | |
|     UNLOCK_KEYS(dk);
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| /* Like _PyDictKeys_StringLookup() but only works on split keys.  Note
 | |
|  * that in free-threaded builds this locks the keys object as required.
 | |
|  */
 | |
| Py_ssize_t
 | |
| _PyDictKeys_StringLookupSplit(PyDictKeysObject* dk, PyObject *key)
 | |
| {
 | |
|     assert(dk->dk_kind == DICT_KEYS_SPLIT);
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
|     Py_hash_t hash = unicode_get_hash(key);
 | |
|     if (hash == -1) {
 | |
|         hash = PyUnicode_Type.tp_hash(key);
 | |
|         if (hash == -1) {
 | |
|             PyErr_Clear();
 | |
|             return DKIX_ERROR;
 | |
|         }
 | |
|     }
 | |
|     return unicodekeys_lookup_split(dk, key, hash);
 | |
| }
 | |
| 
 | |
| /*
 | |
| The basic lookup function used by all operations.
 | |
| This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
 | |
| Open addressing is preferred over chaining since the link overhead for
 | |
| chaining would be substantial (100% with typical malloc overhead).
 | |
| 
 | |
| The initial probe index is computed as hash mod the table size. Subsequent
 | |
| probe indices are computed as explained earlier.
 | |
| 
 | |
| All arithmetic on hash should ignore overflow.
 | |
| 
 | |
| _Py_dict_lookup() is general-purpose, and may return DKIX_ERROR if (and only if) a
 | |
| comparison raises an exception.
 | |
| When the key isn't found a DKIX_EMPTY is returned.
 | |
| */
 | |
| Py_ssize_t
 | |
| _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr)
 | |
| {
 | |
|     PyDictKeysObject *dk;
 | |
|     DictKeysKind kind;
 | |
|     Py_ssize_t ix;
 | |
| 
 | |
|     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp);
 | |
| start:
 | |
|     dk = mp->ma_keys;
 | |
|     kind = dk->dk_kind;
 | |
| 
 | |
|     if (kind != DICT_KEYS_GENERAL) {
 | |
|         if (PyUnicode_CheckExact(key)) {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|             if (kind == DICT_KEYS_SPLIT) {
 | |
|                 ix = unicodekeys_lookup_split(dk, key, hash);
 | |
|             }
 | |
|             else {
 | |
|                 ix = unicodekeys_lookup_unicode(dk, key, hash);
 | |
|             }
 | |
| #else
 | |
|             ix = unicodekeys_lookup_unicode(dk, key, hash);
 | |
| #endif
 | |
|         }
 | |
|         else {
 | |
|             INCREF_KEYS_FT(dk);
 | |
|             LOCK_KEYS_IF_SPLIT(dk, kind);
 | |
| 
 | |
|             ix = unicodekeys_lookup_generic(mp, dk, key, hash);
 | |
| 
 | |
|             UNLOCK_KEYS_IF_SPLIT(dk, kind);
 | |
|             DECREF_KEYS_FT(dk, IS_DICT_SHARED(mp));
 | |
|             if (ix == DKIX_KEY_CHANGED) {
 | |
|                 goto start;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (ix >= 0) {
 | |
|             if (kind == DICT_KEYS_SPLIT) {
 | |
|                 *value_addr = mp->ma_values->values[ix];
 | |
|             }
 | |
|             else {
 | |
|                 *value_addr = DK_UNICODE_ENTRIES(dk)[ix].me_value;
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             *value_addr = NULL;
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         ix = dictkeys_generic_lookup(mp, dk, key, hash);
 | |
|         if (ix == DKIX_KEY_CHANGED) {
 | |
|             goto start;
 | |
|         }
 | |
|         if (ix >= 0) {
 | |
|             *value_addr = DK_ENTRIES(dk)[ix].me_value;
 | |
|         }
 | |
|         else {
 | |
|             *value_addr = NULL;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| static inline void
 | |
| ensure_shared_on_read(PyDictObject *mp)
 | |
| {
 | |
|     if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) {
 | |
|         // The first time we access a dict from a non-owning thread we mark it
 | |
|         // as shared. This ensures that a concurrent resize operation will
 | |
|         // delay freeing the old keys or values using QSBR, which is necessary
 | |
|         // to safely allow concurrent reads without locking...
 | |
|         Py_BEGIN_CRITICAL_SECTION(mp);
 | |
|         if (!IS_DICT_SHARED(mp)) {
 | |
|             SET_DICT_SHARED(mp);
 | |
|         }
 | |
|         Py_END_CRITICAL_SECTION();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyDict_EnsureSharedOnRead(PyDictObject *mp)
 | |
| {
 | |
|     ensure_shared_on_read(mp);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static inline void
 | |
| ensure_shared_on_resize(PyDictObject *mp)
 | |
| {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp);
 | |
| 
 | |
|     if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) {
 | |
|         // We are writing to the dict from another thread that owns
 | |
|         // it and we haven't marked it as shared which will ensure
 | |
|         // that when we re-size ma_keys or ma_values that we will
 | |
|         // free using QSBR.  We need to lock the dictionary to
 | |
|         // contend with writes from the owning thread, mark it as
 | |
|         // shared, and then we can continue with lock-free reads.
 | |
|         // Technically this is a little heavy handed, we could just
 | |
|         // free the individual old keys / old-values using qsbr
 | |
|         SET_DICT_SHARED(mp);
 | |
|     }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static inline void
 | |
| ensure_shared_on_keys_version_assignment(PyDictObject *mp)
 | |
| {
 | |
|     ASSERT_DICT_LOCKED((PyObject *) mp);
 | |
|     #ifdef Py_GIL_DISABLED
 | |
|     if (!IS_DICT_SHARED(mp)) {
 | |
|         // This ensures that a concurrent resize operation will delay
 | |
|         // freeing the old keys or values using QSBR, which is necessary to
 | |
|         // safely allow concurrent reads without locking.
 | |
|         SET_DICT_SHARED(mp);
 | |
|     }
 | |
|     #endif
 | |
| }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| 
 | |
| static inline Py_ALWAYS_INLINE int
 | |
| compare_unicode_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
 | |
|                                    void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
 | |
|     PyObject *startkey = _Py_atomic_load_ptr_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);
 | |
|     if (startkey == key) {
 | |
|         assert(PyUnicode_CheckExact(startkey));
 | |
|         return 1;
 | |
|     }
 | |
|     if (startkey != NULL) {
 | |
|         if (_Py_IsImmortal(startkey)) {
 | |
|             assert(PyUnicode_CheckExact(startkey));
 | |
|             return unicode_get_hash(startkey) == hash && unicode_eq(startkey, key);
 | |
|         }
 | |
|         else {
 | |
|             if (!_Py_TryIncrefCompare(&ep->me_key, startkey)) {
 | |
|                 return DKIX_KEY_CHANGED;
 | |
|             }
 | |
|             assert(PyUnicode_CheckExact(startkey));
 | |
|             if (unicode_get_hash(startkey) == hash && unicode_eq(startkey, key)) {
 | |
|                 Py_DECREF(startkey);
 | |
|                 return 1;
 | |
|             }
 | |
|             Py_DECREF(startkey);
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static Py_ssize_t _Py_HOT_FUNCTION
 | |
| unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     return do_lookup(NULL, dk, key, hash, compare_unicode_unicode_threadsafe);
 | |
| }
 | |
| 
 | |
| static inline Py_ALWAYS_INLINE int
 | |
| compare_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
 | |
|                            void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix];
 | |
|     PyObject *startkey = _Py_atomic_load_ptr_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;
 | |
| }
 | |
| 
 | |
| static Py_ssize_t
 | |
| lookup_threadsafe_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr)
 | |
| {
 | |
|     assert(dk->dk_kind == DICT_KEYS_UNICODE);
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
| 
 | |
|     Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
 | |
|     if (ix == DKIX_EMPTY) {
 | |
|         *value_addr = PyStackRef_NULL;
 | |
|         return ix;
 | |
|     }
 | |
|     else if (ix >= 0) {
 | |
|         PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value;
 | |
|         PyObject *value = _Py_atomic_load_ptr(addr_of_value);
 | |
|         if (value == NULL) {
 | |
|             *value_addr = PyStackRef_NULL;
 | |
|             return DKIX_EMPTY;
 | |
|         }
 | |
|         if (_PyObject_HasDeferredRefcount(value)) {
 | |
|             *value_addr =  (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED };
 | |
|             return ix;
 | |
|         }
 | |
|         if (_Py_TryIncrefCompare(addr_of_value, value)) {
 | |
|             *value_addr = PyStackRef_FromPyObjectSteal(value);
 | |
|             return ix;
 | |
|         }
 | |
|         return DKIX_KEY_CHANGED;
 | |
|     }
 | |
|     assert(ix == DKIX_KEY_CHANGED);
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| Py_ssize_t
 | |
| _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr)
 | |
| {
 | |
|     PyDictKeysObject *dk = _Py_atomic_load_ptr(&mp->ma_keys);
 | |
|     if (dk->dk_kind == DICT_KEYS_UNICODE && PyUnicode_CheckExact(key)) {
 | |
|         Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, value_addr);
 | |
|         if (ix != DKIX_KEY_CHANGED) {
 | |
|             return ix;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     PyObject *obj;
 | |
|     Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj);
 | |
|     if (ix >= 0 && obj != NULL) {
 | |
|         *value_addr = PyStackRef_FromPyObjectSteal(obj);
 | |
|     }
 | |
|     else {
 | |
|         *value_addr = PyStackRef_NULL;
 | |
|     }
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| #else   // Py_GIL_DISABLED
 | |
| 
 | |
| Py_ssize_t
 | |
| _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr)
 | |
| {
 | |
|     Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, value_addr);
 | |
|     Py_XNewRef(*value_addr);
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| Py_ssize_t
 | |
| _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr)
 | |
| {
 | |
|     PyObject *val;
 | |
|     Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &val);
 | |
|     if (val == NULL) {
 | |
|         *value_addr = PyStackRef_NULL;
 | |
|     }
 | |
|     else {
 | |
|         *value_addr = PyStackRef_FromPyObjectNew(val);
 | |
|     }
 | |
|     return ix;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| // Looks up the unicode key `key` in the dictionary. Note that `*method` may
 | |
| // already contain a valid value! See _PyObject_GetMethodStackRef().
 | |
| int
 | |
| _PyDict_GetMethodStackRef(PyDictObject *mp, PyObject *key, _PyStackRef *method)
 | |
| {
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
|     Py_hash_t hash = hash_unicode_key(key);
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys);
 | |
|     if (dk->dk_kind == DICT_KEYS_UNICODE) {
 | |
|         _PyStackRef ref;
 | |
|         Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, &ref);
 | |
|         if (ix >= 0) {
 | |
|             assert(!PyStackRef_IsNull(ref));
 | |
|             PyStackRef_XSETREF(*method, ref);
 | |
|             return 1;
 | |
|         }
 | |
|         else if (ix == DKIX_EMPTY) {
 | |
|             return 0;
 | |
|         }
 | |
|         assert(ix == DKIX_KEY_CHANGED);
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     PyObject *obj;
 | |
|     Py_INCREF(mp);
 | |
|     Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj);
 | |
|     Py_DECREF(mp);
 | |
|     if (ix == DKIX_ERROR) {
 | |
|         PyStackRef_CLEAR(*method);
 | |
|         return -1;
 | |
|     }
 | |
|     else if (ix >= 0 && obj != NULL) {
 | |
|         PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(obj));
 | |
|         return 1;
 | |
|     }
 | |
|     return 0;  // not found
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_HasOnlyStringKeys(PyObject *dict)
 | |
| {
 | |
|     Py_ssize_t pos = 0;
 | |
|     PyObject *key, *value;
 | |
|     assert(PyDict_Check(dict));
 | |
|     /* Shortcut */
 | |
|     if (((PyDictObject *)dict)->ma_keys->dk_kind != DICT_KEYS_GENERAL)
 | |
|         return 1;
 | |
|     while (PyDict_Next(dict, &pos, &key, &value))
 | |
|         if (!PyUnicode_Check(key))
 | |
|             return 0;
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyDict_EnablePerThreadRefcounting(PyObject *op)
 | |
| {
 | |
|     assert(PyDict_Check(op));
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     Py_ssize_t id = _PyObject_AssignUniqueId(op);
 | |
|     if (id == _Py_INVALID_UNIQUE_ID) {
 | |
|         return;
 | |
|     }
 | |
|     if ((uint64_t)id >= (uint64_t)DICT_UNIQUE_ID_MAX) {
 | |
|         _PyObject_ReleaseUniqueId(id);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     PyDictObject *mp = (PyDictObject *)op;
 | |
|     assert((mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT) == 0);
 | |
|     mp->_ma_watcher_tag += (uint64_t)id << DICT_UNIQUE_ID_SHIFT;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| is_unusable_slot(Py_ssize_t ix)
 | |
| {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     return ix >= 0 || ix == DKIX_DUMMY;
 | |
| #else
 | |
|     return ix >= 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /* Internal function to find slot for an item from its hash
 | |
|    when it is known that the key is not present in the dict.
 | |
|  */
 | |
| static Py_ssize_t
 | |
| find_empty_slot(PyDictKeysObject *keys, Py_hash_t hash)
 | |
| {
 | |
|     assert(keys != NULL);
 | |
| 
 | |
|     const size_t mask = DK_MASK(keys);
 | |
|     size_t i = hash & mask;
 | |
|     Py_ssize_t ix = dictkeys_get_index(keys, i);
 | |
|     for (size_t perturb = hash; is_unusable_slot(ix);) {
 | |
|         perturb >>= PERTURB_SHIFT;
 | |
|         i = (i*5 + perturb + 1) & mask;
 | |
|         ix = dictkeys_get_index(keys, i);
 | |
|     }
 | |
|     return i;
 | |
| }
 | |
| 
 | |
| static int
 | |
| insertion_resize(PyDictObject *mp, int unicode)
 | |
| {
 | |
|     return dictresize(mp, calculate_log2_keysize(GROWTH_RATE(mp)), unicode);
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| insert_combined_dict(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(mp, 1) < 0) {
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
 | |
|     FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0);
 | |
| 
 | |
|     Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
 | |
|     dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
 | |
| 
 | |
|     if (DK_IS_UNICODE(mp->ma_keys)) {
 | |
|         PyDictUnicodeEntry *ep;
 | |
|         ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
 | |
|         STORE_KEY(ep, key);
 | |
|         STORE_VALUE(ep, value);
 | |
|     }
 | |
|     else {
 | |
|         PyDictKeyEntry *ep;
 | |
|         ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
 | |
|         STORE_KEY(ep, key);
 | |
|         STORE_VALUE(ep, value);
 | |
|         STORE_HASH(ep, hash);
 | |
|     }
 | |
|     STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - 1);
 | |
|     STORE_KEYS_NENTRIES(mp->ma_keys, mp->ma_keys->dk_nentries + 1);
 | |
|     assert(mp->ma_keys->dk_usable >= 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static Py_ssize_t
 | |
| insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
|     Py_ssize_t ix;
 | |
| 
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     ix = unicodekeys_lookup_unicode_threadsafe(keys, key, hash);
 | |
|     if (ix >= 0) {
 | |
|         return ix;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     LOCK_KEYS(keys);
 | |
|     ix = unicodekeys_lookup_unicode(keys, key, hash);
 | |
|     if (ix == DKIX_EMPTY && keys->dk_usable > 0) {
 | |
|         // Insert into new slot
 | |
|         FT_ATOMIC_STORE_UINT32_RELAXED(keys->dk_version, 0);
 | |
|         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);
 | |
|     PyObject *old_value = mp->ma_values->values[ix];
 | |
|     if (old_value == NULL) {
 | |
|         _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);
 | |
|     }
 | |
|     else {
 | |
|         _PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
 | |
|         STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
 | |
|         // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
 | |
|         // when dict only holds the strong reference to value in ep->me_value.
 | |
|         Py_DECREF(old_value);
 | |
|     }
 | |
|     ASSERT_CONSISTENT(mp);
 | |
| }
 | |
| 
 | |
| /*
 | |
| 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(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(mp, 1) < 0) {
 | |
|             goto Fail;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value);
 | |
|     if (ix == DKIX_ERROR)
 | |
|         goto Fail;
 | |
| 
 | |
|     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) {
 | |
|         _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);
 | |
|         }
 | |
|     }
 | |
|     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(PyDict_LOG_MINSIZE, unicode);
 | |
|     if (newkeys == NULL) {
 | |
|         Py_DECREF(key);
 | |
|         Py_DECREF(value);
 | |
|         return -1;
 | |
|     }
 | |
|     _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);
 | |
| 
 | |
|     size_t hashpos = (size_t)hash & (PyDict_MINSIZE-1);
 | |
|     dictkeys_set_index(newkeys, hashpos, 0);
 | |
|     if (unicode) {
 | |
|         PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(newkeys);
 | |
|         ep->me_key = key;
 | |
|         STORE_VALUE(ep, value);
 | |
|     }
 | |
|     else {
 | |
|         PyDictKeyEntry *ep = DK_ENTRIES(newkeys);
 | |
|         ep->me_key = key;
 | |
|         ep->me_hash = hash;
 | |
|         STORE_VALUE(ep, value);
 | |
|     }
 | |
|     STORE_USED(mp, mp->ma_used + 1);
 | |
|     newkeys->dk_usable--;
 | |
|     newkeys->dk_nentries++;
 | |
|     // We store the keys last so no one can see them in a partially inconsistent
 | |
|     // state so that we don't need to switch the keys to being shared yet for
 | |
|     // the case where we're inserting from the non-owner thread.  We don't use
 | |
|     // set_keys here because the transition from empty to non-empty is safe
 | |
|     // as the empty keys will never be freed.
 | |
|     FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_keys, newkeys);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
| Internal routine used by dictresize() to build a hashtable of entries.
 | |
| */
 | |
| static void
 | |
| build_indices_generic(PyDictKeysObject *keys, PyDictKeyEntry *ep, Py_ssize_t n)
 | |
| {
 | |
|     size_t mask = DK_MASK(keys);
 | |
|     for (Py_ssize_t ix = 0; ix != n; ix++, ep++) {
 | |
|         Py_hash_t hash = ep->me_hash;
 | |
|         size_t i = hash & mask;
 | |
|         for (size_t perturb = hash; dictkeys_get_index(keys, i) != DKIX_EMPTY;) {
 | |
|             perturb >>= PERTURB_SHIFT;
 | |
|             i = mask & (i*5 + perturb + 1);
 | |
|         }
 | |
|         dictkeys_set_index(keys, i, ix);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| build_indices_unicode(PyDictKeysObject *keys, PyDictUnicodeEntry *ep, Py_ssize_t n)
 | |
| {
 | |
|     size_t mask = DK_MASK(keys);
 | |
|     for (Py_ssize_t ix = 0; ix != n; ix++, ep++) {
 | |
|         Py_hash_t hash = unicode_get_hash(ep->me_key);
 | |
|         assert(hash != -1);
 | |
|         size_t i = hash & mask;
 | |
|         for (size_t perturb = hash; dictkeys_get_index(keys, i) != DKIX_EMPTY;) {
 | |
|             perturb >>= PERTURB_SHIFT;
 | |
|             i = mask & (i*5 + perturb + 1);
 | |
|         }
 | |
|         dictkeys_set_index(keys, i, ix);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| invalidate_and_clear_inline_values(PyDictValues *values)
 | |
| {
 | |
|     assert(values->embedded);
 | |
|     FT_ATOMIC_STORE_UINT8(values->valid, 0);
 | |
|     for (int i = 0; i < values->capacity; i++) {
 | |
|         FT_ATOMIC_STORE_PTR_RELEASE(values->values[i], NULL);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /*
 | |
| Restructure the table by allocating a new table and reinserting all
 | |
| items again.  When entries have been deleted, the new table may
 | |
| actually be smaller than the old one.
 | |
| If a table is split (its keys and hashes are shared, its values are not),
 | |
| then the values are temporarily copied into the table, it is resized as
 | |
| a combined table, then the me_value slots in the old table are NULLed out.
 | |
| After resizing, a table is always combined.
 | |
| 
 | |
| This function supports:
 | |
|  - Unicode split -> Unicode combined or Generic
 | |
|  - Unicode combined -> Unicode combined or Generic
 | |
|  - Generic -> Generic
 | |
| */
 | |
| static int
 | |
| dictresize(PyDictObject *mp,
 | |
|            uint8_t log2_newsize, int unicode)
 | |
| {
 | |
|     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(log2_newsize, unicode);
 | |
|     if (newkeys == NULL) {
 | |
|         return -1;
 | |
|     }
 | |
|     // New table must be large enough.
 | |
|     assert(newkeys->dk_usable >= mp->ma_used);
 | |
| 
 | |
|     Py_ssize_t numentries = mp->ma_used;
 | |
| 
 | |
|     if (oldvalues != NULL) {
 | |
|         LOCK_KEYS(oldkeys);
 | |
|         PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys);
 | |
|         /* Convert split table into new combined table.
 | |
|          * We must incref keys; we can transfer values.
 | |
|          */
 | |
|         if (newkeys->dk_kind == DICT_KEYS_GENERAL) {
 | |
|             // split -> generic
 | |
|             PyDictKeyEntry *newentries = DK_ENTRIES(newkeys);
 | |
| 
 | |
|             for (Py_ssize_t i = 0; i < numentries; i++) {
 | |
|                 int index = get_index_from_order(mp, i);
 | |
|                 PyDictUnicodeEntry *ep = &oldentries[index];
 | |
|                 assert(oldvalues->values[index] != NULL);
 | |
|                 newentries[i].me_key = Py_NewRef(ep->me_key);
 | |
|                 newentries[i].me_hash = unicode_get_hash(ep->me_key);
 | |
|                 newentries[i].me_value = oldvalues->values[index];
 | |
|             }
 | |
|             build_indices_generic(newkeys, newentries, numentries);
 | |
|         }
 | |
|         else { // split -> combined unicode
 | |
|             PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys);
 | |
| 
 | |
|             for (Py_ssize_t i = 0; i < numentries; i++) {
 | |
|                 int index = get_index_from_order(mp, i);
 | |
|                 PyDictUnicodeEntry *ep = &oldentries[index];
 | |
|                 assert(oldvalues->values[index] != NULL);
 | |
|                 newentries[i].me_key = Py_NewRef(ep->me_key);
 | |
|                 newentries[i].me_value = oldvalues->values[index];
 | |
|             }
 | |
|             build_indices_unicode(newkeys, newentries, numentries);
 | |
|         }
 | |
|         UNLOCK_KEYS(oldkeys);
 | |
|         set_keys(mp, newkeys);
 | |
|         dictkeys_decref(oldkeys, IS_DICT_SHARED(mp));
 | |
|         set_values(mp, NULL);
 | |
|         if (oldvalues->embedded) {
 | |
|             assert(oldvalues->embedded == 1);
 | |
|             assert(oldvalues->valid == 1);
 | |
|             invalidate_and_clear_inline_values(oldvalues);
 | |
|         }
 | |
|         else {
 | |
|             free_values(oldvalues, IS_DICT_SHARED(mp));
 | |
|         }
 | |
|     }
 | |
|     else {  // oldkeys is combined.
 | |
|         if (oldkeys->dk_kind == DICT_KEYS_GENERAL) {
 | |
|             // generic -> generic
 | |
|             assert(newkeys->dk_kind == DICT_KEYS_GENERAL);
 | |
|             PyDictKeyEntry *oldentries = DK_ENTRIES(oldkeys);
 | |
|             PyDictKeyEntry *newentries = DK_ENTRIES(newkeys);
 | |
|             if (oldkeys->dk_nentries == numentries) {
 | |
|                 memcpy(newentries, oldentries, numentries * sizeof(PyDictKeyEntry));
 | |
|             }
 | |
|             else {
 | |
|                 PyDictKeyEntry *ep = oldentries;
 | |
|                 for (Py_ssize_t i = 0; i < numentries; i++) {
 | |
|                     while (ep->me_value == NULL)
 | |
|                         ep++;
 | |
|                     newentries[i] = *ep++;
 | |
|                 }
 | |
|             }
 | |
|             build_indices_generic(newkeys, newentries, numentries);
 | |
|         }
 | |
|         else {  // oldkeys is combined unicode
 | |
|             PyDictUnicodeEntry *oldentries = DK_UNICODE_ENTRIES(oldkeys);
 | |
|             if (unicode) { // combined unicode -> combined unicode
 | |
|                 PyDictUnicodeEntry *newentries = DK_UNICODE_ENTRIES(newkeys);
 | |
|                 if (oldkeys->dk_nentries == numentries && mp->ma_keys->dk_kind == DICT_KEYS_UNICODE) {
 | |
|                     memcpy(newentries, oldentries, numentries * sizeof(PyDictUnicodeEntry));
 | |
|                 }
 | |
|                 else {
 | |
|                     PyDictUnicodeEntry *ep = oldentries;
 | |
|                     for (Py_ssize_t i = 0; i < numentries; i++) {
 | |
|                         while (ep->me_value == NULL)
 | |
|                             ep++;
 | |
|                         newentries[i] = *ep++;
 | |
|                     }
 | |
|                 }
 | |
|                 build_indices_unicode(newkeys, newentries, numentries);
 | |
|             }
 | |
|             else { // combined unicode -> generic
 | |
|                 PyDictKeyEntry *newentries = DK_ENTRIES(newkeys);
 | |
|                 PyDictUnicodeEntry *ep = oldentries;
 | |
|                 for (Py_ssize_t i = 0; i < numentries; i++) {
 | |
|                     while (ep->me_value == NULL)
 | |
|                         ep++;
 | |
|                     newentries[i].me_key = ep->me_key;
 | |
|                     newentries[i].me_hash = unicode_get_hash(ep->me_key);
 | |
|                     newentries[i].me_value = ep->me_value;
 | |
|                     ep++;
 | |
|                 }
 | |
|                 build_indices_generic(newkeys, newentries, numentries);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         set_keys(mp, newkeys);
 | |
| 
 | |
|         if (oldkeys != Py_EMPTY_KEYS) {
 | |
| #ifdef Py_REF_DEBUG
 | |
|             _Py_DecRefTotal(_PyThreadState_GET());
 | |
| #endif
 | |
|             assert(oldkeys->dk_kind != DICT_KEYS_SPLIT);
 | |
|             assert(oldkeys->dk_refcnt == 1);
 | |
|             free_keys_object(oldkeys, IS_DICT_SHARED(mp));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - numentries);
 | |
|     STORE_KEYS_NENTRIES(mp->ma_keys, numentries);
 | |
|     ASSERT_CONSISTENT(mp);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| dict_new_presized(Py_ssize_t minused, bool unicode)
 | |
| {
 | |
|     const uint8_t log2_max_presize = 17;
 | |
|     const Py_ssize_t max_presize = ((Py_ssize_t)1) << log2_max_presize;
 | |
|     uint8_t log2_newsize;
 | |
|     PyDictKeysObject *new_keys;
 | |
| 
 | |
|     if (minused <= USABLE_FRACTION(PyDict_MINSIZE)) {
 | |
|         return PyDict_New();
 | |
|     }
 | |
|     /* There are no strict guarantee that returned dict can contain minused
 | |
|      * items without resize.  So we create medium size dict instead of very
 | |
|      * large dict or MemoryError.
 | |
|      */
 | |
|     if (minused > USABLE_FRACTION(max_presize)) {
 | |
|         log2_newsize = log2_max_presize;
 | |
|     }
 | |
|     else {
 | |
|         log2_newsize = estimate_log2_keysize(minused);
 | |
|     }
 | |
| 
 | |
|     new_keys = new_keys_object(log2_newsize, unicode);
 | |
|     if (new_keys == NULL)
 | |
|         return NULL;
 | |
|     return new_dict(new_keys, NULL, 0, 0);
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_NewPresized(Py_ssize_t minused)
 | |
| {
 | |
|     return dict_new_presized(minused, false);
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset,
 | |
|                   PyObject *const *values, Py_ssize_t values_offset,
 | |
|                   Py_ssize_t length)
 | |
| {
 | |
|     bool unicode = true;
 | |
|     PyObject *const *ks = keys;
 | |
| 
 | |
|     for (Py_ssize_t i = 0; i < length; i++) {
 | |
|         if (!PyUnicode_CheckExact(*ks)) {
 | |
|             unicode = false;
 | |
|             break;
 | |
|         }
 | |
|         ks += keys_offset;
 | |
|     }
 | |
| 
 | |
|     PyObject *dict = dict_new_presized(length, unicode);
 | |
|     if (dict == NULL) {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     ks = keys;
 | |
|     PyObject *const *vs = values;
 | |
| 
 | |
|     for (Py_ssize_t i = 0; i < length; i++) {
 | |
|         PyObject *key = *ks;
 | |
|         PyObject *value = *vs;
 | |
|         if (setitem_lock_held((PyDictObject *)dict, key, value) < 0) {
 | |
|             Py_DECREF(dict);
 | |
|             return NULL;
 | |
|         }
 | |
|         ks += keys_offset;
 | |
|         vs += values_offset;
 | |
|     }
 | |
| 
 | |
|     return dict;
 | |
| }
 | |
| 
 | |
| /* Note that, for historical reasons, PyDict_GetItem() suppresses all errors
 | |
|  * that may occur (originally dicts supported only string keys, and exceptions
 | |
|  * weren't possible).  So, while the original intent was that a NULL return
 | |
|  * meant the key wasn't present, in reality it can mean that, or that an error
 | |
|  * (suppressed) occurred while computing the key's hash, or that some error
 | |
|  * (suppressed) occurred when comparing keys in the dict's internal probe
 | |
|  * sequence.  A nasty example of the latter is when a Python-coded comparison
 | |
|  * function hits a stack-depth error, which can cause this to return NULL
 | |
|  * even if the key is present.
 | |
|  */
 | |
| static PyObject *
 | |
| dict_getitem(PyObject *op, PyObject *key, const char *warnmsg)
 | |
| {
 | |
|     if (!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()");
 | |
| }
 | |
| 
 | |
| static void
 | |
| dict_unhashable_type(PyObject *key)
 | |
| {
 | |
|     PyObject *exc = PyErr_GetRaisedException();
 | |
|     assert(exc != NULL);
 | |
|     if (!Py_IS_TYPE(exc, (PyTypeObject*)PyExc_TypeError)) {
 | |
|         PyErr_SetRaisedException(exc);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     PyErr_Format(PyExc_TypeError,
 | |
|                  "cannot use '%T' as a dict key (%S)",
 | |
|                  key, exc);
 | |
|     Py_DECREF(exc);
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|         dict_unhashable_type(key);
 | |
|         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) {
 | |
|         dict_unhashable_type(key);
 | |
|         *result = NULL;
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     return _PyDict_GetItemRef_KnownHash((PyDictObject *)op, key, hash, result);
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject **result)
 | |
| {
 | |
|     ASSERT_DICT_LOCKED(op);
 | |
|     assert(PyUnicode_CheckExact(key));
 | |
| 
 | |
|     Py_hash_t hash = _PyObject_HashFast(key);
 | |
|     if (hash == -1) {
 | |
|         dict_unhashable_type(key);
 | |
|         *result = NULL;
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     PyObject *value;
 | |
|     Py_ssize_t ix = _Py_dict_lookup(op, key, hash, &value);
 | |
|     assert(ix >= 0 || value == NULL);
 | |
|     if (ix == DKIX_ERROR) {
 | |
|         *result = NULL;
 | |
|         return -1;
 | |
|     }
 | |
|     if (value == NULL) {
 | |
|         *result = NULL;
 | |
|         return 0;  // missing key
 | |
|     }
 | |
|     *result = Py_NewRef(value);
 | |
|     return 1;  // key is present
 | |
| }
 | |
| 
 | |
| /* Variant of PyDict_GetItem() that doesn't suppress exceptions.
 | |
|    This returns NULL *with* an exception set if an exception occurred.
 | |
|    It returns NULL *without* an exception set if the key wasn't present.
 | |
| */
 | |
| PyObject *
 | |
| PyDict_GetItemWithError(PyObject *op, PyObject *key)
 | |
| {
 | |
|     Py_ssize_t ix; (void)ix;
 | |
|     Py_hash_t hash;
 | |
|     PyDictObject*mp = (PyDictObject *)op;
 | |
|     PyObject *value;
 | |
| 
 | |
|     if (!PyDict_Check(op)) {
 | |
|         PyErr_BadInternalCall();
 | |
|         return NULL;
 | |
|     }
 | |
|     hash = _PyObject_HashFast(key);
 | |
|     if (hash == -1) {
 | |
|         dict_unhashable_type(key);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value);
 | |
|     Py_XDECREF(value);
 | |
| #else
 | |
|     ix = _Py_dict_lookup(mp, key, hash, &value);
 | |
| #endif
 | |
|     assert(ix >= 0 || value == NULL);
 | |
|     return value;  // borrowed reference
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_GetItemWithError(PyObject *dp, PyObject *kv)
 | |
| {
 | |
|     assert(PyUnicode_CheckExact(kv));
 | |
|     Py_hash_t hash = Py_TYPE(kv)->tp_hash(kv);
 | |
|     if (hash == -1) {
 | |
|         return NULL;
 | |
|     }
 | |
|     return _PyDict_GetItem_KnownHash(dp, kv, hash);  // borrowed reference
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_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;
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObject *key, _PyStackRef *res)
 | |
| {
 | |
|     Py_ssize_t ix;
 | |
|     Py_hash_t hash;
 | |
| 
 | |
|     hash = _PyObject_HashFast(key);
 | |
|     if (hash == -1) {
 | |
|         *res = PyStackRef_NULL;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* namespace 1: globals */
 | |
|     ix = _Py_dict_lookup_threadsafe_stackref(globals, key, hash, res);
 | |
|     if (ix == DKIX_ERROR) {
 | |
|         return;
 | |
|     }
 | |
|     if (ix != DKIX_EMPTY && !PyStackRef_IsNull(*res)) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* namespace 2: builtins */
 | |
|     ix = _Py_dict_lookup_threadsafe_stackref(builtins, key, hash, res);
 | |
|     assert(ix >= 0 || PyStackRef_IsNull(*res));
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_LoadBuiltinsFromGlobals(PyObject *globals)
 | |
| {
 | |
|     if (!PyDict_Check(globals)) {
 | |
|         PyErr_BadInternalCall();
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     PyDictObject *mp = (PyDictObject *)globals;
 | |
|     PyObject *key = &_Py_ID(__builtins__);
 | |
|     Py_hash_t hash = unicode_get_hash(key);
 | |
| 
 | |
|     // Use the stackref variant to avoid reference count contention on the
 | |
|     // builtins module in the free threading build. It's important not to
 | |
|     // make any escaping calls between the lookup and the `PyStackRef_CLOSE()`
 | |
|     // because the `ref` is not visible to the GC.
 | |
|     _PyStackRef ref;
 | |
|     Py_ssize_t ix = _Py_dict_lookup_threadsafe_stackref(mp, key, hash, &ref);
 | |
|     if (ix == DKIX_ERROR) {
 | |
|         return NULL;
 | |
|     }
 | |
|     if (PyStackRef_IsNull(ref)) {
 | |
|         return Py_NewRef(PyEval_GetBuiltins());
 | |
|     }
 | |
|     PyObject *builtins = PyStackRef_AsPyObjectBorrow(ref);
 | |
|     if (PyModule_Check(builtins)) {
 | |
|         builtins = _PyModule_GetDict(builtins);
 | |
|         assert(builtins != NULL);
 | |
|     }
 | |
|     _Py_INCREF_BUILTINS(builtins);
 | |
|     PyStackRef_CLOSE(ref);
 | |
|     return builtins;
 | |
| }
 | |
| 
 | |
| /* 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) {
 | |
|         dict_unhashable_type(key);
 | |
|         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)
 | |
| {
 | |
|     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);
 | |
|     if (_PyDict_HasSplitTable(mp)) {
 | |
|         assert(old_value == mp->ma_values->values[ix]);
 | |
|         STORE_SPLIT_VALUE(mp, ix, NULL);
 | |
|         assert(ix < SHARED_KEYS_MAX_SIZE);
 | |
|         /* Update order */
 | |
|         delete_index_from_values(mp->ma_values, ix);
 | |
|         ASSERT_CONSISTENT(mp);
 | |
|     }
 | |
|     else {
 | |
|         FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0);
 | |
|         dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);
 | |
|         if (DK_IS_UNICODE(mp->ma_keys)) {
 | |
|             PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[ix];
 | |
|             old_key = ep->me_key;
 | |
|             STORE_KEY(ep, NULL);
 | |
|             STORE_VALUE(ep, NULL);
 | |
|         }
 | |
|         else {
 | |
|             PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix];
 | |
|             old_key = ep->me_key;
 | |
|             STORE_KEY(ep, NULL);
 | |
|             STORE_VALUE(ep, NULL);
 | |
|             STORE_HASH(ep, 0);
 | |
|         }
 | |
|         Py_DECREF(old_key);
 | |
|     }
 | |
|     Py_DECREF(old_value);
 | |
| 
 | |
|     ASSERT_CONSISTENT(mp);
 | |
| }
 | |
| 
 | |
| int
 | |
| PyDict_DelItem(PyObject *op, PyObject *key)
 | |
| {
 | |
|     assert(key);
 | |
|     Py_hash_t hash = _PyObject_HashFast(key);
 | |
|     if (hash == -1) {
 | |
|         dict_unhashable_type(key);
 | |
|         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();
 | |
|     _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
 | |
|     delitem_common(mp, hash, ix, old_value);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
 | |
| {
 | |
|     int res;
 | |
|     Py_BEGIN_CRITICAL_SECTION(op);
 | |
|     res = 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();
 | |
|         _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
 | |
|         delitem_common(mp, hash, ix, old_value);
 | |
|         return 1;
 | |
|     } else {
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| /* This function promises that the predicate -> deletion sequence is atomic
 | |
|  * (i.e. protected by the GIL or the per-dict mutex in free threaded builds),
 | |
|  * assuming the predicate itself doesn't release the GIL (or cause re-entrancy
 | |
|  * which would release the per-dict mutex)
 | |
|  */
 | |
| int
 | |
| _PyDict_DelItemIf(PyObject *op, PyObject *key,
 | |
|                   int (*predicate)(PyObject *value, void *arg),
 | |
|                   void *arg)
 | |
| {
 | |
|     assert(PyDict_Check(op));
 | |
|     int res;
 | |
|     Py_BEGIN_CRITICAL_SECTION(op);
 | |
|     res = delitemif_lock_held(op, key, predicate, arg);
 | |
|     Py_END_CRITICAL_SECTION();
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| 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();
 | |
|     _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);
 | |
|     STORE_USED(mp, 0);
 | |
|     if (oldvalues == NULL) {
 | |
|         set_keys(mp, Py_EMPTY_KEYS);
 | |
|         assert(oldkeys->dk_refcnt == 1);
 | |
|         dictkeys_decref(oldkeys, IS_DICT_SHARED(mp));
 | |
|     }
 | |
|     else {
 | |
|         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(oldkeys, false);
 | |
|         }
 | |
|     }
 | |
|     ASSERT_CONSISTENT(mp);
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyDict_Clear_LockHeld(PyObject *op) {
 | |
|     clear_lock_held(op);
 | |
| }
 | |
| 
 | |
| void
 | |
| PyDict_Clear(PyObject *op)
 | |
| {
 | |
|     Py_BEGIN_CRITICAL_SECTION(op);
 | |
|     clear_lock_held(op);
 | |
|     Py_END_CRITICAL_SECTION();
 | |
| }
 | |
| 
 | |
| /* Internal version of PyDict_Next that returns a hash value in addition
 | |
|  * to the key and value.
 | |
|  * Return 1 on success, return 0 when the reached the end of the dictionary
 | |
|  * (or if op is not a dictionary)
 | |
|  */
 | |
| int
 | |
| _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey,
 | |
|              PyObject **pvalue, Py_hash_t *phash)
 | |
| {
 | |
|     Py_ssize_t i;
 | |
|     PyDictObject *mp;
 | |
|     PyObject *key, *value;
 | |
|     Py_hash_t hash;
 | |
| 
 | |
|     if (!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();
 | |
|     _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
 | |
|     delitem_common(mp, hash, ix, Py_NewRef(old_value));
 | |
| 
 | |
|     ASSERT_CONSISTENT(mp);
 | |
|     if (result) {
 | |
|         *result = old_value;
 | |
|     }
 | |
|     else {
 | |
|         Py_DECREF(old_value);
 | |
|     }
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| static int
 | |
| pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
 | |
| {
 | |
|     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) {
 | |
|         dict_unhashable_type(key);
 | |
|         if (result) {
 | |
|             *result = NULL;
 | |
|         }
 | |
|         return -1;
 | |
|     }
 | |
|     return _PyDict_Pop_KnownHash(dict, key, hash, result);
 | |
| }
 | |
| 
 | |
| int
 | |
| PyDict_Pop(PyObject *op, PyObject *key, PyObject **result)
 | |
| {
 | |
|     int err;
 | |
|     Py_BEGIN_CRITICAL_SECTION(op);
 | |
|     err = pop_lock_held(op, key, result);
 | |
|     Py_END_CRITICAL_SECTION();
 | |
| 
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| int
 | |
| PyDict_PopString(PyObject *op, const char *key, PyObject **result)
 | |
| {
 | |
|     PyObject *key_obj = PyUnicode_FromString(key);
 | |
|     if (key_obj == NULL) {
 | |
|         if (result != NULL) {
 | |
|             *result = NULL;
 | |
|         }
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     int res = PyDict_Pop(op, key_obj, result);
 | |
|     Py_DECREF(key_obj);
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| static PyObject *
 | |
| dict_pop_default(PyObject *dict, PyObject *key, PyObject *default_value)
 | |
| {
 | |
|     PyObject *result;
 | |
|     if (PyDict_Pop(dict, key, &result) == 0) {
 | |
|         if (default_value != NULL) {
 | |
|             return Py_NewRef(default_value);
 | |
|         }
 | |
|         _PyErr_SetKeyError(key);
 | |
|         return NULL;
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value)
 | |
| {
 | |
|     return dict_pop_default(dict, key, default_value);
 | |
| }
 | |
| 
 | |
| static PyDictObject *
 | |
| dict_dict_fromkeys(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(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;
 | |
|     uint8_t new_size = Py_MAX(
 | |
|         estimate_log2_keysize(PySet_GET_SIZE(iterable)),
 | |
|         DK_LOG_SIZE(mp->ma_keys));
 | |
|     if (dictresize(mp, new_size, 0)) {
 | |
|         Py_DECREF(mp);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(iterable);
 | |
|     while (_PySet_NextEntryRef(iterable, &pos, &key, &hash)) {
 | |
|         if (insertdict(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();
 | |
|     _PyObject_ResurrectStart(self);
 | |
|     _PyDict_NotifyEvent(interp, PyDict_EVENT_DEALLOCATED, mp, NULL, NULL);
 | |
|     if (_PyObject_ResurrectEnd(self)) {
 | |
|         return;
 | |
|     }
 | |
|     PyDictValues *values = mp->ma_values;
 | |
|     PyDictKeysObject *keys = mp->ma_keys;
 | |
|     Py_ssize_t i, n;
 | |
| 
 | |
|     /* bpo-31095: UnTrack is needed before calling any callbacks */
 | |
|     PyObject_GC_UnTrack(mp);
 | |
|     if (values != NULL) {
 | |
|         if (values->embedded == 0) {
 | |
|             for (i = 0, n = values->capacity; i < n; i++) {
 | |
|                 Py_XDECREF(values->values[i]);
 | |
|             }
 | |
|             free_values(values, false);
 | |
|         }
 | |
|         dictkeys_decref(keys, false);
 | |
|     }
 | |
|     else if (keys != NULL) {
 | |
|         assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS);
 | |
|         dictkeys_decref(keys, false);
 | |
|     }
 | |
|     if (Py_IS_TYPE(mp, &PyDict_Type)) {
 | |
|         _Py_FREELIST_FREE(dicts, mp, Py_TYPE(mp)->tp_free);
 | |
|     }
 | |
|     else {
 | |
|         Py_TYPE(mp)->tp_free((PyObject *)mp);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| static PyObject *
 | |
| dict_repr_lock_held(PyObject *self)
 | |
| {
 | |
|     PyDictObject *mp = (PyDictObject *)self;
 | |
|     PyObject *key = NULL, *value = NULL;
 | |
|     ASSERT_DICT_LOCKED(mp);
 | |
| 
 | |
|     int res = Py_ReprEnter((PyObject *)mp);
 | |
|     if (res != 0) {
 | |
|         return (res > 0 ? PyUnicode_FromString("{...}") : NULL);
 | |
|     }
 | |
| 
 | |
|     if (mp->ma_used == 0) {
 | |
|         Py_ReprLeave((PyObject *)mp);
 | |
|         return PyUnicode_FromString("{}");
 | |
|     }
 | |
| 
 | |
|     // "{" + "1: 2" + ", 3: 4" * (len - 1) + "}"
 | |
|     Py_ssize_t prealloc = 1 + 4 + 6 * (mp->ma_used - 1) + 1;
 | |
|     PyUnicodeWriter *writer = PyUnicodeWriter_Create(prealloc);
 | |
|     if (writer == NULL) {
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     if (PyUnicodeWriter_WriteChar(writer, '{') < 0) {
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     /* Do repr() on each key+value pair, and insert ": " between them.
 | |
|        Note that repr may mutate the dict. */
 | |
|     Py_ssize_t i = 0;
 | |
|     int first = 1;
 | |
|     while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) {
 | |
|         // Prevent repr from deleting key or value during key format.
 | |
|         Py_INCREF(key);
 | |
|         Py_INCREF(value);
 | |
| 
 | |
|         if (!first) {
 | |
|             // Write ", "
 | |
|             if (PyUnicodeWriter_WriteChar(writer, ',') < 0) {
 | |
|                 goto error;
 | |
|             }
 | |
|             if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) {
 | |
|                 goto error;
 | |
|             }
 | |
|         }
 | |
|         first = 0;
 | |
| 
 | |
|         // Write repr(key)
 | |
|         if (PyUnicodeWriter_WriteRepr(writer, key) < 0) {
 | |
|             goto error;
 | |
|         }
 | |
| 
 | |
|         // Write ": "
 | |
|         if (PyUnicodeWriter_WriteChar(writer, ':') < 0) {
 | |
|             goto error;
 | |
|         }
 | |
|         if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) {
 | |
|             goto error;
 | |
|         }
 | |
| 
 | |
|         // Write repr(value)
 | |
|         if (PyUnicodeWriter_WriteRepr(writer, value) < 0) {
 | |
|             goto error;
 | |
|         }
 | |
| 
 | |
|         Py_CLEAR(key);
 | |
|         Py_CLEAR(value);
 | |
|     }
 | |
| 
 | |
|     if (PyUnicodeWriter_WriteChar(writer, '}') < 0) {
 | |
|         goto error;
 | |
|     }
 | |
| 
 | |
|     Py_ReprLeave((PyObject *)mp);
 | |
| 
 | |
|     return PyUnicodeWriter_Finish(writer);
 | |
| 
 | |
| error:
 | |
|     Py_ReprLeave((PyObject *)mp);
 | |
|     PyUnicodeWriter_Discard(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) {
 | |
|         dict_unhashable_type(key);
 | |
|         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, "object is not iterable");
 | |
|         if (fast == NULL) {
 | |
|             if (PyErr_ExceptionMatches(PyExc_TypeError)) {
 | |
|                 _PyErr_FormatNote(
 | |
|                     "Cannot convert dictionary update "
 | |
|                     "sequence element #%zd to a sequence",
 | |
|                     i);
 | |
|             }
 | |
|             goto Fail;
 | |
|         }
 | |
|         n = PySequence_Fast_GET_SIZE(fast);
 | |
|         if (n != 2) {
 | |
|             PyErr_Format(PyExc_ValueError,
 | |
|                          "dictionary update sequence element #%zd "
 | |
|                          "has length %zd; 2 is required",
 | |
|                          i, n);
 | |
|             goto Fail;
 | |
|         }
 | |
| 
 | |
|         /* Update/merge with this (key, value) pair. */
 | |
|         key = PySequence_Fast_GET_ITEM(fast, 0);
 | |
|         value = PySequence_Fast_GET_ITEM(fast, 1);
 | |
|         Py_INCREF(key);
 | |
|         Py_INCREF(value);
 | |
|         if (override) {
 | |
|             if (setitem_lock_held((PyDictObject *)d, key, value) < 0) {
 | |
|                 Py_DECREF(key);
 | |
|                 Py_DECREF(value);
 | |
|                 goto Fail;
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             if (dict_setdefault_ref_lock_held(d, key, value, NULL, 0) < 0) {
 | |
|                 Py_DECREF(key);
 | |
|                 Py_DECREF(value);
 | |
|                 goto Fail;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Py_DECREF(key);
 | |
|         Py_DECREF(value);
 | |
|         Py_DECREF(fast);
 | |
|         Py_DECREF(item);
 | |
|     }
 | |
| 
 | |
|     i = 0;
 | |
|     ASSERT_CONSISTENT(d);
 | |
|     goto Return;
 | |
| Fail:
 | |
|     Py_XDECREF(item);
 | |
|     Py_XDECREF(fast);
 | |
|     i = -1;
 | |
| Return:
 | |
|     Py_DECREF(it);
 | |
|     return Py_SAFE_DOWNCAST(i, Py_ssize_t, int);
 | |
| }
 | |
| 
 | |
| 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)
 | |
|         ) {
 | |
|             _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(mp->ma_keys, IS_DICT_SHARED(mp));
 | |
|             set_keys(mp, keys);
 | |
|             STORE_USED(mp, other->ma_used);
 | |
|             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(mp, estimate_log2_keysize(mp->ma_used + other->ma_used),
 | |
|                         unicode)) {
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Py_ssize_t orig_size = other->ma_used;
 | |
|     Py_ssize_t pos = 0;
 | |
|     Py_hash_t hash;
 | |
|     PyObject *key, *value;
 | |
| 
 | |
|     while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) {
 | |
|         int err = 0;
 | |
|         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_used) {
 | |
|             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_watcher_tag = 0;
 | |
|         dictkeys_incref(mp->ma_keys);
 | |
|         _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(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);
 | |
|         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___impl(PyDictObject *self, PyObject *key)
 | |
| /*[clinic end generated code: output=1b314e6da7687dae input=fe1cb42ad831e820]*/
 | |
| {
 | |
|     int contains = PyDict_Contains((PyObject *)self, key);
 | |
|     if (contains < 0) {
 | |
|         return NULL;
 | |
|     }
 | |
|     if (contains) {
 | |
|         Py_RETURN_TRUE;
 | |
|     }
 | |
|     Py_RETURN_FALSE;
 | |
| }
 | |
| 
 | |
| /*[clinic input]
 | |
| dict.get
 | |
| 
 | |
|     key: object
 | |
|     default: object = None
 | |
|     /
 | |
| 
 | |
| Return the value for key if key is in the dictionary, else default.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
 | |
| /*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/
 | |
| {
 | |
|     PyObject *val = NULL;
 | |
|     Py_hash_t hash;
 | |
|     Py_ssize_t ix;
 | |
| 
 | |
|     hash = _PyObject_HashFast(key);
 | |
|     if (hash == -1) {
 | |
|         dict_unhashable_type(key);
 | |
|         return NULL;
 | |
|     }
 | |
|     ix = _Py_dict_lookup_threadsafe(self, key, hash, &val);
 | |
|     if (ix == DKIX_ERROR)
 | |
|         return NULL;
 | |
|     if (ix == DKIX_EMPTY || val == NULL) {
 | |
|         val = Py_NewRef(default_value);
 | |
|     }
 | |
|     return val;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value,
 | |
|                     PyObject **result, int incref_result)
 | |
| {
 | |
|     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) {
 | |
|         dict_unhashable_type(key);
 | |
|         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(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(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;
 | |
|             }
 | |
|             return -1;
 | |
|         }
 | |
| 
 | |
|         STORE_USED(mp, mp->ma_used + 1);
 | |
|         assert(mp->ma_keys->dk_usable >= 0);
 | |
|         ASSERT_CONSISTENT(mp);
 | |
|         if (result) {
 | |
|             *result = incref_result ? Py_NewRef(value) : value;
 | |
|         }
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     assert(value != NULL);
 | |
|     ASSERT_CONSISTENT(mp);
 | |
|     if (result) {
 | |
|         *result = incref_result ? Py_NewRef(value) : value;
 | |
|     }
 | |
|     return 1;
 | |
| 
 | |
| 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]
 | |
| @permit_long_summary
 | |
| dict.pop
 | |
| 
 | |
|     key: object
 | |
|     default: object = NULL
 | |
|     /
 | |
| 
 | |
| D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 | |
| 
 | |
| If the key is not found, return the default if given; otherwise,
 | |
| raise a KeyError.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
 | |
| /*[clinic end generated code: output=3abb47b89f24c21c input=d409c7eb2de67e38]*/
 | |
| {
 | |
|     return dict_pop_default((PyObject*)self, key, default_value);
 | |
| }
 | |
| 
 | |
| /*[clinic input]
 | |
| @critical_section
 | |
| dict.popitem
 | |
| 
 | |
| Remove and return a (key, value) pair as a 2-tuple.
 | |
| 
 | |
| Pairs are returned in LIFO (last-in, first-out) order.
 | |
| Raises KeyError if the dict is empty.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_popitem_impl(PyDictObject *self)
 | |
| /*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/
 | |
| {
 | |
|     Py_ssize_t i, j;
 | |
|     PyObject *res;
 | |
|     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(self, DK_LOG_SIZE(self->ma_keys), 1) < 0) {
 | |
|             Py_DECREF(res);
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
|     FT_ATOMIC_STORE_UINT32_RELAXED(self->ma_keys->dk_version, 0);
 | |
| 
 | |
|     /* Pop last item */
 | |
|     PyObject *key, *value;
 | |
|     Py_hash_t hash;
 | |
|     if (DK_IS_UNICODE(self->ma_keys)) {
 | |
|         PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(self->ma_keys);
 | |
|         i = self->ma_keys->dk_nentries - 1;
 | |
|         while (i >= 0 && ep0[i].me_value == NULL) {
 | |
|             i--;
 | |
|         }
 | |
|         assert(i >= 0);
 | |
| 
 | |
|         key = ep0[i].me_key;
 | |
|         _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
 | |
|         hash = unicode_get_hash(key);
 | |
|         value = ep0[i].me_value;
 | |
|         STORE_KEY(&ep0[i], NULL);
 | |
|         STORE_VALUE(&ep0[i], NULL);
 | |
|     }
 | |
|     else {
 | |
|         PyDictKeyEntry *ep0 = DK_ENTRIES(self->ma_keys);
 | |
|         i = self->ma_keys->dk_nentries - 1;
 | |
|         while (i >= 0 && ep0[i].me_value == NULL) {
 | |
|             i--;
 | |
|         }
 | |
|         assert(i >= 0);
 | |
| 
 | |
|         key = ep0[i].me_key;
 | |
|         _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
 | |
|         hash = ep0[i].me_hash;
 | |
|         value = ep0[i].me_value;
 | |
|         STORE_KEY(&ep0[i], NULL);
 | |
|         STORE_HASH(&ep0[i], -1);
 | |
|         STORE_VALUE(&ep0[i], NULL);
 | |
|     }
 | |
| 
 | |
|     j = lookdict_index(self->ma_keys, hash, i);
 | |
|     assert(j >= 0);
 | |
|     assert(dictkeys_get_index(self->ma_keys, j) == i);
 | |
|     dictkeys_set_index(self->ma_keys, j, DKIX_DUMMY);
 | |
| 
 | |
|     PyTuple_SET_ITEM(res, 0, key);
 | |
|     PyTuple_SET_ITEM(res, 1, value);
 | |
|     /* We can't dk_usable++ since there is DKIX_DUMMY in indices */
 | |
|     STORE_KEYS_NENTRIES(self->ma_keys, i);
 | |
|     STORE_USED(self, self->ma_used - 1);
 | |
|     ASSERT_CONSISTENT(self);
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dict_traverse(PyObject *op, visitproc visit, void *arg)
 | |
| {
 | |
|     PyDictObject *mp = (PyDictObject *)op;
 | |
|     PyDictKeysObject *keys = mp->ma_keys;
 | |
|     Py_ssize_t i, n = keys->dk_nentries;
 | |
| 
 | |
|     if (DK_IS_UNICODE(keys)) {
 | |
|         if (_PyDict_HasSplitTable(mp)) {
 | |
|             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) {
 | |
|         dict_unhashable_type(key);
 | |
|         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_watcher_tag = 0;
 | |
|     // We don't inc ref empty keys because they're immortal
 | |
|     assert((Py_EMPTY_KEYS)->dk_refcnt == _Py_DICT_IMMORTAL_INITIAL_REFCNT);
 | |
|     d->ma_keys = Py_EMPTY_KEYS;
 | |
|     d->ma_values = NULL;
 | |
|     ASSERT_CONSISTENT(d);
 | |
|     if (!_PyObject_GC_IS_TRACKED(d)) {
 | |
|         _PyObject_GC_TRACK(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,
 | |
|     .tp_version_tag = _Py_TYPE_VERSION_DICT,
 | |
| };
 | |
| 
 | |
| /* For backward compatibility with old dictionary interface */
 | |
| 
 | |
| PyObject *
 | |
| PyDict_GetItemString(PyObject *v, const char *key)
 | |
| {
 | |
|     PyObject *kv, *rv;
 | |
|     kv = PyUnicode_FromString(key);
 | |
|     if (kv == NULL) {
 | |
|         PyErr_FormatUnraisable(
 | |
|             "Exception ignored in PyDict_GetItemString(); consider using "
 | |
|             "PyDict_GetItemStringRef()");
 | |
|         return NULL;
 | |
|     }
 | |
|     rv = dict_getitem(v, kv,
 | |
|             "Exception ignored in PyDict_GetItemString(); consider using "
 | |
|             "PyDict_GetItemStringRef()");
 | |
|     Py_DECREF(kv);
 | |
|     return rv;  // borrowed reference
 | |
| }
 | |
| 
 | |
| int
 | |
| PyDict_GetItemStringRef(PyObject *v, const char *key, PyObject **result)
 | |
| {
 | |
|     PyObject *key_obj = PyUnicode_FromString(key);
 | |
|     if (key_obj == NULL) {
 | |
|         *result = NULL;
 | |
|         return -1;
 | |
|     }
 | |
|     int res = PyDict_GetItemRef(v, key_obj, result);
 | |
|     Py_DECREF(key_obj);
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_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 unsuccessful
 | |
| // nothing is incref'd and returns -1.
 | |
| static int
 | |
| acquire_key_value(PyObject **key_loc, PyObject *value, PyObject **value_loc,
 | |
|                   PyObject **out_key, PyObject **out_value)
 | |
| {
 | |
|     if (out_key) {
 | |
|         *out_key = _Py_TryXGetRef(key_loc);
 | |
|         if (*out_key == NULL) {
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (out_value) {
 | |
|         if (!_Py_TryIncrefCompare(value_loc, value)) {
 | |
|             if (out_key) {
 | |
|                 Py_DECREF(*out_key);
 | |
|             }
 | |
|             return -1;
 | |
|         }
 | |
|         *out_value = value;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self,
 | |
|                              PyObject **out_key, PyObject **out_value)
 | |
| {
 | |
|     int res;
 | |
|     dictiterobject *di = (dictiterobject *)self;
 | |
|     Py_ssize_t i;
 | |
|     PyDictKeysObject *k;
 | |
| 
 | |
|     assert (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_acquire(&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:
 | |
|             _PyTuple_Recycle(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:
 | |
|             _PyTuple_Recycle(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, 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*/
 | |
|     dictviews_sub,                      /*nb_subtract*/
 | |
|     0,                                  /*nb_multiply*/
 | |
|     0,                                  /*nb_remainder*/
 | |
|     0,                                  /*nb_divmod*/
 | |
|     0,                                  /*nb_power*/
 | |
|     0,                                  /*nb_negative*/
 | |
|     0,                                  /*nb_positive*/
 | |
|     0,                                  /*nb_absolute*/
 | |
|     0,                                  /*nb_bool*/
 | |
|     0,                                  /*nb_invert*/
 | |
|     0,                                  /*nb_lshift*/
 | |
|     0,                                  /*nb_rshift*/
 | |
|     _PyDictView_Intersect,              /*nb_and*/
 | |
|     dictviews_xor,                      /*nb_xor*/
 | |
|     dictviews_or,                       /*nb_or*/
 | |
| };
 | |
| 
 | |
| static PyObject*
 | |
| dictviews_isdisjoint(PyObject *self, PyObject *other)
 | |
| {
 | |
|     PyObject *it;
 | |
|     PyObject *item = NULL;
 | |
| 
 | |
|     if (self == other) {
 | |
|         if (dictview_len(self) == 0)
 | |
|             Py_RETURN_TRUE;
 | |
|         else
 | |
|             Py_RETURN_FALSE;
 | |
|     }
 | |
| 
 | |
|     /* Iterate over the shorter object (only if other is a set,
 | |
|      * because PySequence_Contains may be expensive otherwise): */
 | |
|     if (PyAnySet_Check(other) || PyDictViewSet_Check(other)) {
 | |
|         Py_ssize_t len_self = dictview_len(self);
 | |
|         Py_ssize_t len_other = PyObject_Size(other);
 | |
|         if (len_other == -1)
 | |
|             return NULL;
 | |
| 
 | |
|         if ((len_other > len_self)) {
 | |
|             PyObject *tmp = other;
 | |
|             other = self;
 | |
|             self = tmp;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     it = PyObject_GetIter(other);
 | |
|     if (it == NULL)
 | |
|         return NULL;
 | |
| 
 | |
|     while ((item = PyIter_Next(it)) != NULL) {
 | |
|         int contains = PySequence_Contains(self, item);
 | |
|         Py_DECREF(item);
 | |
|         if (contains == -1) {
 | |
|             Py_DECREF(it);
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         if (contains) {
 | |
|             Py_DECREF(it);
 | |
|             Py_RETURN_FALSE;
 | |
|         }
 | |
|     }
 | |
|     Py_DECREF(it);
 | |
|     if (PyErr_Occurred())
 | |
|         return NULL; /* PyIter_Next raised an exception. */
 | |
|     Py_RETURN_TRUE;
 | |
| }
 | |
| 
 | |
| PyDoc_STRVAR(isdisjoint_doc,
 | |
| "Return True if the view and the given iterable have a null intersection.");
 | |
| 
 | |
| static PyObject* dictkeys_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored));
 | |
| 
 | |
| PyDoc_STRVAR(reversed_keys_doc,
 | |
| "Return a reverse iterator over the dict keys.");
 | |
| 
 | |
| static PyMethodDef dictkeys_methods[] = {
 | |
|     {"isdisjoint",      dictviews_isdisjoint,           METH_O,
 | |
|      isdisjoint_doc},
 | |
|     {"__reversed__",    dictkeys_reversed,              METH_NOARGS,
 | |
|      reversed_keys_doc},
 | |
|     {NULL,              NULL}           /* sentinel */
 | |
| };
 | |
| 
 | |
| PyTypeObject PyDictKeys_Type = {
 | |
|     PyVarObject_HEAD_INIT(&PyType_Type, 0)
 | |
|     "dict_keys",                                /* tp_name */
 | |
|     sizeof(_PyDictViewObject),                  /* tp_basicsize */
 | |
|     0,                                          /* tp_itemsize */
 | |
|     /* methods */
 | |
|     dictview_dealloc,                           /* tp_dealloc */
 | |
|     0,                                          /* tp_vectorcall_offset */
 | |
|     0,                                          /* tp_getattr */
 | |
|     0,                                          /* tp_setattr */
 | |
|     0,                                          /* tp_as_async */
 | |
|     dictview_repr,                              /* tp_repr */
 | |
|     &dictviews_as_number,                       /* tp_as_number */
 | |
|     &dictkeys_as_sequence,                      /* tp_as_sequence */
 | |
|     0,                                          /* tp_as_mapping */
 | |
|     0,                                          /* tp_hash */
 | |
|     0,                                          /* tp_call */
 | |
|     0,                                          /* tp_str */
 | |
|     PyObject_GenericGetAttr,                    /* tp_getattro */
 | |
|     0,                                          /* tp_setattro */
 | |
|     0,                                          /* tp_as_buffer */
 | |
|     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
 | |
|     0,                                          /* tp_doc */
 | |
|     dictview_traverse,                          /* tp_traverse */
 | |
|     0,                                          /* tp_clear */
 | |
|     dictview_richcompare,                       /* tp_richcompare */
 | |
|     0,                                          /* tp_weaklistoffset */
 | |
|     dictkeys_iter,                              /* tp_iter */
 | |
|     0,                                          /* tp_iternext */
 | |
|     dictkeys_methods,                           /* tp_methods */
 | |
|     .tp_getset = dictview_getset,
 | |
| };
 | |
| 
 | |
| /*[clinic input]
 | |
| dict.keys
 | |
| 
 | |
| Return a set-like object providing a view on the dict's keys.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_keys_impl(PyDictObject *self)
 | |
| /*[clinic end generated code: output=aac2830c62990358 input=42f48a7a771212a7]*/
 | |
| {
 | |
|     return _PyDictView_New((PyObject *)self, &PyDictKeys_Type);
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| dictkeys_reversed(PyObject *self, PyObject *Py_UNUSED(ignored))
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     if (dv->dv_dict == NULL) {
 | |
|         Py_RETURN_NONE;
 | |
|     }
 | |
|     return dictiter_new(dv->dv_dict, &PyDictRevIterKey_Type);
 | |
| }
 | |
| 
 | |
| /*** dict_items ***/
 | |
| 
 | |
| static PyObject *
 | |
| dictitems_iter(PyObject *self)
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     if (dv->dv_dict == NULL) {
 | |
|         Py_RETURN_NONE;
 | |
|     }
 | |
|     return dictiter_new(dv->dv_dict, &PyDictIterItem_Type);
 | |
| }
 | |
| 
 | |
| static int
 | |
| dictitems_contains(PyObject *self, PyObject *obj)
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     int result;
 | |
|     PyObject *key, *value, *found;
 | |
|     if (dv->dv_dict == NULL)
 | |
|         return 0;
 | |
|     if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2)
 | |
|         return 0;
 | |
|     key = PyTuple_GET_ITEM(obj, 0);
 | |
|     value = PyTuple_GET_ITEM(obj, 1);
 | |
|     result = PyDict_GetItemRef((PyObject *)dv->dv_dict, key, &found);
 | |
|     if (result == 1) {
 | |
|         result = PyObject_RichCompareBool(found, value, Py_EQ);
 | |
|         Py_DECREF(found);
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| static PySequenceMethods dictitems_as_sequence = {
 | |
|     dictview_len,                       /* sq_length */
 | |
|     0,                                  /* sq_concat */
 | |
|     0,                                  /* sq_repeat */
 | |
|     0,                                  /* sq_item */
 | |
|     0,                                  /* sq_slice */
 | |
|     0,                                  /* sq_ass_item */
 | |
|     0,                                  /* sq_ass_slice */
 | |
|     dictitems_contains,                 /* sq_contains */
 | |
| };
 | |
| 
 | |
| static PyObject* dictitems_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored));
 | |
| 
 | |
| PyDoc_STRVAR(reversed_items_doc,
 | |
| "Return a reverse iterator over the dict items.");
 | |
| 
 | |
| static PyMethodDef dictitems_methods[] = {
 | |
|     {"isdisjoint",      dictviews_isdisjoint,           METH_O,
 | |
|      isdisjoint_doc},
 | |
|     {"__reversed__",    dictitems_reversed,             METH_NOARGS,
 | |
|      reversed_items_doc},
 | |
|     {NULL,              NULL}           /* sentinel */
 | |
| };
 | |
| 
 | |
| PyTypeObject PyDictItems_Type = {
 | |
|     PyVarObject_HEAD_INIT(&PyType_Type, 0)
 | |
|     "dict_items",                               /* tp_name */
 | |
|     sizeof(_PyDictViewObject),                  /* tp_basicsize */
 | |
|     0,                                          /* tp_itemsize */
 | |
|     /* methods */
 | |
|     dictview_dealloc,                           /* tp_dealloc */
 | |
|     0,                                          /* tp_vectorcall_offset */
 | |
|     0,                                          /* tp_getattr */
 | |
|     0,                                          /* tp_setattr */
 | |
|     0,                                          /* tp_as_async */
 | |
|     dictview_repr,                              /* tp_repr */
 | |
|     &dictviews_as_number,                       /* tp_as_number */
 | |
|     &dictitems_as_sequence,                     /* tp_as_sequence */
 | |
|     0,                                          /* tp_as_mapping */
 | |
|     0,                                          /* tp_hash */
 | |
|     0,                                          /* tp_call */
 | |
|     0,                                          /* tp_str */
 | |
|     PyObject_GenericGetAttr,                    /* tp_getattro */
 | |
|     0,                                          /* tp_setattro */
 | |
|     0,                                          /* tp_as_buffer */
 | |
|     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
 | |
|     0,                                          /* tp_doc */
 | |
|     dictview_traverse,                          /* tp_traverse */
 | |
|     0,                                          /* tp_clear */
 | |
|     dictview_richcompare,                       /* tp_richcompare */
 | |
|     0,                                          /* tp_weaklistoffset */
 | |
|     dictitems_iter,                             /* tp_iter */
 | |
|     0,                                          /* tp_iternext */
 | |
|     dictitems_methods,                          /* tp_methods */
 | |
|     .tp_getset = dictview_getset,
 | |
| };
 | |
| 
 | |
| /*[clinic input]
 | |
| dict.items
 | |
| 
 | |
| Return a set-like object providing a view on the dict's items.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_items_impl(PyDictObject *self)
 | |
| /*[clinic end generated code: output=88c7db7150c7909a input=87c822872eb71f5a]*/
 | |
| {
 | |
|     return _PyDictView_New((PyObject *)self, &PyDictItems_Type);
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| dictitems_reversed(PyObject *self, PyObject *Py_UNUSED(ignored))
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     if (dv->dv_dict == NULL) {
 | |
|         Py_RETURN_NONE;
 | |
|     }
 | |
|     return dictiter_new(dv->dv_dict, &PyDictRevIterItem_Type);
 | |
| }
 | |
| 
 | |
| /*** dict_values ***/
 | |
| 
 | |
| static PyObject *
 | |
| dictvalues_iter(PyObject *self)
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     if (dv->dv_dict == NULL) {
 | |
|         Py_RETURN_NONE;
 | |
|     }
 | |
|     return dictiter_new(dv->dv_dict, &PyDictIterValue_Type);
 | |
| }
 | |
| 
 | |
| static PySequenceMethods dictvalues_as_sequence = {
 | |
|     dictview_len,                       /* sq_length */
 | |
|     0,                                  /* sq_concat */
 | |
|     0,                                  /* sq_repeat */
 | |
|     0,                                  /* sq_item */
 | |
|     0,                                  /* sq_slice */
 | |
|     0,                                  /* sq_ass_item */
 | |
|     0,                                  /* sq_ass_slice */
 | |
|     0,                                  /* sq_contains */
 | |
| };
 | |
| 
 | |
| static PyObject* dictvalues_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored));
 | |
| 
 | |
| PyDoc_STRVAR(reversed_values_doc,
 | |
| "Return a reverse iterator over the dict values.");
 | |
| 
 | |
| static PyMethodDef dictvalues_methods[] = {
 | |
|     {"__reversed__",    dictvalues_reversed,            METH_NOARGS,
 | |
|      reversed_values_doc},
 | |
|     {NULL,              NULL}           /* sentinel */
 | |
| };
 | |
| 
 | |
| PyTypeObject PyDictValues_Type = {
 | |
|     PyVarObject_HEAD_INIT(&PyType_Type, 0)
 | |
|     "dict_values",                              /* tp_name */
 | |
|     sizeof(_PyDictViewObject),                  /* tp_basicsize */
 | |
|     0,                                          /* tp_itemsize */
 | |
|     /* methods */
 | |
|     dictview_dealloc,                           /* tp_dealloc */
 | |
|     0,                                          /* tp_vectorcall_offset */
 | |
|     0,                                          /* tp_getattr */
 | |
|     0,                                          /* tp_setattr */
 | |
|     0,                                          /* tp_as_async */
 | |
|     dictview_repr,                              /* tp_repr */
 | |
|     0,                                          /* tp_as_number */
 | |
|     &dictvalues_as_sequence,                    /* tp_as_sequence */
 | |
|     0,                                          /* tp_as_mapping */
 | |
|     0,                                          /* tp_hash */
 | |
|     0,                                          /* tp_call */
 | |
|     0,                                          /* tp_str */
 | |
|     PyObject_GenericGetAttr,                    /* tp_getattro */
 | |
|     0,                                          /* tp_setattro */
 | |
|     0,                                          /* tp_as_buffer */
 | |
|     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
 | |
|     0,                                          /* tp_doc */
 | |
|     dictview_traverse,                          /* tp_traverse */
 | |
|     0,                                          /* tp_clear */
 | |
|     0,                                          /* tp_richcompare */
 | |
|     0,                                          /* tp_weaklistoffset */
 | |
|     dictvalues_iter,                            /* tp_iter */
 | |
|     0,                                          /* tp_iternext */
 | |
|     dictvalues_methods,                         /* tp_methods */
 | |
|     .tp_getset = dictview_getset,
 | |
| };
 | |
| 
 | |
| /*[clinic input]
 | |
| dict.values
 | |
| 
 | |
| Return an object providing a view on the dict's values.
 | |
| [clinic start generated code]*/
 | |
| 
 | |
| static PyObject *
 | |
| dict_values_impl(PyDictObject *self)
 | |
| /*[clinic end generated code: output=ce9f2e9e8a959dd4 input=b46944f85493b230]*/
 | |
| {
 | |
|     return _PyDictView_New((PyObject *)self, &PyDictValues_Type);
 | |
| }
 | |
| 
 | |
| static PyObject *
 | |
| dictvalues_reversed(PyObject *self, PyObject *Py_UNUSED(ignored))
 | |
| {
 | |
|     _PyDictViewObject *dv = (_PyDictViewObject *)self;
 | |
|     if (dv->dv_dict == NULL) {
 | |
|         Py_RETURN_NONE;
 | |
|     }
 | |
|     return dictiter_new(dv->dv_dict, &PyDictRevIterValue_Type);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Returns NULL if cannot allocate a new PyDictKeysObject,
 | |
|    but does not set an error */
 | |
| PyDictKeysObject *
 | |
| _PyDict_NewKeysForClass(PyHeapTypeObject *cls)
 | |
| {
 | |
|     PyDictKeysObject *keys = new_keys_object(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;
 | |
|     }
 | |
|     if (cls->ht_type.tp_dict) {
 | |
|         PyObject *attrs = PyDict_GetItem(cls->ht_type.tp_dict, &_Py_ID(__static_attributes__));
 | |
|         if (attrs != NULL && PyTuple_Check(attrs)) {
 | |
|             for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(attrs); i++) {
 | |
|                 PyObject *key = PyTuple_GET_ITEM(attrs, i);
 | |
|                 Py_hash_t hash;
 | |
|                 if (PyUnicode_CheckExact(key) && (hash = unicode_get_hash(key)) != -1) {
 | |
|                     if (insert_split_key(keys, key, hash) == DKIX_EMPTY) {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return keys;
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp)
 | |
| {
 | |
|     assert(tp->tp_flags & Py_TPFLAGS_HEAPTYPE);
 | |
|     assert(tp->tp_flags & Py_TPFLAGS_INLINE_VALUES);
 | |
|     assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
 | |
|     PyDictKeysObject *keys = CACHED_KEYS(tp);
 | |
|     assert(keys != NULL);
 | |
|     OBJECT_STAT_INC(inline_values);
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     Py_ssize_t usable = _Py_atomic_load_ssize_relaxed(&keys->dk_usable);
 | |
|     if (usable > 1) {
 | |
|         LOCK_KEYS(keys);
 | |
|         if (keys->dk_usable > 1) {
 | |
|             _Py_atomic_store_ssize(&keys->dk_usable, keys->dk_usable - 1);
 | |
|         }
 | |
|         UNLOCK_KEYS(keys);
 | |
|     }
 | |
| #else
 | |
|     if (keys->dk_usable > 1) {
 | |
|         keys->dk_usable--;
 | |
|     }
 | |
| #endif
 | |
|     size_t size = shared_keys_usable_size(keys);
 | |
|     PyDictValues *values = _PyObject_InlineValues(obj);
 | |
|     assert(size < 256);
 | |
|     values->capacity = (uint8_t)size;
 | |
|     values->size = 0;
 | |
|     values->embedded = 1;
 | |
|     values->valid = 1;
 | |
|     for (size_t i = 0; i < size; i++) {
 | |
|         values->values[i] = NULL;
 | |
|     }
 | |
|     _PyObject_ManagedDictPointer(obj)->dict = NULL;
 | |
| }
 | |
| 
 | |
| static PyDictObject *
 | |
| make_dict_from_instance_attributes(PyDictKeysObject *keys, PyDictValues *values)
 | |
| {
 | |
|     dictkeys_incref(keys);
 | |
|     Py_ssize_t used = 0;
 | |
|     size_t size = shared_keys_usable_size(keys);
 | |
|     for (size_t i = 0; i < size; i++) {
 | |
|         PyObject *val = values->values[i];
 | |
|         if (val != NULL) {
 | |
|             used += 1;
 | |
|         }
 | |
|     }
 | |
|     PyDictObject *res = (PyDictObject *)new_dict(keys, values, used, 0);
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| PyDictObject *
 | |
| _PyObject_MaterializeManagedDict_LockHeld(PyObject *obj)
 | |
| {
 | |
|     ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
 | |
| 
 | |
|     OBJECT_STAT_INC(dict_materialized_on_request);
 | |
| 
 | |
|     PyDictValues *values = _PyObject_InlineValues(obj);
 | |
|     PyDictObject *dict;
 | |
|     if (values->valid) {
 | |
|         PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
 | |
|         dict = make_dict_from_instance_attributes(keys, values);
 | |
|     }
 | |
|     else {
 | |
|         dict = (PyDictObject *)PyDict_New();
 | |
|     }
 | |
|     FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                                 dict);
 | |
|     return dict;
 | |
| }
 | |
| 
 | |
| PyDictObject *
 | |
| _PyObject_MaterializeManagedDict(PyObject *obj)
 | |
| {
 | |
|     PyDictObject *dict = _PyObject_GetManagedDict(obj);
 | |
|     if (dict != NULL) {
 | |
|         return dict;
 | |
|     }
 | |
| 
 | |
|     Py_BEGIN_CRITICAL_SECTION(obj);
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     dict = _PyObject_GetManagedDict(obj);
 | |
|     if (dict != NULL) {
 | |
|         // We raced with another thread creating the dict
 | |
|         goto exit;
 | |
|     }
 | |
| #endif
 | |
|     dict = _PyObject_MaterializeManagedDict_LockHeld(obj);
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| exit:
 | |
| #endif
 | |
|     Py_END_CRITICAL_SECTION();
 | |
|     return dict;
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value)
 | |
| {
 | |
|     if (value == NULL) {
 | |
|         Py_hash_t hash = _PyObject_HashFast(name);
 | |
|         if (hash == -1) {
 | |
|             dict_unhashable_type(name);
 | |
|             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(keys, values);
 | |
|             if (dict == NULL ||
 | |
|                 _PyDict_SetItem_LockHeld(dict, name, value) < 0) {
 | |
|                 Py_XDECREF(dict);
 | |
|                 return -1;
 | |
|             }
 | |
| 
 | |
|             FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                                         (PyDictObject *)dict);
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(dict);
 | |
| 
 | |
|         res = _PyDict_SetItem_LockHeld(dict, name, value);
 | |
|         return res;
 | |
|     }
 | |
| 
 | |
|     PyObject *old_value = values->values[ix];
 | |
|     if (old_value == NULL && value == NULL) {
 | |
|         PyErr_Format(PyExc_AttributeError,
 | |
|                         "'%.100s' object has no attribute '%U'",
 | |
|                         Py_TYPE(obj)->tp_name, name);
 | |
|         (void)_PyObject_SetAttributeErrorContext(obj, name);
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     if (dict) {
 | |
|         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_StringLookupSplit(keys, name);
 | |
|     if (ix == DKIX_EMPTY) {
 | |
|         *attr = NULL;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     PyObject *value = _Py_atomic_load_ptr_acquire(&values->values[ix]);
 | |
|     if (value == NULL) {
 | |
|         if (FT_ATOMIC_LOAD_UINT8(values->valid)) {
 | |
|             *attr = NULL;
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     else if (_Py_TryIncrefCompare(&values->values[ix], value)) {
 | |
|         *attr = value;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     PyDictObject *dict = _PyObject_GetManagedDict(obj);
 | |
|     if (dict == NULL) {
 | |
|         // No dict, lock the object to prevent one from being
 | |
|         // materialized...
 | |
|         bool success = false;
 | |
|         Py_BEGIN_CRITICAL_SECTION(obj);
 | |
| 
 | |
|         dict = _PyObject_GetManagedDict(obj);
 | |
|         if (dict == NULL) {
 | |
|             // Still no dict, we can read from the values
 | |
|             assert(values->valid);
 | |
|             value = values->values[ix];
 | |
|             *attr = _Py_XNewRefWithLock(value);
 | |
|             success = true;
 | |
|         }
 | |
| 
 | |
|         Py_END_CRITICAL_SECTION();
 | |
| 
 | |
|         if (success) {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // We have a dictionary, we'll need to lock it to prevent
 | |
|     // the values from being resized.
 | |
|     assert(dict != NULL);
 | |
| 
 | |
|     bool success;
 | |
|     Py_BEGIN_CRITICAL_SECTION(dict);
 | |
| 
 | |
|     if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) {
 | |
|         value = _Py_atomic_load_ptr_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
 | |
| clear_inline_values(PyDictValues *values)
 | |
| {
 | |
|     if (values->valid) {
 | |
|         FT_ATOMIC_STORE_UINT8(values->valid, 0);
 | |
|         for (Py_ssize_t i = 0; i < values->capacity; i++) {
 | |
|             Py_CLEAR(values->values[i]);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
 | |
| {
 | |
|     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
 | |
| 
 | |
|     PyDictValues *values = _PyObject_InlineValues(obj);
 | |
| 
 | |
|     Py_XINCREF(new_dict);
 | |
|     FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict);
 | |
| 
 | |
|     clear_inline_values(values);
 | |
| }
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| 
 | |
| // Trys and sets the dictionary for an object in the easy case when our current
 | |
| // dictionary is either completely not materialized or is a dictionary which
 | |
| // does not point at the inline values.
 | |
| static bool
 | |
| try_set_dict_inline_only_or_other_dict(PyObject *obj, PyObject *new_dict, PyDictObject **cur_dict)
 | |
| {
 | |
|     bool replaced = false;
 | |
|     Py_BEGIN_CRITICAL_SECTION(obj);
 | |
| 
 | |
|     PyDictObject *dict = *cur_dict = _PyObject_GetManagedDict(obj);
 | |
|     if (dict == NULL) {
 | |
|         // We only have inline values, we can just completely replace them.
 | |
|         set_dict_inline_values(obj, (PyDictObject *)new_dict);
 | |
|         replaced = true;
 | |
|         goto exit_lock;
 | |
|     }
 | |
| 
 | |
|     if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) != _PyObject_InlineValues(obj)) {
 | |
|         // We have a materialized dict which doesn't point at the inline values,
 | |
|         // We get to simply swap dictionaries and free the old dictionary.
 | |
|         FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                             (PyDictObject *)Py_XNewRef(new_dict));
 | |
|         replaced = true;
 | |
|         goto exit_lock;
 | |
|     }
 | |
|     else {
 | |
|         // We have inline values, we need to lock the dict and the object
 | |
|         // at the same time to safely dematerialize them. To do that while releasing
 | |
|         // the object lock we need a strong reference to the current dictionary.
 | |
|         Py_INCREF(dict);
 | |
|     }
 | |
| exit_lock:
 | |
|     Py_END_CRITICAL_SECTION();
 | |
|     return replaced;
 | |
| }
 | |
| 
 | |
| // Replaces a dictionary that is probably the dictionary which has been
 | |
| // materialized and points at the inline values. We could have raced
 | |
| // and replaced it with another dictionary though.
 | |
| static int
 | |
| replace_dict_probably_inline_materialized(PyObject *obj, PyDictObject *inline_dict,
 | |
|                                           PyDictObject *cur_dict, PyObject *new_dict)
 | |
| {
 | |
|     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
 | |
| 
 | |
|     if (cur_dict == inline_dict) {
 | |
|         assert(FT_ATOMIC_LOAD_PTR_RELAXED(inline_dict->ma_values) == _PyObject_InlineValues(obj));
 | |
| 
 | |
|         int err = _PyDict_DetachFromObject(inline_dict, obj);
 | |
|         if (err != 0) {
 | |
|             assert(new_dict == NULL);
 | |
|             return err;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                         (PyDictObject *)Py_XNewRef(new_dict));
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| static void
 | |
| decref_maybe_delay(PyObject *obj, bool delay)
 | |
| {
 | |
|     if (delay) {
 | |
|         _PyObject_XDecRefDelayed(obj);
 | |
|     }
 | |
|     else {
 | |
|         Py_XDECREF(obj);
 | |
|     }
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
 | |
| {
 | |
|     assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
 | |
| #ifndef NDEBUG
 | |
|     Py_BEGIN_CRITICAL_SECTION(obj);
 | |
|     assert(_PyObject_InlineValuesConsistencyCheck(obj));
 | |
|     Py_END_CRITICAL_SECTION();
 | |
| #endif
 | |
|     int err = 0;
 | |
|     PyTypeObject *tp = Py_TYPE(obj);
 | |
|     if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|         PyDictObject *prev_dict;
 | |
|         if (!try_set_dict_inline_only_or_other_dict(obj, new_dict, &prev_dict)) {
 | |
|             // We had a materialized dictionary which pointed at the inline
 | |
|             // values. We need to lock both the object and the dict at the
 | |
|             // same time to safely replace it. We can't merely lock the dictionary
 | |
|             // while the object is locked because it could suspend the object lock.
 | |
|             PyDictObject *cur_dict;
 | |
| 
 | |
|             assert(prev_dict != NULL);
 | |
|             Py_BEGIN_CRITICAL_SECTION2(obj, prev_dict);
 | |
| 
 | |
|             // We could have had another thread race in between the call to
 | |
|             // try_set_dict_inline_only_or_other_dict where we locked the object
 | |
|             // and when we unlocked and re-locked the dictionary.
 | |
|             cur_dict = _PyObject_GetManagedDict(obj);
 | |
| 
 | |
|             err = replace_dict_probably_inline_materialized(obj, prev_dict,
 | |
|                                                             cur_dict, new_dict);
 | |
| 
 | |
|             Py_END_CRITICAL_SECTION2();
 | |
| 
 | |
|             // Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict
 | |
|             // while the object was locked
 | |
|             decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict);
 | |
|             if (err != 0) {
 | |
|                 return err;
 | |
|             }
 | |
| 
 | |
|             prev_dict = cur_dict;
 | |
|         }
 | |
| 
 | |
|         if (prev_dict != NULL) {
 | |
|             // decref for the dictionary that we replaced
 | |
|             decref_maybe_delay((PyObject *)prev_dict, true);
 | |
|         }
 | |
| 
 | |
|         return 0;
 | |
| #else
 | |
|         PyDictObject *dict = _PyObject_GetManagedDict(obj);
 | |
|         if (dict == NULL) {
 | |
|             set_dict_inline_values(obj, (PyDictObject *)new_dict);
 | |
|             return 0;
 | |
|         }
 | |
|         if (_PyDict_DetachFromObject(dict, obj) == 0) {
 | |
|             _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)Py_XNewRef(new_dict);
 | |
|             Py_DECREF(dict);
 | |
|             return 0;
 | |
|         }
 | |
|         assert(new_dict == NULL);
 | |
|         return -1;
 | |
| #endif
 | |
|     }
 | |
|     else {
 | |
|         PyDictObject *dict;
 | |
| 
 | |
|         Py_BEGIN_CRITICAL_SECTION(obj);
 | |
| 
 | |
|         dict = _PyObject_ManagedDictPointer(obj)->dict;
 | |
| 
 | |
|         FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                             (PyDictObject *)Py_XNewRef(new_dict));
 | |
| 
 | |
|         Py_END_CRITICAL_SECTION();
 | |
|         decref_maybe_delay((PyObject *)dict, true);
 | |
|     }
 | |
|     assert(_PyObject_InlineValuesConsistencyCheck(obj));
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| static int
 | |
| detach_dict_from_object(PyDictObject *mp, PyObject *obj)
 | |
| {
 | |
|     assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
 | |
|     assert(_PyObject_InlineValuesConsistencyCheck(obj));
 | |
| 
 | |
|     if (FT_ATOMIC_LOAD_PTR_RELAXED(mp->ma_values) != _PyObject_InlineValues(obj)) {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     // We could be called with an unlocked dict when the caller knows the
 | |
|     // values are already detached, so we assert after inline values check.
 | |
|     ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(mp);
 | |
|     assert(mp->ma_values->embedded == 1);
 | |
|     assert(mp->ma_values->valid == 1);
 | |
|     assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
 | |
| 
 | |
|     PyDictValues *values = copy_values(mp->ma_values);
 | |
| 
 | |
|     if (values == NULL) {
 | |
|         PyErr_NoMemory();
 | |
|         return -1;
 | |
|     }
 | |
|     mp->ma_values = values;
 | |
| 
 | |
|     invalidate_and_clear_inline_values(_PyObject_InlineValues(obj));
 | |
| 
 | |
|     assert(_PyObject_InlineValuesConsistencyCheck(obj));
 | |
|     ASSERT_CONSISTENT(mp);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void
 | |
| PyObject_ClearManagedDict(PyObject *obj)
 | |
| {
 | |
|     // This is called when the object is being freed or cleared
 | |
|     // by the GC and therefore known to have no references.
 | |
|     if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
 | |
|         PyDictObject *dict = _PyObject_GetManagedDict(obj);
 | |
|         if (dict == NULL) {
 | |
|             // We have no materialized dictionary and inline values
 | |
|             // that just need to be cleared.
 | |
|             // No dict to clear, we're done
 | |
|             clear_inline_values(_PyObject_InlineValues(obj));
 | |
|             return;
 | |
|         }
 | |
|         else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) ==
 | |
|                     _PyObject_InlineValues(obj)) {
 | |
|             // We have a materialized object which points at the inline
 | |
|             // values. We need to materialize the keys. Nothing can modify
 | |
|             // this object, but we need to lock the dictionary.
 | |
|             int err;
 | |
|             Py_BEGIN_CRITICAL_SECTION(dict);
 | |
|             err = detach_dict_from_object(dict, obj);
 | |
|             Py_END_CRITICAL_SECTION();
 | |
| 
 | |
|             if (err) {
 | |
|                 /* Must be out of memory */
 | |
|                 assert(PyErr_Occurred() == PyExc_MemoryError);
 | |
|                 PyErr_FormatUnraisable("Exception ignored while "
 | |
|                                        "clearing an object managed dict");
 | |
|                 /* Clear the dict */
 | |
|                 Py_BEGIN_CRITICAL_SECTION(dict);
 | |
|                 PyDictKeysObject *oldkeys = dict->ma_keys;
 | |
|                 set_keys(dict, Py_EMPTY_KEYS);
 | |
|                 dict->ma_values = NULL;
 | |
|                 dictkeys_decref(oldkeys, IS_DICT_SHARED(dict));
 | |
|                 STORE_USED(dict, 0);
 | |
|                 clear_inline_values(_PyObject_InlineValues(obj));
 | |
|                 Py_END_CRITICAL_SECTION();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
 | |
| {
 | |
|     ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
 | |
| 
 | |
|     return detach_dict_from_object(mp, obj);
 | |
| }
 | |
| 
 | |
| static inline PyObject *
 | |
| ensure_managed_dict(PyObject *obj)
 | |
| {
 | |
|     PyDictObject *dict = _PyObject_GetManagedDict(obj);
 | |
|     if (dict == NULL) {
 | |
|         PyTypeObject *tp = Py_TYPE(obj);
 | |
|         if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
 | |
|             FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(obj)->valid)) {
 | |
|             dict = _PyObject_MaterializeManagedDict(obj);
 | |
|         }
 | |
|         else {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|             // Check again that we're not racing with someone else creating the dict
 | |
|             Py_BEGIN_CRITICAL_SECTION(obj);
 | |
|             dict = _PyObject_GetManagedDict(obj);
 | |
|             if (dict != NULL) {
 | |
|                 goto done;
 | |
|             }
 | |
| #endif
 | |
|             dict = (PyDictObject *)new_dict_with_shared_keys(CACHED_KEYS(tp));
 | |
|             FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
 | |
|                                         (PyDictObject *)dict);
 | |
| 
 | |
| #ifdef Py_GIL_DISABLED
 | |
| done:
 | |
|             Py_END_CRITICAL_SECTION();
 | |
| #endif
 | |
|         }
 | |
|     }
 | |
|     return (PyObject *)dict;
 | |
| }
 | |
| 
 | |
| static inline PyObject *
 | |
| ensure_nonmanaged_dict(PyObject *obj, PyObject **dictptr)
 | |
| {
 | |
|     PyDictKeysObject *cached;
 | |
| 
 | |
|     PyObject *dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr);
 | |
|     if (dict == NULL) {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|         Py_BEGIN_CRITICAL_SECTION(obj);
 | |
|         dict = *dictptr;
 | |
|         if (dict != NULL) {
 | |
|             goto done;
 | |
|         }
 | |
| #endif
 | |
|         PyTypeObject *tp = Py_TYPE(obj);
 | |
|         if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) && (cached = CACHED_KEYS(tp))) {
 | |
|             assert(!_PyType_HasFeature(tp, Py_TPFLAGS_INLINE_VALUES));
 | |
|             dict = new_dict_with_shared_keys(cached);
 | |
|         }
 | |
|         else {
 | |
|             dict = PyDict_New();
 | |
|         }
 | |
|         FT_ATOMIC_STORE_PTR_RELEASE(*dictptr, dict);
 | |
| #ifdef Py_GIL_DISABLED
 | |
| done:
 | |
|         Py_END_CRITICAL_SECTION();
 | |
| #endif
 | |
|     }
 | |
|     return dict;
 | |
| }
 | |
| 
 | |
| PyObject *
 | |
| PyObject_GenericGetDict(PyObject *obj, void *context)
 | |
| {
 | |
|     PyTypeObject *tp = Py_TYPE(obj);
 | |
|     if (_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT)) {
 | |
|         return Py_XNewRef(ensure_managed_dict(obj));
 | |
|     }
 | |
|     else {
 | |
|         PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
 | |
|         if (dictptr == NULL) {
 | |
|             PyErr_SetString(PyExc_AttributeError,
 | |
|                             "This object has no __dict__");
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         return Py_XNewRef(ensure_nonmanaged_dict(obj, dictptr));
 | |
|     }
 | |
| }
 | |
| 
 | |
| int
 | |
| _PyObjectDict_SetItem(PyTypeObject *tp, PyObject *obj, PyObject **dictptr,
 | |
|                       PyObject *key, PyObject *value)
 | |
| {
 | |
|     PyObject *dict;
 | |
|     int res;
 | |
| 
 | |
|     assert(dictptr != NULL);
 | |
|     dict = ensure_nonmanaged_dict(obj, dictptr);
 | |
|     if (dict == NULL) {
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     Py_BEGIN_CRITICAL_SECTION(dict);
 | |
|     res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, key, value);
 | |
|     ASSERT_CONSISTENT(dict);
 | |
|     Py_END_CRITICAL_SECTION();
 | |
|     return res;
 | |
| }
 | |
| 
 | |
| void
 | |
| _PyDictKeys_DecRef(PyDictKeysObject *keys)
 | |
| {
 | |
|     dictkeys_decref(keys, false);
 | |
| }
 | |
| 
 | |
| static inline uint32_t
 | |
| get_next_dict_keys_version(PyInterpreterState *interp)
 | |
| {
 | |
| #ifdef Py_GIL_DISABLED
 | |
|     uint32_t v;
 | |
|     do {
 | |
|         v = _Py_atomic_load_uint32_relaxed(
 | |
|             &interp->dict_state.next_keys_version);
 | |
|         if (v == 0) {
 | |
|             return 0;
 | |
|         }
 | |
|     } while (!_Py_atomic_compare_exchange_uint32(
 | |
|         &interp->dict_state.next_keys_version, &v, v + 1));
 | |
| #else
 | |
|     if (interp->dict_state.next_keys_version == 0) {
 | |
|         return 0;
 | |
|     }
 | |
|     uint32_t v = interp->dict_state.next_keys_version++;
 | |
| #endif
 | |
|     return v;
 | |
| }
 | |
| 
 | |
| // In free-threaded builds the caller must ensure that the keys object is not
 | |
| // being mutated concurrently by another thread.
 | |
| uint32_t
 | |
| _PyDictKeys_GetVersionForCurrentState(PyInterpreterState *interp,
 | |
|                                       PyDictKeysObject *dictkeys)
 | |
| {
 | |
|     uint32_t dk_version = FT_ATOMIC_LOAD_UINT32_RELAXED(dictkeys->dk_version);
 | |
|     if (dk_version != 0) {
 | |
|         return dk_version;
 | |
|     }
 | |
|     dk_version = get_next_dict_keys_version(interp);
 | |
|     FT_ATOMIC_STORE_UINT32_RELAXED(dictkeys->dk_version, dk_version);
 | |
|     return dk_version;
 | |
| }
 | |
| 
 | |
| uint32_t
 | |
| _PyDict_GetKeysVersionForCurrentState(PyInterpreterState *interp,
 | |
|                                       PyDictObject *dict)
 | |
| {
 | |
|     ASSERT_DICT_LOCKED((PyObject *) dict);
 | |
|     uint32_t dk_version =
 | |
|         _PyDictKeys_GetVersionForCurrentState(interp, dict->ma_keys);
 | |
|     ensure_shared_on_keys_version_assignment(dict);
 | |
|     return dk_version;
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| validate_watcher_id(PyInterpreterState *interp, int watcher_id)
 | |
| {
 | |
|     if (watcher_id < 0 || watcher_id >= DICT_MAX_WATCHERS) {
 | |
|         PyErr_Format(PyExc_ValueError, "Invalid dict watcher ID %d", watcher_id);
 | |
|         return -1;
 | |
|     }
 | |
|     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_watcher_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_watcher_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
 |