8000 More eval typecheck · RustPython/RustPython@7ebe018 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7ebe018

Browse files
committed
More eval typecheck
1 parent a576569 commit 7ebe018

File tree

2 files changed

+114
-8
lines changed

2 files changed

+114
-8
lines changed

extra_tests/snippets/builtin_eval.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,76 @@
22

33
code = compile("5+3", "x.py", "eval")
44
assert eval(code) == 8
5+
6+
# Test that globals must be a dict
7+
import collections
8+
9+
# UserDict is a mapping but not a dict - should fail in eval
10+
user_dict = collections.UserDict({"x": 5})
11+
try:
12+
eval("x", user_dict)
13+
assert False, "eval with UserDict globals should fail"
14+
except TypeError as e:
15+
# CPython: "globals must be a real dict; try eval(expr, {}, mapping)"
16+
assert "globals must be a real dict" in str(e), e
17+
18+
# Non-mapping should have different error message
19+
try:
20+
eval("x", 123)
21+
assert False, "eval with int globals should fail"
22+
except TypeError as e:
23+
# CPython: "globals must be a dict"
24+
assert "globals must be a dict" in str(e)
25+
assert "real dict" not in str(e)
26+
27+
# List is not a mapping
28+
try:
29+
eval("x", [])
30+
assert False, "eval with list globals should fail"
31+
except TypeError as e:
32+
assert "globals must be a real dict" in str(e), e
33+
34+
# Regular dict should work
35+
assert eval("x", {"x": 42}) == 42
36+
37+
# None should use current globals
38+
x = 100
39+
assert eval("x", None) == 100
40+
41+
# Test locals parameter
42+
# Locals can be any mapping (unlike globals which must be dict)
43+
assert eval("y", {"y": 1}, user_dict) == 1 # UserDict as locals is OK
44+
45+
# But locals must still be a mapping
46+
try:
47+
eval("x", {"x": 1}, 123)
48+
assert False, "eval with int locals should fail"
49+
except TypeError as e:
50+
# This error is handled by ArgMapping validation
51+
assert "not a mapping" in str(e) or "locals must be a mapping" in str(e)
52+
53+
# Test that __builtins__ is added if missing
54+
globals_without_builtins = {"x": 5}
55+
result = eval("x", globals_without_builtins)
56+
assert result == 5
57+
assert "__builtins__" in globals_without_builtins
58+
59+
# Test with both globals and locals
60+
assert eval("x + y", {"x": 10}, {"y": 20}) == 30
61+
62+
# Test that when globals is None and locals is provided, it still works
63+
assert eval("x + y", None, {"x": 1, "y": 2}) == 3
64+
65+
66+
# Test code object with free variables
67+
def make_closure():
68+
z = 10
69+
return compile("x + z", "<string>", "eval")
70+
71+
72+
closure_code = make_closure()
73+
try:
74+
eval(closure_code, {"x": 5})
75+
assert False, "eval with code containing free variables should fail"
76+
except NameError as e:
77+
pass

vm/src/stdlib/builtins.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,24 +249,56 @@ mod builtins {
249249
#[derive(FromArgs)]
250250
struct ScopeArgs {
251251
#[pyarg(any, default)]
252-
globals: Option<PyDictRef>,
252+
globals: Option<PyObjectRef>,
253253
#[pyarg(any, default)]
254254
locals: Option<ArgMapping>,
255255
}
256256

257257
impl ScopeArgs {
258-
fn make_scope(self, vm: &VirtualMachine) -> PyResult<crate::scope::Scope> {
258+
fn make_scope(
259+
self,
260+
vm: &VirtualMachine,
261+
func_name: &'static str,
262+
) -> PyResult<crate::scope::Scope> {
263+
fn validate_globals_dict(
264+
globals: &PyObjectRef,
265+
vm: &VirtualMachine,
266+
func_name: &'static str,
267+
) -> PyResult<()> {
268+
if !globals.fast_isinstance(vm.ctx.types.dict_type) {
269+
return Err(match func_name {
270+
"eval" => {
271+
let is_mapping = crate::protocol::PyMapping::check(globals);
272+
vm.new_type_error(if is_mapping {
273+
"globals must be a real dict; try eval(expr, {}, mapping)"
274+
.to_owned()
275+
} else {
276+
"globals must be a dict".to_owned()
277+
})
278+
}
279+
"exec" => vm.new_type_error(format!(
280+
"exec() globals must be a dict, not {}",
281+
globals.class().name()
282+
)),
283+
_ => vm.new_type_error("globals must be a dict".to_owned()),
284+
});
285+
}
286+
Ok(())
287+
}
288+
259289
let (globals, locals) = match self.globals {
260290
Some(globals) => {
291+
validate_globals_dict(&globals, vm, func_name)?;
292+
293+
let globals = PyDictRef::try_from_object(vm, globals)?;
261294
if !globals.contains_key(identifier!(vm, __builtins__), vm) {
262295
let builtins_dict = vm.builtins.dict().into();
263296
globals.set_item(identifier!(vm, __builtins__), builtins_dict, vm)?;
264297
}
265298
(
266299
globals.clone(),
267-
self.locals.unwrap_or_else(|| {
268-
ArgMapping::try_from_object(vm, globals.into()).unwrap()
269-
}),
300+
self.locals
301+
.unwrap_or_else(|| ArgMapping::from_dict_exact(globals.clone())),
270302
)
271303
}
272304
None => (
@@ -290,6 +322,8 @@ mod builtins {
290322
scope: ScopeArgs,
291323
vm: &VirtualMachine,
292324
) -> PyResult {
325+
let scope = scope.make_scope(vm, "eval")?;
326+
293327
// source as string
294328
let code = match source {
295329
Either::A(either) => {
@@ -323,18 +357,17 @@ mod builtins {
323357
scope: ScopeArgs,
324358
vm: &VirtualMachine,
325359
) -> PyResult {
360+
let scope = scope.make_scope(vm, "exec")?;
326361
run_code(vm, source, scope, crate::compiler::Mode::Exec, "exec")
327362
}
328363

329364
fn run_code(
330365
vm: &VirtualMachine,
331366
source: Either<PyStrRef, PyRef<crate::builtins::PyCode>>,
332-
scope: ScopeArgs,
367+
scope: crate::scope::Scope,
333368
#[allow(unused_variables)] mode: crate::compiler::Mode,
334369
func: &str,
335370
) -> PyResult {
336-
let scope = scope.make_scope(vm)?;
337-
338371
// Determine code object:
339372
let code_obj = match source {
340373
#[cfg(feature = "rustpython-compiler")]

0 commit comments

Comments
 (0)
0