// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use crate::compilers::CompiledModule; use crate::compilers::JsCompiler; use crate::compilers::JsonCompiler; use crate::compilers::TsCompiler; use crate::compilers::WasmCompiler; use crate::deno_dir; use crate::deno_error::permission_denied; use crate::file_fetcher::SourceFileFetcher; use crate::flags; use crate::lockfile::Lockfile; use crate::metrics::Metrics; use crate::msg; use crate::permissions::DenoPermissions; use crate::progress::Progress; use deno::ErrBox; use deno::ModuleSpecifier; use futures::future::TryFutureExt; use std; use std::env; use std::future::Future; use std::ops::Deref; use std::str; use std::sync::Arc; use std::sync::Mutex; /// Holds state of the program and can be accessed by V8 isolate. pub struct ThreadSafeGlobalState(Arc); /// This structure represents state of single "deno" program. /// /// It is shared by all created workers (thus V8 isolates). #[cfg_attr(feature = "cargo-clippy", allow(stutter))] pub struct GlobalState { /// Flags parsed from `argv` contents. pub flags: flags::DenoFlags, /// Entry script parsed from CLI arguments. pub main_module: Option, /// Permissions parsed from `flags`. pub permissions: DenoPermissions, pub dir: deno_dir::DenoDir, pub metrics: Metrics, pub progress: Progress, pub file_fetcher: SourceFileFetcher, pub js_compiler: JsCompiler, pub json_compiler: JsonCompiler, pub ts_compiler: TsCompiler, pub wasm_compiler: WasmCompiler, pub lockfile: Option>, } impl Clone for ThreadSafeGlobalState { fn clone(&self) -> Self { ThreadSafeGlobalState(self.0.clone()) } } impl Deref for ThreadSafeGlobalState { type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } impl ThreadSafeGlobalState { pub fn new( flags: flags::DenoFlags, progress: Progress, ) -> Result { let custom_root = env::var("DENO_DIR").map(String::into).ok(); let dir = deno_dir::DenoDir::new(custom_root)?; let file_fetcher = SourceFileFetcher::new( dir.deps_cache.clone(), progress.clone(), !flags.reload, flags.cache_blacklist.clone(), flags.no_fetch, )?; let ts_compiler = TsCompiler::new( file_fetcher.clone(), dir.gen_cache.clone(), !flags.reload, flags.config_path.clone(), )?; let main_module: Option = if flags.argv.len() <= 1 { None } else { let root_specifier = flags.argv[1].clone(); Some(ModuleSpecifier::resolve_url_or_path(&root_specifier)?) }; // Note: reads lazily from disk on first call to lockfile.check() let lockfile = if let Some(filename) = &flags.lock { Some(Mutex::new(Lockfile::new(filename.to_string()))) } else { None }; let state = GlobalState { main_module, dir, permissions: DenoPermissions::from_flags(&flags), flags, metrics: Metrics::default(), progress, file_fetcher, ts_compiler, js_compiler: JsCompiler {}, json_compiler: JsonCompiler {}, wasm_compiler: WasmCompiler::default(), lockfile, }; Ok(ThreadSafeGlobalState(Arc::new(state))) } pub fn fetch_compiled_module( self: &Self, module_specifier: &ModuleSpecifier, maybe_referrer: Option, ) -> impl Future> { let state1 = self.clone(); let state2 = self.clone(); self .file_fetcher .fetch_source_file_async(&module_specifier, maybe_referrer) .and_then(move |out| match out.media_type { msg::MediaType::Unknown => state1.js_compiler.compile_async(&out), msg::MediaType::Json => state1.json_compiler.compile_async(&out), msg::MediaType::Wasm => { state1.wasm_compiler.compile_async(state1.clone(), &out) } msg::MediaType::TypeScript | msg::MediaType::TSX | msg::MediaType::JSX => { state1.ts_compiler.compile_async(state1.clone(), &out) } msg::MediaType::JavaScript => { if state1.ts_compiler.compile_js { state1.ts_compiler.compile_async(state1.clone(), &out) } else { state1.js_compiler.compile_async(&out) } } }) .and_then(move |compiled_module| { if let Some(ref lockfile) = state2.lockfile { let mut g = lockfile.lock().unwrap(); if state2.flags.lock_write { g.insert(&compiled_module); } else { let check = match g.check(&compiled_module) { Err(e) => return futures::future::err(ErrBox::from(e)), Ok(v) => v, }; if !check { eprintln!( "Subresource integrety check failed --lock={}\n{}", g.filename, compiled_module.name ); std::process::exit(10); } } } futures::future::ok(compiled_module) }) } #[inline] pub fn check_read(&self, filename: &str) -> Result<(), ErrBox> { self.permissions.check_read(filename) } #[inline] pub fn check_write(&self, filename: &str) -> Result<(), ErrBox> { self.permissions.check_write(filename) } #[inline] pub fn check_env(&self) -> Result<(), ErrBox> { self.permissions.check_env() } #[inline] pub fn check_net(&self, hostname: &str, port: u16) -> Result<(), ErrBox> { self.permissions.check_net(hostname, port) } #[inline] pub fn check_net_url(&self, url: &url::Url) -> Result<(), ErrBox> { self.permissions.check_net_url(url) } #[inline] pub fn check_run(&self) -> Result<(), ErrBox> { self.permissions.check_run() } pub fn check_dyn_import( self: &Self, module_specifier: &ModuleSpecifier, ) -> Result<(), ErrBox> { let u = module_specifier.as_url(); match u.scheme() { "http" | "https" => { self.check_net_url(u)?; Ok(()) } "file" => { let filename = u .to_file_path() .unwrap() .into_os_string() .into_string() .unwrap(); self.check_read(&filename)?; Ok(()) } _ => Err(permission_denied()), } } #[cfg(test)] pub fn mock(argv: Vec) -> ThreadSafeGlobalState { ThreadSafeGlobalState::new( flags::DenoFlags { argv, ..flags::DenoFlags::default() }, Progress::new(), ) .unwrap() } } #[test] fn thread_safe() { fn f(_: S) {} f(ThreadSafeGlobalState::mock(vec![ String::from("./deno"), String::from("hello.js"), ])); } #[test] fn import_map_given_for_repl() { let _result = ThreadSafeGlobalState::new( flags::DenoFlags { argv: vec![String::from("./deno")], import_map_path: Some("import_map.json".to_string()), ..flags::DenoFlags::default() }, Progress::new(), ); }