1010
1111#define OFF (x ) offsetof(PyFrameObject, x)
1212
13+ /* PEP558:
14+ *
15+ * Forward declaration of fastlocalsproxy
16+ * PyEval_GetLocals will need a new PyFrame_GetLocals() helper function
17+ * that ensures it always gets the snapshot reference and never the proxy
18+ * even when tracing is enabled.
19+ * That should probably be a public API for the benefit of third party debuggers
20+ * implemented in C.
21+ *
22+ */
23+
1324static PyMemberDef frame_memberlist [] = {
1425 {"f_back" , T_OBJECT , OFF (f_back ), READONLY },
1526 {"f_code" , T_OBJECT , OFF (f_code ), READONLY },
@@ -813,18 +824,20 @@ map_to_dict(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
813824
814825 map and values are input arguments. map is a tuple of strings.
815826 values is an array of PyObject*. At index i, map[i] is the name of
816- the variable with value values[i]. The function copies the first
817- nmap variable from map/values into dict. If values[i] is NULL,
818- the variable is deleted from dict.
827+ the variable with value values[i]. The function gets the new value
828+ for values[i] by looking up map[i] in the dict.
829+
830+ If clear is true and map[i] is missing from the dict, then values[i] is
831+ set to NULL. If clear is false, then values[i] is left alone in that case.
819832
820833 If deref is true, then the values being copied are cell variables
821- and the value is extracted from the cell variable before being put
822- in dict. If clear is true, then variables in map but not in dict
823- are set to NULL in map; if clear is false, variables missing in
824- dict are ignored .
834+ and the value is inserted into the cell variable rather than overwriting
835+ the value directly. If the value in the dict *is* the cell itself, then
836+ the cell value is left alone, and instead that value is written back
837+ into the dict (replacing the cell reference) .
825838
826- Exceptions raised while modifying the dict are silently ignored,
827- because there is no good way to report them.
839+ Exceptions raised while reading or updating the dict or updating a cell
840+ reference are silently ignored, because there is no good way to report them.
828841*/
829842
830843static void
@@ -847,7 +860,16 @@ dict_to_map(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
847860 }
848861 if (deref ) {
849862 assert (PyCell_Check (values [j ]));
850- if (PyCell_GET (values [j ]) != value ) {
863+ PyObject * cell_value = PyCell_GET (values [j ]);
864+ if (values [j ] == value ) {
865+ /* The dict currently contains the cell itself, so write the
866+ cell's value into the dict rather than the other way around
867+ */
868+ if (PyObject_SetItem (dict , key , cell_value ) != 0 ) {
869+ PyErr_Clear ();
870+ }
871+ } else if (cell_value != value ) {
872+ /* Write the value from the dict back into the cell */
851873 if (PyCell_Set (values [j ], value ) < 0 )
852874 PyErr_Clear ();
853875 }
@@ -860,7 +882,7 @@ dict_to_map(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
860882}
861883
862884int
863- PyFrame_FastToLocalsWithError (PyFrameObject * f )
885+ _PyFrame_FastToLocalsInternal (PyFrameObject * f , int deref )
864886{
865887 /* Merge fast locals into f->f_locals */
866888 PyObject * locals , * map ;
@@ -879,6 +901,14 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
879901 if (locals == NULL )
880902 return -1 ;
881903 }
904+ /* PEP558:
905+ *
906+ * If a trace function is active, ensure f_locals is a fastlocalsproxy
907+ * instance, while locals still refers to the underlying mapping.
908+ * If a trace function is *not* active, discard the proxy, if any (and
909+ * invalidate its reference back to the frame) to disallow further writes.
910+ */
911+
882912 co = f -> f_code ;
883913 map = co -> co_varnames ;
884914 if (!PyTuple_Check (map )) {
@@ -898,8 +928,17 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
898928 ncells = PyTuple_GET_SIZE (co -> co_cellvars );
899929 nfreevars = PyTuple_GET_SIZE (co -> co_freevars );
900930 if (ncells || nfreevars ) {
931+ /* If deref is true, we'll replace cells with their values in the
932+ namespace. If it's false, we'll include the cells themselves, which
933+ means PyFrame_LocalsToFast will skip writing them back (unless
934+ they've actually been modified).
935+
936+ The trace hook implementation relies on this to allow debuggers to
937+ inject changes to local variables without inadvertently resetting
938+ closure variables to a previous value.
939+ */
901940 if (map_to_dict (co -> co_cellvars , ncells ,
902- locals , fast + co -> co_nlocals , 1 ))
941+ locals , fast + co -> co_nlocals , deref ))
903942 return -1 ;
904943
905944 /* If the namespace is unoptimized, then one of the
@@ -912,13 +951,20 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
912951 */
913952 if (co -> co_flags & CO_OPTIMIZED ) {
914953 if (map_to_dict (co -> co_freevars , nfreevars ,
915- locals , fast + co -> co_nlocals + ncells , 1 ) < 0 )
954+ locals , fast + co -> co_nlocals + ncells , deref ) < 0 )
916955 return -1 ;
917956 }
918957 }
958+
919959 return 0 ;
920960}
921961
962+ int
963+ PyFrame_FastToLocalsWithError (PyFrameObject * f )
964+ {
965+ return _PyFrame_FastToLocalsInternal (f , 1 );
966+ }
967+
922968void
923969PyFrame_FastToLocals (PyFrameObject * f )
924970{
0 commit comments