Skip to content

Commit 61e12cf

Browse files
committed
Clarify proceedure with given arguments that are actually borrowed references.
1 parent af1d027 commit 61e12cf

File tree

1 file changed

+48
-4
lines changed

1 file changed

+48
-4
lines changed

doc/sphinx/source/parsing_arguments.rst

+48-4
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ There is no parsing required here, a single ``PyObject`` is expected:
6262
) {
6363
PyObject *ret = NULL;
6464
assert(arg);
65-
/* Treat arg as a borrowed reference. */
66-
Py_INCREF(arg);
65+
/* arg as a borrowed reference and the general rule is that you Py_INCREF them
66+
* whilst you have an interest in them. We do _not_ do that here for reasons
67+
* explained below.
68+
*/
69+
// Py_INCREF(arg);
6770
6871
/* Your code here...*/
6972
@@ -76,8 +79,9 @@ There is no parsing required here, a single ``PyObject`` is expected:
7679
Py_XDECREF(ret);
7780
ret = NULL;
7881
finally:
79-
/* Treat arg as a borrowed reference. */
80-
Py_DECREF(arg);
82+
/* If we were to treat arg as a borrowed reference and had Py_INCREF'd above we
83+
* should do this. See below. */
84+
// Py_DECREF(arg);
8185
return ret;
8286
}
8387
@@ -94,6 +98,46 @@ This function can be added to the module with the ``METH_O`` flag:
9498
{NULL, NULL, 0, NULL} /* Sentinel */
9599
};
96100
101+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
102+
Arguments as Borrowed References
103+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104+
105+
There is some subtlety here as indicated by the comments. ``*arg`` is not our reference, it is a borrowed reference so why don't we increment it at the beginning of this function and decrement it at the end? After all we are trying to protect against calling into some malicious/badly written code that could hurt us. For example:
106+
107+
.. code-block:: c
108+
109+
static PyObject *foo(PyObject *module,
110+
PyObject *arg
111+
) {
112+
/* arg has a minimum recount of 1. */
113+
call_malicious_code_that_decrefs_by_one_this_argument(arg);
114+
/* arg potentially could have had a ref count of 0 and been deallocated. */
115+
/* ... */
116+
/* So now doing something with arg could be undefined. */
117+
}
118+
119+
A solution would be, since ``arg`` is a 'borrowed' reference and borrowed references should always be incremented whilst in use and decremented when done with. This would suggest the following:
120+
121+
.. code-block:: c
122+
123+
static PyObject *foo(PyObject *module,
124+
PyObject *arg
125+
) {
126+
/* arg has a minimum recount of 1. */
127+
Py_INCREF(arg);
128+
/* arg now has a minimum recount of 2. */
129+
call_malicious_code_that_decrefs_by_one_this_argument(arg);
130+
/* arg can not have a ref count of 0 so is safe to use. */
131+
/* Use arg to your hearts content... */
132+
/* Do a matching decref. */
133+
Py_DECREF(arg);
134+
/* But now arg could have had a ref count of 0 so is unsafe to use by the caller. */
135+
}
136+
137+
But now we have just pushed the burden onto our caller. They created ``arg`` and passed it to us in good faith and whilst we have protected ourselves have not protected the caller and they can fail unexpectedly. So it is best to fail fast, an near the error site, that dastardly ``call_malicious_code_that_decrefs_by_one_this_argument()``.
138+
139+
Side note: Of course this does not protect you from malicious/badly written code that decrements by more than one :-)
140+
97141
----------------------------------------------------
98142
Variable Number of Arguments
99143
----------------------------------------------------

0 commit comments

Comments
 (0)