Skip to content

Commit 4d9aec0

Browse files
bpo-31572: Get rid of PyObject_HasAttr() and _PyObject_HasAttrId() in the _io module. (#3726)
1 parent 378edee commit 4d9aec0

File tree

5 files changed

+105
-63
lines changed

5 files changed

+105
-63
lines changed

Modules/_io/_iomodule.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ PyObject *_PyIO_str_getstate = NULL;
3636
PyObject *_PyIO_str_isatty = NULL;
3737
PyObject *_PyIO_str_newlines = NULL;
3838
PyObject *_PyIO_str_nl = NULL;
39+
PyObject *_PyIO_str_peek = NULL;
3940
PyObject *_PyIO_str_read = NULL;
4041
PyObject *_PyIO_str_read1 = NULL;
4142
PyObject *_PyIO_str_readable = NULL;
@@ -740,6 +741,7 @@ PyInit__io(void)
740741
ADD_INTERNED(getstate)
741742
ADD_INTERNED(isatty)
742743
ADD_INTERNED(newlines)
744+
ADD_INTERNED(peek)
743745
ADD_INTERNED(read)
744746
ADD_INTERNED(read1)
745747
ADD_INTERNED(readable)

Modules/_io/_iomodule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ extern PyObject *_PyIO_str_getstate;
164164
extern PyObject *_PyIO_str_isatty;
165165
extern PyObject *_PyIO_str_newlines;
166166
extern PyObject *_PyIO_str_nl;
167+
extern PyObject *_PyIO_str_peek;
167168
extern PyObject *_PyIO_str_read;
168169
extern PyObject *_PyIO_str_read1;
169170
extern PyObject *_PyIO_str_readable;

Modules/_io/bufferedio.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ static PyObject *
15211521
_bufferedreader_read_all(buffered *self)
15221522
{
15231523
Py_ssize_t current_size;
1524-
PyObject *res = NULL, *data = NULL, *tmp = NULL, *chunks = NULL;
1524+
PyObject *res = NULL, *data = NULL, *tmp = NULL, *chunks = NULL, *readall;
15251525

15261526
/* First copy what we have in the current buffer. */
15271527
current_size = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t);
@@ -1541,32 +1541,28 @@ _bufferedreader_read_all(buffered *self)
15411541
}
15421542
_bufferedreader_reset_buf(self);
15431543

1544-
if (PyObject_HasAttr(self->raw, _PyIO_str_readall)) {
1545-
tmp = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_readall, NULL);
1544+
readall = _PyObject_GetAttrWithoutError(self->raw, _PyIO_str_readall);
1545+
if (readall) {
1546+
tmp = _PyObject_CallNoArg(readall);
1547+
Py_DECREF(readall);
15461548
if (tmp == NULL)
15471549
goto cleanup;
15481550
if (tmp != Py_None && !PyBytes_Check(tmp)) {
15491551
PyErr_SetString(PyExc_TypeError, "readall() should return bytes");
15501552
goto cleanup;
15511553
}
1552-
if (tmp == Py_None) {
1553-
if (current_size == 0) {
1554-
res = Py_None;
1555-
goto cleanup;
1556-
} else {
1557-
res = data;
1558-
goto cleanup;
1554+
if (current_size == 0) {
1555+
res = tmp;
1556+
} else {
1557+
if (tmp != Py_None) {
1558+
PyBytes_Concat(&data, tmp);
15591559
}
1560-
}
1561-
else if (current_size) {
1562-
PyBytes_Concat(&data, tmp);
15631560
res = data;
1564-
goto cleanup;
1565-
}
1566-
else {
1567-
res = tmp;
1568-
goto cleanup;
15691561
}
1562+
goto cleanup;
1563+
}
1564+
else if (PyErr_Occurred()) {
1565+
goto cleanup;
15701566
}
15711567

15721568
chunks = PyList_New(0);

Modules/_io/iobase.c

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,9 @@ PyDoc_STRVAR(iobase_doc,
6868
by whatever subclass. */
6969

7070
_Py_IDENTIFIER(__IOBase_closed);
71-
#define IS_CLOSED(self) \
72-
_PyObject_HasAttrId(self, &PyId___IOBase_closed)
73-
7471
_Py_IDENTIFIER(read);
7572

73+
7674
/* Internal methods */
7775
static PyObject *
7876
iobase_unsupported(const char *message)
@@ -131,6 +129,24 @@ iobase_truncate(PyObject *self, PyObject *args)
131129
return iobase_unsupported("truncate");
132130
}
133131

132+
static int
133+
iobase_is_closed(PyObject *self)
134+
{
135+
PyObject *res;
136+
/* This gets the derived attribute, which is *not* __IOBase_closed
137+
in most cases! */
138+
res = _PyObject_GetAttrId(self, &PyId___IOBase_closed);
139+
if (res == NULL) {
140+
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
141+
return -1;
142+
}
143+
PyErr_Clear();
144+
return 0;
145+
}
146+
Py_DECREF(res);
147+
return 1;
148+
}
149+
134150
/* Flush and close methods */
135151

136152
/*[clinic input]
@@ -146,45 +162,60 @@ _io__IOBase_flush_impl(PyObject *self)
146162
/*[clinic end generated code: output=7cef4b4d54656a3b input=773be121abe270aa]*/
147163
{
148164
/* XXX Should this return the number of bytes written??? */
149-
if (IS_CLOSED(self)) {
165+
int closed = iobase_is_closed(self);
166+
167+
if (!closed) {
168+
Py_RETURN_NONE;
169+
}
170+
if (closed > 0) {
150171
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
172+
}
173+
return NULL;
174+
}
175+
176+
static PyObject *
177+
iobase_closed_get(PyObject *self, void *context)
178+
{
179+
int closed = iobase_is_closed(self);
180+
if (closed < 0) {
151181
return NULL;
152182
}
153-
Py_RETURN_NONE;
183+
return PyBool_FromLong(closed);
154184
}
155185

156186
static int
157-
iobase_closed(PyObject *self)
187+
iobase_check_closed(PyObject *self)
158188
{
159189
PyObject *res;
160190
int closed;
161191
/* This gets the derived attribute, which is *not* __IOBase_closed
162192
in most cases! */
163-
res = PyObject_GetAttr(self, _PyIO_str_closed);
164-
if (res == NULL)
193+
res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
194+
if (res == NULL) {
195+
if (PyErr_Occurred()) {
196+
return -1;
197+
}
165198
return 0;
199+
}
166200
closed = PyObject_IsTrue(res);
167201
Py_DECREF(res);
168-
return closed;
169-
}
170-
171-
static PyObject *
172-
iobase_closed_get(PyObject *self, void *context)
173-
{
174-
return PyBool_FromLong(IS_CLOSED(self));
202+
if (closed <= 0) {
203+
return closed;
204+
}
205+
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
206+
return -1;
175207
}
176208

177209
PyObject *
178210
_PyIOBase_check_closed(PyObject *self, PyObject *args)
179211
{
180-
if (iobase_closed(self)) {
181-
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
212+
if (iobase_check_closed(self)) {
182213
return NULL;
183214
}
184-
if (args == Py_True)
215+
if (args == Py_True) {
185216
return Py_None;
186-
else
187-
Py_RETURN_NONE;
217+
}
218+
Py_RETURN_NONE;
188219
}
189220

190221
/* XXX: IOBase thinks it has to maintain its own internal state in
@@ -204,9 +235,14 @@ _io__IOBase_close_impl(PyObject *self)
204235
/*[clinic end generated code: output=63c6a6f57d783d6d input=f4494d5c31dbc6b7]*/
205236
{
206237
PyObject *res;
238+
int closed = iobase_is_closed(self);
207239

208-
if (IS_CLOSED(self))
240+
if (closed < 0) {
241+
return NULL;
242+
}
243+
if (closed) {
209244
Py_RETURN_NONE;
245+
}
210246

211247
res = PyObject_CallMethodObjArgs(self, _PyIO_str_flush, NULL);
212248

@@ -237,7 +273,7 @@ iobase_finalize(PyObject *self)
237273

238274
/* If `closed` doesn't exist or can't be evaluated as bool, then the
239275
object is probably in an unusable state, so ignore. */
240-
res = PyObject_GetAttr(self, _PyIO_str_closed);
276+
res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
241277
if (res == NULL) {
242278
PyErr_Clear();
243279
closed = -1;
@@ -428,7 +464,7 @@ _PyIOBase_check_writable(PyObject *self, PyObject *args)
428464
static PyObject *
429465
iobase_enter(PyObject *self, PyObject *args)
430466
{
431-
if (_PyIOBase_check_closed(self, Py_True) == NULL)
467+
if (iobase_check_closed(self))
432468
return NULL;
433469

434470
Py_INCREF(self);
@@ -472,7 +508,7 @@ static PyObject *
472508
_io__IOBase_isatty_impl(PyObject *self)
473509
/*[clinic end generated code: output=60cab77cede41cdd input=9ef76530d368458b]*/
474510
{
475-
if (_PyIOBase_check_closed(self, Py_True) == NULL)
511+
if (iobase_check_closed(self))
476512
return NULL;
477513
Py_RETURN_FALSE;
478514
}
@@ -499,24 +535,26 @@ _io__IOBase_readline_impl(PyObject *self, Py_ssize_t limit)
499535
{
500536
/* For backwards compatibility, a (slowish) readline(). */
501537

502-
int has_peek = 0;
503-
PyObject *buffer, *result;
538+
PyObject *peek, *buffer, *result;
504539
Py_ssize_t old_size = -1;
505-
_Py_IDENTIFIER(peek);
506540

507-
if (_PyObject_HasAttrId(self, &PyId_peek))
508-
has_peek = 1;
541+
peek = _PyObject_GetAttrWithoutError(self, _PyIO_str_peek);
542+
if (peek == NULL && PyErr_Occurred()) {
543+
return NULL;
544+
}
509545

510546
buffer = PyByteArray_FromStringAndSize(NULL, 0);
511-
if (buffer == NULL)
547+
if (buffer == NULL) {
548+
Py_XDECREF(peek);
512549
return NULL;
550+
}
513551

514552
while (limit < 0 || PyByteArray_GET_SIZE(buffer) < limit) {
515553
Py_ssize_t nreadahead = 1;
516554
PyObject *b;
517555

518-
if (has_peek) {
519-
PyObject *readahead = _PyObject_CallMethodId(self, &PyId_peek, "i", 1);
556+
if (peek != NULL) {
557+
PyObject *readahead = PyObject_CallFunctionObjArgs(peek, _PyLong_One, NULL);
520558
if (readahead == NULL) {
521559
/* NOTE: PyErr_SetFromErrno() calls PyErr_CheckSignals()
522560
when EINTR occurs so we needn't do it ourselves. */
@@ -593,17 +631,19 @@ _io__IOBase_readline_impl(PyObject *self, Py_ssize_t limit)
593631

594632
result = PyBytes_FromStringAndSize(PyByteArray_AS_STRING(buffer),
595633
PyByteArray_GET_SIZE(buffer));
634+
Py_XDECREF(peek);
596635
Py_DECREF(buffer);
597636
return result;
598637
fail:
638+
Py_XDECREF(peek);
599639
Py_DECREF(buffer);
600640
return NULL;
601641
}
602642

603643
static PyObject *
604644
iobase_iter(PyObject *self)
605645
{
606-
if (_PyIOBase_check_closed(self, Py_True) == NULL)
646+
if (iobase_check_closed(self))
607647
return NULL;
608648

609649
Py_INCREF(self);
@@ -716,7 +756,7 @@ _io__IOBase_writelines(PyObject *self, PyObject *lines)
716756
{
717757
PyObject *iter, *res;
718758

719-
if (_PyIOBase_check_closed(self, Py_True) == NULL)
759+
if (iobase_check_closed(self))
720760
return NULL;
721761

722762
iter = PyObject_GetIter(lines);

Modules/_io/textio.c

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ _Py_IDENTIFIER(mode);
2929
_Py_IDENTIFIER(name);
3030
_Py_IDENTIFIER(raw);
3131
_Py_IDENTIFIER(read);
32-
_Py_IDENTIFIER(read1);
3332
_Py_IDENTIFIER(readable);
3433
_Py_IDENTIFIER(replace);
3534
_Py_IDENTIFIER(reset);
@@ -1202,7 +1201,17 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
12021201
goto error;
12031202
self->seekable = self->telling = r;
12041203

1205-
self->has_read1 = _PyObject_HasAttrId(buffer, &PyId_read1);
1204+
res = _PyObject_GetAttrWithoutError(buffer, _PyIO_str_read1);
1205+
if (res != NULL) {
1206+
Py_DECREF(res);
1207+
self->has_read1 = 1;
1208+
}
1209+
else if (!PyErr_Occurred()) {
1210+
self->has_read1 = 0;
1211+
}
1212+
else {
1213+
goto error;
1214+
}
12061215

12071216
self->encoding_start_of_stream = 0;
12081217
if (_textiowrapper_fix_encoder_state(self) < 0) {
@@ -3013,15 +3022,9 @@ textiowrapper_newlines_get(textio *self, void *context)
30133022
CHECK_ATTACHED(self);
30143023
if (self->decoder == NULL)
30153024
Py_RETURN_NONE;
3016-
res = PyObject_GetAttr(self->decoder, _PyIO_str_newlines);
3017-
if (res == NULL) {
3018-
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
3019-
PyErr_Clear();
3020-
Py_RETURN_NONE;
3021-
}
3022-
else {
3023-
return NULL;
3024-
}
3025+
res = _PyObject_GetAttrWithoutError(self->decoder, _PyIO_str_newlines);
3026+
if (res == NULL && !PyErr_Occurred()) {
3027+
Py_RETURN_NONE;
30253028
}
30263029
return res;
30273030
}

0 commit comments

Comments
 (0)