@@ -3,7 +3,7 @@ use crate::pystate::with_vm;
33use crate :: unicodeobject:: decode_fsdefault_and_size;
44use crate :: util:: FfiPtrExt ;
55use core:: ffi:: { CStr , c_char, c_int} ;
6- use rustpython_vm:: builtins:: { PyCode , PyDict } ;
6+ use rustpython_vm:: builtins:: { PyAnyDictRef , PyCode } ;
77use rustpython_vm:: function:: ArgMapping ;
88use rustpython_vm:: scope:: Scope ;
99use rustpython_vm:: { AsObject , PyObject , TryFromObject } ;
@@ -32,12 +32,15 @@ pub unsafe extern "C" fn PyEval_EvalCode(
3232) -> * mut PyObject {
3333 with_vm ( |vm| {
3434 let code = unsafe { co. assume_borrowed_and_cast :: < PyCode > ( vm) } ?;
35- let globals = unsafe { globals. assume_borrowed_and_cast :: < PyDict > ( vm) } ?;
35+ let globals =
36+ PyAnyDictRef :: try_from_object ( vm, unsafe { globals. assume_borrowed ( ) } . to_owned ( ) ) ?;
3637 let locals = unsafe { locals. assume_borrowed_or_opt ( ) }
3738 . map ( |obj| ArgMapping :: try_from_object ( vm, obj. to_owned ( ) ) )
3839 . transpose ( ) ?;
3940
40- let scope = Scope :: with_builtins ( locals, globals. to_owned ( ) , vm) ;
41+ let locals = locals. or_else ( || Some ( ArgMapping :: from_anydict_exact ( globals. clone ( ) ) ) ) ;
42+ // Unlike Python's eval/exec, this API does not insert __builtins__.
43+ let scope = Scope :: new ( locals, globals) ;
4144
4245 vm. run_code_obj ( code. to_owned ( ) , scope)
4346 } )
@@ -268,4 +271,122 @@ assert not hidden_leaked
268271 . unwrap ( ) ;
269272 } )
270273 }
274+
275+ #[ pyfunction]
276+ fn frame_global_views ( py : Python < ' _ > ) -> PyResult < Vec < Bound < ' _ , PyAny > > > {
277+ unsafe {
278+ let frame = super :: PyEval_GetFrame ( ) ;
279+ Ok ( vec ! [
280+ Bound :: from_borrowed_ptr( py, super :: PyEval_GetGlobals ( ) . cast( ) ) ,
281+ Bound :: from_owned_ptr_or_err( py, super :: PyEval_GetFrameGlobals ( ) . cast( ) ) ?,
282+ Bound :: from_owned_ptr_or_err( py, crate :: pyframe:: PyFrame_GetGlobals ( frame) . cast( ) ) ?,
283+ ] )
284+ }
285+ }
286+
287+ fn eval_with_globals < ' py > (
288+ globals : & Bound < ' py , PyAny > ,
289+ source : & str ,
290+ mode : & str ,
291+ locals : Option < & Bound < ' py , PyAny > > ,
292+ ) -> PyResult < Bound < ' py , PyAny > > {
293+ let py = globals. py ( ) ;
294+ let code = py. import ( "builtins" ) ?. getattr ( "compile" ) ?. call1 ( (
295+ source,
296+ "<capi-frozendict>" ,
297+ mode,
298+ ) ) ?;
299+ unsafe {
300+ Bound :: from_owned_ptr_or_err (
301+ py,
302+ pyo3:: ffi:: PyEval_EvalCode (
303+ code. as_ptr ( ) ,
304+ globals. as_ptr ( ) ,
305+ locals. map_or ( core:: ptr:: null_mut ( ) , Bound :: as_ptr) ,
306+ ) ,
307+ )
308+ }
309+ }
310+
311+ #[ test]
312+ fn code_eval_frozen_globals ( ) {
313+ Python :: attach ( |py| {
314+ let globals = py. eval ( c"frozendict(answer=40)" , None , None ) . unwrap ( ) ;
315+ let result = eval_with_globals ( & globals, "answer + len((1, 2))" , "eval" , None ) . unwrap ( ) ;
316+ assert_eq ! ( result. extract:: <i32 >( ) . unwrap( ) , 42 ) ;
317+ let identity = eval_with_globals ( & globals, "globals()" , "eval" , None ) . unwrap ( ) ;
318+ assert ! ( identity. is( & globals) ) ;
319+ assert ! ( !globals. contains( "__builtins__" ) . unwrap( ) ) ;
320+
321+ let namespace = pyo3:: types:: PyDict :: new ( py) ;
322+ namespace
323+ . set_item (
324+ "frame_global_views" ,
325+ wrap_pyfunction ! ( frame_global_views, py) . unwrap ( ) ,
326+ )
327+ . unwrap ( ) ;
328+ let globals = py
329+ . import ( "builtins" )
330+ . unwrap ( )
331+ . getattr ( "frozendict" )
332+ . unwrap ( )
333+ . call1 ( ( namespace, ) )
334+ . unwrap ( ) ;
335+ let views = eval_with_globals ( & globals, "frame_global_views()" , "eval" , None ) . unwrap ( ) ;
336+ for view in views. try_iter ( ) . unwrap ( ) {
337+ assert ! ( view. unwrap( ) . is( & globals) ) ;
338+ }
339+
340+ let explicit = py
341+ . eval (
342+ c"frozendict(answer=40, __builtins__=frozendict(len=lambda x: 9))" ,
343+ None ,
344+ None ,
345+ )
346+ . unwrap ( ) ;
347+ let result = eval_with_globals ( & explicit, "answer + len(())" , "eval" , None ) . unwrap ( ) ;
348+ assert_eq ! ( result. extract:: <i32 >( ) . unwrap( ) , 49 ) ;
349+ } )
350+ }
351+
352+ #[ test]
353+ fn code_eval_frozen_globals_mutation ( ) {
354+ Python :: attach ( |py| {
355+ let globals = py. eval ( c"frozendict(answer=40)" , None , None ) . unwrap ( ) ;
356+ for source in [
357+ "answer = 1" ,
358+ "global answer; answer = 1" ,
359+ "global answer; del answer" ,
360+ ] {
361+ let error = eval_with_globals ( & globals, source, "exec" , None ) . unwrap_err ( ) ;
362+ assert ! ( error. is_instance_of:: <pyo3:: exceptions:: PyTypeError >( py) ) ;
363+ }
364+ let locals = pyo3:: types:: PyDict :: new ( py) ;
365+ eval_with_globals (
366+ & globals,
367+ "local_answer = answer + 2" ,
368+ "exec" ,
369+ Some ( locals. as_any ( ) ) ,
370+ )
371+ . unwrap ( ) ;
372+ assert_eq ! (
373+ locals
374+ . get_item( "local_answer" )
375+ . unwrap( )
376+ . unwrap( )
377+ . extract:: <i32 >( )
378+ . unwrap( ) ,
379+ 42
380+ ) ;
381+ assert_eq ! (
382+ globals
383+ . get_item( "answer" )
384+ . unwrap( )
385+ . extract:: <i32 >( )
386+ . unwrap( ) ,
387+ 40
388+ ) ;
389+ assert ! ( !globals. contains( "__builtins__" ) . unwrap( ) ) ;
390+ } )
391+ }
271392}
0 commit comments