Skip to content

Commit f739025

Browse files
committed
PYTHON-698 - Try encoding types with broken __getattr__ methods
1 parent 6991b73 commit f739025

File tree

2 files changed

+28
-10
lines changed

2 files changed

+28
-10
lines changed

bson/_cbsonmodule.c

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -554,33 +554,40 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
554554
unsigned char check_keys,
555555
unsigned char uuid_subtype) {
556556
struct module_state *state = GETSTATE(self);
557+
PyObject* type_marker = NULL;
557558

558559
/*
559560
* Don't use PyObject_IsInstance for our custom types. It causes
560561
* problems with python sub interpreters. Our custom types should
561562
* have a _type_marker attribute, which we can switch on instead.
562563
*/
563564
if (PyObject_HasAttrString(value, "_type_marker")) {
564-
long type;
565-
PyObject* type_marker = PyObject_GetAttrString(value, "_type_marker");
566-
if (type_marker == NULL)
565+
type_marker = PyObject_GetAttrString(value, "_type_marker");
566+
if (type_marker == NULL) {
567567
return 0;
568+
}
569+
}
570+
/*
571+
* Python objects with broken __getattr__ implementations could return
572+
* arbitrary types for a call to PyObject_GetAttrString. For example
573+
* pymongo.database.Database returns a new Collection instance for
574+
* __getattr__ calls with names that don't match an existing attribute
575+
* or method. In some cases "value" could be a subtype of something
576+
* we know how to serialize. Make a best effort to encode these types.
577+
*/
568578
#if PY_MAJOR_VERSION >= 3
569-
type = PyLong_AsLong(type_marker);
579+
if (type_marker && PyLong_CheckExact(type_marker)) {
580+
long type = PyLong_AsLong(type_marker);
570581
#else
571-
type = PyInt_AsLong(type_marker);
582+
if (type_marker && PyInt_CheckExact(type_marker)) {
583+
long type = PyInt_AsLong(type_marker);
572584
#endif
573585
Py_DECREF(type_marker);
574586
/*
575587
* Py(Long|Int)_AsLong returns -1 for error but -1 is a valid value
576588
* so we call PyErr_Occurred to differentiate.
577-
*
578-
* One potential reason for an error is the user passing an invalid
579-
* type that overrides __getattr__ (e.g. pymongo.collection.Collection)
580589
*/
581590
if (type == -1 && PyErr_Occurred()) {
582-
PyErr_Clear();
583-
_set_cannot_encode(value);
584591
return 0;
585592
}
586593
switch (type) {
@@ -792,6 +799,8 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
792799
return 1;
793800
}
794801
}
802+
} else {
803+
Py_XDECREF(type_marker);
795804
}
796805

797806
/* No _type_marker attibute or not one of our types. */

test/test_collection.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,8 +2085,17 @@ def test_drop_indexes_non_existant(self):
20852085
# (Shame on me)
20862086
def test_bad_encode(self):
20872087
c = self.db.test
2088+
c.drop()
20882089
self.assertRaises(InvalidDocument, c.save, {"x": c})
20892090

2091+
class BadGetAttr(dict):
2092+
def __getattr__(self, name):
2093+
pass
2094+
2095+
bad = BadGetAttr([('foo', 'bar')])
2096+
c.insert({'bad': bad})
2097+
self.assertEqual('bar', c.find_one()['bad']['foo'])
2098+
20902099
def test_bad_dbref(self):
20912100
c = self.db.test
20922101
c.drop()

0 commit comments

Comments
 (0)