@@ -177,18 +177,29 @@ fm_remove_hooks(void)
177177 }
178178}
179179
180+ static void
181+ fm_set_nomemory (int start , int stop )
182+ {
183+ /* Memory allocation fails after 'start' allocation requests, and until
184+ * 'stop' allocation requests except when 'stop' is negative or equal
185+ * to 0 (default) in which case allocation failures never stop. */
186+ FmData .start = start ;
187+ FmData .stop = stop ;
188+ FmData .count = 0 ;
189+ fm_setup_hooks ();
190+ }
191+
180192static PyObject *
181193set_nomemory (PyObject * self , PyObject * args )
182194{
183195 /* Memory allocation fails after 'start' allocation requests, and until
184196 * 'stop' allocation requests except when 'stop' is negative or equal
185197 * to 0 (default) in which case allocation failures never stop. */
186- FmData .count = 0 ;
187- FmData .stop = 0 ;
188- if (!PyArg_ParseTuple (args , "i|i" , & FmData .start , & FmData .stop )) {
198+ int start , stop = 0 ;
199+ if (!PyArg_ParseTuple (args , "i|i" , & start , & stop )) {
189200 return NULL ;
190201 }
191- fm_setup_hooks ( );
202+ fm_set_nomemory ( start , stop );
192203 Py_RETURN_NONE ;
193204}
194205
@@ -821,6 +832,129 @@ get_process_memory_usage(PyObject *self, PyObject *args)
821832#endif
822833
823834
835+ struct bytes_resize_tracer {
836+ PyObject * create ;
837+ PyObject * destroy ;
838+ };
839+
840+
841+ static int
842+ bytes_resize_tracer (PyObject * obj , PyRefTracerEvent event , void * data )
843+ {
844+ if (event != PyRefTracer_CREATE && event != PyRefTracer_DESTROY ) {
845+ return 0 ;
846+ }
847+
848+ struct bytes_resize_tracer * tracer = (struct bytes_resize_tracer * )data ;
849+ if (!PyBytes_Check (obj )) {
850+ return 0 ;
851+ }
852+
853+ switch (event ) {
854+ case PyRefTracer_CREATE :
855+ tracer -> create = obj ;
856+ break ;
857+ case PyRefTracer_DESTROY :
858+ tracer -> destroy = obj ;
859+ break ;
860+ default :
861+ break ;
862+ }
863+ return 0 ;
864+ }
865+
866+
867+ // When _PyBytes_Resize() resizes a bytes object in-place, check that
868+ // PyRefTracer_DESTROY and PyRefTracer_CREATE events are emitted.
869+ // If no_memory is non-zero, inject MemoryError.
870+ static int
871+ check_bytes_resize_tracer (int no_memory )
872+ {
873+ PyObject * bytes = NULL ;
874+ PyRefTracer old_tracer = NULL ;
875+ void * old_tracer_data = NULL ;
876+ int restore_tracer = 0 ;
877+
878+ bytes = PyBytes_FromString ("hello" );
879+ if (bytes == NULL ) {
880+ goto error ;
881+ }
882+ assert (PyUnstable_Object_IsUniquelyReferenced (bytes ));
883+
884+ old_tracer = PyRefTracer_GetTracer (& old_tracer_data );
885+ restore_tracer = 1 ;
886+
887+ struct bytes_resize_tracer tracer = {0 };
888+ if (PyRefTracer_SetTracer (bytes_resize_tracer , & tracer ) != 0 ) {
889+ goto error ;
890+ }
891+
892+ PyObject * old_bytes = bytes ; // borrowed reference
893+ if (no_memory ) {
894+ fm_set_nomemory (0 , 0 );
895+ int res = _PyBytes_Resize (& bytes , 100 );
896+ assert (res < 0 );
897+ assert (bytes == NULL );
898+ fm_remove_hooks ();
899+
900+ assert (PyErr_ExceptionMatches (PyExc_MemoryError ));
901+ PyErr_Clear ();
902+ }
903+ else {
904+ if (_PyBytes_Resize (& bytes , 100 ) < 0 ) {
905+ assert (bytes == NULL );
906+ goto error ;
907+ }
908+ }
909+
910+ if (tracer .destroy != old_bytes ) {
911+ PyErr_SetString (PyExc_AssertionError , "PyRefTracer_DESTROY not seen" );
912+ goto error ;
913+ }
914+
915+ int seen_create ;
916+ if (no_memory ) {
917+ seen_create = (tracer .create == old_bytes );
918+ }
919+ else {
920+ seen_create = (tracer .create == bytes );
921+ }
922+ if (!seen_create ) {
923+ PyErr_SetString (PyExc_AssertionError , "PyRefTracer_CREATE not seen" );
924+ goto error ;
925+ }
926+
927+ Py_CLEAR (bytes );
928+ if (PyRefTracer_SetTracer (old_tracer , old_tracer_data ) != 0 ) {
929+ restore_tracer = 0 ;
930+ goto error ;
931+ }
932+ return 0 ;
933+
934+ error :
935+ Py_XDECREF (bytes );
936+ if (restore_tracer ) {
937+ if (PyRefTracer_SetTracer (old_tracer , old_tracer_data ) != 0 ) {
938+ return -1 ;
939+ }
940+ }
941+ return -1 ;
942+ }
943+
944+
945+ static PyObject *
946+ test_bytes_resize_tracer (PyObject * self , PyObject * Py_UNUSED (ignored ))
947+ {
948+ if (check_bytes_resize_tracer (0 ) < 0 ) {
949+ return NULL ;
950+ }
951+ if (check_bytes_resize_tracer (1 ) < 0 ) {
952+ return NULL ;
953+ }
954+ Py_RETURN_NONE ;
955+ }
956+
957+
824958static PyMethodDef test_methods [] = {
825959 {"pymem_api_misuse" , pymem_api_misuse , METH_NOARGS },
826960 {"pymem_buffer_overflow" , pymem_buffer_overflow , METH_NOARGS },
@@ -838,6 +972,7 @@ static PyMethodDef test_methods[] = {
838972#if TARGET_OS_OSX || defined (__FreeBSD__ )
839973 {"get_process_memory_usage" , get_process_memory_usage , METH_VARARGS },
840974#endif
975+ {"test_bytes_resize_tracer" , test_bytes_resize_tracer , METH_NOARGS },
841976
842977 // Tracemalloc tests
843978 {"tracemalloc_track" , tracemalloc_track , METH_VARARGS },
0 commit comments