Rename //src/ to //cli/ (#1962)

To better distinguish the deno_core crate from the executable deno,
which will now be called "the cli" internally.
This commit is contained in:
Ryan Dahl 2019-03-19 12:18:05 -04:00 committed by GitHub
parent c7d81fa9ff
commit fa3c35301a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 21 additions and 17 deletions

70
cli/ansi.rs Normal file
View file

@ -0,0 +1,70 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use ansi_term::Color::Fixed;
use ansi_term::Color::Red;
use ansi_term::Style;
use regex::Regex;
use std::env;
use std::fmt;
lazy_static! {
// STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
static ref STRIP_ANSI_RE: Regex = Regex::new(
r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
).unwrap();
static ref NO_COLOR: bool = {
env::var_os("NO_COLOR").is_some()
};
}
/// Helper function to strip ansi codes.
#[cfg(test)]
pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
STRIP_ANSI_RE.replace_all(s, "")
}
pub fn use_color() -> bool {
!(*NO_COLOR)
}
pub fn red_bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.bold().fg(Red);
}
style.paint(s)
}
pub fn italic_bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.italic().bold();
}
style.paint(s)
}
pub fn yellow(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
// matches TypeScript's ForegroundColorEscapeSequences.Yellow
style = style.fg(Fixed(11));
}
style.paint(s)
}
pub fn cyan(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
// matches TypeScript's ForegroundColorEscapeSequences.Cyan
style = style.fg(Fixed(14));
}
style.paint(s)
}
pub fn bold(s: String) -> impl fmt::Display {
let mut style = Style::new();
if use_color() {
style = style.bold();
}
style.paint(s)
}

90
cli/cli.rs Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
#![allow(unused_variables)]
#![allow(dead_code)]
use crate::errors::DenoResult;
use crate::isolate_state::IsolateState;
use crate::ops;
use crate::permissions::DenoPermissions;
use deno_core::deno_buf;
use deno_core::deno_mod;
use deno_core::Behavior;
use deno_core::Op;
use deno_core::StartupData;
use std::sync::atomic::Ordering;
use std::sync::Arc;
// Buf represents a byte array returned from a "Op". The message might be empty
// (which will be translated into a null object on the javascript side) or it is
// a heap allocated opaque sequence of bytes. Usually a flatbuffer message.
pub type Buf = Box<[u8]>;
/// Implements deno_core::Behavior for the main Deno command-line.
pub struct Cli {
startup_data: Option<StartupData>,
pub state: Arc<IsolateState>,
pub permissions: Arc<DenoPermissions>, // TODO(ry) move to IsolateState
}
impl Cli {
pub fn new(
startup_data: Option<StartupData>,
state: Arc<IsolateState>,
permissions: DenoPermissions,
) -> Self {
Self {
startup_data,
state,
permissions: Arc::new(permissions),
}
}
#[inline]
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
self.permissions.check_read(filename)
}
#[inline]
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
self.permissions.check_write(filename)
}
#[inline]
pub fn check_env(&self) -> DenoResult<()> {
self.permissions.check_env()
}
#[inline]
pub fn check_net(&self, filename: &str) -> DenoResult<()> {
self.permissions.check_net(filename)
}
#[inline]
pub fn check_run(&self) -> DenoResult<()> {
self.permissions.check_run()
}
}
impl Behavior for Cli {
fn startup_data(&mut self) -> Option<StartupData> {
self.startup_data.take()
}
fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod {
self
.state
.metrics
.resolve_count
.fetch_add(1, Ordering::Relaxed);
let mut modules = self.state.modules.lock().unwrap();
modules.resolve_cb(&self.state.dir, specifier, referrer)
}
fn dispatch(
&mut self,
control: &[u8],
zero_copy: deno_buf,
) -> (bool, Box<Op>) {
ops::dispatch(self, control, zero_copy)
}
}

151
cli/compiler.rs Normal file
View file

@ -0,0 +1,151 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::cli::Buf;
use crate::isolate_state::IsolateState;
use crate::msg;
use crate::permissions::{DenoPermissions, PermissionAccessor};
use crate::resources;
use crate::resources::Resource;
use crate::resources::ResourceId;
use crate::startup_data;
use crate::workers;
use futures::Future;
use serde_json;
use std::str;
use std::sync::Mutex;
lazy_static! {
static ref C_RID: Mutex<Option<ResourceId>> = Mutex::new(None);
}
// This corresponds to JS ModuleMetaData.
// TODO Rename one or the other so they correspond.
#[derive(Debug)]
pub struct ModuleMetaData {
pub module_name: String,
pub filename: String,
pub media_type: msg::MediaType,
pub source_code: Vec<u8>,
pub maybe_output_code_filename: Option<String>,
pub maybe_output_code: Option<Vec<u8>>,
pub maybe_source_map_filename: Option<String>,
pub maybe_source_map: Option<Vec<u8>>,
}
impl ModuleMetaData {
pub fn js_source(&self) -> String {
if self.media_type == msg::MediaType::Json {
return format!(
"export default {};",
str::from_utf8(&self.source_code).unwrap()
);
}
match self.maybe_output_code {
None => str::from_utf8(&self.source_code).unwrap().to_string(),
Some(ref output_code) => str::from_utf8(output_code).unwrap().to_string(),
}
}
}
fn lazy_start(parent_state: &IsolateState) -> Resource {
let mut cell = C_RID.lock().unwrap();
let startup_data = startup_data::compiler_isolate_init();
let permissions = DenoPermissions {
allow_read: PermissionAccessor::from(true),
allow_write: PermissionAccessor::from(true),
allow_net: PermissionAccessor::from(true),
..Default::default()
};
let rid = cell.get_or_insert_with(|| {
let resource = workers::spawn(
Some(startup_data),
parent_state,
"compilerMain()".to_string(),
permissions,
);
resource.rid
});
Resource { rid: *rid }
}
fn req(specifier: &str, referrer: &str) -> Buf {
json!({
"specifier": specifier,
"referrer": referrer,
}).to_string()
.into_boxed_str()
.into_boxed_bytes()
}
pub fn compile_sync(
parent_state: &IsolateState,
specifier: &str,
referrer: &str,
module_meta_data: &ModuleMetaData,
) -> ModuleMetaData {
let req_msg = req(specifier, referrer);
let compiler = lazy_start(parent_state);
let send_future = resources::worker_post_message(compiler.rid, req_msg);
send_future.wait().unwrap();
let recv_future = resources::worker_recv_message(compiler.rid);
let result = recv_future.wait().unwrap();
assert!(result.is_some());
let res_msg = result.unwrap();
let res_json = std::str::from_utf8(&res_msg).unwrap();
match serde_json::from_str::<serde_json::Value>(res_json) {
Ok(serde_json::Value::Object(map)) => ModuleMetaData {
module_name: module_meta_data.module_name.clone(),
filename: module_meta_data.filename.clone(),
media_type: module_meta_data.media_type,
source_code: module_meta_data.source_code.clone(),
maybe_output_code: match map["outputCode"].as_str() {
Some(str) => Some(str.as_bytes().to_owned()),
_ => None,
},
maybe_output_code_filename: None,
maybe_source_map: match map["sourceMap"].as_str() {
Some(str) => Some(str.as_bytes().to_owned()),
_ => None,
},
maybe_source_map_filename: None,
},
_ => panic!("error decoding compiler response"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_sync() {
let cwd = std::env::current_dir().unwrap();
let cwd_string = cwd.to_str().unwrap().to_owned();
let specifier = "./tests/002_hello.ts";
let referrer = cwd_string + "/";
let mut out = ModuleMetaData {
module_name: "xxx".to_owned(),
filename: "/tests/002_hello.ts".to_owned(),
media_type: msg::MediaType::TypeScript,
source_code: "console.log(\"Hello World\");".as_bytes().to_owned(),
maybe_output_code_filename: None,
maybe_output_code: None,
maybe_source_map_filename: None,
maybe_source_map: None,
};
out = compile_sync(&IsolateState::mock(), specifier, &referrer, &mut out);
assert!(
out
.maybe_output_code
.unwrap()
.starts_with("console.log(\"Hello World\");".as_bytes())
);
}
}

1369
cli/deno_dir.rs Normal file

File diff suppressed because it is too large Load diff

207
cli/errors.rs Normal file
View file

@ -0,0 +1,207 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::js_errors::JSErrorColor;
pub use crate::msg::ErrorKind;
use crate::resolve_addr::ResolveAddrError;
use deno_core::JSError;
use hyper;
use std;
use std::fmt;
use std::io;
use url;
pub type DenoResult<T> = std::result::Result<T, DenoError>;
#[derive(Debug)]
pub struct DenoError {
repr: Repr,
}
#[derive(Debug)]
enum Repr {
Simple(ErrorKind, String),
IoErr(io::Error),
UrlErr(url::ParseError),
HyperErr(hyper::Error),
}
pub fn new(kind: ErrorKind, msg: String) -> DenoError {
DenoError {
repr: Repr::Simple(kind, msg),
}
}
impl DenoError {
pub fn kind(&self) -> ErrorKind {
match self.repr {
Repr::Simple(kind, ref _msg) => kind,
// Repr::Simple(kind) => kind,
Repr::IoErr(ref err) => {
use std::io::ErrorKind::*;
match err.kind() {
NotFound => ErrorKind::NotFound,
PermissionDenied => ErrorKind::PermissionDenied,
ConnectionRefused => ErrorKind::ConnectionRefused,
ConnectionReset => ErrorKind::ConnectionReset,
ConnectionAborted => ErrorKind::ConnectionAborted,
NotConnected => ErrorKind::NotConnected,
AddrInUse => ErrorKind::AddrInUse,
AddrNotAvailable => ErrorKind::AddrNotAvailable,
BrokenPipe => ErrorKind::BrokenPipe,
AlreadyExists => ErrorKind::AlreadyExists,
WouldBlock => ErrorKind::WouldBlock,
InvalidInput => ErrorKind::InvalidInput,
InvalidData => ErrorKind::InvalidData,
TimedOut => ErrorKind::TimedOut,
Interrupted => ErrorKind::Interrupted,
WriteZero => ErrorKind::WriteZero,
Other => ErrorKind::Other,
UnexpectedEof => ErrorKind::UnexpectedEof,
_ => unreachable!(),
}
}
Repr::UrlErr(ref err) => {
use url::ParseError::*;
match err {
EmptyHost => ErrorKind::EmptyHost,
IdnaError => ErrorKind::IdnaError,
InvalidPort => ErrorKind::InvalidPort,
InvalidIpv4Address => ErrorKind::InvalidIpv4Address,
InvalidIpv6Address => ErrorKind::InvalidIpv6Address,
InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter,
RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase,
RelativeUrlWithCannotBeABaseBase => {
ErrorKind::RelativeUrlWithCannotBeABaseBase
}
SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl,
Overflow => ErrorKind::Overflow,
}
}
Repr::HyperErr(ref err) => {
// For some reason hyper::errors::Kind is private.
if err.is_parse() {
ErrorKind::HttpParse
} else if err.is_user() {
ErrorKind::HttpUser
} else if err.is_canceled() {
ErrorKind::HttpCanceled
} else if err.is_closed() {
ErrorKind::HttpClosed
} else {
ErrorKind::HttpOther
}
}
}
}
}
impl fmt::Display for DenoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.repr {
Repr::Simple(_kind, ref err_str) => f.pad(err_str),
Repr::IoErr(ref err) => err.fmt(f),
Repr::UrlErr(ref err) => err.fmt(f),
Repr::HyperErr(ref err) => err.fmt(f),
}
}
}
impl std::error::Error for DenoError {
fn description(&self) -> &str {
match self.repr {
Repr::Simple(_kind, ref msg) => msg.as_str(),
Repr::IoErr(ref err) => err.description(),
Repr::UrlErr(ref err) => err.description(),
Repr::HyperErr(ref err) => err.description(),
}
}
fn cause(&self) -> Option<&dyn std::error::Error> {
match self.repr {
Repr::Simple(_kind, ref _msg) => None,
Repr::IoErr(ref err) => Some(err),
Repr::UrlErr(ref err) => Some(err),
Repr::HyperErr(ref err) => Some(err),
}
}
}
impl From<io::Error> for DenoError {
#[inline]
fn from(err: io::Error) -> Self {
Self {
repr: Repr::IoErr(err),
}
}
}
impl From<url::ParseError> for DenoError {
#[inline]
fn from(err: url::ParseError) -> Self {
Self {
repr: Repr::UrlErr(err),
}
}
}
impl From<hyper::Error> for DenoError {
#[inline]
fn from(err: hyper::Error) -> Self {
Self {
repr: Repr::HyperErr(err),
}
}
}
impl From<ResolveAddrError> for DenoError {
fn from(e: ResolveAddrError) -> Self {
match e {
ResolveAddrError::Syntax => Self {
repr: Repr::Simple(
ErrorKind::InvalidInput,
"invalid address syntax".to_string(),
),
},
ResolveAddrError::Resolution(io_err) => Self {
repr: Repr::IoErr(io_err),
},
}
}
}
pub fn bad_resource() -> DenoError {
new(ErrorKind::BadResource, String::from("bad resource id"))
}
pub fn permission_denied() -> DenoError {
new(
ErrorKind::PermissionDenied,
String::from("permission denied"),
)
}
#[derive(Debug)]
pub enum RustOrJsError {
Rust(DenoError),
Js(JSError),
}
impl From<DenoError> for RustOrJsError {
fn from(e: DenoError) -> Self {
RustOrJsError::Rust(e)
}
}
impl From<JSError> for RustOrJsError {
fn from(e: JSError) -> Self {
RustOrJsError::Js(e)
}
}
impl fmt::Display for RustOrJsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RustOrJsError::Rust(e) => e.fmt(f),
RustOrJsError::Js(e) => JSErrorColor(e).fmt(f),
}
}
}

291
cli/flags.rs Normal file
View file

@ -0,0 +1,291 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use deno_core::v8_set_flags;
use getopts;
use getopts::Options;
// Creates vector of strings, Vec<String>
#[cfg(test)]
macro_rules! svec {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct DenoFlags {
pub help: bool,
pub log_debug: bool,
pub version: bool,
pub reload: bool,
pub recompile: bool,
pub allow_read: bool,
pub allow_write: bool,
pub allow_net: bool,
pub allow_env: bool,
pub allow_run: bool,
pub no_prompts: bool,
pub types: bool,
pub prefetch: bool,
pub info: bool,
pub fmt: bool,
}
pub fn get_usage(opts: &Options) -> String {
format!(
"Usage: deno script.ts {}
Environment variables:
DENO_DIR Set deno's base directory
NO_COLOR Set to disable color",
opts.usage("")
)
}
/// Checks provided arguments for known options and sets appropriate Deno flags
/// for them. Unknown options are returned for further use.
/// Note:
///
/// 1. This assumes that privileged flags do not accept parameters deno --foo bar.
/// This assumption is currently valid. But if it were to change in the future,
/// this parsing technique would need to be modified. I think we want to keep the
/// privileged flags minimal - so having this restriction is maybe a good thing.
///
/// 2. Misspelled flags will be forwarded to user code - e.g. --allow-ne would
/// not cause an error. I also think this is ok because missing any of the
/// privileged flags is not destructive. Userland flag parsing would catch these
/// errors.
fn set_recognized_flags(
opts: &Options,
flags: &mut DenoFlags,
args: Vec<String>,
) -> Result<Vec<String>, getopts::Fail> {
let mut rest = Vec::<String>::new();
// getopts doesn't allow parsing unknown options so we check them
// one-by-one and handle unrecognized ones manually
// better solution welcome!
for arg in args {
let fake_args = vec![arg];
match opts.parse(&fake_args) {
Err(getopts::Fail::UnrecognizedOption(_)) => {
rest.extend(fake_args);
}
Err(e) => {
return Err(e);
}
Ok(matches) => {
if matches.opt_present("help") {
flags.help = true;
}
if matches.opt_present("log-debug") {
flags.log_debug = true;
}
if matches.opt_present("version") {
flags.version = true;
}
if matches.opt_present("reload") {
flags.reload = true;
}
if matches.opt_present("recompile") {
flags.recompile = true;
}
if matches.opt_present("allow-read") {
flags.allow_read = true;
}
if matches.opt_present("allow-write") {
flags.allow_write = true;
}
if matches.opt_present("allow-net") {
flags.allow_net = true;
}
if matches.opt_present("allow-env") {
flags.allow_env = true;
}
if matches.opt_present("allow-run") {
flags.allow_run = true;
}
if matches.opt_present("allow-all") {
flags.allow_read = true;
flags.allow_env = true;
flags.allow_net = true;
flags.allow_run = true;
flags.allow_read = true;
flags.allow_write = true;
}
if matches.opt_present("no-prompt") {
flags.no_prompts = true;
}
if matches.opt_present("types") {
flags.types = true;
}
if matches.opt_present("prefetch") {
flags.prefetch = true;
}
if matches.opt_present("info") {
flags.info = true;
}
if matches.opt_present("fmt") {
flags.fmt = true;
}
if !matches.free.is_empty() {
rest.extend(matches.free);
}
}
}
}
Ok(rest)
}
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
pub fn set_flags(
args: Vec<String>,
) -> Result<(DenoFlags, Vec<String>, String), String> {
// TODO: all flags passed after "--" are swallowed by v8_set_flags
// eg. deno --allow-net ./test.ts -- --title foobar
// args === ["deno", "--allow-net" "./test.ts"]
let args = v8_set_flags(args);
let mut opts = Options::new();
// TODO(kevinkassimo): v8_set_flags intercepts '-help' with single '-'
// Resolve that and then uncomment line below (enabling Go style -long-flag)
// opts.long_only(true);
opts.optflag("", "allow-read", "Allow file system read access");
opts.optflag("", "allow-write", "Allow file system write access");
opts.optflag("", "allow-net", "Allow network access");
opts.optflag("", "allow-env", "Allow environment access");
opts.optflag("", "allow-run", "Allow running subprocesses");
opts.optflag("A", "allow-all", "Allow all permissions");
opts.optflag("", "no-prompt", "Do not use prompts");
opts.optflag("", "recompile", "Force recompilation of TypeScript code");
opts.optflag("h", "help", "Print this message");
opts.optflag("D", "log-debug", "Log debug output");
opts.optflag("v", "version", "Print the version");
opts.optflag("r", "reload", "Reload cached remote resources");
opts.optflag("", "v8-options", "Print V8 command line options");
opts.optflag("", "types", "Print runtime TypeScript declarations");
opts.optflag("", "prefetch", "Prefetch the dependencies");
opts.optflag("", "info", "Show source file related info");
opts.optflag("", "fmt", "Format code");
let mut flags = DenoFlags::default();
let rest =
set_recognized_flags(&opts, &mut flags, args).map_err(|e| e.to_string())?;
Ok((flags, rest, get_usage(&opts)))
}
#[test]
fn test_set_flags_1() {
let (flags, rest, _) = set_flags(svec!["deno", "--version"]).unwrap();
assert_eq!(rest, svec!["deno"]);
assert_eq!(
flags,
DenoFlags {
version: true,
..DenoFlags::default()
}
);
}
#[test]
fn test_set_flags_2() {
let (flags, rest, _) =
set_flags(svec!["deno", "-r", "-D", "script.ts"]).unwrap();
assert_eq!(rest, svec!["deno", "script.ts"]);
assert_eq!(
flags,
DenoFlags {
log_debug: true,
reload: true,
..DenoFlags::default()
}
);
}
#[test]
fn test_set_flags_3() {
let (flags, rest, _) =
set_flags(svec!["deno", "-r", "script.ts", "--allow-write"]).unwrap();
assert_eq!(rest, svec!["deno", "script.ts"]);
assert_eq!(
flags,
DenoFlags {
reload: true,
allow_write: true,
..DenoFlags::default()
}
);
}
#[test]
fn test_set_flags_4() {
let (flags, rest, _) =
set_flags(svec!["deno", "-Dr", "script.ts", "--allow-write"]).unwrap();
assert_eq!(rest, svec!["deno", "script.ts"]);
assert_eq!(
flags,
DenoFlags {
log_debug: true,
reload: true,
allow_write: true,
..DenoFlags::default()
}
);
}
#[test]
fn test_set_flags_5() {
let (flags, rest, _) = set_flags(svec!["deno", "--types"]).unwrap();
assert_eq!(rest, svec!["deno"]);
assert_eq!(
flags,
DenoFlags {
types: true,
..DenoFlags::default()
}
)
}
#[test]
fn test_set_flags_6() {
let (flags, rest, _) =
set_flags(svec!["deno", "gist.ts", "--title", "X", "--allow-net"]).unwrap();
assert_eq!(rest, svec!["deno", "gist.ts", "--title", "X"]);
assert_eq!(
flags,
DenoFlags {
allow_net: true,
..DenoFlags::default()
}
)
}
#[test]
fn test_set_flags_7() {
let (flags, rest, _) =
set_flags(svec!["deno", "gist.ts", "--allow-all"]).unwrap();
assert_eq!(rest, svec!["deno", "gist.ts"]);
assert_eq!(
flags,
DenoFlags {
allow_net: true,
allow_env: true,
allow_run: true,
allow_read: true,
allow_write: true,
..DenoFlags::default()
}
)
}
#[test]
fn test_set_flags_8() {
let (flags, rest, _) =
set_flags(svec!["deno", "gist.ts", "--allow-read"]).unwrap();
assert_eq!(rest, svec!["deno", "gist.ts"]);
assert_eq!(
flags,
DenoFlags {
allow_read: true,
..DenoFlags::default()
}
)
}

110
cli/fs.rs Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use std;
use std::fs::{create_dir, DirBuilder, File, OpenOptions};
use std::io::ErrorKind;
use std::io::Write;
use std::path::{Path, PathBuf};
use rand;
use rand::Rng;
#[cfg(any(unix))]
use std::os::unix::fs::DirBuilderExt;
#[cfg(any(unix))]
use std::os::unix::fs::PermissionsExt;
pub fn write_file<T: AsRef<[u8]>>(
filename: &Path,
data: T,
perm: u32,
) -> std::io::Result<()> {
write_file_2(filename, data, true, perm, true, false)
}
pub fn write_file_2<T: AsRef<[u8]>>(
filename: &Path,
data: T,
update_perm: bool,
perm: u32,
is_create: bool,
is_append: bool,
) -> std::io::Result<()> {
let mut file = OpenOptions::new()
.read(false)
.write(true)
.append(is_append)
.truncate(!is_append)
.create(is_create)
.open(filename)?;
if update_perm {
set_permissions(&mut file, perm)?;
}
file.write_all(data.as_ref())
}
#[cfg(any(unix))]
fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> {
debug!("set file perm to {}", perm);
file.set_permissions(PermissionsExt::from_mode(perm & 0o777))
}
#[cfg(not(any(unix)))]
fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> {
// NOOP on windows
Ok(())
}
pub fn make_temp_dir(
dir: Option<&Path>,
prefix: Option<&str>,
suffix: Option<&str>,
) -> std::io::Result<PathBuf> {
let prefix_ = prefix.unwrap_or("");
let suffix_ = suffix.unwrap_or("");
let mut buf: PathBuf = match dir {
Some(ref p) => p.to_path_buf(),
None => std::env::temp_dir(),
}.join("_");
let mut rng = rand::thread_rng();
loop {
let unique = rng.gen::<u32>();
buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_));
// TODO: on posix, set mode flags to 0o700.
let r = create_dir(buf.as_path());
match r {
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue,
Ok(_) => return Ok(buf),
Err(e) => return Err(e),
}
}
}
pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> {
debug!("mkdir -p {}", path.display());
let mut builder = DirBuilder::new();
builder.recursive(recursive);
set_dir_permission(&mut builder, perm);
builder.create(path)
}
#[cfg(any(unix))]
fn set_dir_permission(builder: &mut DirBuilder, perm: u32) {
debug!("set dir perm to {}", perm);
builder.mode(perm & 0o777);
}
#[cfg(not(any(unix)))]
fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) {
// NOOP on windows
}
pub fn normalize_path(path: &Path) -> String {
let s = String::from(path.to_str().unwrap());
if cfg!(windows) {
// TODO This isn't correct. Probbly should iterate over components.
s.replace("\\", "/")
} else {
s
}
}

49
cli/global_timer.rs Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
//! This module helps deno implement timers.
//!
//! As an optimization, we want to avoid an expensive calls into rust for every
//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is
//! implemented that calls into Rust for only the smallest timeout. Thus we
//! only need to be able to start and cancel a single timer (or Delay, as Tokio
//! calls it) for an entire Isolate. This is what is implemented here.
use crate::tokio_util::panic_on_error;
use futures::Future;
use std::time::Instant;
use tokio::sync::oneshot;
use tokio::timer::Delay;
pub struct GlobalTimer {
tx: Option<oneshot::Sender<()>>,
}
impl GlobalTimer {
pub fn new() -> Self {
Self { tx: None }
}
pub fn cancel(&mut self) {
if let Some(tx) = self.tx.take() {
tx.send(()).ok();
}
}
pub fn new_timeout(
&mut self,
deadline: Instant,
) -> impl Future<Item = (), Error = ()> {
if self.tx.is_some() {
self.cancel();
}
assert!(self.tx.is_none());
let (tx, rx) = oneshot::channel();
self.tx = Some(tx);
let delay = panic_on_error(Delay::new(deadline));
let rx = panic_on_error(rx);
delay.select(rx).then(|_| Ok(()))
}
}

112
cli/http_body.rs Normal file
View file

@ -0,0 +1,112 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use futures::Async;
use futures::Poll;
use hyper::body::Payload;
use hyper::Body;
use hyper::Chunk;
use std::cmp::min;
use std::io;
use std::io::Read;
use tokio::io::AsyncRead;
/// Wraps `hyper::Body` so that it can be exposed as an `AsyncRead` and integrated
/// into resources more easily.
pub struct HttpBody {
body: Body,
chunk: Option<Chunk>,
pos: usize,
}
impl HttpBody {
pub fn from(body: Body) -> Self {
Self {
body,
chunk: None,
pos: 0,
}
}
}
impl Read for HttpBody {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
unimplemented!();
}
}
impl AsyncRead for HttpBody {
fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, io::Error> {
if let Some(chunk) = self.chunk.take() {
debug!(
"HttpBody Fake Read buf {} chunk {} pos {}",
buf.len(),
chunk.len(),
self.pos
);
let n = min(buf.len(), chunk.len() - self.pos);
{
let rest = &chunk[self.pos..];
buf[..n].clone_from_slice(&rest[..n]);
}
self.pos += n;
if self.pos == chunk.len() {
self.pos = 0;
} else {
self.chunk = Some(chunk);
}
return Ok(Async::Ready(n));
} else {
assert_eq!(self.pos, 0);
}
let p = self.body.poll_data();
match p {
Err(e) => Err(
// TODO Need to map hyper::Error into std::io::Error.
io::Error::new(io::ErrorKind::Other, e),
),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(maybe_chunk)) => match maybe_chunk {
None => Ok(Async::Ready(0)),
Some(chunk) => {
debug!(
"HttpBody Real Read buf {} chunk {} pos {}",
buf.len(),
chunk.len(),
self.pos
);
let n = min(buf.len(), chunk.len());
buf[..n].clone_from_slice(&chunk[..n]);
if buf.len() < chunk.len() {
self.pos = n;
self.chunk = Some(chunk);
}
Ok(Async::Ready(n))
}
},
}
}
}
#[test]
fn test_body_async_read() {
use std::str::from_utf8;
let body = Body::from("hello world");
let mut body = HttpBody::from(body);
let buf = &mut [0, 0, 0, 0, 0];
let r = body.poll_read(buf);
assert!(r.is_ok());
assert_eq!(r.unwrap(), Async::Ready(5));
assert_eq!(from_utf8(buf).unwrap(), "hello");
let r = body.poll_read(buf);
assert!(r.is_ok());
assert_eq!(r.unwrap(), Async::Ready(5));
assert_eq!(from_utf8(buf).unwrap(), " worl");
let r = body.poll_read(buf);
assert!(r.is_ok());
assert_eq!(r.unwrap(), Async::Ready(1));
assert_eq!(from_utf8(&buf[0..1]).unwrap(), "d");
}

166
cli/http_util.rs Normal file
View file

@ -0,0 +1,166 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::errors;
use crate::errors::{DenoError, DenoResult};
use crate::tokio_util;
use futures::future::{loop_fn, Loop};
use futures::{future, Future, Stream};
use hyper;
use hyper::client::{Client, HttpConnector};
use hyper::header::CONTENT_TYPE;
use hyper::Uri;
use hyper_rustls;
type Connector = hyper_rustls::HttpsConnector<HttpConnector>;
lazy_static! {
static ref CONNECTOR: Connector = {
let num_dns_threads = 4;
Connector::new(num_dns_threads)
};
}
pub fn get_client() -> Client<Connector, hyper::Body> {
// TODO use Hyper's connection pool.
let c = CONNECTOR.clone();
Client::builder().build(c)
}
/// Construct the next uri based on base uri and location header fragment
/// See <https://tools.ietf.org/html/rfc3986#section-4.2>
fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri {
if location.starts_with("http://") || location.starts_with("https://") {
// absolute uri
location
.parse::<Uri>()
.expect("provided redirect url should be a valid url")
} else if location.starts_with("//") {
// "//" authority path-abempty
format!("{}:{}", base_uri.scheme_part().unwrap().as_str(), location)
.parse::<Uri>()
.expect("provided redirect url should be a valid url")
} else if location.starts_with('/') {
// path-absolute
let mut new_uri_parts = base_uri.clone().into_parts();
new_uri_parts.path_and_query = Some(location.parse().unwrap());
Uri::from_parts(new_uri_parts).unwrap()
} else {
// assuming path-noscheme | path-empty
let mut new_uri_parts = base_uri.clone().into_parts();
new_uri_parts.path_and_query =
Some(format!("{}/{}", base_uri.path(), location).parse().unwrap());
Uri::from_parts(new_uri_parts).unwrap()
}
}
// The CodeFetch message is used to load HTTP javascript resources and expects a
// synchronous response, this utility method supports that.
pub fn fetch_sync_string(module_name: &str) -> DenoResult<(String, String)> {
let url = module_name.parse::<Uri>().unwrap();
let client = get_client();
// TODO(kevinkassimo): consider set a max redirection counter
// to avoid bouncing between 2 or more urls
let fetch_future = loop_fn((client, url), |(client, url)| {
client
.get(url.clone())
.map_err(DenoError::from)
.and_then(move |response| {
if response.status().is_redirection() {
let location_string = response
.headers()
.get("location")
.expect("url redirection should provide 'location' header")
.to_str()
.unwrap()
.to_string();
debug!("Redirecting to {}...", &location_string);
let new_url = resolve_uri_from_location(&url, &location_string);
return Ok(Loop::Continue((client, new_url)));
}
if !response.status().is_success() {
return Err(errors::new(
errors::ErrorKind::NotFound,
"module not found".to_string(),
));
}
Ok(Loop::Break(response))
})
}).and_then(|response| {
let content_type = response
.headers()
.get(CONTENT_TYPE)
.map(|content_type| content_type.to_str().unwrap().to_string());
let body = response
.into_body()
.concat2()
.map(|body| String::from_utf8(body.to_vec()).unwrap())
.map_err(DenoError::from);
body.join(future::ok(content_type))
}).and_then(|(body_string, maybe_content_type)| {
future::ok((body_string, maybe_content_type.unwrap()))
});
tokio_util::block_on(fetch_future)
}
#[test]
fn test_fetch_sync_string() {
// Relies on external http server. See tools/http_server.py
tokio_util::init(|| {
let (p, m) =
fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap();
println!("package.json len {}", p.len());
assert!(p.len() > 1);
assert!(m == "application/json")
});
}
#[test]
fn test_fetch_sync_string_with_redirect() {
// Relies on external http server. See tools/http_server.py
tokio_util::init(|| {
let (p, m) =
fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap();
println!("package.json len {}", p.len());
assert!(p.len() > 1);
assert!(m == "application/json")
});
}
#[test]
fn test_resolve_uri_from_location_full_1() {
let url = "http://deno.land".parse::<Uri>().unwrap();
let new_uri = resolve_uri_from_location(&url, "http://golang.org");
assert_eq!(new_uri.host().unwrap(), "golang.org");
}
#[test]
fn test_resolve_uri_from_location_full_2() {
let url = "https://deno.land".parse::<Uri>().unwrap();
let new_uri = resolve_uri_from_location(&url, "https://golang.org");
assert_eq!(new_uri.host().unwrap(), "golang.org");
}
#[test]
fn test_resolve_uri_from_location_relative_1() {
let url = "http://deno.land/x".parse::<Uri>().unwrap();
let new_uri = resolve_uri_from_location(&url, "//rust-lang.org/en-US");
assert_eq!(new_uri.host().unwrap(), "rust-lang.org");
assert_eq!(new_uri.path(), "/en-US");
}
#[test]
fn test_resolve_uri_from_location_relative_2() {
let url = "http://deno.land/x".parse::<Uri>().unwrap();
let new_uri = resolve_uri_from_location(&url, "/y");
assert_eq!(new_uri.host().unwrap(), "deno.land");
assert_eq!(new_uri.path(), "/y");
}
#[test]
fn test_resolve_uri_from_location_relative_3() {
let url = "http://deno.land/x".parse::<Uri>().unwrap();
let new_uri = resolve_uri_from_location(&url, "z");
assert_eq!(new_uri.host().unwrap(), "deno.land");
assert_eq!(new_uri.path(), "/x/z");
}

236
cli/isolate.rs Normal file
View file

@ -0,0 +1,236 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::cli::Cli;
use crate::compiler::compile_sync;
use crate::compiler::ModuleMetaData;
use crate::errors::DenoError;
use crate::errors::RustOrJsError;
use crate::isolate_state::IsolateState;
use crate::js_errors;
use crate::msg;
use deno_core;
use deno_core::deno_mod;
use deno_core::JSError;
use futures::Async;
use futures::Future;
use std::sync::Arc;
type CoreIsolate = deno_core::Isolate<Cli>;
/// Wraps deno_core::Isolate to provide source maps, ops for the CLI, and
/// high-level module loading
pub struct Isolate {
inner: CoreIsolate,
state: Arc<IsolateState>,
}
impl Isolate {
pub fn new(cli: Cli) -> Isolate {
let state = cli.state.clone();
Self {
inner: CoreIsolate::new(cli),
state,
}
}
/// Same as execute2() but the filename defaults to "<anonymous>".
pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
self.execute2("<anonymous>", js_source)
}
/// Executes the provided JavaScript source code. The js_filename argument is
/// provided only for debugging purposes.
pub fn execute2(
&mut self,
js_filename: &str,
js_source: &str,
) -> Result<(), JSError> {
self.inner.execute(js_filename, js_source)
}
// TODO(ry) make this return a future.
fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> {
// basically iterate over the imports, start loading them.
let referrer_name = {
let g = self.state.modules.lock().unwrap();
g.get_name(id).unwrap().clone()
};
for specifier in self.inner.mod_get_imports(id) {
let (name, _local_filename) = self
.state
.dir
.resolve_module(&specifier, &referrer_name)
.map_err(DenoError::from)
.map_err(RustOrJsError::from)?;
debug!("mod_load_deps {}", name);
if !self.state.modules.lock().unwrap().is_registered(&name) {
let out = fetch_module_meta_data_and_maybe_compile(
&self.state,
&specifier,
&referrer_name,
)?;
let child_id = self.mod_new_and_register(
false,
&out.module_name.clone(),
&out.js_source(),
)?;
self.mod_load_deps(child_id)?;
}
}
Ok(())
}
/// Executes the provided JavaScript module.
pub fn execute_mod(
&mut self,
js_filename: &str,
is_prefetch: bool,
) -> Result<(), RustOrJsError> {
// TODO move isolate_state::execute_mod impl here.
self
.execute_mod_inner(js_filename, is_prefetch)
.map_err(|err| match err {
RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)),
x => x,
})
}
/// High-level way to execute modules.
/// This will issue HTTP requests and file system calls.
/// Blocks. TODO(ry) Don't block.
fn execute_mod_inner(
&mut self,
url: &str,
is_prefetch: bool,
) -> Result<(), RustOrJsError> {
let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".")
.map_err(RustOrJsError::from)?;
let id = self
.mod_new_and_register(true, &out.module_name.clone(), &out.js_source())
.map_err(RustOrJsError::from)?;
self.mod_load_deps(id)?;
self
.inner
.mod_instantiate(id)
.map_err(RustOrJsError::from)?;
if !is_prefetch {
self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?;
}
Ok(())
}
/// Wraps Isolate::mod_new but registers with modules.
fn mod_new_and_register(
&self,
main: bool,
name: &str,
source: &str,
) -> Result<deno_mod, JSError> {
let id = self.inner.mod_new(main, name, source)?;
self.state.modules.lock().unwrap().register(id, &name);
Ok(id)
}
pub fn print_file_info(&self, module: &str) {
let m = self.state.modules.lock().unwrap();
m.print_file_info(&self.state.dir, module.to_string());
}
/// Applies source map to the error.
fn apply_source_map(&self, err: JSError) -> JSError {
js_errors::apply_source_map(&err, &self.state.dir)
}
}
impl Future for Isolate {
type Item = ();
type Error = JSError;
fn poll(&mut self) -> Result<Async<()>, Self::Error> {
self.inner.poll().map_err(|err| self.apply_source_map(err))
}
}
fn fetch_module_meta_data_and_maybe_compile(
state: &Arc<IsolateState>,
specifier: &str,
referrer: &str,
) -> Result<ModuleMetaData, DenoError> {
let mut out = state.dir.fetch_module_meta_data(specifier, referrer)?;
if (out.media_type == msg::MediaType::TypeScript
&& out.maybe_output_code.is_none())
|| state.flags.recompile
{
debug!(">>>>> compile_sync START");
out = compile_sync(state, specifier, &referrer, &out);
debug!(">>>>> compile_sync END");
state.dir.code_cache(&out)?;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flags;
use crate::permissions::DenoPermissions;
use crate::tokio_util;
use futures::future::lazy;
use std::sync::atomic::Ordering;
#[test]
fn execute_mod() {
let filename = std::env::current_dir()
.unwrap()
.join("tests/esm_imports_a.js");
let filename = filename.to_str().unwrap().to_string();
let argv = vec![String::from("./deno"), filename.clone()];
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
let state_ = state.clone();
tokio_util::run(lazy(move || {
let cli = Cli::new(None, state.clone(), DenoPermissions::default());
let mut isolate = Isolate::new(cli);
if let Err(err) = isolate.execute_mod(&filename, false) {
eprintln!("execute_mod err {:?}", err);
}
tokio_util::panic_on_error(isolate)
}));
let metrics = &state_.metrics;
assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1);
}
#[test]
fn execute_mod_circular() {
let filename = std::env::current_dir().unwrap().join("tests/circular1.js");
let filename = filename.to_str().unwrap().to_string();
let argv = vec![String::from("./deno"), filename.clone()];
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
let state_ = state.clone();
tokio_util::run(lazy(move || {
let cli = Cli::new(None, state.clone(), DenoPermissions::default());
let mut isolate = Isolate::new(cli);
if let Err(err) = isolate.execute_mod(&filename, false) {
eprintln!("execute_mod err {:?}", err);
}
tokio_util::panic_on_error(isolate)
}));
let metrics = &state_.metrics;
assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2);
}
}

110
cli/isolate_state.rs Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::cli::Buf;
use crate::deno_dir;
use crate::flags;
use crate::global_timer::GlobalTimer;
use crate::modules::Modules;
use futures::sync::mpsc as async_mpsc;
use std;
use std::env;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
pub type WorkerSender = async_mpsc::Sender<Buf>;
pub type WorkerReceiver = async_mpsc::Receiver<Buf>;
pub type WorkerChannels = (WorkerSender, WorkerReceiver);
// AtomicU64 is currently unstable
#[derive(Default)]
pub struct Metrics {
pub ops_dispatched: AtomicUsize,
pub ops_completed: AtomicUsize,
pub bytes_sent_control: AtomicUsize,
pub bytes_sent_data: AtomicUsize,
pub bytes_received: AtomicUsize,
pub resolve_count: AtomicUsize,
}
// Isolate cannot be passed between threads but IsolateState can.
// IsolateState satisfies Send and Sync.
// So any state that needs to be accessed outside the main V8 thread should be
// inside IsolateState.
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
pub struct IsolateState {
pub dir: deno_dir::DenoDir,
pub argv: Vec<String>,
pub flags: flags::DenoFlags,
pub metrics: Metrics,
pub modules: Mutex<Modules>,
pub worker_channels: Option<Mutex<WorkerChannels>>,
pub global_timer: Mutex<GlobalTimer>,
}
impl IsolateState {
pub fn new(
flags: flags::DenoFlags,
argv_rest: Vec<String>,
worker_channels: Option<WorkerChannels>,
) -> Self {
let custom_root = env::var("DENO_DIR").map(|s| s.into()).ok();
Self {
dir: deno_dir::DenoDir::new(flags.reload, flags.recompile, custom_root)
.unwrap(),
argv: argv_rest,
flags,
metrics: Metrics::default(),
modules: Mutex::new(Modules::new()),
worker_channels: worker_channels.map(Mutex::new),
global_timer: Mutex::new(GlobalTimer::new()),
}
}
pub fn main_module(&self) -> Option<String> {
if self.argv.len() <= 1 {
None
} else {
let specifier = self.argv[1].clone();
let referrer = ".";
match self.dir.resolve_module_url(&specifier, referrer) {
Ok(url) => Some(url.to_string()),
Err(e) => {
debug!("Potentially swallowed error {}", e);
None
}
}
}
}
#[cfg(test)]
pub fn mock() -> IsolateState {
let argv = vec![String::from("./deno"), String::from("hello.js")];
// For debugging: argv.push_back(String::from("-D"));
let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
IsolateState::new(flags, rest_argv, None)
}
pub fn metrics_op_dispatched(
&self,
bytes_sent_control: usize,
bytes_sent_data: usize,
) {
self.metrics.ops_dispatched.fetch_add(1, Ordering::SeqCst);
self
.metrics
.bytes_sent_control
.fetch_add(bytes_sent_control, Ordering::SeqCst);
self
.metrics
.bytes_sent_data
.fetch_add(bytes_sent_data, Ordering::SeqCst);
}
pub fn metrics_op_completed(&self, bytes_received: usize) {
self.metrics.ops_completed.fetch_add(1, Ordering::SeqCst);
self
.metrics
.bytes_received
.fetch_add(bytes_received, Ordering::SeqCst);
}
}

424
cli/js_errors.rs Normal file
View file

@ -0,0 +1,424 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
//! This mod adds source maps and ANSI color display to deno_core::JSError.
use crate::ansi;
use deno_core::JSError;
use deno_core::StackFrame;
use source_map_mappings::parse_mappings;
use source_map_mappings::Bias;
use source_map_mappings::Mappings;
use std::collections::HashMap;
use std::fmt;
use std::str;
/// Wrapper around JSError which provides color to_string.
pub struct JSErrorColor<'a>(pub &'a JSError);
struct StackFrameColor<'a>(&'a StackFrame);
pub trait SourceMapGetter {
/// Returns the raw source map file.
fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>>;
}
/// Cached filename lookups. The key can be None if a previous lookup failed to
/// find a SourceMap.
type CachedMaps = HashMap<String, Option<SourceMap>>;
struct SourceMap {
mappings: Mappings,
sources: Vec<String>,
}
impl<'a> fmt::Display for StackFrameColor<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let frame = self.0;
// Note when we print to string, we change from 0-indexed to 1-indexed.
let function_name = ansi::italic_bold(frame.function_name.clone());
let script_line_column =
format_script_line_column(&frame.script_name, frame.line, frame.column);
if !frame.function_name.is_empty() {
write!(f, " at {} ({})", function_name, script_line_column)
} else if frame.is_eval {
write!(f, " at eval ({})", script_line_column)
} else {
write!(f, " at {}", script_line_column)
}
}
}
fn format_script_line_column(
script_name: &str,
line: i64,
column: i64,
) -> String {
// TODO match this style with how typescript displays errors.
let line = ansi::yellow((1 + line).to_string());
let column = ansi::yellow((1 + column).to_string());
let script_name = ansi::cyan(script_name.to_string());
format!("{}:{}:{}", script_name, line, column)
}
impl<'a> fmt::Display for JSErrorColor<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let e = self.0;
if e.script_resource_name.is_some() {
let script_resource_name = e.script_resource_name.as_ref().unwrap();
// Avoid showing internal code from gen/bundle/main.js
if script_resource_name != "gen/bundle/main.js"
&& script_resource_name != "gen/bundle/compiler.js"
{
if e.line_number.is_some() && e.start_column.is_some() {
assert!(e.line_number.is_some());
assert!(e.start_column.is_some());
let script_line_column = format_script_line_column(
script_resource_name,
e.line_number.unwrap() - 1,
e.start_column.unwrap() - 1,
);
write!(f, "{}", script_line_column)?;
}
if e.source_line.is_some() {
write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?;
let mut s = String::new();
for i in 0..e.end_column.unwrap() {
if i >= e.start_column.unwrap() {
s.push('^');
} else {
s.push(' ');
}
}
writeln!(f, "{}", ansi::red_bold(s))?;
}
}
}
write!(f, "{}", ansi::bold(e.message.clone()))?;
for frame in &e.frames {
write!(f, "\n{}", StackFrameColor(&frame).to_string())?;
}
Ok(())
}
}
impl SourceMap {
fn from_json(json_str: &str) -> Option<Self> {
// Ugly. Maybe use serde_derive.
match serde_json::from_str::<serde_json::Value>(json_str) {
Ok(serde_json::Value::Object(map)) => match map["mappings"].as_str() {
None => None,
Some(mappings_str) => {
match parse_mappings::<()>(mappings_str.as_bytes()) {
Err(_) => None,
Ok(mappings) => {
if !map["sources"].is_array() {
return None;
}
let sources_val = map["sources"].as_array().unwrap();
let mut sources = Vec::<String>::new();
for source_val in sources_val {
match source_val.as_str() {
None => return None,
Some(source) => {
sources.push(source.to_string());
}
}
}
Some(SourceMap { sources, mappings })
}
}
}
},
_ => None,
}
}
}
fn frame_apply_source_map(
frame: &StackFrame,
mappings_map: &mut CachedMaps,
getter: &dyn SourceMapGetter,
) -> StackFrame {
let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter);
let frame_pos = (
frame.script_name.to_owned(),
frame.line as i64,
frame.column as i64,
);
let (script_name, line, column) = match maybe_sm {
None => frame_pos,
Some(sm) => match sm.mappings.original_location_for(
frame.line as u32,
frame.column as u32,
Bias::default(),
) {
None => frame_pos,
Some(mapping) => match &mapping.original {
None => frame_pos,
Some(original) => {
let orig_source = sm.sources[original.source as usize].clone();
(
orig_source,
i64::from(original.original_line),
i64::from(original.original_column),
)
}
},
},
};
StackFrame {
script_name,
function_name: frame.function_name.clone(),
line,
column,
is_eval: frame.is_eval,
is_constructor: frame.is_constructor,
is_wasm: frame.is_wasm,
}
}
pub fn apply_source_map(
js_error: &JSError,
getter: &dyn SourceMapGetter,
) -> JSError {
let mut mappings_map: CachedMaps = HashMap::new();
let mut frames = Vec::<StackFrame>::new();
for frame in &js_error.frames {
let f = frame_apply_source_map(&frame, &mut mappings_map, getter);
frames.push(f);
}
JSError {
message: js_error.message.clone(),
frames,
error_level: js_error.error_level,
source_line: js_error.source_line.clone(),
// TODO the following need to be source mapped:
script_resource_name: js_error.script_resource_name.clone(),
line_number: js_error.line_number,
start_position: js_error.start_position,
end_position: js_error.end_position,
start_column: js_error.start_column,
end_column: js_error.end_column,
}
}
// The bundle does not get built for 'cargo check', so we don't embed the
// bundle source map.
#[cfg(feature = "check-only")]
fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
None
}
#[cfg(not(feature = "check-only"))]
fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
match script_name {
"gen/bundle/main.js" => Some(
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js.map"))
.to_vec(),
),
"gen/bundle/compiler.js" => Some(
include_bytes!(concat!(
env!("GN_OUT_DIR"),
"/gen/bundle/compiler.js.map"
)).to_vec(),
),
_ => None,
}
}
fn parse_map_string(
script_name: &str,
getter: &dyn SourceMapGetter,
) -> Option<SourceMap> {
builtin_source_map(script_name)
.or_else(|| getter.get_source_map(script_name))
.and_then(|raw_source_map| {
SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap())
})
}
fn get_mappings<'a>(
script_name: &str,
mappings_map: &'a mut CachedMaps,
getter: &dyn SourceMapGetter,
) -> &'a Option<SourceMap> {
mappings_map
.entry(script_name.to_string())
.or_insert_with(|| parse_map_string(script_name, getter))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ansi::strip_ansi_codes;
fn error1() -> JSError {
JSError {
message: "Error: foo bar".to_string(),
source_line: None,
script_resource_name: None,
line_number: None,
start_position: None,
end_position: None,
error_level: None,
start_column: None,
end_column: None,
frames: vec![
StackFrame {
line: 4,
column: 16,
script_name: "foo_bar.ts".to_string(),
function_name: "foo".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
StackFrame {
line: 5,
column: 20,
script_name: "bar_baz.ts".to_string(),
function_name: "qat".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
StackFrame {
line: 1,
column: 1,
script_name: "deno_main.js".to_string(),
function_name: "".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
],
}
}
struct MockSourceMapGetter {}
impl SourceMapGetter for MockSourceMapGetter {
fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> {
let s = match script_name {
"foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
"bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
_ => return None,
};
Some(s.as_bytes().to_owned())
}
}
#[test]
fn js_error_to_string() {
let e = error1();
assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", strip_ansi_codes(&e.to_string()));
}
#[test]
fn js_error_apply_source_map_1() {
let e = error1();
let getter = MockSourceMapGetter {};
let actual = apply_source_map(&e, &getter);
let expected = JSError {
message: "Error: foo bar".to_string(),
source_line: None,
script_resource_name: None,
line_number: None,
start_position: None,
end_position: None,
error_level: None,
start_column: None,
end_column: None,
frames: vec![
StackFrame {
line: 5,
column: 12,
script_name: "foo_bar.ts".to_string(),
function_name: "foo".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
StackFrame {
line: 4,
column: 14,
script_name: "bar_baz.ts".to_string(),
function_name: "qat".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
StackFrame {
line: 1,
column: 1,
script_name: "deno_main.js".to_string(),
function_name: "".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
},
],
};
assert_eq!(actual, expected);
}
#[test]
fn js_error_apply_source_map_2() {
let e = JSError {
message: "TypeError: baz".to_string(),
source_line: None,
script_resource_name: None,
line_number: None,
start_position: None,
end_position: None,
error_level: None,
start_column: None,
end_column: None,
frames: vec![StackFrame {
line: 11,
column: 12,
script_name: "gen/bundle/main.js".to_string(),
function_name: "setLogDebug".to_string(),
is_eval: false,
is_constructor: false,
is_wasm: false,
}],
};
let getter = MockSourceMapGetter {};
let actual = apply_source_map(&e, &getter);
assert_eq!(actual.message, "TypeError: baz");
// Because this is accessing the live bundle, this test might be more fragile
assert_eq!(actual.frames.len(), 1);
assert!(actual.frames[0].script_name.ends_with("js/util.ts"));
}
#[test]
fn source_map_from_json() {
let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#;
let sm = SourceMap::from_json(json).unwrap();
assert_eq!(sm.sources.len(), 1);
assert_eq!(
sm.sources[0],
"file:///Users/rld/src/deno/tests/error_001.ts"
);
let mapping = sm
.mappings
.original_location_for(1, 10, Bias::default())
.unwrap();
assert_eq!(mapping.generated_line, 1);
assert_eq!(mapping.generated_column, 10);
assert_eq!(
mapping.original,
Some(source_map_mappings::OriginalLocation {
source: 0,
original_line: 1,
original_column: 8,
name: None
})
);
}
}

140
cli/main.rs Normal file
View file

@ -0,0 +1,140 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate futures;
#[macro_use]
extern crate serde_json;
mod ansi;
pub mod cli;
pub mod compiler;
pub mod deno_dir;
pub mod errors;
pub mod flags;
mod fs;
mod global_timer;
mod http_body;
mod http_util;
pub mod isolate;
pub mod isolate_state;
pub mod js_errors;
pub mod modules;
pub mod msg;
pub mod msg_util;
pub mod ops;
pub mod permissions;
mod repl;
pub mod resolve_addr;
pub mod resources;
mod startup_data;
mod tokio_util;
mod tokio_write;
pub mod version;
pub mod workers;
use crate::cli::Cli;
use crate::errors::RustOrJsError;
use crate::isolate::Isolate;
use crate::isolate_state::IsolateState;
use futures::lazy;
use futures::Future;
use log::{LevelFilter, Metadata, Record};
use std::env;
use std::sync::Arc;
static LOGGER: Logger = Logger;
struct Logger;
impl log::Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("{} RS - {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
fn print_err_and_exit(err: RustOrJsError) {
eprintln!("{}", err.to_string());
std::process::exit(1);
}
fn js_check<E>(r: Result<(), E>)
where
E: Into<RustOrJsError>,
{
if let Err(err) = r {
print_err_and_exit(err.into());
}
}
fn main() {
#[cfg(windows)]
ansi_term::enable_ansi_support().ok(); // For Windows 10
log::set_logger(&LOGGER).unwrap();
let args = env::args().collect();
let (mut flags, mut rest_argv, usage_string) = flags::set_flags(args)
.unwrap_or_else(|err| {
eprintln!("{}", err);
std::process::exit(1)
});
if flags.help {
println!("{}", &usage_string);
std::process::exit(0);
}
log::set_max_level(if flags.log_debug {
LevelFilter::Debug
} else {
LevelFilter::Warn
});
if flags.fmt {
rest_argv.insert(1, "https://deno.land/std/prettier/main.ts".to_string());
flags.allow_read = true;
flags.allow_write = true;
}
let should_prefetch = flags.prefetch || flags.info;
let should_display_info = flags.info;
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
let state_ = state.clone();
let startup_data = startup_data::deno_isolate_init();
let permissions = permissions::DenoPermissions::from_flags(&state.flags);
let cli = Cli::new(Some(startup_data), state_, permissions);
let mut isolate = Isolate::new(cli);
let main_future = lazy(move || {
// Setup runtime.
js_check(isolate.execute("denoMain()"));
// Execute main module.
if let Some(main_module) = state.main_module() {
debug!("main_module {}", main_module);
js_check(isolate.execute_mod(&main_module, should_prefetch));
if should_display_info {
// Display file info and exit. Do not run file
isolate.print_file_info(&main_module);
std::process::exit(0);
}
}
isolate.then(|result| {
js_check(result);
Ok(())
})
});
tokio_util::run(main_future);
}

204
cli/modules.rs Normal file
View file

@ -0,0 +1,204 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::ansi;
use crate::deno_dir::DenoDir;
use crate::msg;
use deno_core::deno_mod;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt;
pub struct ModuleInfo {
name: String,
children: Vec<deno_mod>,
}
/// A collection of JS modules.
#[derive(Default)]
pub struct Modules {
pub info: HashMap<deno_mod, ModuleInfo>,
pub by_name: HashMap<String, deno_mod>,
}
impl Modules {
pub fn new() -> Modules {
Self {
info: HashMap::new(),
by_name: HashMap::new(),
}
}
pub fn get_id(&self, name: &str) -> Option<deno_mod> {
self.by_name.get(name).cloned()
}
pub fn get_children(&self, id: deno_mod) -> Option<&Vec<deno_mod>> {
self.info.get(&id).map(|i| &i.children)
}
pub fn get_name(&self, id: deno_mod) -> Option<&String> {
self.info.get(&id).map(|i| &i.name)
}
pub fn is_registered(&self, name: &str) -> bool {
self.by_name.get(name).is_some()
}
pub fn register(&mut self, id: deno_mod, name: &str) {
let name = String::from(name);
debug!("register {}", name);
self.by_name.insert(name.clone(), id);
self.info.insert(
id,
ModuleInfo {
name,
children: Vec::new(),
},
);
}
pub fn resolve_cb(
&mut self,
deno_dir: &DenoDir,
specifier: &str,
referrer: deno_mod,
) -> deno_mod {
debug!("resolve_cb {}", specifier);
let maybe_info = self.info.get_mut(&referrer);
if maybe_info.is_none() {
debug!("cant find referrer {}", referrer);
return 0;
}
let info = maybe_info.unwrap();
let referrer_name = &info.name;
let r = deno_dir.resolve_module(specifier, referrer_name);
if let Err(err) = r {
debug!("potentially swallowed err: {}", err);
return 0;
}
let (name, _local_filename) = r.unwrap();
if let Some(id) = self.by_name.get(&name) {
let child_id = *id;
info.children.push(child_id);
return child_id;
} else {
return 0;
}
}
pub fn print_file_info(&self, deno_dir: &DenoDir, filename: String) {
let maybe_out = deno_dir.fetch_module_meta_data(&filename, ".");
if maybe_out.is_err() {
println!("{}", maybe_out.unwrap_err());
return;
}
let out = maybe_out.unwrap();
println!("{} {}", ansi::bold("local:".to_string()), &(out.filename));
println!(
"{} {}",
ansi::bold("type:".to_string()),
msg::enum_name_media_type(out.media_type)
);
if out.maybe_output_code_filename.is_some() {
println!(
"{} {}",
ansi::bold("compiled:".to_string()),
out.maybe_output_code_filename.as_ref().unwrap(),
);
}
if out.maybe_source_map_filename.is_some() {
println!(
"{} {}",
ansi::bold("map:".to_string()),
out.maybe_source_map_filename.as_ref().unwrap()
);
}
let deps = Deps::new(self, &out.module_name);
println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name);
if let Some(ref depsdeps) = deps.deps {
for d in depsdeps {
println!("{}", d);
}
}
}
}
pub struct Deps {
pub name: String,
pub deps: Option<Vec<Deps>>,
prefix: String,
is_last: bool,
}
impl Deps {
pub fn new(modules: &Modules, module_name: &str) -> Deps {
let mut seen = HashSet::new();
let id = modules.get_id(module_name).unwrap();
Self::helper(&mut seen, "".to_string(), true, modules, id)
}
fn helper(
seen: &mut HashSet<deno_mod>,
prefix: String,
is_last: bool,
modules: &Modules,
id: deno_mod,
) -> Deps {
let name = modules.get_name(id).unwrap().to_string();
if seen.contains(&id) {
Deps {
name,
prefix,
deps: None,
is_last,
}
} else {
seen.insert(id);
let child_ids = modules.get_children(id).unwrap();
let child_count = child_ids.iter().count();
let deps = child_ids
.iter()
.enumerate()
.map(|(index, dep_id)| {
let new_is_last = index == child_count - 1;
let mut new_prefix = prefix.clone();
new_prefix.push(if is_last { ' ' } else { '│' });
new_prefix.push(' ');
Self::helper(seen, new_prefix, new_is_last, modules, *dep_id)
}).collect();
Deps {
name,
prefix,
deps: Some(deps),
is_last,
}
}
}
}
impl fmt::Display for Deps {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut has_children = false;
if let Some(ref deps) = self.deps {
has_children = !deps.is_empty();
}
write!(
f,
"{}{}─{} {}",
self.prefix,
if self.is_last { "" } else { "" },
if has_children { "" } else { "" },
self.name
)?;
if let Some(ref deps) = self.deps {
for d in deps {
write!(f, "\n{}", d)?;
}
}
Ok(())
}
}

524
cli/msg.fbs Normal file
View file

@ -0,0 +1,524 @@
union Any {
Accept,
Chdir,
Chmod,
Close,
CopyFile,
Cwd,
CwdRes,
Dial,
Environ,
EnvironRes,
Exit,
Fetch,
FetchModuleMetaData,
FetchModuleMetaDataRes,
FetchRes,
FormatError,
FormatErrorRes,
GlobalTimer,
GlobalTimerRes,
GlobalTimerStop,
IsTTY,
IsTTYRes,
Listen,
ListenRes,
MakeTempDir,
MakeTempDirRes,
Metrics,
MetricsRes,
Mkdir,
NewConn,
Now,
NowRes,
Open,
OpenRes,
PermissionRevoke,
Permissions,
PermissionsRes,
Read,
ReadDir,
ReadDirRes,
ReadFile,
ReadFileRes,
ReadRes,
Readlink,
ReadlinkRes,
Remove,
Rename,
ReplReadline,
ReplReadlineRes,
ReplStart,
ReplStartRes,
Resources,
ResourcesRes,
Run,
RunRes,
RunStatus,
RunStatusRes,
Seek,
SetEnv,
Shutdown,
Start,
StartRes,
Stat,
StatRes,
Symlink,
Truncate,
WorkerGetMessage,
WorkerGetMessageRes,
WorkerPostMessage,
Write,
WriteFile,
WriteRes,
}
enum ErrorKind: byte {
NoError = 0,
// io errors
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
BrokenPipe,
AlreadyExists,
WouldBlock,
InvalidInput,
InvalidData,
TimedOut,
Interrupted,
WriteZero,
Other,
UnexpectedEof,
BadResource,
CommandFailed,
// url errors
EmptyHost,
IdnaError,
InvalidPort,
InvalidIpv4Address,
InvalidIpv6Address,
InvalidDomainCharacter,
RelativeUrlWithoutBase,
RelativeUrlWithCannotBeABaseBase,
SetHostOnCannotBeABaseUrl,
Overflow,
// hyper errors
HttpUser,
HttpClosed,
HttpCanceled,
HttpParse,
HttpOther,
TooLarge,
// custom errors
InvalidUri,
InvalidSeekMode,
}
table Cwd {}
table CwdRes {
cwd: string;
}
enum MediaType: byte {
JavaScript = 0,
TypeScript,
Json,
Unknown
}
table Base {
cmd_id: uint32;
sync: bool = false;
error_kind: ErrorKind = NoError;
error: string;
inner: Any;
}
table Start {
unused: int8;
}
table StartRes {
cwd: string;
pid: uint32;
argv: [string];
exec_path: string;
main_module: string; // Absolute URL.
debug_flag: bool;
deps_flag: bool;
types_flag: bool;
version_flag: bool;
deno_version: string;
v8_version: string;
no_color: bool;
}
table FormatError {
error: string;
}
table FormatErrorRes {
error: string;
}
table WorkerGetMessage {
unused: int8;
}
table WorkerGetMessageRes {
data: [ubyte];
}
table WorkerPostMessage {
// data passed thru the zero-copy data parameter.
}
table FetchModuleMetaData {
specifier: string;
referrer: string;
}
table FetchModuleMetaDataRes {
// If it's a non-http module, moduleName and filename will be the same.
// For http modules, moduleName is its resolved http URL, and filename
// is the location of the locally downloaded source code.
module_name: string;
filename: string;
media_type: MediaType;
data: [ubyte];
}
table Chdir {
directory: string;
}
table GlobalTimer {
timeout: int;
}
table GlobalTimerRes { }
table GlobalTimerStop { }
table Exit {
code: int;
}
table Environ {}
table SetEnv {
key: string;
value: string;
}
table EnvironRes {
map: [KeyValue];
}
table KeyValue {
key: string;
value: string;
}
table Permissions {}
table PermissionRevoke {
permission: string;
}
table PermissionsRes {
run: bool;
read: bool;
write: bool;
net: bool;
env: bool;
}
// Note this represents The WHOLE header of an http message, not just the key
// value pairs. That means it includes method and url for Requests and status
// for responses. This is why it is singular "Header" instead of "Headers".
table HttpHeader {
is_request: bool;
// Request only:
method: string;
url: string;
// Response only:
status: uint16;
// Both:
fields: [KeyValue];
}
table Fetch {
header: HttpHeader;
}
table FetchRes {
header: HttpHeader;
body_rid: uint32;
}
table MakeTempDir {
dir: string;
prefix: string;
suffix: string;
}
table MakeTempDirRes {
path: string;
}
table Mkdir {
path: string;
recursive: bool;
mode: uint; // Specified by https://godoc.org/os#FileMode
}
table Chmod {
path: string;
mode: uint; // Specified by https://godoc.org/os#FileMode
}
table Remove {
path: string;
recursive: bool;
}
table ReadFile {
filename: string;
}
table ReadFileRes {
data: [ubyte];
}
table ReadDir {
path: string;
}
table ReadDirRes {
entries: [StatRes];
}
table WriteFile {
filename: string;
data: [ubyte];
update_perm: bool;
perm: uint;
// perm specified by https://godoc.org/os#FileMode
is_create: bool;
is_append: bool;
}
table CopyFile {
from: string;
to: string;
}
table Rename {
oldpath: string;
newpath: string;
}
table Readlink {
name: string;
}
table ReadlinkRes {
path: string;
}
table ReplStart {
history_file: string;
// TODO add config
}
table ReplStartRes {
rid: uint32;
}
table ReplReadline {
rid: uint32;
prompt: string;
}
table ReplReadlineRes {
line: string;
}
table Resources {}
table Resource {
rid: uint32;
repr: string;
}
table ResourcesRes {
resources: [Resource];
}
table Symlink {
oldname: string;
newname: string;
}
table Stat {
filename: string;
lstat: bool;
}
table StatRes {
is_file: bool;
is_symlink: bool;
len: ulong;
modified:ulong;
accessed:ulong;
created:ulong;
mode: uint;
has_mode: bool; // false on windows
name: string;
path: string;
}
table Truncate {
name: string;
len: uint;
}
table Open {
filename: string;
perm: uint;
mode: string;
}
table OpenRes {
rid: uint32;
}
table Read {
rid: uint32;
// (ptr, len) is passed as second parameter to libdeno.send().
}
table ReadRes {
nread: uint;
eof: bool;
}
table Write {
rid: uint32;
}
table WriteRes {
nbyte: uint;
}
table Close {
rid: uint32;
}
table Shutdown {
rid: uint32;
how: uint;
}
table Listen {
network: string;
address: string;
}
table ListenRes {
rid: uint32;
}
table Accept {
rid: uint32;
}
table Dial {
network: string;
address: string;
}
// Response to Accept and Dial.
table NewConn {
rid: uint32;
remote_addr: string;
local_addr: string;
}
table Metrics {}
table MetricsRes {
ops_dispatched: uint64;
ops_completed: uint64;
bytes_sent_control: uint64;
bytes_sent_data: uint64;
bytes_received: uint64;
}
enum ProcessStdio: byte { Inherit, Piped, Null }
table Run {
args: [string];
cwd: string;
env: [KeyValue];
stdin: ProcessStdio;
stdout: ProcessStdio;
stderr: ProcessStdio;
}
table RunRes {
rid: uint32;
pid: uint32;
// The following stdio rids are only valid if "Piped" was specified for the
// corresponding stdio stream. The caller MUST issue a close op for all valid
// stdio streams.
stdin_rid: uint32;
stdout_rid: uint32;
stderr_rid: uint32;
}
table RunStatus {
rid: uint32;
}
table RunStatusRes {
got_signal: bool;
exit_code: int;
exit_signal: int;
}
table Now {}
table NowRes {
time: uint64;
}
table IsTTY {}
table IsTTYRes {
stdin: bool;
stdout: bool;
stderr: bool;
}
table Seek {
rid: uint32;
offset: int;
whence: uint;
}
root_type Base;

26
cli/msg.rs Normal file
View file

@ -0,0 +1,26 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
#![allow(unused_imports)]
#![allow(dead_code)]
#![cfg_attr(
feature = "cargo-clippy",
allow(clippy::all, clippy::pedantic)
)]
use crate::isolate_state;
use flatbuffers;
use std::sync::atomic::Ordering;
// GN_OUT_DIR is set either by build.rs (for the Cargo build), or by
// build_extra/rust/run.py (for the GN+Ninja build).
include!(concat!(env!("GN_OUT_DIR"), "/gen/msg_generated.rs"));
impl<'a> From<&'a isolate_state::Metrics> for MetricsResArgs {
fn from(m: &'a isolate_state::Metrics) -> Self {
MetricsResArgs {
ops_dispatched: m.ops_dispatched.load(Ordering::SeqCst) as u64,
ops_completed: m.ops_completed.load(Ordering::SeqCst) as u64,
bytes_sent_control: m.bytes_sent_control.load(Ordering::SeqCst) as u64,
bytes_sent_data: m.bytes_sent_data.load(Ordering::SeqCst) as u64,
bytes_received: m.bytes_received.load(Ordering::SeqCst) as u64,
}
}
}

127
cli/msg_util.rs Normal file
View file

@ -0,0 +1,127 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Helpers for serialization.
use crate::errors;
use crate::errors::DenoResult;
use crate::msg;
use flatbuffers;
use http::header::HeaderName;
use http::uri::Uri;
use http::Method;
use hyper::header::HeaderMap;
use hyper::header::HeaderValue;
use hyper::Body;
use hyper::Request;
use hyper::Response;
use std::str::FromStr;
type Headers = HeaderMap<HeaderValue>;
pub fn serialize_key_value<'bldr>(
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
key: &str,
value: &str,
) -> flatbuffers::WIPOffset<msg::KeyValue<'bldr>> {
let key = builder.create_string(&key);
let value = builder.create_string(&value);
msg::KeyValue::create(
builder,
&msg::KeyValueArgs {
key: Some(key),
value: Some(value),
},
)
}
pub fn serialize_request_header<'bldr>(
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
r: &Request<Body>,
) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
let method = builder.create_string(r.method().as_str());
let url = builder.create_string(r.uri().to_string().as_ref());
let mut fields = Vec::new();
for (key, val) in r.headers().iter() {
let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
fields.push(kv);
}
let fields = builder.create_vector(fields.as_ref());
msg::HttpHeader::create(
builder,
&msg::HttpHeaderArgs {
is_request: true,
method: Some(method),
url: Some(url),
fields: Some(fields),
..Default::default()
},
)
}
pub fn serialize_fields<'bldr>(
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
headers: &Headers,
) -> flatbuffers::WIPOffset<
flatbuffers::Vector<
'bldr,
flatbuffers::ForwardsUOffset<msg::KeyValue<'bldr>>,
>,
> {
let mut fields = Vec::new();
for (key, val) in headers.iter() {
let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
fields.push(kv);
}
builder.create_vector(fields.as_ref())
}
// Not to be confused with serialize_response which has nothing to do with HTTP.
pub fn serialize_http_response<'bldr>(
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
r: &Response<Body>,
) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
let status = r.status().as_u16();
let fields = serialize_fields(builder, r.headers());
msg::HttpHeader::create(
builder,
&msg::HttpHeaderArgs {
is_request: false,
status,
fields: Some(fields),
..Default::default()
},
)
}
pub fn deserialize_request(
header_msg: msg::HttpHeader<'_>,
body: Body,
) -> DenoResult<Request<Body>> {
let mut r = Request::new(body);
assert!(header_msg.is_request());
let u = header_msg.url().unwrap();
let u = Uri::from_str(u)
.map_err(|e| errors::new(msg::ErrorKind::InvalidUri, e.to_string()))?;
*r.uri_mut() = u;
if let Some(method) = header_msg.method() {
let method = Method::from_str(method).unwrap();
*r.method_mut() = method;
}
if let Some(fields) = header_msg.fields() {
let headers = r.headers_mut();
for i in 0..fields.len() {
let kv = fields.get(i);
let key = kv.key().unwrap();
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
let value = kv.value().unwrap();
let v = HeaderValue::from_str(value).unwrap();
headers.insert(name, v);
}
}
Ok(r)
}

2020
cli/ops.rs Normal file

File diff suppressed because it is too large Load diff

343
cli/permissions.rs Normal file
View file

@ -0,0 +1,343 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use atty;
use crate::flags::DenoFlags;
use ansi_term::Style;
use crate::errors::permission_denied;
use crate::errors::DenoResult;
use std::fmt;
use std::io;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
/// Tri-state value for storing permission state
pub enum PermissionAccessorState {
Allow = 0,
Ask = 1,
Deny = 2,
}
impl From<usize> for PermissionAccessorState {
fn from(val: usize) -> Self {
match val {
0 => PermissionAccessorState::Allow,
1 => PermissionAccessorState::Ask,
2 => PermissionAccessorState::Deny,
_ => unreachable!(),
}
}
}
impl From<bool> for PermissionAccessorState {
fn from(val: bool) -> Self {
match val {
true => PermissionAccessorState::Allow,
false => PermissionAccessorState::Ask,
}
}
}
impl fmt::Display for PermissionAccessorState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PermissionAccessorState::Allow => f.pad("Allow"),
PermissionAccessorState::Ask => f.pad("Ask"),
PermissionAccessorState::Deny => f.pad("Deny"),
}
}
}
#[derive(Debug)]
pub struct PermissionAccessor {
state: Arc<AtomicUsize>,
}
impl PermissionAccessor {
pub fn new(state: PermissionAccessorState) -> Self {
Self {
state: Arc::new(AtomicUsize::new(state as usize)),
}
}
pub fn is_allow(&self) -> bool {
match self.get_state() {
PermissionAccessorState::Allow => true,
_ => false,
}
}
/// If the state is "Allow" walk it back to the default "Ask"
/// Don't do anything if state is "Deny"
pub fn revoke(&self) {
if self.is_allow() {
self.ask();
}
}
pub fn allow(&self) {
self.set_state(PermissionAccessorState::Allow)
}
pub fn ask(&self) {
self.set_state(PermissionAccessorState::Ask)
}
pub fn deny(&self) {
self.set_state(PermissionAccessorState::Deny)
}
/// Update this accessors state based on a PromptResult value
/// This will only update the state if the PromptResult value
/// is one of the "Always" values
pub fn update_with_prompt_result(&self, prompt_result: &PromptResult) {
match prompt_result {
PromptResult::AllowAlways => self.allow(),
PromptResult::DenyAlways => self.deny(),
_ => {}
}
}
#[inline]
pub fn get_state(&self) -> PermissionAccessorState {
self.state.load(Ordering::SeqCst).into()
}
fn set_state(&self, state: PermissionAccessorState) {
self.state.store(state as usize, Ordering::SeqCst)
}
}
impl From<bool> for PermissionAccessor {
fn from(val: bool) -> Self {
Self::new(PermissionAccessorState::from(val))
}
}
impl Default for PermissionAccessor {
fn default() -> Self {
Self {
state: Arc::new(AtomicUsize::new(PermissionAccessorState::Ask as usize)),
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
#[derive(Debug, Default)]
pub struct DenoPermissions {
// Keep in sync with src/permissions.ts
pub allow_read: PermissionAccessor,
pub allow_write: PermissionAccessor,
pub allow_net: PermissionAccessor,
pub allow_env: PermissionAccessor,
pub allow_run: PermissionAccessor,
pub no_prompts: AtomicBool,
}
impl DenoPermissions {
pub fn from_flags(flags: &DenoFlags) -> Self {
Self {
allow_read: PermissionAccessor::from(flags.allow_read),
allow_write: PermissionAccessor::from(flags.allow_write),
allow_env: PermissionAccessor::from(flags.allow_env),
allow_net: PermissionAccessor::from(flags.allow_net),
allow_run: PermissionAccessor::from(flags.allow_run),
no_prompts: AtomicBool::new(flags.no_prompts),
}
}
pub fn check_run(&self) -> DenoResult<()> {
match self.allow_run.get_state() {
PermissionAccessorState::Allow => Ok(()),
PermissionAccessorState::Ask => {
match self.try_permissions_prompt("access to run a subprocess") {
Err(e) => Err(e),
Ok(v) => {
self.allow_run.update_with_prompt_result(&v);
v.check()?;
Ok(())
}
}
}
PermissionAccessorState::Deny => Err(permission_denied()),
}
}
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
match self.allow_read.get_state() {
PermissionAccessorState::Allow => Ok(()),
PermissionAccessorState::Ask => match self
.try_permissions_prompt(&format!("read access to \"{}\"", filename))
{
Err(e) => Err(e),
Ok(v) => {
self.allow_read.update_with_prompt_result(&v);
v.check()?;
Ok(())
}
},
PermissionAccessorState::Deny => Err(permission_denied()),
}
}
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
match self.allow_write.get_state() {
PermissionAccessorState::Allow => Ok(()),
PermissionAccessorState::Ask => match self
.try_permissions_prompt(&format!("write access to \"{}\"", filename))
{
Err(e) => Err(e),
Ok(v) => {
self.allow_write.update_with_prompt_result(&v);
v.check()?;
Ok(())
}
},
PermissionAccessorState::Deny => Err(permission_denied()),
}
}
pub fn check_net(&self, domain_name: &str) -> DenoResult<()> {
match self.allow_net.get_state() {
PermissionAccessorState::Allow => Ok(()),
PermissionAccessorState::Ask => match self.try_permissions_prompt(
&format!("network access to \"{}\"", domain_name),
) {
Err(e) => Err(e),
Ok(v) => {
self.allow_net.update_with_prompt_result(&v);
v.check()?;
Ok(())
}
},
PermissionAccessorState::Deny => Err(permission_denied()),
}
}
pub fn check_env(&self) -> DenoResult<()> {
match self.allow_env.get_state() {
PermissionAccessorState::Allow => Ok(()),
PermissionAccessorState::Ask => {
match self.try_permissions_prompt("access to environment variables") {
Err(e) => Err(e),
Ok(v) => {
self.allow_env.update_with_prompt_result(&v);
v.check()?;
Ok(())
}
}
}
PermissionAccessorState::Deny => Err(permission_denied()),
}
}
/// Try to present the user with a permission prompt
/// will error with permission_denied if no_prompts is enabled
fn try_permissions_prompt(&self, message: &str) -> DenoResult<PromptResult> {
if self.no_prompts.load(Ordering::SeqCst) {
return Err(permission_denied());
}
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
return Err(permission_denied());
};
permission_prompt(message)
}
pub fn allows_run(&self) -> bool {
return self.allow_run.is_allow();
}
pub fn allows_read(&self) -> bool {
return self.allow_read.is_allow();
}
pub fn allows_write(&self) -> bool {
return self.allow_write.is_allow();
}
pub fn allows_net(&self) -> bool {
return self.allow_net.is_allow();
}
pub fn allows_env(&self) -> bool {
return self.allow_env.is_allow();
}
pub fn revoke_run(&self) -> DenoResult<()> {
self.allow_run.revoke();
return Ok(());
}
pub fn revoke_read(&self) -> DenoResult<()> {
self.allow_read.revoke();
return Ok(());
}
pub fn revoke_write(&self) -> DenoResult<()> {
self.allow_write.revoke();
return Ok(());
}
pub fn revoke_net(&self) -> DenoResult<()> {
self.allow_net.revoke();
return Ok(());
}
pub fn revoke_env(&self) -> DenoResult<()> {
self.allow_env.revoke();
return Ok(());
}
}
/// Quad-state value for representing user input on permission prompt
#[derive(Debug, Clone)]
pub enum PromptResult {
AllowAlways = 0,
AllowOnce = 1,
DenyOnce = 2,
DenyAlways = 3,
}
impl PromptResult {
/// If value is any form of deny this will error with permission_denied
pub fn check(&self) -> DenoResult<()> {
match self {
PromptResult::DenyOnce => Err(permission_denied()),
PromptResult::DenyAlways => Err(permission_denied()),
_ => Ok(()),
}
}
}
impl fmt::Display for PromptResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PromptResult::AllowAlways => f.pad("AllowAlways"),
PromptResult::AllowOnce => f.pad("AllowOnce"),
PromptResult::DenyOnce => f.pad("DenyOnce"),
PromptResult::DenyAlways => f.pad("DenyAlways"),
}
}
}
fn permission_prompt(message: &str) -> DenoResult<PromptResult> {
let msg = format!("⚠️ Deno requests {}. Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", message);
// print to stderr so that if deno is > to a file this is still displayed.
eprint!("{}", Style::new().bold().paint(msg));
loop {
let mut input = String::new();
let stdin = io::stdin();
let _nread = stdin.read_line(&mut input)?;
let ch = input.chars().next().unwrap();
match ch.to_ascii_lowercase() {
'a' => return Ok(PromptResult::AllowAlways),
'y' => return Ok(PromptResult::AllowOnce),
'n' => return Ok(PromptResult::DenyOnce),
'd' => return Ok(PromptResult::DenyAlways),
_ => {
// If we don't get a recognized option try again.
let msg_again = format!("Unrecognized option '{}' [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", ch);
eprint!("{}", Style::new().bold().paint(msg_again));
}
};
}
}

114
cli/repl.rs Normal file
View file

@ -0,0 +1,114 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use rustyline;
use crate::msg::ErrorKind;
use std::error::Error;
use crate::deno_dir::DenoDir;
use crate::errors::new as deno_error;
use crate::errors::DenoResult;
use std::path::PathBuf;
#[cfg(not(windows))]
use rustyline::Editor;
// Work around the issue that on Windows, `struct Editor` does not implement the
// `Send` trait, because it embeds a windows HANDLE which is a type alias for
// *mut c_void. This value isn't actually a pointer and there's nothing that
// can be mutated through it, so hack around it. TODO: a prettier solution.
#[cfg(windows)]
use std::ops::{Deref, DerefMut};
#[cfg(windows)]
struct Editor<T: rustyline::Helper> {
inner: rustyline::Editor<T>,
}
#[cfg(windows)]
unsafe impl<T: rustyline::Helper> Send for Editor<T> {}
#[cfg(windows)]
impl<T: rustyline::Helper> Editor<T> {
pub fn new() -> Editor<T> {
Editor {
inner: rustyline::Editor::<T>::new(),
}
}
}
#[cfg(windows)]
impl<T: rustyline::Helper> Deref for Editor<T> {
type Target = rustyline::Editor<T>;
fn deref(&self) -> &rustyline::Editor<T> {
&self.inner
}
}
#[cfg(windows)]
impl<T: rustyline::Helper> DerefMut for Editor<T> {
fn deref_mut(&mut self) -> &mut rustyline::Editor<T> {
&mut self.inner
}
}
pub struct Repl {
editor: Editor<()>,
history_file: PathBuf,
}
impl Repl {
pub fn new(history_file: PathBuf) -> Self {
let mut repl = Self {
editor: Editor::<()>::new(),
history_file,
};
repl.load_history();
repl
}
fn load_history(&mut self) {
debug!("Loading REPL history: {:?}", self.history_file);
self
.editor
.load_history(&self.history_file.to_str().unwrap())
.map_err(|e| debug!("Unable to load history file: {:?} {}", self.history_file, e))
// ignore this error (e.g. it occurs on first load)
.unwrap_or(())
}
fn save_history(&mut self) -> DenoResult<()> {
self
.editor
.save_history(&self.history_file.to_str().unwrap())
.map(|_| debug!("Saved REPL history to: {:?}", self.history_file))
.map_err(|e| {
eprintln!("Unable to save REPL history: {:?} {}", self.history_file, e);
deno_error(ErrorKind::Other, e.description().to_string())
})
}
pub fn readline(&mut self, prompt: &str) -> DenoResult<String> {
self
.editor
.readline(&prompt)
.map(|line| {
self.editor.add_history_entry(line.as_ref());
line
}).map_err(|e| deno_error(ErrorKind::Other, e.description().to_string()))
// Forward error to TS side for processing
}
}
impl Drop for Repl {
fn drop(&mut self) {
self.save_history().unwrap();
}
}
pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf {
let mut p: PathBuf = dir.root.clone();
p.push(history_file);
p
}

156
cli/resolve_addr.rs Normal file
View file

@ -0,0 +1,156 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use futures::Async;
use futures::Future;
use futures::Poll;
use std::error::Error;
use std::fmt;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
/// Go-style network address parsing. Returns a future.
/// Examples:
/// "192.0.2.1:25"
/// ":80"
/// "[2001:db8::1]:80"
/// "198.51.100.1:80"
/// "deno.land:443"
pub fn resolve_addr(address: &str) -> ResolveAddrFuture {
ResolveAddrFuture {
address: address.to_string(),
}
}
#[derive(Debug)]
pub enum ResolveAddrError {
Syntax,
Resolution(std::io::Error),
}
impl fmt::Display for ResolveAddrError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.description())
}
}
impl Error for ResolveAddrError {
fn description(&self) -> &str {
match self {
ResolveAddrError::Syntax => "invalid address syntax",
ResolveAddrError::Resolution(e) => e.description(),
}
}
}
pub struct ResolveAddrFuture {
address: String,
}
impl Future for ResolveAddrFuture {
type Item = SocketAddr;
type Error = ResolveAddrError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// The implementation of this is not actually async at the moment,
// however we intend to use async DNS resolution in the future and
// so we expose this as a future instead of Result.
match split(&self.address) {
None => Err(ResolveAddrError::Syntax),
Some(addr_port_pair) => {
// I absolutely despise the .to_socket_addrs() API.
let r = addr_port_pair
.to_socket_addrs()
.map_err(ResolveAddrError::Resolution);
r.and_then(|mut iter| match iter.next() {
Some(a) => Ok(Async::Ready(a)),
None => panic!("There should be at least one result"),
})
}
}
}
}
fn split(address: &str) -> Option<(&str, u16)> {
address.rfind(':').and_then(|i| {
let (a, p) = address.split_at(i);
// Default to localhost if given just the port. Example: ":80"
let addr = if !a.is_empty() { a } else { "0.0.0.0" };
// If this looks like an ipv6 IP address. Example: "[2001:db8::1]"
// Then we remove the brackets.
let addr = if addr.starts_with('[') && addr.ends_with(']') {
let l = addr.len() - 1;
addr.get(1..l).unwrap()
} else {
addr
};
let p = p.trim_start_matches(':');
match p.parse::<u16>() {
Err(_) => None,
Ok(port) => Some((addr, port)),
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddrV4;
use std::net::SocketAddrV6;
#[test]
fn split1() {
assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80)));
}
#[test]
fn split2() {
assert_eq!(split(":80"), Some(("0.0.0.0", 80)));
}
#[test]
fn split3() {
assert_eq!(split("no colon"), None);
}
#[test]
fn split4() {
assert_eq!(split("deno.land:443"), Some(("deno.land", 443)));
}
#[test]
fn split5() {
assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080)));
}
#[test]
fn resolve_addr1() {
let expected =
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80));
let actual = resolve_addr("127.0.0.1:80").wait().unwrap();
assert_eq!(actual, expected);
}
#[test]
fn resolve_addr3() {
let expected =
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25));
let actual = resolve_addr("192.0.2.1:25").wait().unwrap();
assert_eq!(actual, expected);
}
#[test]
fn resolve_addr_ipv6() {
let expected = SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
8080,
0,
0,
));
let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap();
assert_eq!(actual, expected);
}
}

494
cli/resources.rs Normal file
View file

@ -0,0 +1,494 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Think of Resources as File Descriptors. They are integers that are allocated
// by the privileged side of Deno to refer to various resources. The simplest
// example are standard file system files and stdio - but there will be other
// resources added in the future that might not correspond to operating system
// level File Descriptors. To avoid confusion we call them "resources" not "file
// descriptors". This module implements a global resource table. Ops (AKA
// handlers) look up resources by their integer id here.
use crate::cli::Buf;
use crate::errors;
use crate::errors::bad_resource;
use crate::errors::DenoError;
use crate::errors::DenoResult;
use crate::http_body::HttpBody;
use crate::isolate_state::WorkerChannels;
use crate::repl::Repl;
use futures;
use futures::Future;
use futures::Poll;
use futures::Sink;
use futures::Stream;
use hyper;
use std;
use std::collections::HashMap;
use std::io::{Error, Read, Seek, SeekFrom, Write};
use std::net::{Shutdown, SocketAddr};
use std::process::ExitStatus;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use tokio;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio_process;
pub type ResourceId = u32; // Sometimes referred to RID.
// These store Deno's file descriptors. These are not necessarily the operating
// system ones.
type ResourceTable = HashMap<ResourceId, Repr>;
#[cfg(not(windows))]
use std::os::unix::io::FromRawFd;
#[cfg(windows)]
use std::os::windows::io::FromRawHandle;
#[cfg(windows)]
extern crate winapi;
lazy_static! {
// Starts at 3 because stdio is [0-2].
static ref NEXT_RID: AtomicUsize = AtomicUsize::new(3);
static ref RESOURCE_TABLE: Mutex<ResourceTable> = Mutex::new({
let mut m = HashMap::new();
// TODO Load these lazily during lookup?
m.insert(0, Repr::Stdin(tokio::io::stdin()));
m.insert(1, Repr::Stdout({
#[cfg(not(windows))]
let stdout = unsafe { std::fs::File::from_raw_fd(1) };
#[cfg(windows)]
let stdout = unsafe {
std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle(
winapi::um::winbase::STD_OUTPUT_HANDLE))
};
tokio::fs::File::from_std(stdout)
}));
m.insert(2, Repr::Stderr(tokio::io::stderr()));
m
});
}
// Internal representation of Resource.
enum Repr {
Stdin(tokio::io::Stdin),
Stdout(tokio::fs::File),
Stderr(tokio::io::Stderr),
FsFile(tokio::fs::File),
// Since TcpListener might be closed while there is a pending accept task,
// we need to track the task so that when the listener is closed,
// this pending task could be notified and die.
// Currently TcpListener itself does not take care of this issue.
// See: https://github.com/tokio-rs/tokio/issues/846
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
TcpStream(tokio::net::TcpStream),
HttpBody(HttpBody),
Repl(Arc<Mutex<Repl>>),
// Enum size is bounded by the largest variant.
// Use `Box` around large `Child` struct.
// https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
Child(Box<tokio_process::Child>),
ChildStdin(tokio_process::ChildStdin),
ChildStdout(tokio_process::ChildStdout),
ChildStderr(tokio_process::ChildStderr),
Worker(WorkerChannels),
}
/// If the given rid is open, this returns the type of resource, E.G. "worker".
/// If the rid is closed or was never open, it returns None.
pub fn get_type(rid: ResourceId) -> Option<String> {
let table = RESOURCE_TABLE.lock().unwrap();
table.get(&rid).map(inspect_repr)
}
pub fn table_entries() -> Vec<(u32, String)> {
let table = RESOURCE_TABLE.lock().unwrap();
table
.iter()
.map(|(key, value)| (*key, inspect_repr(&value)))
.collect()
}
#[test]
fn test_table_entries() {
let mut entries = table_entries();
entries.sort();
assert_eq!(entries[0], (0, String::from("stdin")));
assert_eq!(entries[1], (1, String::from("stdout")));
assert_eq!(entries[2], (2, String::from("stderr")));
}
fn inspect_repr(repr: &Repr) -> String {
let h_repr = match repr {
Repr::Stdin(_) => "stdin",
Repr::Stdout(_) => "stdout",
Repr::Stderr(_) => "stderr",
Repr::FsFile(_) => "fsFile",
Repr::TcpListener(_, _) => "tcpListener",
Repr::TcpStream(_) => "tcpStream",
Repr::HttpBody(_) => "httpBody",
Repr::Repl(_) => "repl",
Repr::Child(_) => "child",
Repr::ChildStdin(_) => "childStdin",
Repr::ChildStdout(_) => "childStdout",
Repr::ChildStderr(_) => "childStderr",
Repr::Worker(_) => "worker",
};
String::from(h_repr)
}
// Abstract async file interface.
// Ideally in unix, if Resource represents an OS rid, it will be the same.
#[derive(Clone, Debug)]
pub struct Resource {
pub rid: ResourceId,
}
impl Resource {
// TODO Should it return a Resource instead of net::TcpStream?
pub fn poll_accept(&mut self) -> Poll<(TcpStream, SocketAddr), Error> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
None => Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Listener has been closed",
)),
Some(repr) => match repr {
Repr::TcpListener(ref mut s, _) => s.poll_accept(),
_ => panic!("Cannot accept"),
},
}
}
// close(2) is done by dropping the value. Therefore we just need to remove
// the resource from the RESOURCE_TABLE.
pub fn close(&self) {
let mut table = RESOURCE_TABLE.lock().unwrap();
let r = table.remove(&self.rid);
assert!(r.is_some());
}
pub fn shutdown(&mut self, how: Shutdown) -> Result<(), DenoError> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
None => panic!("bad rid"),
Some(repr) => match repr {
Repr::TcpStream(ref mut f) => {
TcpStream::shutdown(f, how).map_err(DenoError::from)
}
_ => panic!("Cannot shutdown"),
},
}
}
}
impl Read for Resource {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
unimplemented!();
}
}
impl AsyncRead for Resource {
fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, Error> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
None => panic!("bad rid"),
Some(repr) => match repr {
Repr::FsFile(ref mut f) => f.poll_read(buf),
Repr::Stdin(ref mut f) => f.poll_read(buf),
Repr::TcpStream(ref mut f) => f.poll_read(buf),
Repr::HttpBody(ref mut f) => f.poll_read(buf),
Repr::ChildStdout(ref mut f) => f.poll_read(buf),
Repr::ChildStderr(ref mut f) => f.poll_read(buf),
_ => panic!("Cannot read"),
},
}
}
}
impl Write for Resource {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
unimplemented!()
}
fn flush(&mut self) -> std::io::Result<()> {
unimplemented!()
}
}
impl AsyncWrite for Resource {
fn poll_write(&mut self, buf: &[u8]) -> Poll<usize, Error> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
None => panic!("bad rid"),
Some(repr) => match repr {
Repr::FsFile(ref mut f) => f.poll_write(buf),
Repr::Stdout(ref mut f) => f.poll_write(buf),
Repr::Stderr(ref mut f) => f.poll_write(buf),
Repr::TcpStream(ref mut f) => f.poll_write(buf),
Repr::ChildStdin(ref mut f) => f.poll_write(buf),
_ => panic!("Cannot write"),
},
}
}
fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
unimplemented!()
}
}
fn new_rid() -> ResourceId {
let next_rid = NEXT_RID.fetch_add(1, Ordering::SeqCst);
next_rid as ResourceId
}
pub fn add_fs_file(fs_file: tokio::fs::File) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
match tg.insert(rid, Repr::FsFile(fs_file)) {
Some(_) => panic!("There is already a file with that rid"),
None => Resource { rid },
}
}
pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let r = tg.insert(rid, Repr::TcpListener(listener, None));
assert!(r.is_none());
Resource { rid }
}
pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let r = tg.insert(rid, Repr::TcpStream(stream));
assert!(r.is_none());
Resource { rid }
}
pub fn add_hyper_body(body: hyper::Body) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let body = HttpBody::from(body);
let r = tg.insert(rid, Repr::HttpBody(body));
assert!(r.is_none());
Resource { rid }
}
pub fn add_repl(repl: Repl) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl))));
assert!(r.is_none());
Resource { rid }
}
pub fn add_worker(wc: WorkerChannels) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let r = tg.insert(rid, Repr::Worker(wc));
assert!(r.is_none());
Resource { rid }
}
pub fn worker_post_message(
rid: ResourceId,
buf: Buf,
) -> futures::sink::Send<futures::sync::mpsc::Sender<Buf>> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&rid);
match maybe_repr {
Some(Repr::Worker(ref mut wc)) => {
// unwrap here is incorrect, but doing it anyway
wc.0.clone().send(buf)
}
_ => panic!("bad resource"), // futures::future::err(bad_resource()).into(),
}
}
pub struct WorkerReceiver {
rid: ResourceId,
}
// Invert the dumbness that tokio_process causes by making Child itself a future.
impl Future for WorkerReceiver {
type Item = Option<Buf>;
type Error = DenoError;
fn poll(&mut self) -> Poll<Option<Buf>, DenoError> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|()| {
errors::new(errors::ErrorKind::Other, "recv msg error".to_string())
}),
_ => Err(bad_resource()),
}
}
}
pub fn worker_recv_message(rid: ResourceId) -> WorkerReceiver {
WorkerReceiver { rid }
}
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
pub struct ChildResources {
pub child_rid: ResourceId,
pub stdin_rid: Option<ResourceId>,
pub stdout_rid: Option<ResourceId>,
pub stderr_rid: Option<ResourceId>,
}
pub fn add_child(mut c: tokio_process::Child) -> ChildResources {
let child_rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
let mut resources = ChildResources {
child_rid,
stdin_rid: None,
stdout_rid: None,
stderr_rid: None,
};
if c.stdin().is_some() {
let stdin = c.stdin().take().unwrap();
let rid = new_rid();
let r = tg.insert(rid, Repr::ChildStdin(stdin));
assert!(r.is_none());
resources.stdin_rid = Some(rid);
}
if c.stdout().is_some() {
let stdout = c.stdout().take().unwrap();
let rid = new_rid();
let r = tg.insert(rid, Repr::ChildStdout(stdout));
assert!(r.is_none());
resources.stdout_rid = Some(rid);
}
if c.stderr().is_some() {
let stderr = c.stderr().take().unwrap();
let rid = new_rid();
let r = tg.insert(rid, Repr::ChildStderr(stderr));
assert!(r.is_none());
resources.stderr_rid = Some(rid);
}
let r = tg.insert(child_rid, Repr::Child(Box::new(c)));
assert!(r.is_none());
resources
}
pub struct ChildStatus {
rid: ResourceId,
}
// Invert the dumbness that tokio_process causes by making Child itself a future.
impl Future for ChildStatus {
type Item = ExitStatus;
type Error = DenoError;
fn poll(&mut self) -> Poll<ExitStatus, DenoError> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&self.rid);
match maybe_repr {
Some(Repr::Child(ref mut child)) => child.poll().map_err(DenoError::from),
_ => Err(bad_resource()),
}
}
}
pub fn child_status(rid: ResourceId) -> DenoResult<ChildStatus> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&rid);
match maybe_repr {
Some(Repr::Child(ref mut _child)) => Ok(ChildStatus { rid }),
_ => Err(bad_resource()),
}
}
pub fn get_repl(rid: ResourceId) -> DenoResult<Arc<Mutex<Repl>>> {
let mut table = RESOURCE_TABLE.lock().unwrap();
let maybe_repr = table.get_mut(&rid);
match maybe_repr {
Some(Repr::Repl(ref mut r)) => Ok(r.clone()),
_ => Err(bad_resource()),
}
}
pub fn lookup(rid: ResourceId) -> Option<Resource> {
debug!("resource lookup {}", rid);
let table = RESOURCE_TABLE.lock().unwrap();
table.get(&rid).map(|_| Resource { rid })
}
// TODO(kevinkassimo): revamp this after the following lands:
// https://github.com/tokio-rs/tokio/pull/785
pub fn seek(
resource: Resource,
offset: i32,
whence: u32,
) -> Box<dyn Future<Item = (), Error = DenoError> + Send> {
let mut table = RESOURCE_TABLE.lock().unwrap();
// We take ownership of File here.
// It is put back below while still holding the lock.
let maybe_repr = table.remove(&resource.rid);
match maybe_repr {
None => panic!("bad rid"),
Some(Repr::FsFile(f)) => {
let seek_from = match whence {
0 => SeekFrom::Start(offset as u64),
1 => SeekFrom::Current(offset as i64),
2 => SeekFrom::End(offset as i64),
_ => {
return Box::new(futures::future::err(errors::new(
errors::ErrorKind::InvalidSeekMode,
format!("Invalid seek mode: {}", whence),
)));
}
};
// Trait Clone not implemented on tokio::fs::File,
// so convert to std File first.
let std_file = f.into_std();
// Create a copy and immediately put back.
// We don't want to block other resource ops.
// try_clone() would yield a copy containing the same
// underlying fd, so operations on the copy would also
// affect the one in resource table, and we don't need
// to write back.
let maybe_std_file_copy = std_file.try_clone();
// Insert the entry back with the same rid.
table.insert(
resource.rid,
Repr::FsFile(tokio_fs::File::from_std(std_file)),
);
if maybe_std_file_copy.is_err() {
return Box::new(futures::future::err(DenoError::from(
maybe_std_file_copy.unwrap_err(),
)));
}
let mut std_file_copy = maybe_std_file_copy.unwrap();
return Box::new(futures::future::lazy(move || {
let result = std_file_copy
.seek(seek_from)
.map(|_| {
return ();
}).map_err(DenoError::from);
futures::future::result(result)
}));
}
_ => panic!("cannot seek"),
}
}

57
cli/startup_data.rs Normal file
View file

@ -0,0 +1,57 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use deno_core::deno_buf;
use deno_core::{StartupData, StartupScript};
pub fn deno_isolate_init() -> StartupData {
if cfg!(feature = "no-snapshot-init") {
debug!("Deno isolate init without snapshots.");
#[cfg(not(feature = "check-only"))]
let source_bytes =
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js"));
#[cfg(feature = "check-only")]
let source_bytes = vec![];
StartupData::Script(StartupScript {
filename: "gen/bundle/main.js".to_string(),
source: std::str::from_utf8(source_bytes).unwrap().to_string(),
})
} else {
debug!("Deno isolate init with snapshots.");
#[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))]
let data =
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_deno.bin"));
#[cfg(any(feature = "check-only", feature = "no-snapshot-init"))]
let data = vec![];
unsafe {
StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len()))
}
}
}
pub fn compiler_isolate_init() -> StartupData {
if cfg!(feature = "no-snapshot-init") {
debug!("Deno isolate init without snapshots.");
#[cfg(not(feature = "check-only"))]
let source_bytes =
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/compiler.js"));
#[cfg(feature = "check-only")]
let source_bytes = vec![];
StartupData::Script(StartupScript {
filename: "gen/bundle/compiler.js".to_string(),
source: std::str::from_utf8(source_bytes).unwrap().to_string(),
})
} else {
debug!("Deno isolate init with snapshots.");
#[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))]
let data =
include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_compiler.bin"));
#[cfg(any(feature = "check-only", feature = "no-snapshot-init"))]
let data = vec![];
unsafe {
StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len()))
}
}
}

118
cli/tokio_util.rs Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::resources::Resource;
use futures;
use futures::Future;
use futures::Poll;
use std::io;
use std::mem;
use std::net::SocketAddr;
use tokio;
use tokio::net::TcpStream;
pub fn run<F>(future: F)
where
F: Future<Item = (), Error = ()> + Send + 'static,
{
// tokio::runtime::current_thread::run(future)
tokio::run(future)
}
pub fn block_on<F, R, E>(future: F) -> Result<R, E>
where
F: Send + 'static + Future<Item = R, Error = E>,
R: Send + 'static,
E: Send + 'static,
{
let (tx, rx) = futures::sync::oneshot::channel();
tokio::spawn(future.then(move |r| tx.send(r).map_err(|_| unreachable!())));
rx.wait().unwrap()
}
// Set the default executor so we can use tokio::spawn(). It's difficult to
// pass around mut references to the runtime, so using with_default is
// preferable. Ideally Tokio would provide this function.
#[cfg(test)]
pub fn init<F>(f: F)
where
F: FnOnce(),
{
use tokio_executor;
let rt = tokio::runtime::Runtime::new().unwrap();
let mut executor = rt.executor();
let mut enter = tokio_executor::enter().expect("Multiple executors at once");
tokio_executor::with_default(&mut executor, &mut enter, move |_enter| f());
}
#[derive(Debug)]
enum AcceptState {
Pending(Resource),
Empty,
}
/// Simply accepts a connection.
pub fn accept(r: Resource) -> Accept {
Accept {
state: AcceptState::Pending(r),
}
}
/// A future which can be used to easily read available number of bytes to fill
/// a buffer.
///
/// Created by the [`read`] function.
#[derive(Debug)]
pub struct Accept {
state: AcceptState,
}
impl Future for Accept {
type Item = (TcpStream, SocketAddr);
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let (stream, addr) = match self.state {
AcceptState::Pending(ref mut r) => try_ready!(r.poll_accept()),
AcceptState::Empty => panic!("poll Accept after it's done"),
};
match mem::replace(&mut self.state, AcceptState::Empty) {
AcceptState::Pending(_) => Ok((stream, addr).into()),
AcceptState::Empty => panic!("invalid internal state"),
}
}
}
/// `futures::future::poll_fn` only support `F: FnMut()->Poll<T, E>`
/// However, we require that `F: FnOnce()->Poll<T, E>`.
/// Therefore, we created our version of `poll_fn`.
pub fn poll_fn<T, E, F>(f: F) -> PollFn<F>
where
F: FnOnce() -> Poll<T, E>,
{
PollFn { inner: Some(f) }
}
pub struct PollFn<F> {
inner: Option<F>,
}
impl<T, E, F> Future for PollFn<F>
where
F: FnOnce() -> Poll<T, E>,
{
type Item = T;
type Error = E;
fn poll(&mut self) -> Poll<T, E> {
let f = self.inner.take().expect("Inner fn has been taken.");
f()
}
}
pub fn panic_on_error<I, E, F>(f: F) -> impl Future<Item = I, Error = ()>
where
F: Future<Item = I, Error = E>,
E: std::fmt::Debug,
{
f.map_err(|err| panic!("Future got unexpected error: {:?}", err))
}

62
cli/tokio_write.rs Normal file
View file

@ -0,0 +1,62 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// TODO Submit this file upstream into tokio-io/src/io/write.rs
use std::io;
use std::mem;
use futures::{Future, Poll};
use tokio::io::AsyncWrite;
/// A future used to write some data to a stream.
///
/// This is created by the [`write`] top-level method.
///
/// [`write`]: fn.write.html
#[derive(Debug)]
pub struct Write<A, T> {
state: State<A, T>,
}
#[derive(Debug)]
enum State<A, T> {
Pending { a: A, buf: T },
Empty,
}
/// Creates a future that will write some of the buffer `buf` to
/// the stream `a` provided.
///
/// Any error which happens during writing will cause both the stream and the
/// buffer to get destroyed.
pub fn write<A, T>(a: A, buf: T) -> Write<A, T>
where
A: AsyncWrite,
T: AsRef<[u8]>,
{
Write {
state: State::Pending { a, buf },
}
}
impl<A, T> Future for Write<A, T>
where
A: AsyncWrite,
T: AsRef<[u8]>,
{
type Item = (A, T, usize);
type Error = io::Error;
fn poll(&mut self) -> Poll<(A, T, usize), io::Error> {
let nwritten = match self.state {
State::Pending {
ref mut a,
ref mut buf,
} => try_ready!(a.poll_write(buf.as_ref())),
State::Empty => panic!("poll a Read after it's done"),
};
match mem::replace(&mut self.state, State::Empty) {
State::Pending { a, buf } => Ok((a, buf, nwritten).into()),
State::Empty => panic!("invalid internal state"),
}
}
}

6
cli/version.rs Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
pub const DENO: &str = env!("CARGO_PKG_VERSION");
pub fn v8() -> &'static str {
deno_core::v8_version()
}

181
cli/workers.rs Normal file
View file

@ -0,0 +1,181 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::cli::Buf;
use crate::cli::Cli;
use crate::flags::DenoFlags;
use crate::isolate::Isolate;
use crate::isolate_state::IsolateState;
use crate::isolate_state::WorkerChannels;
use crate::js_errors::JSErrorColor;
use crate::permissions::DenoPermissions;
use crate::resources;
use crate::tokio_util;
use deno_core::JSError;
use deno_core::StartupData;
use futures::future::lazy;
use futures::sync::mpsc;
use futures::sync::oneshot;
use futures::Future;
use futures::Poll;
use std::sync::Arc;
use std::thread;
/// Rust interface for WebWorkers.
pub struct Worker {
isolate: Isolate,
}
impl Worker {
pub fn new(
startup_data: Option<StartupData>,
flags: DenoFlags,
argv: Vec<String>,
permissions: DenoPermissions,
) -> (Self, WorkerChannels) {
let (worker_in_tx, worker_in_rx) = mpsc::channel::<Buf>(1);
let (worker_out_tx, worker_out_rx) = mpsc::channel::<Buf>(1);
let internal_channels = (worker_out_tx, worker_in_rx);
let external_channels = (worker_in_tx, worker_out_rx);
let state =
Arc::new(IsolateState::new(flags, argv, Some(internal_channels)));
let cli = Cli::new(startup_data, state, permissions);
let isolate = Isolate::new(cli);
let worker = Worker { isolate };
(worker, external_channels)
}
pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
self.isolate.execute(js_source)
}
}
impl Future for Worker {
type Item = ();
type Error = JSError;
fn poll(&mut self) -> Poll<(), JSError> {
self.isolate.poll()
}
}
pub fn spawn(
startup_data: Option<StartupData>,
state: &IsolateState,
js_source: String,
permissions: DenoPermissions,
) -> resources::Resource {
// TODO This function should return a Future, so that the caller can retrieve
// the JSError if one is thrown. Currently it just prints to stderr and calls
// exit(1).
// let (js_error_tx, js_error_rx) = oneshot::channel::<JSError>();
let (p, c) = oneshot::channel::<resources::Resource>();
let builder = thread::Builder::new().name("worker".to_string());
let flags = state.flags.clone();
let argv = state.argv.clone();
let _tid = builder
.spawn(move || {
tokio_util::run(lazy(move || {
let (mut worker, external_channels) =
Worker::new(startup_data, flags, argv, permissions);
let resource = resources::add_worker(external_channels);
p.send(resource.clone()).unwrap();
worker
.execute("denoMain()")
.expect("worker denoMain failed");
worker
.execute("workerMain()")
.expect("worker workerMain failed");
worker.execute(&js_source).expect("worker js_source failed");
worker.then(move |r| -> Result<(), ()> {
resource.close();
debug!("workers.rs after resource close");
if let Err(err) = r {
eprintln!("{}", JSErrorColor(&err).to_string());
std::process::exit(1);
}
Ok(())
})
}));
debug!("workers.rs after spawn");
}).unwrap();
c.wait().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::startup_data;
#[test]
fn test_spawn() {
let startup_data = startup_data::compiler_isolate_init();
let resource = spawn(
Some(startup_data),
&IsolateState::mock(),
r#"
onmessage = function(e) {
let s = new TextDecoder().decode(e.data);;
console.log("msg from main script", s);
if (s == "exit") {
close();
return;
} else {
console.assert(s === "hi");
}
postMessage(new Uint8Array([1, 2, 3]));
console.log("after postMessage");
}
"#.into(),
DenoPermissions::default(),
);
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
let r = resources::worker_post_message(resource.rid, msg).wait();
assert!(r.is_ok());
let maybe_msg =
resources::worker_recv_message(resource.rid).wait().unwrap();
assert!(maybe_msg.is_some());
assert_eq!(*maybe_msg.unwrap(), [1, 2, 3]);
let msg = String::from("exit").into_boxed_str().into_boxed_bytes();
let r = resources::worker_post_message(resource.rid, msg).wait();
assert!(r.is_ok());
}
#[test]
fn removed_from_resource_table_on_close() {
let startup_data = startup_data::compiler_isolate_init();
let resource = spawn(
Some(startup_data),
&IsolateState::mock(),
"onmessage = () => close();".into(),
DenoPermissions::default(),
);
assert_eq!(
resources::get_type(resource.rid),
Some("worker".to_string())
);
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
let r = resources::worker_post_message(resource.rid, msg).wait();
assert!(r.is_ok());
println!("rid {:?}", resource.rid);
// TODO Need a way to get a future for when a resource closes.
// For now, just sleep for a bit.
// resource.close();
thread::sleep(std::time::Duration::from_millis(1000));
assert_eq!(resources::get_type(resource.rid), None);
}
}