Skip to content

Commit 8ca49cb

Browse files
authored
Merge pull request #47 from Eddy114514/execscope_warn
Add warning for scope of exec
2 parents c5e6b31 + 4c3c3c6 commit 8ca49cb

2 files changed

Lines changed: 283 additions & 0 deletions

File tree

Lib/test/test_py3kwarn.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
import sys
33
from test.test_support import check_py3k_warnings, CleanImport, run_unittest
4+
from test.script_helper import assert_python_ok
45
import warnings
56
import base64
67
from test import test_support
@@ -430,6 +431,25 @@ def test_b16encode_warns(self):
430431
base64.b16encode(b'test')
431432
check_py3k_warnings(expected, UserWarning)
432433

434+
def assertExecLocalWritebackWarning(self, recorder, local_name):
435+
self.assertEqual(len(recorder.warnings), 1)
436+
msg = str(recorder.warnings[0].message)
437+
self.assertTrue(msg.startswith(
438+
"exec() modified local '%s'" % local_name))
439+
recorder.reset()
440+
441+
def test_exec_local_writeback_warning(self):
442+
rc, out, err = assert_python_ok(
443+
"-3",
444+
"-c",
445+
"def f(code):\n"
446+
" b = 42\n"
447+
" exec code\n"
448+
" return b\n"
449+
"f('b = 99')\n")
450+
self.assertEqual(rc, 0)
451+
self.assertIn("exec() modified local 'b' which is read later", err)
452+
433453

434454
class TestStdlibRemovals(unittest.TestCase):
435455

Python/ceval.c

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,171 @@ _Py3kWarn_NextOpcode(void)
6666
return -1;
6767
}
6868

69+
static PyObject *exec_local_writeback_map = NULL;
70+
71+
static PyObject *
72+
_get_exec_local_writeback_map(void)
73+
{
74+
if (exec_local_writeback_map == NULL) {
75+
exec_local_writeback_map = PyDict_New();
76+
}
77+
return exec_local_writeback_map;
78+
}
79+
80+
static void
81+
_clear_exec_local_writeback_for_frame(PyFrameObject *f)
82+
{
83+
PyObject *frame_key;
84+
PyObject *exc_type, *exc_value, *exc_tb;
85+
86+
if (exec_local_writeback_map == NULL) {
87+
return;
88+
}
89+
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
90+
frame_key = PyLong_FromVoidPtr(f);
91+
if (frame_key == NULL) {
92+
PyErr_Clear();
93+
PyErr_Restore(exc_type, exc_value, exc_tb);
94+
return;
95+
}
96+
if (PyDict_GetItem(exec_local_writeback_map, frame_key) != NULL &&
97+
PyDict_DelItem(exec_local_writeback_map, frame_key) < 0) {
98+
PyErr_Clear();
99+
}
100+
Py_DECREF(frame_key);
101+
PyErr_Restore(exc_type, exc_value, exc_tb);
102+
}
103+
104+
static int
105+
_warn_exec_local_writeback(PyFrameObject *f, int oparg)
106+
{
107+
PyObject *frame_key = NULL;
108+
PyObject *frame_dict = NULL;
109+
PyObject *entry = NULL;
110+
PyObject *idx = NULL;
111+
PyObject *msg = NULL;
112+
PyObject *name_obj = NULL;
113+
const char *local_name = NULL;
114+
int read_lineno = 0;
115+
int warn_result;
116+
117+
if (exec_local_writeback_map == NULL) {
118+
return 0;
119+
}
120+
frame_key = PyLong_FromVoidPtr(f);
121+
if (frame_key == NULL) {
122+
PyErr_Clear();
123+
return 0;
124+
}
125+
frame_dict = PyDict_GetItem(exec_local_writeback_map, frame_key);
126+
if (frame_dict == NULL) {
127+
Py_DECREF(frame_key);
128+
return 0;
129+
}
130+
idx = PyInt_FromLong(oparg);
131+
if (idx == NULL) {
132+
Py_DECREF(frame_key);
133+
PyErr_Clear();
134+
return 0;
135+
}
136+
entry = PyDict_GetItem(frame_dict, idx);
137+
if (entry == NULL) {
138+
Py_DECREF(idx);
139+
Py_DECREF(frame_key);
140+
return 0;
141+
}
142+
read_lineno = PyCode_Addr2Line(f->f_code, f->f_lasti);
143+
name_obj = PyTuple_GetItem(f->f_code->co_varnames, oparg);
144+
if (name_obj != NULL) {
145+
local_name = PyString_AsString(name_obj);
146+
}
147+
else if (local_name == NULL) {
148+
local_name = "<local>";
149+
}
150+
msg = PyString_FromFormat(
151+
"exec() modified local '%s' which is read later in the enclosing function; "
152+
"in 3.x exec() does not reliably write back to function locals without an explicit locals mapping",
153+
local_name);
154+
if (msg == NULL) {
155+
Py_DECREF(idx);
156+
Py_DECREF(frame_key);
157+
PyErr_Clear();
158+
return 0;
159+
}
160+
161+
warn_result = PyErr_WarnExplicit_WithFix(
162+
PyExc_Py3xWarning,
163+
PyString_AsString(msg),
164+
"use exec(code, globals, locals) with an explicit locals mapping",
165+
PyString_AsString(f->f_code->co_filename),
166+
read_lineno,
167+
NULL,
168+
NULL);
169+
170+
Py_DECREF(msg);
171+
if (warn_result < 0) {
172+
Py_DECREF(idx);
173+
Py_DECREF(frame_key);
174+
return -1;
175+
}
176+
177+
if (PyDict_DelItem(frame_dict, idx) < 0) {
178+
PyErr_Clear();
179+
}
180+
Py_DECREF(idx);
181+
182+
if (PyDict_Size(frame_dict) <= 0 &&
183+
PyDict_DelItem(exec_local_writeback_map, frame_key) < 0) {
184+
PyErr_Clear();
185+
}
186+
Py_DECREF(frame_key);
187+
return 0;
188+
}
189+
190+
static void
191+
_clear_exec_local_writeback_for_local(PyFrameObject *f, int oparg)
192+
{
193+
PyObject *frame_key = NULL;
194+
PyObject *frame_dict = NULL;
195+
PyObject *idx = NULL;
196+
PyObject *exc_type, *exc_value, *exc_tb;
197+
198+
if (exec_local_writeback_map == NULL) {
199+
return;
200+
}
201+
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
202+
frame_key = PyLong_FromVoidPtr(f);
203+
if (frame_key == NULL) {
204+
PyErr_Clear();
205+
PyErr_Restore(exc_type, exc_value, exc_tb);
206+
return;
207+
}
208+
frame_dict = PyDict_GetItem(exec_local_writeback_map, frame_key);
209+
if (frame_dict == NULL) {
210+
Py_DECREF(frame_key);
211+
PyErr_Restore(exc_type, exc_value, exc_tb);
212+
return;
213+
}
214+
idx = PyInt_FromLong(oparg);
215+
if (idx == NULL) {
216+
Py_DECREF(frame_key);
217+
PyErr_Clear();
218+
PyErr_Restore(exc_type, exc_value, exc_tb);
219+
return;
220+
}
221+
if (PyDict_GetItem(frame_dict, idx) != NULL &&
222+
PyDict_DelItem(frame_dict, idx) < 0) {
223+
PyErr_Clear();
224+
}
225+
Py_DECREF(idx);
226+
if (PyDict_Size(frame_dict) <= 0 &&
227+
PyDict_DelItem(exec_local_writeback_map, frame_key) < 0) {
228+
PyErr_Clear();
229+
}
230+
Py_DECREF(frame_key);
231+
PyErr_Restore(exc_type, exc_value, exc_tb);
232+
}
233+
69234
#ifndef WITH_TSC
70235

71236
#define READ_TIMESTAMP(var)
@@ -1273,6 +1438,11 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
12731438
{
12741439
x = GETLOCAL(oparg);
12751440
if (x != NULL) {
1441+
if (Py_Py3kWarningFlag &&
1442+
_warn_exec_local_writeback(f, oparg) < 0) {
1443+
err = -1;
1444+
break;
1445+
}
12761446
Py_INCREF(x);
12771447
PUSH(x);
12781448
FAST_DISPATCH();
@@ -1296,6 +1466,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
12961466
{
12971467
v = POP();
12981468
SETLOCAL(oparg, v);
1469+
if (Py_Py3kWarningFlag) {
1470+
_clear_exec_local_writeback_for_local(f, oparg);
1471+
}
12991472
FAST_DISPATCH();
13001473
}
13011474

@@ -2454,6 +2627,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
24542627
x = GETLOCAL(oparg);
24552628
if (x != NULL) {
24562629
SETLOCAL(oparg, NULL);
2630+
if (Py_Py3kWarningFlag) {
2631+
_clear_exec_local_writeback_for_local(f, oparg);
2632+
}
24572633
DISPATCH();
24582634
}
24592635
format_exc_check_arg(
@@ -3404,6 +3580,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
34043580

34053581
/* pop frame */
34063582
exit_eval_frame:
3583+
_clear_exec_local_writeback_for_frame(f);
34073584
Py_LeaveRecursiveCall();
34083585
tstate->frame = f->f_back;
34093586

@@ -5086,6 +5263,12 @@ exec_statement(PyFrameObject *f, PyObject *prog, PyObject *globals,
50865263
int n;
50875264
PyObject *v;
50885265
int plain = 0;
5266+
int track_exec_writeback = 0;
5267+
int exec_lineno = 0;
5268+
int exec_offset = 0;
5269+
int nlocals = 0;
5270+
PyObject **before = NULL;
5271+
int i;
50895272

50905273
if (PyTuple_Check(prog) && globals == Py_None && locals == Py_None &&
50915274
((n = PyTuple_Size(prog)) == 2 || n == 3)) {
@@ -5131,6 +5314,25 @@ exec_statement(PyFrameObject *f, PyObject *prog, PyObject *globals,
51315314
}
51325315
if (PyDict_GetItemString(globals, "__builtins__") == NULL)
51335316
PyDict_SetItemString(globals, "__builtins__", f->f_builtins);
5317+
5318+
/* Snapshot fast locals for plain exec so later LOAD_FAST warns only
5319+
about bindings that exec actually wrote back. */
5320+
if (plain && Py_Py3kWarningFlag &&
5321+
(f->f_code->co_flags & CO_NEWLOCALS) &&
5322+
f->f_code->co_nlocals > 0 &&
5323+
f->f_localsplus != NULL) {
5324+
nlocals = f->f_code->co_nlocals;
5325+
before = PyMem_New(PyObject *, nlocals);
5326+
if (before != NULL) {
5327+
for (i = 0; i < nlocals; i++) {
5328+
before[i] = f->f_localsplus[i];
5329+
Py_XINCREF(before[i]);
5330+
}
5331+
track_exec_writeback = 1;
5332+
exec_offset = f->f_lasti;
5333+
exec_lineno = PyCode_Addr2Line(f->f_code, exec_offset);
5334+
}
5335+
}
51345336
if (PyCode_Check(prog)) {
51355337
if (PyCode_GetNumFree((PyCodeObject *)prog) > 0) {
51365338
PyErr_SetString(PyExc_TypeError,
@@ -5178,6 +5380,67 @@ exec_statement(PyFrameObject *f, PyObject *prog, PyObject *globals,
51785380
}
51795381
if (plain)
51805382
PyFrame_LocalsToFast(f, 0);
5383+
if (track_exec_writeback && v != NULL) {
5384+
PyObject *frame_key = NULL;
5385+
PyObject *frame_dict = NULL;
5386+
PyObject *map = NULL;
5387+
for (i = 0; i < nlocals; i++) {
5388+
PyObject *before_obj = before[i];
5389+
PyObject *after_obj = f->f_localsplus[i];
5390+
if (before_obj == after_obj) {
5391+
continue;
5392+
}
5393+
if (frame_key == NULL) {
5394+
frame_key = PyLong_FromVoidPtr(f);
5395+
if (frame_key == NULL) {
5396+
PyErr_Clear();
5397+
break;
5398+
}
5399+
}
5400+
if (map == NULL) {
5401+
map = _get_exec_local_writeback_map();
5402+
if (map == NULL) {
5403+
PyErr_Clear();
5404+
break;
5405+
}
5406+
}
5407+
if (frame_dict == NULL) {
5408+
frame_dict = PyDict_GetItem(map, frame_key);
5409+
if (frame_dict == NULL) {
5410+
frame_dict = PyDict_New();
5411+
if (frame_dict == NULL) {
5412+
PyErr_Clear();
5413+
break;
5414+
}
5415+
if (PyDict_SetItem(map, frame_key, frame_dict) < 0) {
5416+
PyErr_Clear();
5417+
Py_DECREF(frame_dict);
5418+
frame_dict = NULL;
5419+
break;
5420+
}
5421+
Py_DECREF(frame_dict);
5422+
frame_dict = PyDict_GetItem(map, frame_key);
5423+
}
5424+
}
5425+
if (frame_dict != NULL) {
5426+
PyObject *idx = PyInt_FromLong(i);
5427+
PyObject *val = Py_BuildValue("ii", exec_lineno, exec_offset);
5428+
if (idx == NULL || val == NULL ||
5429+
PyDict_SetItem(frame_dict, idx, val) < 0) {
5430+
PyErr_Clear();
5431+
}
5432+
Py_XDECREF(idx);
5433+
Py_XDECREF(val);
5434+
}
5435+
}
5436+
Py_XDECREF(frame_key);
5437+
}
5438+
if (before != NULL) {
5439+
for (i = 0; i < nlocals; i++) {
5440+
Py_XDECREF(before[i]);
5441+
}
5442+
PyMem_Free(before);
5443+
}
51815444
if (v == NULL)
51825445
return -1;
51835446
Py_DECREF(v);

0 commit comments

Comments
 (0)