Skip to content

Commit 679d146

Browse files
Add a series of assert statements to ensure that wrapped references
are always valid.
1 parent 760df88 commit 679d146

File tree

1 file changed

+62
-25
lines changed

1 file changed

+62
-25
lines changed

dynd/include/utility_functions.hpp

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@ inline void decref(PyObject *) noexcept;
4747
template <>
4848
inline void decref<false>(PyObject *obj) noexcept
4949
{
50+
// Assert that the reference is valid if it is not null.
51+
PYDYND_ASSERT_IF(obj != nullptr, Py_REFCNT(obj) > 0);
5052
Py_XDECREF(obj);
5153
}
5254

5355
template <>
5456
inline void decref<true>(PyObject *obj) noexcept
5557
{
5658
assert(obj != nullptr);
59+
// Assert that the reference is valid.
60+
assert(Py_REFCNT(obj) > 0);
5761
Py_DECREF(obj);
5862
}
5963

@@ -90,23 +94,33 @@ template <>
9094
inline void decref_if_owned<true, true>(PyObject *obj) noexcept
9195
{
9296
assert(obj != nullptr);
97+
// Assert that the reference is valid.
98+
assert(Py_REFCNT(obj) > 0);
9399
Py_DECREF(obj);
94100
}
95101

96102
template <>
97103
inline void decref_if_owned<true, false>(PyObject *obj) noexcept
98104
{
105+
// Assert that the reference is valid if it is not null.
106+
PYDYND_ASSERT_IF(obj != nullptr, Py_REFCNT(obj) > 0);
99107
Py_XDECREF(obj);
100108
}
101109

110+
// This is a no-op in non-debug builds.
111+
// In debug builds, it asserts that the reference is actually valid.
102112
template <>
103113
inline void decref_if_owned<false, true>(PyObject *obj) noexcept
104114
{
115+
assert(Py_REFCNT(obj) > 0);
105116
}
106117

118+
// This is a no-op in non-debug builds.
119+
// In debug builds, it asserts that the reference is actually valid.
107120
template <>
108121
inline void decref_if_owned<false, false>(PyObject *obj) noexcept
109122
{
123+
PYDYND_ASSERT_IF(obj != nullptr, Py_REFCNT(obj) > 0);
110124
}
111125

112126
template <bool owns_ref = true, bool not_null = true>
@@ -125,6 +139,8 @@ class py_ref_tmpl {
125139
PyObject *get() noexcept
126140
{
127141
PYDYND_ASSERT_IF(not_null, o != nullptr);
142+
// Assert that the accessed reference is still valid.
143+
PYDYND_ASSERT_IF(o != nullptr, Py_REFCNT(o) > 0);
128144
return o;
129145
}
130146

@@ -133,18 +149,18 @@ class py_ref_tmpl {
133149
// they are also declared noexcept.
134150

135151
/* If:
136-
* this type allows null,
137-
* Or:
138-
* this type doesn't allow null
139-
* and the input type doesn't allow null,
152+
* This type allows null or the input type does not,
140153
* Then:
141154
* Conversions from the other py_ref_tmpl type to this type do not raise exceptions.
142155
*/
143156
template <bool other_owns_ref, bool other_not_null,
144-
typename std::enable_if_t<(other_not_null && not_null) || !not_null> * = nullptr>
157+
typename std::enable_if_t<other_not_null || !not_null> * = nullptr>
145158
py_ref_tmpl(const py_ref_tmpl<other_owns_ref, other_not_null> &other) noexcept
146159
{
147-
PYDYND_ASSERT_IF(not_null || other_not_null, other.o != nullptr);
160+
// If the input is not null, assert that it isn't.
161+
PYDYND_ASSERT_IF(other_not_null, other.o != nullptr);
162+
// Assert that the input reference is valid if it is not null.
163+
PYDYND_ASSERT_IF(other.o != nullptr, Py_REFCNT(other.o) > 0);
148164
o = other.o;
149165
incref_if_owned<owns_ref, not_null>(o);
150166
}
@@ -162,6 +178,8 @@ class py_ref_tmpl {
162178
py_ref_tmpl(const py_ref_tmpl<other_owns_ref, other_not_null> &other)
163179
{
164180
if (other.o != nullptr) {
181+
// Assert that the input reference is valid.
182+
assert(Py_REFCNT(other.o) > 0);
165183
o = other.o;
166184
incref_if_owned<owns_ref, not_null>(o);
167185
}
@@ -180,22 +198,26 @@ class py_ref_tmpl {
180198
* Then:
181199
* a move operation is defined and will not raise an exception.
182200
*/
183-
template <bool other_not_null, typename std::enable_if_t<(other_not_null || !not_null)> * = nullptr>
201+
template <bool other_not_null, typename std::enable_if_t<other_not_null || !not_null> * = nullptr>
184202
py_ref_tmpl(py_ref_tmpl<true, other_not_null> &&other) noexcept
185203
{
186204
// If this type is a non-null type, the assigned value should not be null.
187205
// If the other type is a non-null type, the provided value should not be null,
188206
// unless it is being used uninitialized or after being moved from.
189207
PYDYND_ASSERT_IF(not_null || other_not_null, other.o != nullptr);
208+
// If the reference is not null, assert that it is valid.
209+
PYDYND_ASSERT_IF(other.o != nullptr, Py_REFCNT(other.o) > 0);
190210
// Use get to assert that other.o is not null if other_not_null is true.
191211
o = other.o;
212+
// Make sure other can be destructed without decrefing
213+
// this object's wrapped pointer.
192214
other.o = nullptr;
193215
// The other type owns its reference.
194216
// If this one does not, decref the new pointer.
195217
// If this type is a non-null type, the assigned value should not be null.
196218
decref_if_owned<!owns_ref, not_null>(o);
197-
// Make sure other can be destructed without decrefing
198-
// this object's wrapped pointer.
219+
// If the reference is not null, assert that it is still valid.
220+
PYDYND_ASSERT_IF(o != nullptr, Py_REFCNT(o) > 0)
199221
}
200222

201223
/* If:
@@ -208,14 +230,18 @@ class py_ref_tmpl {
208230
py_ref_tmpl(py_ref_tmpl<true, other_not_null> &&other)
209231
{
210232
if (other.o != nullptr) {
233+
// Assert that the input reference is valid.
234+
assert(Py_REFCNT(other.o) > 0);
211235
o = other.o;
236+
// Make sure other can be destructed without decrefing
237+
// this object's wrapped pointer.
212238
other.o = nullptr;
213239
// The other type owns its reference.
214240
// If this one does not, decref the new pointer.
215241
// The assigned value is already known not be null.
216242
decref_if_owned<!owns_ref, not_null>(o);
217-
// Make sure other can be destructed without decrefing
218-
// this object's wrapped pointer.
243+
// Assert that the stored reference is still valid
244+
assert(Py_REFCNT(o) > 0);
219245
}
220246
else {
221247
throw std::invalid_argument("Cannot convert null valued pointer to non-null reference.");
@@ -239,6 +265,9 @@ class py_ref_tmpl {
239265
else {
240266
incref_if_owned<owns_ref, not_null>(o);
241267
}
268+
// Regardless of whether or not a reference was consumed,
269+
// assert that it is valid if it is not null.
270+
PYDYND_ASSERT_IF(obj != null, Py_REFCNT(obj) > 0);
242271
}
243272

244273
~py_ref_tmpl()
@@ -257,18 +286,18 @@ class py_ref_tmpl {
257286
// Assignment never comsumes a reference.
258287

259288
/* If:
260-
* this type allows null,
261-
* Or:
262-
* this type doesn't allow null
263-
* and the input type doesn't allow null,
289+
* This type allows null or the input type does not,
264290
* Then:
265291
* Assignment from the other py_ref_tmpl type to this type may not raise an exception.
266292
*/
267293
template <bool other_owns_ref, bool other_not_null,
268-
typename std::enable_if_t<(other_not_null && not_null) || !not_null> * = nullptr>
294+
typename std::enable_if_t<other_not_null || !not_null> * = nullptr>
269295
py_ref_tmpl<owns_ref, not_null> &operator=(const py_ref_tmpl<other_owns_ref, other_not_null> &other) noexcept
270296
{
271-
PYDYND_ASSERT_IF(not_null || other_not_null, other.o != nullptr);
297+
// Assert that the input reference is not null if the input type does not allow nulls.
298+
PYDYND_ASSERT_IF(other_not_null, other.o != nullptr);
299+
// Assert that the input reference is valid if it is not null.
300+
PYDYND_ASSERT_IF(other.o != nullptr, Py_REFCNT(other.o) > 0);
272301
// Nullcheck when doing decref in case this object
273302
// is uninitialized or has been moved from.
274303
decref_if_owned<owns_ref, false>(o);
@@ -288,6 +317,8 @@ class py_ref_tmpl {
288317
py_ref_tmpl<owns_ref, not_null> &operator=(const py_ref_tmpl<other_owns_ref, other_not_null> &other) noexcept
289318
{
290319
if (other.o != nullptr) {
320+
// Assert that the input reference is valid.
321+
assert(Py_REFCNT(other.o) > 0);
291322
// Nullcheck when doing decref in case this object
292323
// is uninitialized or has been moved from.
293324
decref_if_owned<owns_ref, false>(o);
@@ -311,10 +342,13 @@ class py_ref_tmpl {
311342
* Assignment from the other py_ref_tmpl type to this type may not raise an exception.
312343
*/
313344
template <bool other_owns_ref, bool other_not_null,
314-
typename std::enable_if_t<(other_not_null && not_null) || !not_null> * = nullptr>
345+
typename std::enable_if_t<other_not_null || !not_null> * = nullptr>
315346
py_ref_tmpl<owns_ref, not_null> &operator=(py_ref_tmpl<other_owns_ref, other_not_null> &&other) noexcept
316347
{
317-
PYDYND_ASSERT_IF(not_null || other_not_null, other.o != nullptr);
348+
// If the input reference should not be null, assert that that is the case.
349+
PYDYND_ASSERT_IF(other_not_null, other.o != nullptr);
350+
// If the input reference is not null, assert that it is valid.
351+
PYDYND_ASSERT_IF(other.o != nullptr, Py_REFCNT(other.o) > 0);
318352
// Nullcheck when doing decref in case this object
319353
// is uninitialized or has been moved from.
320354
decref_if_owned<owns_ref, false>(o);
@@ -335,6 +369,8 @@ class py_ref_tmpl {
335369
py_ref_tmpl<owns_ref, not_null> &operator=(py_ref_tmpl<other_owns_ref, other_not_null> &&other) noexcept
336370
{
337371
if (other.o != nullptr) {
372+
// Assert that the input reference is valid.
373+
assert(Py_REFCNT(other.o) > 0);
338374
// Nullcheck when doing decref in case this object
339375
// is uninitialized or has been moved from.
340376
decref_if_owned<owns_ref, false>(o);
@@ -352,16 +388,16 @@ class py_ref_tmpl {
352388
// Set the encapsulated pointer to NULL.
353389
PyObject *release() noexcept
354390
{
391+
// If the contained reference should not be null, assert that it isn't.
355392
PYDYND_ASSERT_IF(not_null, o != nullptr);
393+
// If the contained reference is not null, assert that it is valid.
394+
PYDYND_ASSERT_IF(o != nullptr, Py_REFCNT(o) > 0);
356395
auto ret = o;
357396
o = nullptr;
358397
incref_if_owned<!owns_ref, not_null>(ret);
359398
return ret;
360399
}
361400

362-
// Check if the wrapped pointer is null.
363-
bool is_null() noexcept { return o != nullptr; }
364-
365401
py_ref_tmpl<false, not_null> borrow() noexcept { return py_ref<false, not_null>(o, false); }
366402
};
367403

@@ -383,9 +419,7 @@ inline py_ref capture_if_not_null(PyObject *o)
383419
if (o == nullptr) {
384420
throw std::runtime_error("Unexpected null pointer.");
385421
}
386-
auto p = py_ref(o, true);
387-
return p;
388-
// return py_ref(o, true);
422+
return py_ref(o, true);
389423
}
390424

391425
/* Convert to a non-null reference.
@@ -408,7 +442,10 @@ inline py_ref_tmpl<owns_ref, true> nullcheck(py_ref_tmpl<owns_ref, not_null> &&o
408442
template <bool owns_ref, bool not_null>
409443
inline py_ref_tmpl<owns_ref, true> nullcheck(py_ref_tmpl<owns_ref, not_null> &&obj) noexcept
410444
{
445+
// Assert that the wrapped pointer is actually not null.
411446
assert(obj.get() != nullptr);
447+
// Assert that the wrapped reference is valid if it is not null.
448+
PYDYND_ASSERT_IF(obj.get() != nullptr, Py_REFCNT(obj.get()) > 0);
412449
return py_ref_tmpl<owns_ref, true>(obj.release(), owns_ref);
413450
}
414451

0 commit comments

Comments
 (0)