@@ -155,18 +155,29 @@ fm_remove_hooks(void)
155155 }
156156}
157157
158+ static void
159+ fm_set_nomemory (int start , int stop )
160+ {
161+ /* Memory allocation fails after 'start' allocation requests, and until
162+ * 'stop' allocation requests except when 'stop' is negative or equal
163+ * to 0 (default) in which case allocation failures never stop. */
164+ FmData .start = start ;
165+ FmData .stop = stop ;
166+ FmData .count = 0 ;
167+ fm_setup_hooks ();
168+ }
169+
158170static PyObject *
159171set_nomemory (PyObject * self , PyObject * args )
160172{
161173 /* Memory allocation fails after 'start' allocation requests, and until
162174 * 'stop' allocation requests except when 'stop' is negative or equal
163175 * to 0 (default) in which case allocation failures never stop. */
164- FmData .count = 0 ;
165- FmData .stop = 0 ;
166- if (!PyArg_ParseTuple (args , "i|i" , & FmData .start , & FmData .stop )) {
176+ int start , stop = 0 ;
177+ if (!PyArg_ParseTuple (args , "i|i" , & start , & stop )) {
167178 return NULL ;
168179 }
169- fm_setup_hooks ( );
180+ fm_set_nomemory ( start , stop );
170181 Py_RETURN_NONE ;
171182}
172183
@@ -731,6 +742,129 @@ tracemalloc_track_race(PyObject *self, PyObject *args)
731742}
732743
733744
745+ struct bytes_resize_tracer {
746+ PyObject * create ;
747+ PyObject * destroy ;
748+ };
749+
750+
751+ static int
752+ bytes_resize_tracer (PyObject * obj , PyRefTracerEvent event , void * data )
753+ {
754+ if (event != PyRefTracer_CREATE && event != PyRefTracer_DESTROY ) {
755+ return 0 ;
756+ }
757+
758+ struct bytes_resize_tracer * tracer = (struct bytes_resize_tracer * )data ;
759+ if (!PyBytes_Check (obj )) {
760+ return 0 ;
761+ }
762+
763+ switch (event ) {
764+ case PyRefTracer_CREATE :
765+ tracer -> create = obj ;
766+ break ;
767+ case PyRefTracer_DESTROY :
768+ tracer -> destroy = obj ;
769+ break ;
770+ default :
771+ break ;
772+ }
773+ return 0 ;
774+ }
775+
776+
777+ // When _PyBytes_Resize() resizes a bytes object in-place, check that
778+ // PyRefTracer_DESTROY and PyRefTracer_CREATE events are emitted.
779+ // If no_memory is non-zero, inject MemoryError.
780+ static int
781+ check_bytes_resize_tracer (int no_memory )
782+ {
783+ PyObject * bytes = NULL ;
784+ PyRefTracer old_tracer = NULL ;
785+ void * old_tracer_data = NULL ;
786+ int restore_tracer = 0 ;
787+
788+ bytes = PyBytes_FromString ("hello" );
789+ if (bytes == NULL ) {
790+ goto error ;
791+ }
792+ assert (PyUnstable_Object_IsUniquelyReferenced (bytes ));
793+
794+ old_tracer = PyRefTracer_GetTracer (& old_tracer_data );
795+ restore_tracer = 1 ;
796+
797+ struct bytes_resize_tracer tracer = {0 };
798+ if (PyRefTracer_SetTracer (bytes_resize_tracer , & tracer ) != 0 ) {
799+ goto error ;
800+ }
801+
802+ PyObject * old_bytes = bytes ; // borrowed reference
803+ if (no_memory ) {
804+ fm_set_nomemory (0 , 0 );
805+ int res = _PyBytes_Resize (& bytes , 100 );
806+ assert (res < 0 );
807+ assert (bytes == NULL );
808+ fm_remove_hooks ();
809+
810+ assert (PyErr_ExceptionMatches (PyExc_MemoryError ));
811+ PyErr_Clear ();
812+ }
813+ else {
814+ if (_PyBytes_Resize (& bytes , 100 ) < 0 ) {
815+ assert (bytes == NULL );
816+ goto error ;
817+ }
818+ }
819+
820+ if (tracer .destroy != old_bytes ) {
821+ PyErr_SetString (PyExc_AssertionError , "PyRefTracer_DESTROY not seen" );
822+ goto error ;
823+ }
824+
825+ int seen_create ;
826+ if (no_memory ) {
827+ seen_create = (tracer .create == old_bytes );
828+ }
829+ else {
830+ seen_create = (tracer .create == bytes );
831+ }
832+ if (!seen_create ) {
833+ PyErr_SetString (PyExc_AssertionError , "PyRefTracer_CREATE not seen" );
834+ goto error ;
835+ }
836+
837+ Py_CLEAR (bytes );
838+ if (PyRefTracer_SetTracer (old_tracer , old_tracer_data ) != 0 ) {
839+ restore_tracer = 0 ;
840+ goto error ;
841+ }
842+ return 0 ;
843+
844+ error :
845+ Py_XDECREF (bytes );
846+ if (restore_tracer ) {
847+ if (PyRefTracer_SetTracer (old_tracer , old_tracer_data ) != 0 ) {
848+ return -1 ;
849+ }
850+ }
851+ return -1 ;
852+ }
853+
854+
855+ static PyObject *
856+ test_bytes_resize_tracer (PyObject * self , PyObject * Py_UNUSED (ignored ))
857+ {
858+ if (check_bytes_resize_tracer (0 ) < 0 ) {
859+ return NULL ;
860+ }
861+ if (check_bytes_resize_tracer (1 ) < 0 ) {
862+ return NULL ;
863+ }
864+ Py_RETURN_NONE ;
865+ }
866+
867+
734868static PyMethodDef test_methods [] = {
735869 {"pymem_api_misuse" , pymem_api_misuse , METH_NOARGS },
736870 {"pymem_buffer_overflow" , pymem_buffer_overflow , METH_NOARGS },
@@ -745,6 +879,7 @@ static PyMethodDef test_methods[] = {
745879 {"test_pymem_setrawallocators" , test_pymem_setrawallocators , METH_NOARGS },
746880 {"test_pyobject_new" , test_pyobject_new , METH_NOARGS },
747881 {"test_pyobject_setallocators" , test_pyobject_setallocators , METH_NOARGS },
882+ {"test_bytes_resize_tracer" , test_bytes_resize_tracer , METH_NOARGS },
748883
749884 // Tracemalloc tests
750885 {"tracemalloc_track" , tracemalloc_track , METH_VARARGS },
0 commit comments