Skip to content

Commit 0428f05

Browse files
committed
Modifying the semantics of function freezing
Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
1 parent 7081702 commit 0428f05

2 files changed

Lines changed: 74 additions & 83 deletions

File tree

Lib/test/test_freeze.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,5 +425,6 @@ def test_global_dict_mutation(self):
425425
self.assertTrue(isimmutable(f1))
426426
self.assertRaises(NotWriteableError, f1)
427427

428+
428429
if __name__ == '__main__':
429430
unittest.main()

Python/immutability.c

Lines changed: 73 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,6 @@ static bool is_c_wrapper(PyObject* obj){
8080
} \
8181
} while(0)
8282

83-
static PyObject* make_global_immutable(PyObject* globals, PyObject* name)
84-
{
85-
PyObject* value = PyDict_GetItem(globals, name); // value.rc = x
86-
87-
_PyDict_SetKeyImmutable((PyDictObject*)globals, name);
88-
89-
if(!_Py_IsImmutable(value)){
90-
return value;
91-
}else{
92-
Py_RETURN_NONE;
93-
}
94-
}
9583

9684
/**
9785
* Special function for walking the reachable graph of a function object.
@@ -106,27 +94,26 @@ static PyObject* make_global_immutable(PyObject* globals, PyObject* name)
10694
*/
10795
static PyObject* walk_function(PyObject* op, stack* frontier)
10896
{
109-
PyObject* builtins;
110-
PyObject* globals;
111-
PyObject* module;
112-
PyObject* module_dict;
113-
PyFunctionObject* f;
114-
PyObject* f_ptr;
115-
PyCodeObject* f_code;
97+
PyObject* builtins = NULL;
98+
PyObject* frozen_builtins = NULL;
99+
PyObject* globals = NULL;
100+
PyObject* frozen_globals = NULL;
101+
PyObject* module = NULL;
102+
PyObject* module_dict = NULL;
103+
PyFunctionObject* f = NULL;
104+
PyObject* f_ptr = NULL;
105+
PyCodeObject* f_code = NULL;
116106
Py_ssize_t size;
117-
stack* f_stack;
107+
stack* f_stack = NULL;
118108
bool check_globals = false;
109+
119110
_PyObject_ASSERT(op, PyFunction_Check(op));
120111

121112
_Py_SetImmutable(op);
122113

123114

124115
f = (PyFunctionObject*)op;
125116

126-
// TODO find a way to use traverse to avoid having to manually walk
127-
// the function's members
128-
// f->func_code needs special treatment (see below)
129-
// func_globals, func_builtins, and func_module can stay mutable, but depending on code we may need to make some keys immutable
130117
globals = f->func_globals;
131118
builtins = f->func_builtins;
132119

@@ -163,8 +150,17 @@ static PyObject* walk_function(PyObject* op, stack* frontier)
163150

164151
f_ptr = f->func_code;
165152
if(stack_push(f_stack, f_ptr)){
166-
stack_free(f_stack);
167-
return PyErr_NoMemory();
153+
goto nomemory;
154+
}
155+
156+
frozen_builtins = PyDict_New();
157+
if(frozen_builtins == NULL){
158+
goto nomemory;
159+
}
160+
161+
frozen_globals = PyDict_New();
162+
if(frozen_globals == NULL){
163+
goto nomemory;
168164
}
169165

170166
while(!stack_empty(f_stack)){
@@ -188,21 +184,20 @@ static PyObject* walk_function(PyObject* op, stack* frontier)
188184
}
189185

190186
if(PyDict_Contains(globals, name)){
191-
PyObject* value = make_global_immutable(globals, name);
192-
if(!Py_IsNone(value)){
193-
if(stack_push(frontier, value)){
194-
stack_free(f_stack);
195-
// frontier freed by the caller
196-
return PyErr_NoMemory();
197-
}
187+
PyObject* value = PyDict_GetItem(globals, name);
188+
if(PyDict_SetItem(frozen_globals, name, value)){
189+
Py_DECREF(frozen_builtins);
190+
Py_DECREF(frozen_globals);
191+
stack_free(f_stack);
192+
return NULL;
198193
}
199194
}else if(PyDict_Contains(builtins, name)){
200-
201-
_PyDict_SetKeyImmutable((PyDictObject*)builtins, name);
202-
203-
PyObject* value = PyDict_GetItem(builtins, name); // value.rc = x
204-
if(!_Py_IsImmutable(value)){
205-
_Py_SetImmutable(value);
195+
PyObject* value = PyDict_GetItem(builtins, name);
196+
if(PyDict_SetItem(frozen_builtins, name, value)){
197+
Py_DECREF(frozen_builtins);
198+
Py_DECREF(frozen_globals);
199+
stack_free(f_stack);
200+
return NULL;
206201
}
207202
}else if(PyDict_Contains(module_dict, name)){
208203
PyObject* value = PyDict_GetItem(module_dict, name); // value.rc = x
@@ -211,9 +206,7 @@ static PyObject* walk_function(PyObject* op, stack* frontier)
211206

212207
if(!_Py_IsImmutable(value)){
213208
if(stack_push(frontier, value)){
214-
stack_free(f_stack);
215-
// frontier freed by the caller
216-
return PyErr_NoMemory();
209+
goto nomemory;
217210
}
218211
}
219212
}
@@ -227,29 +220,24 @@ static PyObject* walk_function(PyObject* op, stack* frontier)
227220
_Py_SetImmutable(value);
228221

229222
if(stack_push(f_stack, value)){
230-
stack_free(f_stack);
231-
// frontier freed by the caller
232-
return PyErr_NoMemory();
223+
goto nomemory;
233224
}
234225
}else{
235226
if(stack_push(frontier, value)){
236-
stack_free(f_stack);
237-
// frontier freed by the caller
238-
return PyErr_NoMemory();
227+
goto nomemory;
239228
}
240229
}
241230
}
242231

243232
if(check_globals && PyUnicode_Check(value)){
244233
PyObject* name = value;
245234
if(PyDict_Contains(globals, name)){
246-
value = make_global_immutable(globals, name);
247-
if(!Py_IsNone(value)){
248-
if(stack_push(frontier, value)){
249-
stack_free(f_stack);
250-
// frontier freed by the caller
251-
return PyErr_NoMemory();
252-
}
235+
value = PyDict_GetItem(globals, name);
236+
if(PyDict_SetItem(frozen_globals, name, value)){
237+
Py_DECREF(frozen_builtins);
238+
Py_DECREF(frozen_globals);
239+
stack_free(f_stack);
240+
return NULL;
253241
}
254242
}
255243
}
@@ -270,29 +258,39 @@ static PyObject* walk_function(PyObject* op, stack* frontier)
270258
if(PyUnicode_Check(value)){
271259
PyObject* name = value;
272260
if(PyDict_Contains(globals, name)){
273-
value = make_global_immutable(globals, name);
274-
if(!Py_IsNone(value)){
275-
if(stack_push(frontier, value)){
276-
stack_free(f_stack);
277-
// frontier freed by the caller
278-
return PyErr_NoMemory();
279-
}
261+
value = PyDict_GetItem(globals, name);
262+
if(PyDict_SetItem(frozen_globals, name, value)){
263+
Py_DECREF(frozen_builtins);
264+
Py_DECREF(frozen_globals);
265+
return NULL;
280266
}
281267
}
282268
}
283269
}
284270
}
285271

272+
if(stack_push(frontier, frozen_globals)){
273+
goto nomemory;
274+
}
275+
276+
f->func_globals = frozen_globals;
277+
Py_DECREF(globals);
278+
279+
if(stack_push(frontier, frozen_builtins)){
280+
goto nomemory;
281+
}
282+
283+
f->func_builtins = frozen_builtins;
284+
Py_DECREF(builtins);
285+
286286
Py_RETURN_NONE;
287-
}
288287

289-
#define _Py_FREEZE_CALL(f, item, frontier) do { \
290-
PyObject* err = f((item), (frontier)); \
291-
if(!Py_IsNone(err)){ \
292-
stack_free((frontier)); \
293-
return err; \
294-
} \
295-
} while(0)
288+
nomemory:
289+
Py_XDECREF(frozen_builtins);
290+
Py_XDECREF(frozen_globals);
291+
stack_free(f_stack);
292+
return PyErr_NoMemory();
293+
}
296294

297295
static int _freeze_visit(PyObject* obj, void* frontier)
298296
{
@@ -309,7 +307,6 @@ static int _freeze_visit(PyObject* obj, void* frontier)
309307
}
310308

311309
if(!_Py_IsImmutable(obj)){
312-
//printf("Pushing object %p tp=%p rc=0x%lx\n", (void *)obj, obj->ob_type, obj->ob_refcnt);
313310
if(stack_push((stack*)frontier, obj)){
314311
PyErr_NoMemory();
315312
return -1;
@@ -321,9 +318,6 @@ static int _freeze_visit(PyObject* obj, void* frontier)
321318

322319
PyObject* _Py_Freeze(PyObject* obj)
323320
{
324-
//printf("\nFreezing object: ");
325-
//PyObject_Print(obj, stdout, 0);
326-
//printf(" rc=0x%lx\n", obj->ob_refcnt);
327321
if(_Py_IsImmutable(obj) && _Py_IsImmutable(Py_TYPE(obj))){
328322
Py_RETURN_NONE;
329323
}
@@ -340,32 +334,29 @@ PyObject* _Py_Freeze(PyObject* obj)
340334

341335
while(!stack_empty(frontier)){
342336
PyObject* item = stack_pop(frontier); // item.rc = x + 1
343-
//printf("Current item %p tp=%p rc=0x%lx: ", (void *)item, item->ob_type, item->ob_refcnt);
344-
//fflush(stdout);
345-
//PyObject_Print(item, stdout, 0);
346-
//printf("\n");
347337

348338
PyTypeObject* type = Py_TYPE(item);
349339
traverseproc traverse;
350340
PyObject* type_op = NULL;
351341

352342
if(_Py_IsImmutable(item)){
353-
// Direct access like this is not recommended, but will be removed in the future as
354-
// this is just for debugging purposes.
355343
goto handle_type;
356344
}
357345

358346
_Py_SetImmutable(item);
359347

360-
//printf("Frozen object %p tp=%p rc=0x%lx\n", (void *)item, item->ob_type, item->ob_refcnt);
361348

362349
if(is_c_wrapper(item)) {
363350
// C functions are not mutable, so we can skip them.
364351
continue;
365352
}
366353

367354
if(PyFunction_Check(item)){
368-
_Py_FREEZE_CALL(walk_function, item, frontier);
355+
PyObject* err = walk_function(item, frontier);
356+
if(!Py_IsNone(err)){
357+
stack_free(frontier);
358+
return err;
359+
}
369360
goto handle_type;
370361
}
371362

@@ -380,7 +371,6 @@ PyObject* _Py_Freeze(PyObject* obj)
380371
handle_type:
381372
type_op = _PyObject_CAST(item->ob_type);
382373
if (!_Py_IsImmutable(type_op)){
383-
// Previously this included a check for is_leaf_type, but
384374
if (stack_push(frontier, type_op))
385375
{
386376
stack_free(frontier);

0 commit comments

Comments
 (0)