Fix accidental mutations in Dictionary
(and possibly Array
and NodePath
).
#106600
+230
−166
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Dictionary
currently has a design flaw where it is possible for C++ code to mutate the underlying data even if it is supposed to beconst
. This PR fixes this problem by introducingSharedPtr
.In particular, it was previously possible to accidentally add key-values to
const Dictionary
throughoperator[]
. This bug was triggered at least inAudioStreamWAV::load_from_buffer
.This PR is likely to cause regressions, because it exposes incorrect code. I recommend merging it soon to have time to test it before the 4.5 release.
Explanation
Some of our data structures use shared reference semantics. They currently use a pattern where the data type is merely a pointer to some shared data, and the data has a
SafeRefCount
to keep track of reference counts:godot/core/variant/dictionary.h
Lines 46 to 47 in a2aefab
godot/core/variant/dictionary.cpp
Lines 43 to 44 in a2aefab
Unfortunately, this implementation creates a problem:
const
-ness ofDictionary
is not transferred over to theDictionaryPrivate
pointee of_p
. That means all functions ofDictionary
actually have full write access into the dictionary, evenconst
ones:To address this problem,
DictionaryPrivate *
needs to be wrapped in a smart pointer.I decided to use this opportunity to simplify and unify implementations, and go straight for an owned smart pointer, called
SharedPtr
. This pointer works similarly tostd::shared_ptr
, i.e. all references have read-write access to it. Therefcount
andT
contents are placed next to each other in memory, which perfectly mimicks the way it was previously set up. The new class could be used forDictionary
,Array
andNodePath
, alleviating house-keeping duties from them.Affected Code
This change exposed a few minor bugs in the codebase:
Dictionary::operator[]
accidentally inserts an empty value when the key is not found, even onconst
access.AudioStreamWAV::load_from_buffer
is one affected caller, unknowingly modified theDictionary
parameter, even though it wasconst
.bsearch_custom
was non-const, although it could have beenconst
.dictionary.cpp
,array.cpp
andnode_path.cpp
unknowingly called non-const versions of functions on data, possibly leading to worse performance (or even bugs).