@@ -109,6 +109,7 @@ struct module_state {
109109#define DATETIME_CLAMP 2
110110#define DATETIME_MS 3
111111#define DATETIME_AUTO 4
112+ #define PYTHON_3_12 0x030C0000
112113
113114/* Converts integer to its string representation in decimal notation. */
114115extern int cbson_long_long_to_str (long long num , char * str , size_t size ) {
@@ -249,6 +250,67 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
249250 */
250251static int write_raw_doc (buffer_t buffer , PyObject * raw , PyObject * _raw );
251252
253+ #if PY_VERSION_HEX >= PYTHON_3_12
254+ /* Transfer traceback from old_exc to new_exc.
255+ * Steals reference to old_exc. */
256+ static PyObject * _transfer_traceback (PyObject * old_exc , PyObject * new_exc ) {
257+ PyObject * tb = PyException_GetTraceback (old_exc );
258+ if (tb ) {
259+ PyException_SetTraceback (new_exc , tb );
260+ Py_DECREF (tb );
261+ }
262+ Py_DECREF (old_exc );
263+ return new_exc ;
264+ }
265+ #endif
266+
267+ /* Rewrap the current exception as InvalidBSON(str(e)) if it is not already an InvalidBSON error. */
268+ static void _rewrap_as_invalid_bson (void ) {
269+ #if PY_VERSION_HEX >= PYTHON_3_12
270+ PyObject * exc = PyErr_GetRaisedException ();
271+ if (exc && PyErr_GivenExceptionMatches (exc , PyExc_Exception )) {
272+ PyObject * InvalidBSON = _error ("InvalidBSON" );
273+ if (InvalidBSON ) {
274+ if (!PyErr_GivenExceptionMatches (exc , InvalidBSON )) {
275+ PyObject * err_msg = PyObject_Str (exc );
276+ if (err_msg ) {
277+ PyObject * new_exc = PyObject_CallOneArg (InvalidBSON , err_msg );
278+ if (new_exc ) {
279+ exc = _transfer_traceback (exc , new_exc );
280+ }
281+ }
282+ Py_XDECREF (err_msg );
283+ }
284+ Py_DECREF (InvalidBSON );
285+ }
286+ }
287+ /* Steals reference to exc. */
288+ PyErr_SetRaisedException (exc );
289+ #else
290+ PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
291+ PyObject * InvalidBSON = NULL ;
292+ PyErr_Fetch (& etype , & evalue , & etrace );
293+ if (PyErr_GivenExceptionMatches (etype , PyExc_Exception )) {
294+ InvalidBSON = _error ("InvalidBSON" );
295+ if (InvalidBSON ) {
296+ if (!PyErr_GivenExceptionMatches (etype , InvalidBSON )) {
297+ Py_DECREF (etype );
298+ etype = InvalidBSON ;
299+ if (evalue ) {
300+ PyObject * msg = PyObject_Str (evalue );
301+ Py_DECREF (evalue );
302+ evalue = msg ;
303+ }
304+ PyErr_NormalizeException (& etype , & evalue , & etrace );
305+ } else {
306+ Py_DECREF (InvalidBSON );
307+ }
308+ }
309+ }
310+ PyErr_Restore (etype , evalue , etrace );
311+ #endif
312+ }
313+
252314/* Date stuff */
253315static PyObject * datetime_from_millis (long long millis ) {
254316 /* To encode a datetime instance like datetime(9999, 12, 31, 23, 59, 59, 999999)
@@ -294,34 +356,57 @@ static PyObject* datetime_from_millis(long long millis) {
294356 timeinfo .tm_sec ,
295357 microseconds );
296358 if (!datetime ) {
297- PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
359+ #if PY_VERSION_HEX >= PYTHON_3_12
360+ PyObject * exc = PyErr_GetRaisedException ();
298361
299- /*
300- * Calling _error clears the error state, so fetch it first.
301- */
302- PyErr_Fetch (& etype , & evalue , & etrace );
303-
304- /* Only add addition error message on ValueError exceptions. */
305- if (PyErr_GivenExceptionMatches (etype , PyExc_ValueError )) {
306- if (evalue ) {
307- PyObject * err_msg = PyObject_Str (evalue );
362+ /* Only add additional error message on ValueError exceptions. */
363+ if (exc && PyErr_GivenExceptionMatches (exc , PyExc_ValueError )) {
364+ PyObject * err_msg = PyObject_Str (exc );
308365 if (err_msg ) {
309366 PyObject * appendage = PyUnicode_FromString (" (Consider Using CodecOptions(datetime_conversion=DATETIME_AUTO) or MongoClient(datetime_conversion='DATETIME_AUTO')). See: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/data-formats/dates-and-times/#handling-out-of-range-datetimes" );
310367 if (appendage ) {
311368 PyObject * msg = PyUnicode_Concat (err_msg , appendage );
312369 if (msg ) {
313- Py_DECREF (evalue );
314- evalue = msg ;
370+ PyObject * new_exc = PyObject_CallOneArg (PyExc_ValueError , msg );
371+ if (new_exc ) {
372+ exc = _transfer_traceback (exc , new_exc );
373+ }
374+ Py_DECREF (msg );
315375 }
316376 }
317377 Py_XDECREF (appendage );
318378 }
319379 Py_XDECREF (err_msg );
320380 }
321- PyErr_NormalizeException (& etype , & evalue , & etrace );
322- }
323- /* Steals references to args. */
324- PyErr_Restore (etype , evalue , etrace );
381+ /* Steals reference to exc. */
382+ PyErr_SetRaisedException (exc );
383+ #else
384+ /* Calling _error clears the error state, so fetch it first.*/
385+ PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
386+ PyErr_Fetch (& etype , & evalue , & etrace );
387+
388+ /* Only add additional error message on ValueError exceptions. */
389+ if (PyErr_GivenExceptionMatches (etype , PyExc_ValueError )) {
390+ if (evalue ) {
391+ PyObject * err_msg = PyObject_Str (evalue );
392+ if (err_msg ) {
393+ PyObject * appendage = PyUnicode_FromString (" (Consider Using CodecOptions(datetime_conversion=DATETIME_AUTO) or MongoClient(datetime_conversion='DATETIME_AUTO')). See: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/data-formats/dates-and-times/#handling-out-of-range-datetimes" );
394+ if (appendage ) {
395+ PyObject * msg = PyUnicode_Concat (err_msg , appendage );
396+ if (msg ) {
397+ Py_DECREF (evalue );
398+ evalue = msg ;
399+ }
400+ }
401+ Py_XDECREF (appendage );
402+ }
403+ Py_XDECREF (err_msg );
404+ }
405+ PyErr_NormalizeException (& etype , & evalue , & etrace );
406+ }
407+ /* Steals references to args. */
408+ PyErr_Restore (etype , evalue , etrace );
409+ #endif
325410 }
326411 return datetime ;
327412}
@@ -1681,6 +1766,46 @@ static int write_raw_doc(buffer_t buffer, PyObject* raw, PyObject* _raw_str) {
16811766/* Update Invalid Document error to include doc as a property.
16821767 */
16831768void handle_invalid_doc_error (PyObject * dict ) {
1769+ #if PY_VERSION_HEX >= PYTHON_3_12
1770+ PyObject * exc = PyErr_GetRaisedException ();
1771+ PyObject * msg = NULL , * new_msg = NULL ;
1772+ PyObject * InvalidDocument = NULL ;
1773+
1774+ if (exc == NULL ) {
1775+ return ;
1776+ }
1777+
1778+ InvalidDocument = _error ("InvalidDocument" );
1779+ if (InvalidDocument == NULL ) {
1780+ goto cleanup ;
1781+ }
1782+
1783+ if (PyErr_GivenExceptionMatches (exc , InvalidDocument )) {
1784+ msg = PyObject_Str (exc );
1785+ if (msg ) {
1786+ const char * msg_utf8 = PyUnicode_AsUTF8 (msg );
1787+ if (msg_utf8 == NULL ) {
1788+ goto cleanup ;
1789+ }
1790+ new_msg = PyUnicode_FromFormat ("Invalid document: %s" , msg_utf8 );
1791+ if (new_msg == NULL ) {
1792+ goto cleanup ;
1793+ }
1794+ /* Add doc to the error instance as a property. */
1795+ PyObject * exc_args [2 ] = {new_msg , dict };
1796+ PyObject * new_exc = PyObject_Vectorcall (InvalidDocument , exc_args , 2 , NULL );
1797+ if (new_exc ) {
1798+ exc = _transfer_traceback (exc , new_exc );
1799+ }
1800+ }
1801+ }
1802+ cleanup :
1803+ /* Steals reference to exc. */
1804+ PyErr_SetRaisedException (exc );
1805+ Py_XDECREF (msg );
1806+ Py_XDECREF (InvalidDocument );
1807+ Py_XDECREF (new_msg );
1808+ #else
16841809 PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
16851810 PyObject * msg = NULL , * new_msg = NULL , * new_evalue = NULL ;
16861811 PyErr_Fetch (& etype , & evalue , & etrace );
@@ -1723,6 +1848,7 @@ void handle_invalid_doc_error(PyObject* dict) {
17231848 Py_XDECREF (InvalidDocument );
17241849 Py_XDECREF (new_evalue );
17251850 Py_XDECREF (new_msg );
1851+ #endif
17261852}
17271853
17281854
@@ -2155,7 +2281,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
21552281 }
21562282 memcpy (& length , buffer + * position , 4 );
21572283 length = BSON_UINT32_FROM_LE (length );
2158- if (max < length ) {
2284+ if (max - 5 < length ) { // Account for 5-byte header. max >= 5 guaranteed above
21592285 goto invalid ;
21602286 }
21612287
@@ -2654,42 +2780,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer,
26542780 * Wrap any non-InvalidBSON errors in InvalidBSON.
26552781 */
26562782 if (PyErr_Occurred ()) {
2657- PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
2658- PyObject * InvalidBSON = NULL ;
2659-
2660- /*
2661- * Calling _error clears the error state, so fetch it first.
2662- */
2663- PyErr_Fetch (& etype , & evalue , & etrace );
2664-
2665- /* Dont reraise anything but PyExc_Exceptions as InvalidBSON. */
2666- if (PyErr_GivenExceptionMatches (etype , PyExc_Exception )) {
2667- InvalidBSON = _error ("InvalidBSON" );
2668- if (InvalidBSON ) {
2669- if (!PyErr_GivenExceptionMatches (etype , InvalidBSON )) {
2670- /*
2671- * Raise InvalidBSON(str(e)).
2672- */
2673- Py_DECREF (etype );
2674- etype = InvalidBSON ;
2675-
2676- if (evalue ) {
2677- PyObject * msg = PyObject_Str (evalue );
2678- Py_DECREF (evalue );
2679- evalue = msg ;
2680- }
2681- PyErr_NormalizeException (& etype , & evalue , & etrace );
2682- } else {
2683- /*
2684- * The current exception matches InvalidBSON, so we don't
2685- * need this reference after all.
2686- */
2687- Py_DECREF (InvalidBSON );
2688- }
2689- }
2690- }
2691- /* Steals references to args. */
2692- PyErr_Restore (etype , evalue , etrace );
2783+ _rewrap_as_invalid_bson ();
26932784 } else {
26942785 PyObject * InvalidBSON = _error ("InvalidBSON" );
26952786 if (InvalidBSON ) {
@@ -2727,25 +2818,7 @@ static int _element_to_dict(PyObject* self, const char* string,
27272818 if (!* name ) {
27282819 /* If NULL is returned then wrap the UnicodeDecodeError
27292820 in an InvalidBSON error */
2730- PyObject * etype = NULL , * evalue = NULL , * etrace = NULL ;
2731- PyObject * InvalidBSON = NULL ;
2732-
2733- PyErr_Fetch (& etype , & evalue , & etrace );
2734- if (PyErr_GivenExceptionMatches (etype , PyExc_Exception )) {
2735- InvalidBSON = _error ("InvalidBSON" );
2736- if (InvalidBSON ) {
2737- Py_DECREF (etype );
2738- etype = InvalidBSON ;
2739-
2740- if (evalue ) {
2741- PyObject * msg = PyObject_Str (evalue );
2742- Py_DECREF (evalue );
2743- evalue = msg ;
2744- }
2745- PyErr_NormalizeException (& etype , & evalue , & etrace );
2746- }
2747- }
2748- PyErr_Restore (etype , evalue , etrace );
2821+ _rewrap_as_invalid_bson ();
27492822 return -1 ;
27502823 }
27512824 position += (unsigned )name_length + 1 ;
0 commit comments