10
10
11
11
#define OFF (x ) offsetof(PyFrameObject, x)
12
12
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
+
13
24
static PyMemberDef frame_memberlist [] = {
14
25
{"f_back" , T_OBJECT , OFF (f_back ), READONLY },
15
26
{"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,
813
824
814
825
map and values are input arguments. map is a tuple of strings.
815
826
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.
819
832
820
833
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) .
825
838
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.
828
841
*/
829
842
830
843
static void
@@ -847,7 +860,16 @@ dict_to_map(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
847
860
}
848
861
if (deref ) {
849
862
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 */
851
873
if (PyCell_Set (values [j ], value ) < 0 )
852
874
PyErr_Clear ();
853
875
}
@@ -860,7 +882,7 @@ dict_to_map(PyObject *map, Py_ssize_t nmap, PyObject *dict, PyObject **values,
860
882
}
861
883
862
884
int
863
- PyFrame_FastToLocalsWithError (PyFrameObject * f )
885
+ _PyFrame_FastToLocalsInternal (PyFrameObject * f , int deref )
864
886
{
865
887
/* Merge fast locals into f->f_locals */
866
888
PyObject * locals , * map ;
@@ -879,6 +901,14 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
879
901
if (locals == NULL )
880
902
return -1 ;
881
903
}
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
+
882
912
co = f -> f_code ;
883
913
map = co -> co_varnames ;
884
914
if (!PyTuple_Check (map )) {
@@ -898,8 +928,17 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
898
928
ncells = PyTuple_GET_SIZE (co -> co_cellvars );
899
929
nfreevars = PyTuple_GET_SIZE (co -> co_freevars );
900
930
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
+ */
901
940
if (map_to_dict (co -> co_cellvars , ncells ,
902
- locals , fast + co -> co_nlocals , 1 ))
941
+ locals , fast + co -> co_nlocals , deref ))
903
942
return -1 ;
904
943
905
944
/* If the namespace is unoptimized, then one of the
@@ -912,13 +951,20 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
912
951
*/
913
952
if (co -> co_flags & CO_OPTIMIZED ) {
914
953
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 )
916
955
return -1 ;
917
956
}
918
957
}
958
+
919
959
return 0 ;
920
960
}
921
961
962
+ int
963
+ PyFrame_FastToLocalsWithError (PyFrameObject * f )
964
+ {
965
+ return _PyFrame_FastToLocalsInternal (f , 1 );
966
+ }
967
+
922
968
void
923
969
PyFrame_FastToLocals (PyFrameObject * f )
924
970
{
0 commit comments