Skip to content

Commit 38c43e0

Browse files
Merge pull request RustPython#590 from RustPython/exec
Proper construction of scope for exec/eval.
2 parents 839f906 + c4953ee commit 38c43e0

File tree

4 files changed

+97
-26
lines changed

4 files changed

+97
-26
lines changed

tests/snippets/test_exec.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
exec("def square(x):\n return x * x\n")
2+
assert 16 == square(4)
3+
4+
d = {}
5+
exec("def square(x):\n return x * x\n", {}, d)
6+
assert 16 == d['square'](4)
7+
8+
exec("assert 2 == x", {}, {'x': 2})
9+
exec("assert 2 == x", {'x': 2}, {})
10+
exec("assert 4 == x", {'x': 2}, {'x': 4})
11+
12+
exec("assert max(1, 2) == 2", {}, {})
13+
14+
exec("assert max(1, 5, square(5)) == 25", None)
15+
16+
#
17+
# These doesn't work yet:
18+
#
19+
# Local environment shouldn't replace global environment:
20+
#
21+
# exec("assert max(1, 5, square(5)) == 25", None, {})
22+
#
23+
# Closures aren't available if local scope is replaced:
24+
#
25+
# def g():
26+
# seven = "seven"
27+
# def f():
28+
# try:
29+
# exec("seven", None, {})
30+
# except NameError:
31+
# pass
32+
# else:
33+
# raise NameError("seven shouldn't be in scope")
34+
# f()
35+
# g()
36+
37+
try:
38+
exec("", 1)
39+
except TypeError:
40+
pass
41+
else:
42+
raise TypeError("exec should fail unless globals is a dict or None")

vm/src/builtins.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use crate::frame::{Scope, ScopeRef};
1818
use crate::pyobject::{
1919
AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol,
2020
};
21-
use std::rc::Rc;
2221

2322
#[cfg(not(target_arch = "wasm32"))]
2423
use crate::stdlib::io::io_open;
@@ -191,12 +190,11 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
191190
vm,
192191
args,
193192
required = [(source, None)],
194-
optional = [
195-
(_globals, Some(vm.ctx.dict_type())),
196-
(locals, Some(vm.ctx.dict_type()))
197-
]
193+
optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))]
198194
);
199195

196+
let scope = make_scope(vm, globals, locals)?;
197+
200198
// Determine code object:
201199
let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) {
202200
source.clone()
@@ -215,8 +213,6 @@ fn builtin_eval(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
215213
return Err(vm.new_type_error("code argument must be str or code object".to_string()));
216214
};
217215

218-
let scope = make_scope(vm, locals);
219-
220216
// Run the source:
221217
vm.run_code_obj(code_obj.clone(), scope)
222218
}
@@ -228,12 +224,11 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
228224
vm,
229225
args,
230226
required = [(source, None)],
231-
optional = [
232-
(_globals, Some(vm.ctx.dict_type())),
233-
(locals, Some(vm.ctx.dict_type()))
234-
]
227+
optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))]
235228
);
236229

230+
let scope = make_scope(vm, globals, locals)?;
231+
237232
// Determine code object:
238233
let code_obj = if objtype::isinstance(source, &vm.ctx.str_type()) {
239234
let mode = compile::Mode::Exec;
@@ -252,26 +247,48 @@ fn builtin_exec(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
252247
return Err(vm.new_type_error("source argument must be str or code object".to_string()));
253248
};
254249

255-
let scope = make_scope(vm, locals);
256-
257250
// Run the code:
258251
vm.run_code_obj(code_obj, scope)
259252
}
260253

261-
fn make_scope(vm: &mut VirtualMachine, locals: Option<&PyObjectRef>) -> ScopeRef {
262-
// handle optional global and locals
263-
let locals = if let Some(locals) = locals {
264-
locals.clone()
265-
} else {
266-
vm.new_dict()
254+
fn make_scope(
255+
vm: &mut VirtualMachine,
256+
globals: Option<&PyObjectRef>,
257+
locals: Option<&PyObjectRef>,
258+
) -> PyResult<ScopeRef> {
259+
let dict_type = vm.ctx.dict_type();
260+
let globals = match globals {
261+
Some(arg) => {
262+
if arg.is(&vm.get_none()) {
263+
None
264+
} else {
265+
if vm.isinstance(arg, &dict_type)? {
266+
Some(arg)
267+
} else {
268+
let arg_typ = arg.typ();
269+
let actual_type = vm.to_pystr(&arg_typ)?;
270+
let expected_type_name = vm.to_pystr(&dict_type)?;
271+
return Err(vm.new_type_error(format!(
272+
"globals must be a {}, not {}",
273+
expected_type_name, actual_type
274+
)));
275+
}
276+
}
277+
}
278+
None => None,
267279
};
268280

269-
// TODO: handle optional globals
270-
// Construct new scope:
271-
Rc::new(Scope {
272-
locals,
273-
parent: None,
274-
})
281+
let current_scope = vm.current_scope();
282+
let parent = match globals {
283+
Some(dict) => Some(Scope::new(dict.clone(), Some(vm.get_builtin_scope()))),
284+
None => current_scope.parent.clone(),
285+
};
286+
let locals = match locals {
287+
Some(dict) => dict.clone(),
288+
None => current_scope.locals.clone(),
289+
};
290+
291+
Ok(Scope::new(locals, parent))
275292
}
276293

277294
fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {

vm/src/frame.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ pub struct Scope {
3535
}
3636
pub type ScopeRef = Rc<Scope>;
3737

38+
impl Scope {
39+
pub fn new(locals: PyObjectRef, parent: Option<ScopeRef>) -> ScopeRef {
40+
Rc::new(Scope { locals, parent })
41+
}
42+
}
43+
3844
#[derive(Clone, Debug)]
3945
struct Block {
4046
/// The type of block.

vm/src/vm.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ impl VirtualMachine {
9090
result
9191
}
9292

93+
pub fn current_scope(&self) -> &ScopeRef {
94+
let current_frame = &self.frames[self.frames.len() - 1];
95+
let frame = objframe::get_value(current_frame);
96+
&frame.scope
97+
}
98+
9399
/// Create a new python string object.
94100
pub fn new_str(&self, s: String) -> PyObjectRef {
95101
self.ctx.new_str(s)
@@ -218,7 +224,7 @@ impl VirtualMachine {
218224
&self.ctx
219225
}
220226

221-
pub fn get_builtin_scope(&mut self) -> ScopeRef {
227+
pub fn get_builtin_scope(&self) -> ScopeRef {
222228
let a2 = &*self.builtins;
223229
match a2.payload {
224230
PyObjectPayload::Module { ref scope, .. } => scope.clone(),

0 commit comments

Comments
 (0)