Skip to content

Commit d8d5461

Browse files
committed
Make CowData self-inserts safe
When inserting an element taking the original from the current data block so that it will be resized, potentially invalidating the reference to the original, a backup is made to base the copy on it instead. Fixes godotengine#31736.
1 parent 37f664b commit d8d5461

File tree

2 files changed

+55
-6
lines changed

2 files changed

+55
-6
lines changed

core/cowdata.h

+54-4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,42 @@ class CowData {
5656
private:
5757
mutable T *_ptr;
5858

59+
class ElementBackup {
60+
const T &original;
61+
bool empty;
62+
uint8_t backup[sizeof(T)];
63+
64+
public:
65+
_FORCE_INLINE_ void make_backup() {
66+
if (__has_trivial_copy(T)) {
67+
memcpy(&backup, &original, sizeof(T));
68+
} else {
69+
memnew_placement(&backup, T(original));
70+
}
71+
empty = false;
72+
}
73+
74+
_FORCE_INLINE_ const T *get_original_ptr() {
75+
return reinterpret_cast<const T *>(&original);
76+
}
77+
78+
_FORCE_INLINE_ const T &get_value() {
79+
CRASH_COND(empty);
80+
return *reinterpret_cast<T *>(backup);
81+
}
82+
83+
_FORCE_INLINE_ const bool is_empty() { return empty; }
84+
85+
_FORCE_INLINE_ ElementBackup(const T &p_original) :
86+
original(p_original),
87+
empty(true) {}
88+
_FORCE_INLINE_ ~ElementBackup() {
89+
if (!empty && !__has_trivial_destructor(T)) {
90+
reinterpret_cast<T *>(backup)->~T();
91+
}
92+
}
93+
};
94+
5995
// internal helpers
6096

6197
_FORCE_INLINE_ uint32_t *_get_refcount() const {
@@ -154,7 +190,7 @@ class CowData {
154190
return _get_data()[p_index];
155191
}
156192

157-
Error resize(int p_size);
193+
Error resize(int p_size, ElementBackup *r_elem_backup = NULL);
158194

159195
_FORCE_INLINE_ void remove(int p_index) {
160196

@@ -172,10 +208,17 @@ class CowData {
172208
Error insert(int p_pos, const T &p_val) {
173209

174210
ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER);
175-
resize(size() + 1);
211+
212+
ElementBackup eb(p_val);
213+
resize(size() + 1, &eb);
214+
176215
for (int i = (size() - 1); i > p_pos; i--)
177216
set(i, get(i - 1));
178-
set(p_pos, p_val);
217+
if (unlikely(!eb.is_empty())) {
218+
set(p_pos, eb.get_value());
219+
} else {
220+
set(p_pos, p_val);
221+
}
179222

180223
return OK;
181224
};
@@ -248,7 +291,7 @@ void CowData<T>::_copy_on_write() {
248291
}
249292

250293
template <class T>
251-
Error CowData<T>::resize(int p_size) {
294+
Error CowData<T>::resize(int p_size, ElementBackup *r_elem_backup) {
252295

253296
ERR_FAIL_COND_V(p_size < 0, ERR_INVALID_PARAMETER);
254297

@@ -280,6 +323,13 @@ Error CowData<T>::resize(int p_size) {
280323
_ptr = (T *)ptr;
281324

282325
} else {
326+
if (unlikely(r_elem_backup)) {
327+
const T *original = r_elem_backup->get_original_ptr();
328+
if (unlikely(original >= _ptr && original < _ptr + size())) {
329+
r_elem_backup->make_backup();
330+
}
331+
}
332+
283333
void *_ptrnew = (T *)Memory::realloc_static(_ptr, alloc_size, true);
284334
ERR_FAIL_COND_V(!_ptrnew, ERR_OUT_OF_MEMORY);
285335
_ptr = (T *)(_ptrnew);

core/vector.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,8 @@ void Vector<T>::append_array(const Vector<T> &p_other) {
149149
template <class T>
150150
bool Vector<T>::push_back(const T &p_elem) {
151151

152-
Error err = resize(size() + 1);
152+
Error err = _cowdata.insert(size(), p_elem);
153153
ERR_FAIL_COND_V(err, true);
154-
set(size() - 1, p_elem);
155154

156155
return false;
157156
}

0 commit comments

Comments
 (0)