Skip to content

Commit f320be7

Browse files
serhiy-storchakamethane
authored andcommitted
bpo-32571: Avoid raising unneeded AttributeError and silencing it in C code (GH-5222)
Add two new private APIs: _PyObject_LookupAttr() and _PyObject_LookupAttrId()
1 parent 2b822a0 commit f320be7

22 files changed

+1443
-1430
lines changed

Include/object.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,20 @@ PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *);
536536
PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *);
537537
#ifndef Py_LIMITED_API
538538
PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *);
539-
/* Same as PyObject_GetAttr(), but don't raise AttributeError. */
540-
PyAPI_FUNC(PyObject *) _PyObject_GetAttrWithoutError(PyObject *, PyObject *);
541539
PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, struct _Py_Identifier *);
542540
PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *);
543541
PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *);
542+
/* Replacements of PyObject_GetAttr() and _PyObject_GetAttrId() which
543+
don't raise AttributeError.
544+
545+
Return 1 and set *result != NULL if an attribute is found.
546+
Return 0 and set *result == NULL if an attribute is not found;
547+
an AttributeError is silenced.
548+
Return -1 and set *result == NULL if an error other than AttributeError
549+
is raised.
550+
*/
551+
PyAPI_FUNC(int) _PyObject_LookupAttr(PyObject *, PyObject *, PyObject **);
552+
PyAPI_FUNC(int) _PyObject_LookupAttrId(PyObject *, struct _Py_Identifier *, PyObject **);
544553
PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *);
545554
#endif
546555
PyAPI_FUNC(PyObject *) PyObject_SelfIter(PyObject *);

Modules/_collectionsmodule.c

+3-5
Original file line numberDiff line numberDiff line change
@@ -1339,12 +1339,10 @@ deque_reduce(dequeobject *deque)
13391339
PyObject *dict, *it;
13401340
_Py_IDENTIFIER(__dict__);
13411341

1342-
dict = _PyObject_GetAttrId((PyObject *)deque, &PyId___dict__);
1342+
if (_PyObject_LookupAttrId((PyObject *)deque, &PyId___dict__, &dict) < 0) {
1343+
return NULL;
1344+
}
13431345
if (dict == NULL) {
1344-
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
1345-
return NULL;
1346-
}
1347-
PyErr_Clear();
13481346
dict = Py_None;
13491347
Py_INCREF(dict);
13501348
}

Modules/_io/bufferedio.c

+3-4
Original file line numberDiff line numberDiff line change
@@ -1541,7 +1541,9 @@ _bufferedreader_read_all(buffered *self)
15411541
}
15421542
_bufferedreader_reset_buf(self);
15431543

1544-
readall = _PyObject_GetAttrWithoutError(self->raw, _PyIO_str_readall);
1544+
if (_PyObject_LookupAttr(self->raw, _PyIO_str_readall, &readall) < 0) {
1545+
goto cleanup;
1546+
}
15451547
if (readall) {
15461548
tmp = _PyObject_CallNoArg(readall);
15471549
Py_DECREF(readall);
@@ -1561,9 +1563,6 @@ _bufferedreader_read_all(buffered *self)
15611563
}
15621564
goto cleanup;
15631565
}
1564-
else if (PyErr_Occurred()) {
1565-
goto cleanup;
1566-
}
15671566

15681567
chunks = PyList_New(0);
15691568
if (chunks == NULL)

Modules/_io/fileio.c

+3-5
Original file line numberDiff line numberDiff line change
@@ -1073,12 +1073,10 @@ fileio_repr(fileio *self)
10731073
if (self->fd < 0)
10741074
return PyUnicode_FromFormat("<_io.FileIO [closed]>");
10751075

1076-
nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
1076+
if (_PyObject_LookupAttrId((PyObject *) self, &PyId_name, &nameobj) < 0) {
1077+
return NULL;
1078+
}
10771079
if (nameobj == NULL) {
1078-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
1079-
PyErr_Clear();
1080-
else
1081-
return NULL;
10821080
res = PyUnicode_FromFormat(
10831081
"<_io.FileIO fd=%d mode='%s' closefd=%s>",
10841082
self->fd, mode_string(self), self->closefd ? "True" : "False");

Modules/_io/iobase.c

+13-25
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,12 @@ static int
133133
iobase_is_closed(PyObject *self)
134134
{
135135
PyObject *res;
136+
int ret;
136137
/* This gets the derived attribute, which is *not* __IOBase_closed
137138
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;
139+
ret = _PyObject_LookupAttrId(self, &PyId___IOBase_closed, &res);
140+
Py_XDECREF(res);
141+
return ret;
148142
}
149143

150144
/* Flush and close methods */
@@ -190,20 +184,16 @@ iobase_check_closed(PyObject *self)
190184
int closed;
191185
/* This gets the derived attribute, which is *not* __IOBase_closed
192186
in most cases! */
193-
res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
194-
if (res == NULL) {
195-
if (PyErr_Occurred()) {
187+
closed = _PyObject_LookupAttr(self, _PyIO_str_closed, &res);
188+
if (closed > 0) {
189+
closed = PyObject_IsTrue(res);
190+
Py_DECREF(res);
191+
if (closed > 0) {
192+
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
196193
return -1;
197194
}
198-
return 0;
199-
}
200-
closed = PyObject_IsTrue(res);
201-
Py_DECREF(res);
202-
if (closed <= 0) {
203-
return closed;
204195
}
205-
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
206-
return -1;
196+
return closed;
207197
}
208198

209199
PyObject *
@@ -273,8 +263,7 @@ iobase_finalize(PyObject *self)
273263

274264
/* If `closed` doesn't exist or can't be evaluated as bool, then the
275265
object is probably in an unusable state, so ignore. */
276-
res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
277-
if (res == NULL) {
266+
if (_PyObject_LookupAttr(self, _PyIO_str_closed, &res) <= 0) {
278267
PyErr_Clear();
279268
closed = -1;
280269
}
@@ -538,8 +527,7 @@ _io__IOBase_readline_impl(PyObject *self, Py_ssize_t limit)
538527
PyObject *peek, *buffer, *result;
539528
Py_ssize_t old_size = -1;
540529

541-
peek = _PyObject_GetAttrWithoutError(self, _PyIO_str_peek);
542-
if (peek == NULL && PyErr_Occurred()) {
530+
if (_PyObject_LookupAttr(self, _PyIO_str_peek, &peek) < 0) {
543531
return NULL;
544532
}
545533

Modules/_io/textio.c

+18-30
Original file line numberDiff line numberDiff line change
@@ -924,14 +924,10 @@ _textiowrapper_set_encoder(textio *self, PyObject *codec_info,
924924
return -1;
925925

926926
/* Get the normalized named of the codec */
927-
res = _PyObject_GetAttrId(codec_info, &PyId_name);
928-
if (res == NULL) {
929-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
930-
PyErr_Clear();
931-
else
932-
return -1;
927+
if (_PyObject_LookupAttrId(codec_info, &PyId_name, &res) < 0) {
928+
return -1;
933929
}
934-
else if (PyUnicode_Check(res)) {
930+
if (res != NULL && PyUnicode_Check(res)) {
935931
const encodefuncentry *e = encodefuncs;
936932
while (e->name != NULL) {
937933
if (_PyUnicode_EqualToASCIIString(res, e->name)) {
@@ -1177,19 +1173,17 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
11771173

11781174
if (Py_TYPE(buffer) == &PyBufferedReader_Type ||
11791175
Py_TYPE(buffer) == &PyBufferedWriter_Type ||
1180-
Py_TYPE(buffer) == &PyBufferedRandom_Type) {
1181-
raw = _PyObject_GetAttrId(buffer, &PyId_raw);
1176+
Py_TYPE(buffer) == &PyBufferedRandom_Type)
1177+
{
1178+
if (_PyObject_LookupAttrId(buffer, &PyId_raw, &raw) < 0)
1179+
goto error;
11821180
/* Cache the raw FileIO object to speed up 'closed' checks */
1183-
if (raw == NULL) {
1184-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
1185-
PyErr_Clear();
1181+
if (raw != NULL) {
1182+
if (Py_TYPE(raw) == &PyFileIO_Type)
1183+
self->raw = raw;
11861184
else
1187-
goto error;
1185+
Py_DECREF(raw);
11881186
}
1189-
else if (Py_TYPE(raw) == &PyFileIO_Type)
1190-
self->raw = raw;
1191-
else
1192-
Py_DECREF(raw);
11931187
}
11941188

11951189
res = _PyObject_CallMethodId(buffer, &PyId_seekable, NULL);
@@ -1201,17 +1195,12 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
12011195
goto error;
12021196
self->seekable = self->telling = r;
12031197

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 {
1198+
r = _PyObject_LookupAttr(buffer, _PyIO_str_read1, &res);
1199+
if (r < 0) {
12131200
goto error;
12141201
}
1202+
Py_XDECREF(res);
1203+
self->has_read1 = r;
12151204

12161205
self->encoding_start_of_stream = 0;
12171206
if (_textiowrapper_fix_encoder_state(self) < 0) {
@@ -3020,10 +3009,9 @@ textiowrapper_newlines_get(textio *self, void *context)
30203009
{
30213010
PyObject *res;
30223011
CHECK_ATTACHED(self);
3023-
if (self->decoder == NULL)
3024-
Py_RETURN_NONE;
3025-
res = _PyObject_GetAttrWithoutError(self->decoder, _PyIO_str_newlines);
3026-
if (res == NULL && !PyErr_Occurred()) {
3012+
if (self->decoder == NULL ||
3013+
_PyObject_LookupAttr(self->decoder, _PyIO_str_newlines, &res) == 0)
3014+
{
30273015
Py_RETURN_NONE;
30283016
}
30293017
return res;

0 commit comments

Comments
 (0)