From 80f11466b3d42f50ea71d4247b65dc339eaa2482 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Wed, 5 Feb 2020 01:21:42 +0000 Subject: [PATCH] Add "incognito" compilation --- bytecode/src/bytecode.rs | 2 ++ compiler/src/compile.rs | 56 ++++++++++++++++++++++---------- derive/src/compile_bytecode.rs | 2 +- examples/dis.rs | 11 +++++-- tests/snippets/function.py | 1 + vm/src/import.rs | 9 ++--- vm/src/obj/objframe.rs | 16 ++++++--- vm/src/obj/objfunction.rs | 9 +++++ vm/src/vm.rs | 28 ++++++++++++++-- wasm/lib/src/vm_class.rs | 9 +++-- wasm/tests/test_inject_module.py | 15 +++++++-- 11 files changed, 118 insertions(+), 40 deletions(-) diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs index 18ae446d9..ae07a8b2e 100644 --- a/bytecode/src/bytecode.rs +++ b/bytecode/src/bytecode.rs @@ -46,6 +46,7 @@ pub struct CodeObject { pub source_path: String, pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object + pub incognito: bool, } bitflags! { @@ -393,6 +394,7 @@ impl CodeObject { source_path, first_line_number, obj_name, + incognito: false, } } diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 8f88965b7..448524df1 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -30,7 +30,26 @@ struct Compiler { current_source_location: ast::Location, current_qualified_path: Option, ctx: CompileContext, - optimize: u8, + opts: CompileOpts, +} + +#[derive(Debug, Clone)] +pub struct CompileOpts { + /// How optimized the bytecode output should be; any optimize > 0 does + /// not emit assert statements + pub optimize: u8, + /// Whether introspection on defined functions/other items should be allowed; + /// e.g. when true, `func.__globals__` throws an error (where func is a function defined + /// in an incognito context) + pub incognito: bool, +} +impl Default for CompileOpts { + fn default() -> Self { + CompileOpts { + optimize: 0, + incognito: false, + } + } } #[derive(Clone, Copy)] @@ -60,20 +79,20 @@ pub fn compile( source: &str, mode: Mode, source_path: String, - optimize: u8, + opts: CompileOpts, ) -> CompileResult { match mode { Mode::Exec => { let ast = parser::parse_program(source)?; - compile_program(ast, source_path, optimize) + compile_program(ast, source_path.clone(), opts) } Mode::Eval => { let statement = parser::parse_statement(source)?; - compile_statement_eval(statement, source_path, optimize) + compile_statement_eval(statement, source_path.clone(), opts) } Mode::Single => { let ast = parser::parse_program(source)?; - compile_program_single(ast, source_path, optimize) + compile_program_single(ast, source_path.clone(), opts) } } } @@ -81,10 +100,10 @@ pub fn compile( /// A helper function for the shared code of the different compile functions fn with_compiler( source_path: String, - optimize: u8, + opts: CompileOpts, f: impl FnOnce(&mut Compiler) -> CompileResult<()>, ) -> CompileResult { - let mut compiler = Compiler::new(optimize); + let mut compiler = Compiler::new(opts); compiler.source_path = Some(source_path); compiler.push_new_code_object("".to_owned()); f(&mut compiler)?; @@ -97,9 +116,9 @@ fn with_compiler( pub fn compile_program( ast: ast::Program, source_path: String, - optimize: u8, + opts: CompileOpts, ) -> CompileResult { - with_compiler(source_path, optimize, |compiler| { + with_compiler(source_path, opts, |compiler| { let symbol_table = make_symbol_table(&ast)?; compiler.compile_program(&ast, symbol_table) }) @@ -109,9 +128,9 @@ pub fn compile_program( pub fn compile_statement_eval( statement: Vec, source_path: String, - optimize: u8, + opts: CompileOpts, ) -> CompileResult { - with_compiler(source_path, optimize, |compiler| { + with_compiler(source_path, opts, |compiler| { let symbol_table = statements_to_symbol_table(&statement)?; compiler.compile_statement_eval(&statement, symbol_table) }) @@ -121,9 +140,9 @@ pub fn compile_statement_eval( pub fn compile_program_single( ast: ast::Program, source_path: String, - optimize: u8, + opts: CompileOpts, ) -> CompileResult { - with_compiler(source_path, optimize, |compiler| { + with_compiler(source_path, opts, |compiler| { let symbol_table = make_symbol_table(&ast)?; compiler.compile_program_single(&ast, symbol_table) }) @@ -134,12 +153,12 @@ where O: OutputStream, { fn default() -> Self { - Compiler::new(0) + Compiler::new(CompileOpts::default()) } } impl Compiler { - fn new(optimize: u8) -> Self { + fn new(opts: CompileOpts) -> Self { Compiler { output_stack: Vec::new(), symbol_table_stack: Vec::new(), @@ -151,7 +170,7 @@ impl Compiler { in_loop: false, func: FunctionContext::NoFunction, }, - optimize, + opts, } } @@ -167,7 +186,8 @@ impl Compiler { } } - fn push_output(&mut self, code: CodeObject) { + fn push_output(&mut self, mut code: CodeObject) { + code.incognito = self.opts.incognito; self.output_stack.push(code.into()); } @@ -518,7 +538,7 @@ impl Compiler { } => self.compile_class_def(name, body, bases, keywords, decorator_list)?, Assert { test, msg } => { // if some flag, ignore all assert statements! - if self.optimize == 0 { + if self.opts.optimize == 0 { let end_label = self.new_label(); self.compile_jump_if(test, true, end_label)?; self.emit(Instruction::LoadName { diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 1206469fa..e3ae7eb38 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -49,7 +49,7 @@ impl CompilationSource { module_name: String, origin: F, ) -> Result { - compile::compile(source, mode, module_name, 0).map_err(|err| { + compile::compile(source, mode, module_name, Default::default()).map_err(|err| { Diagnostic::spans_error( self.span, format!("Python compile error from {}: {}", origin(), err), diff --git a/examples/dis.rs b/examples/dis.rs index 49fda5760..bd18537e3 100644 --- a/examples/dis.rs +++ b/examples/dis.rs @@ -62,9 +62,14 @@ fn main() { let optimize = matches.occurrences_of("optimize") as u8; let scripts = matches.values_of_os("scripts").unwrap(); + let opts = compile::CompileOpts { + optimize, + ..Default::default() + }; + for script in scripts.map(Path::new) { if script.exists() && script.is_file() { - let res = display_script(script, mode, optimize, expand_codeobjects); + let res = display_script(script, mode, opts.clone(), expand_codeobjects); if let Err(e) = res { error!("Error while compiling {:?}: {}", script, e); } @@ -77,11 +82,11 @@ fn main() { fn display_script( path: &Path, mode: compile::Mode, - optimize: u8, + opts: compile::CompileOpts, expand_codeobjects: bool, ) -> Result<(), Box> { let source = fs::read_to_string(path)?; - let code = compile::compile(&source, mode, path.to_string_lossy().into_owned(), optimize)?; + let code = compile::compile(&source, mode, path.to_string_lossy().into_owned(), opts)?; println!("{}:", path.display()); if expand_codeobjects { println!("{}", code.display_expand_codeobjects()); diff --git a/tests/snippets/function.py b/tests/snippets/function.py index bb9b45329..ebea34fe5 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -13,6 +13,7 @@ assert foo.__doc__ == "test" assert foo.__name__ == "foo" assert foo.__qualname__ == "foo" assert foo.__module__ == "function" +assert foo.__globals__ is globals() def my_func(a,): return a+2 diff --git a/vm/src/import.rs b/vm/src/import.rs index bcfe4ba22..362af5825 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -74,13 +74,8 @@ pub fn import_file( file_path: String, content: String, ) -> PyResult { - let code_obj = compile::compile( - &content, - compile::Mode::Exec, - file_path, - vm.settings.optimize, - ) - .map_err(|err| vm.new_syntax_error(&err))?; + let code_obj = compile::compile(&content, compile::Mode::Exec, file_path, vm.compile_opts()) + .map_err(|err| vm.new_syntax_error(&err))?; import_codeobj(vm, module_name, code_obj, true) } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 5372d382d..7ad7b8119 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -30,13 +30,21 @@ impl FrameRef { } #[pyproperty] - fn f_globals(self) -> PyDictRef { - self.scope.globals.clone() + fn f_globals(self, vm: &VirtualMachine) -> PyResult { + if self.code.incognito { + Err(vm.new_type_error("Can't get f_globals on an incognito frame".to_owned())) + } else { + Ok(self.scope.globals.clone()) + } } #[pyproperty] - fn f_locals(self) -> PyDictRef { - self.scope.get_locals() + fn f_locals(self, vm: &VirtualMachine) -> PyResult { + if self.code.incognito { + Err(vm.new_type_error("Can't get f_locals on an incognito frame".to_owned())) + } else { + Ok(self.scope.get_locals()) + } } #[pyproperty] diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index c363dd528..e63abfdfb 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -278,6 +278,15 @@ impl PyFunction { fn kwdefaults(&self) -> Option { self.kw_only_defaults.clone() } + + #[pyproperty(magic)] + fn globals(&self, vm: &VirtualMachine) -> PyResult { + if self.code.incognito { + Err(vm.new_type_error("Can't get __globals__ on an incognito function".to_owned())) + } else { + Ok(self.scope.globals.clone()) + } + } } #[pyclass] diff --git a/vm/src/vm.rs b/vm/src/vm.rs index cc13bc92e..ea6907296 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -17,7 +17,10 @@ use num_bigint::BigInt; use num_traits::ToPrimitive; use once_cell::sync::Lazy; #[cfg(feature = "rustpython-compiler")] -use rustpython_compiler::{compile, error::CompileError}; +use rustpython_compiler::{ + compile::{self, CompileOpts}, + error::CompileError, +}; use crate::builtins::{self, to_ascii}; use crate::bytecode; @@ -1000,6 +1003,16 @@ impl VirtualMachine { } } + /// Returns a basic CompileOpts instance with options accurate to the vm. Used + /// as the CompileOpts for `vm.compile()`. + #[cfg(feature = "rustpython-compiler")] + pub fn compile_opts(&self) -> CompileOpts { + CompileOpts { + optimize: self.settings.optimize, + ..Default::default() + } + } + #[cfg(feature = "rustpython-compiler")] pub fn compile( &self, @@ -1007,7 +1020,18 @@ impl VirtualMachine { mode: compile::Mode, source_path: String, ) -> Result { - compile::compile(source, mode, source_path, self.settings.optimize) + self.compile_with_opts(source, mode, source_path, self.compile_opts()) + } + + #[cfg(feature = "rustpython-compiler")] + pub fn compile_with_opts( + &self, + source: &str, + mode: compile::Mode, + source_path: String, + opts: CompileOpts, + ) -> Result { + compile::compile(source, mode, source_path, opts) .map(|codeobj| PyCode::new(codeobj).into_ref(self)) .map_err(|mut compile_error| { compile_error.update_statement_info(source.trim_end().to_owned()); diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 057b8699b..af52d4d04 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -263,8 +263,13 @@ impl WASMVirtualMachine { strict_private: Option, ) -> Result<(), JsValue> { self.with(|StoredVirtualMachine { ref vm, .. }| { + let strict_private = strict_private.unwrap_or(false); + let opts = compile::CompileOpts { + incognito: strict_private, + ..vm.compile_opts() + }; let code = vm - .compile(source, Mode::Exec, name.clone()) + .compile_with_opts(source, Mode::Exec, name.clone(), opts) .map_err(convert::syntax_err)?; let attrs = vm.ctx.new_dict(); attrs @@ -282,7 +287,7 @@ impl WASMVirtualMachine { vm.run_code_obj(code, Scope::new(None, attrs.clone(), vm)) .to_js(vm)?; - let module_attrs = if strict_private.unwrap_or(false) { + let module_attrs = if strict_private { let all = attrs .get_item_option("__all__", vm) .to_js(vm)? diff --git a/wasm/tests/test_inject_module.py b/wasm/tests/test_inject_module.py index 082b69b59..4c2d552cf 100644 --- a/wasm/tests/test_inject_module.py +++ b/wasm/tests/test_inject_module.py @@ -1,5 +1,5 @@ def test_inject_private(wdriver): - assert wdriver.execute_script( + wdriver.execute_script( """ const vm = rp.vmStore.init("vm") vm.injectModule( @@ -11,8 +11,17 @@ def get_thing(): return __thing() { __thing: () => 1 }, true ) -return vm.execSingle( - `import mod; mod.get_thing() == 1 and "__thing" not in dir(mod)` +vm.execSingle( +` +import mod +assert mod.get_thing() == 1 +assert "__thing" not in dir(mod) +try: + globs = mod.get_thing.__globals__ +except TypeError: pass +else: + assert False, "incognito function.__globals__ didn't error" +` ); """ )