Skip to content

Commit 3b832e6

Browse files
committed
Addressing PR comments
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
1 parent 7ad9131 commit 3b832e6

6 files changed

Lines changed: 59 additions & 36 deletions

File tree

Include/internal/pycore_dict.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ typedef struct {
3232
PyObject *_me_value; /* This field is only meaningful for combined tables */
3333
} PyDictUnicodeEntry;
3434

35-
#define _PyDictEntry_IsImmutable(entry) (((uintptr_t)((entry)->_me_value)) & 0x1)
36-
#define _PyDictEntry_SetImmutable(entry) ((entry)->_me_value = (PyObject*)((uintptr_t)(entry)->_me_value | 0x1))
35+
#define _PyDictEntry_IMMUTABLE_FLAG 0x1
36+
#define _PyDictEntry_VALUE_MASK ~_PyDictEntry_IMMUTABLE_FLAG
37+
38+
#define _PyDictEntry_IsImmutable(entry) (((uintptr_t)((entry)->_me_value)) & _PyDictEntry_IMMUTABLE_FLAG)
39+
#define _PyDictEntry_SetImmutable(entry) ((entry)->_me_value = (PyObject*)((uintptr_t)(entry)->_me_value | _PyDictEntry_IMMUTABLE_FLAG))
3740
#define _PyDictEntry_Hash(entry) ((entry)->me_hash)
3841
#define _PyDictEntry_Key(entry) ((entry)->me_key)
39-
#define _PyDictEntry_Value(entry) ((PyObject*)((((uintptr_t)((entry)->_me_value)) >> 1) << 1))
42+
#define _PyDictEntry_Value(entry) ((PyObject*)(((uintptr_t)((entry)->_me_value)) & _PyDictEntry_VALUE_MASK))
4043
#define _PyDictEntry_SetValue(entry, value) ((entry)->_me_value = value)
41-
#define _PyDictEntry_IsEmpty(entry) ((entry)->_me_value == NULL)
44+
#define _PyDictEntry_IsEmpty(entry) ((((uintptr_t)(entry)->_me_value) & _PyDictEntry_VALUE_MASK) == (uintptr_t)NULL)
4245

4346
extern PyObject *_PyDict_IsKeyImmutable(PyObject* op, PyObject* key);
4447

Include/internal/pycore_object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ static inline void _Py_SetImmutable(PyObject *op)
100100
}
101101
#define _Py_SetImmutable(op) _Py_SetImmutable(_PyObject_CAST(op))
102102

103+
// Check whether an object is writeable.
104+
// Note that during runtime finalization, all objects must be mutable
103105
#define Py_CHECKWRITE(op) ((op) && (!_Py_IsImmutable(op) || _Py_IsFinalizing()))
104106
#define Py_REQUIREWRITE(op, msg) {if (Py_CHECKWRITE(op)) { _PyObject_ASSERT_FAILED_MSG(op, msg); }}
105107

Include/object.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@ system also uses the second-to-top bit for managing immutable graphs.
8787

8888
#if SIZEOF_VOID_P > 4
8989
#define _Py_REFCNT_MASK 0xFFFFFFFF
90-
#define _Py_IMMUTABLE_MASK 0xC000000000
9190
#define _Py_IMMUTABLE_FLAG 0x4000000000
9291
#define _Py_IMMUTABLE_SCC_FLAG 0x8000000000
9392
#else
94-
#define _Py_REFCNT_MASK 0x3FFFFFFF
95-
#define _Py_IMMUTABLE_MASK 0xC0000000
96-
#define _Py_IMMUTABLE_FLAG 0x40000000
97-
#define _Py_IMMUTABLE_SCC_FLAG 0x80000000
93+
#define _Py_REFCNT_MASK 0x1FFFFFFF
94+
#define _Py_IMMUTABLE_FLAG 0x20000000
95+
#define _Py_IMMUTABLE_SCC_FLAG 0x40000000
9896
#endif
9997

98+
#define _Py_IMMUTABLE_MASK (_Py_IMMUTABLE_SCC_FLAG | _Py_IMMUTABLE_FLAG)
99+
100100
/*
101101
Immortalization:
102102
@@ -275,7 +275,7 @@ static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
275275

276276
static inline Py_ALWAYS_INLINE int _Py_IsImmutable(PyObject *op)
277277
{
278-
return (op->ob_refcnt & _Py_IMMUTABLE_FLAG) > 0;
278+
return (op->ob_refcnt & _Py_IMMUTABLE_MASK) > 0;
279279
}
280280
#define _Py_IsImmutable(op) _Py_IsImmutable(_PyObject_CAST(op))
281281

Lib/test/test_freeze.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ def d():
293293
def test_globals_copy(self):
294294
def f():
295295
global global0
296+
ref_1 = global0
297+
ref_2 = global0
296298
return global0
297299

298300
expected = f()

Python/immutability.c

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ static PyObject* pop(PyObject* s){
1919
return NULL;
2020
}
2121

22+
Py_INCREF(item);
23+
2224
if(PyList_SetSlice(s, size - 1, size, NULL)){
25+
Py_DECREF(item);
2326
return NULL;
2427
}
2528

26-
Py_INCREF(item);
2729
return item;
2830
}
2931

@@ -44,12 +46,14 @@ static bool is_c_wrapper(PyObject* obj){
4446
* Special function for walking the reachable graph of a function object.
4547
*
4648
* This is necessary because the function object has a pointer to the global
47-
* object, and this is problematic because freezing any function will make the
48-
* global object immutable, which is not always the desired behaviour.
49+
* dictionary, and this is problematic because freezing any function directly
50+
* (as we do with other objects) would make all globals immutable.
4951
*
50-
* This function attempts to find the globals that a function will use, and freeze
51-
* just those, and prevent those keys from being updated in the global dictionary
52-
* from this point onwards.
52+
* Instead, we walk the function and find any places where it references
53+
* global variables or builtins, and then freeze just those objects. The globals
54+
* and builtins dictionaries for the function are then replaced with frozen
55+
* copies containing just those globals and builtins we were able to determine
56+
* the function uses.
5357
*/
5458
static PyObject* walk_function(PyObject* op, PyObject* frontier)
5559
{
@@ -129,15 +133,15 @@ static PyObject* walk_function(PyObject* op, PyObject* frontier)
129133
}
130134

131135
while(PyList_Size(f_stack) != 0){
132-
f_ptr = pop(f_stack); // fp.rc = x + 1
136+
f_ptr = pop(f_stack);
133137
_PyObject_ASSERT(f_ptr, PyCode_Check(f_ptr));
134138
f_code = (PyCodeObject*)f_ptr;
135139

136140
size = 0;
137141
if (f_code->co_names != NULL)
138142
size = PySequence_Fast_GET_SIZE(f_code->co_names);
139143
for(Py_ssize_t i = 0; i < size; i++){
140-
PyObject* name = PySequence_Fast_GET_ITEM(f_code->co_names, i); // name.rc = x
144+
PyObject* name = PySequence_Fast_GET_ITEM(f_code->co_names, i);
141145

142146
if(PyUnicode_CompareWithASCIIString(name, "globals") == 0){
143147
// if the code calls the globals() builtin, then any
@@ -165,7 +169,7 @@ static PyObject* walk_function(PyObject* op, PyObject* frontier)
165169
return NULL;
166170
}
167171
}else if(PyDict_Contains(module_dict, name)){
168-
PyObject* value = PyDict_GetItem(module_dict, name); // value.rc = x
172+
PyObject* value = PyDict_GetItem(module_dict, name);
169173

170174
_PyDict_SetKeyImmutable((PyDictObject*)module_dict, name);
171175

@@ -179,7 +183,7 @@ static PyObject* walk_function(PyObject* op, PyObject* frontier)
179183

180184
size = PySequence_Fast_GET_SIZE(f_code->co_consts);
181185
for(Py_ssize_t i = 0; i < size; i++){
182-
PyObject* value = PySequence_Fast_GET_ITEM(f_code->co_consts, i); // value.rc = x
186+
PyObject* value = PySequence_Fast_GET_ITEM(f_code->co_consts, i);
183187
if(!_Py_IsImmutable(value)){
184188
if(PyCode_Check(value)){
185189
_Py_SetImmutable(value);
@@ -195,6 +199,11 @@ static PyObject* walk_function(PyObject* op, PyObject* frontier)
195199
}
196200

197201
if(check_globals && PyUnicode_Check(value)){
202+
// if the code calls the globals() builtin, then any
203+
// cellvar or const in the function could, potentially, refer to
204+
// a global variable. As such, we need to check if the globals
205+
// dictionary contains that key and then make it immutable
206+
// from this point forwards.
198207
PyObject* name = value;
199208
if(PyDict_Contains(globals, name)){
200209
value = PyDict_GetItem(globals, name);
@@ -212,13 +221,20 @@ static PyObject* walk_function(PyObject* op, PyObject* frontier)
212221
Py_DECREF(f_stack);
213222

214223
if(check_globals){
224+
// if the code calls the globals() builtin, then any
225+
// cellvar or const in the function could, potentially, refer to
226+
// a global variable. As such, we need to check if the globals
227+
// dictionary contains that key and then make it immutable
228+
// from this point forwards.
229+
// we need to check the closure for any cellvars that are not
230+
// referenced in the code object, but are still used in the function
215231
size = 0;
216232
if(f->func_closure != NULL)
217233
size = PySequence_Fast_GET_SIZE(f->func_closure);
218234

219235
for(Py_ssize_t i=0; i < size; ++i){
220-
PyObject* cellvar = PySequence_Fast_GET_ITEM(f->func_closure, i); // cellvar.rc = x
221-
PyObject* value = PyCell_GET(cellvar); // value.rc = x
236+
PyObject* cellvar = PySequence_Fast_GET_ITEM(f->func_closure, i);
237+
PyObject* value = PyCell_GET(cellvar);
222238

223239
if(PyUnicode_Check(value)){
224240
PyObject* name = value;
@@ -271,7 +287,7 @@ static int freeze_visit(PyObject* obj, void* frontier)
271287

272288
PyObject* _Py_Freeze(PyObject* obj)
273289
{
274-
if(_Py_IsImmutable(obj) && _Py_IsImmutable(Py_TYPE(obj))){
290+
if(_Py_IsImmutable(obj)){
275291
Py_RETURN_NONE;
276292
}
277293

@@ -309,7 +325,7 @@ PyObject* _Py_Freeze(PyObject* obj)
309325
Py_DECREF(frozen_importlib);
310326

311327
while(PyList_Size(frontier) != 0){
312-
PyObject* item = pop(frontier); // item.rc = x + 1
328+
PyObject* item = pop(frontier);
313329

314330
if(item == blocking_on ||
315331
item == module_locks){
@@ -323,12 +339,11 @@ PyObject* _Py_Freeze(PyObject* obj)
323339
PyObject* type_op = NULL;
324340

325341
if(_Py_IsImmutable(item)){
326-
goto handle_type;
342+
continue;
327343
}
328344

329345
_Py_SetImmutable(item);
330346

331-
332347
if(is_c_wrapper(item)) {
333348
// C functions are not mutable, so we can skip them.
334349
continue;
@@ -342,20 +357,20 @@ PyObject* _Py_Freeze(PyObject* obj)
342357
Py_DECREF(frontier);
343358
return err;
344359
}
345-
goto handle_type;
346360
}
347-
348-
traverse = type->tp_traverse;
349-
if(traverse != NULL){
350-
if(traverse(item, (visitproc)freeze_visit, frontier)){
351-
Py_DECREF(blocking_on);
352-
Py_DECREF(module_locks);
353-
Py_DECREF(frontier);
354-
return NULL;
361+
else
362+
{
363+
traverse = type->tp_traverse;
364+
if(traverse != NULL){
365+
if(traverse(item, (visitproc)freeze_visit, frontier)){
366+
Py_DECREF(blocking_on);
367+
Py_DECREF(module_locks);
368+
Py_DECREF(frontier);
369+
return NULL;
370+
}
355371
}
356372
}
357373

358-
handle_type:
359374
type_op = _PyObject_CAST(item->ob_type);
360375
if (!_Py_IsImmutable(type_op)){
361376
if (PyList_Append(frontier, type_op))

Tools/gdb/libpython.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ def iteritems(self):
783783
for i in safe_range(nentries):
784784
ep = entries[i]
785785
value_ptr = ep['_me_value'].reinterpret_cast(gdb.lookup_type('uintptr_t'))
786+
# Clear the immutability flag
786787
pyop_value = PyObjectPtr.from_pyobject_ptr((value_ptr >> 1) << 1)
787788
if not pyop_value.is_null():
788789
pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])

0 commit comments

Comments
 (0)