summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/Cargo.toml30
-rw-r--r--core/README.md18
-rw-r--r--core/any_error.rs68
-rw-r--r--core/build.rs148
-rw-r--r--core/examples/http_bench.js139
-rw-r--r--core/examples/http_bench.rs302
-rw-r--r--core/flags.rs44
-rw-r--r--core/isolate.rs1348
-rw-r--r--core/js_errors.rs416
-rw-r--r--core/lib.rs38
-rw-r--r--core/libdeno.rs329
-rw-r--r--core/libdeno/.gn60
-rw-r--r--core/libdeno/BUILD.gn98
-rw-r--r--core/libdeno/api.cc246
-rw-r--r--core/libdeno/binding.cc597
-rw-r--r--core/libdeno/buffer.h140
m---------core/libdeno/build0
l---------core/libdeno/build_overrides1
l---------core/libdeno/buildtools1
-rw-r--r--core/libdeno/deno.h157
-rw-r--r--core/libdeno/exceptions.cc235
-rw-r--r--core/libdeno/exceptions.h27
-rw-r--r--core/libdeno/internal.h200
-rw-r--r--core/libdeno/libdeno_test.cc322
-rw-r--r--core/libdeno/libdeno_test.js271
-rw-r--r--core/libdeno/modules.cc223
-rw-r--r--core/libdeno/modules_test.cc426
-rw-r--r--core/libdeno/test.cc47
-rw-r--r--core/libdeno/test.h12
l---------core/libdeno/testing1
l---------core/libdeno/third_party1
l---------core/libdeno/tools1
l---------core/libdeno/v81
-rw-r--r--core/module_specifier.rs473
-rw-r--r--core/modules.rs1086
-rw-r--r--core/ops.rs111
-rw-r--r--core/shared_queue.js205
-rw-r--r--core/shared_queue.rs289
-rw-r--r--core/shared_queue_test.js83
39 files changed, 8194 insertions, 0 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml
new file mode 100644
index 000000000..040bb4782
--- /dev/null
+++ b/core/Cargo.toml
@@ -0,0 +1,30 @@
+# Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "deno"
+version = "0.20.0"
+edition = "2018"
+description = "A secure JavaScript/TypeScript runtime built with V8, Rust, and Tokio"
+authors = ["the Deno authors"]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/denoland/deno"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+futures = "0.1.29"
+lazy_static = "1.4.0"
+libc = "0.2.62"
+log = "0.4.8"
+serde_json = "1.0.40"
+url = "1.7.2"
+
+[[example]]
+name = "deno_core_http_bench"
+path = "examples/http_bench.rs"
+
+# tokio is only used for deno_core_http_bench
+[dev_dependencies]
+tokio = "0.1.18"
diff --git a/core/README.md b/core/README.md
new file mode 100644
index 000000000..9345b6eff
--- /dev/null
+++ b/core/README.md
@@ -0,0 +1,18 @@
+# Deno Core
+
+This Rust crate contains the essential V8 bindings for Deno's command-line
+interface (Deno CLI). The main abstraction here is the Isolate which provides a
+way to execute JavaScript. The Isolate is modeled as a
+`Future<Item=(), Error=JSError>` which completes once all of its ops have
+completed.
+
+In order to bind Rust functions into JavaScript, use the `Deno.core.dispatch()`
+function to trigger the "dispatch" callback in Rust. The user is responsible for
+encoding both the request and response into a Uint8Array.
+
+Documentation for this crate is thin at the moment. Please see
+[http_bench.rs](https://github.com/denoland/deno/blob/master/core/examples/http_bench.rs)
+as a simple example of usage.
+
+TypeScript support and a lot of other functionality is not available at this
+layer. See the [cli](https://github.com/denoland/deno/tree/master/cli) for that.
diff --git a/core/any_error.rs b/core/any_error.rs
new file mode 100644
index 000000000..60c508e7d
--- /dev/null
+++ b/core/any_error.rs
@@ -0,0 +1,68 @@
+use std::any::{Any, TypeId};
+use std::convert::From;
+use std::error::Error;
+use std::fmt;
+use std::ops::Deref;
+
+// The Send and Sync traits are required because deno is multithreaded and we
+// need to be able to handle errors across threads.
+pub trait AnyError: Any + Error + Send + Sync + 'static {}
+impl<T> AnyError for T where T: Any + Error + Send + Sync + Sized + 'static {}
+
+#[derive(Debug)]
+pub struct ErrBox(Box<dyn AnyError>);
+
+impl dyn AnyError {
+ pub fn downcast_ref<T: AnyError>(&self) -> Option<&T> {
+ if Any::type_id(self) == TypeId::of::<T>() {
+ let target = self as *const Self as *const T;
+ let target = unsafe { &*target };
+ Some(target)
+ } else {
+ None
+ }
+ }
+}
+
+impl ErrBox {
+ pub fn downcast<T: AnyError>(self) -> Result<T, Self> {
+ if Any::type_id(&*self.0) == TypeId::of::<T>() {
+ let target = Box::into_raw(self.0) as *mut T;
+ let target = unsafe { Box::from_raw(target) };
+ Ok(*target)
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl AsRef<dyn AnyError> for ErrBox {
+ fn as_ref(&self) -> &dyn AnyError {
+ self.0.as_ref()
+ }
+}
+
+impl Deref for ErrBox {
+ type Target = Box<dyn AnyError>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T: AnyError> From<T> for ErrBox {
+ fn from(error: T) -> Self {
+ Self(Box::new(error))
+ }
+}
+
+impl From<Box<dyn AnyError>> for ErrBox {
+ fn from(boxed: Box<dyn AnyError>) -> Self {
+ Self(boxed)
+ }
+}
+
+impl fmt::Display for ErrBox {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
diff --git a/core/build.rs b/core/build.rs
new file mode 100644
index 000000000..64b5ca47b
--- /dev/null
+++ b/core/build.rs
@@ -0,0 +1,148 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+// Run "cargo build -vv" if you want to see gn output.
+
+fn main() {
+ let build = gn::Build::setup();
+
+ println!("cargo:rustc-link-search=native={}/obj", build.gn_out_dir);
+
+ build.run("default");
+}
+
+mod gn {
+ use std::env;
+ use std::path::{self, Path, PathBuf};
+ use std::process::Command;
+
+ pub struct Build {
+ gn_mode: String,
+ root: PathBuf,
+ pub gn_out_dir: String,
+ pub gn_out_path: PathBuf,
+ pub check_only: bool,
+ }
+
+ impl Build {
+ pub fn setup() -> Build {
+ let gn_mode = if cfg!(target_os = "windows") {
+ // On Windows, we need to link with a release build of libdeno, because
+ // rust always uses the release CRT.
+ // TODO(piscisaureus): make linking with debug libdeno possible.
+ String::from("release")
+ } else {
+ // Cargo sets PROFILE to either "debug" or "release", which conveniently
+ // matches the build modes we support.
+ env::var("PROFILE").unwrap()
+ };
+
+ // cd into workspace root.
+ assert!(env::set_current_dir("..").is_ok());
+
+ let root = env::current_dir().unwrap();
+ // If not using host default target the output folder will change
+ // target/release will become target/$TARGET/release
+ // Gn should also be using this output directory as well
+ // most things will work with gn using the default
+ // output directory but some tests depend on artifacts
+ // being in a specific directory relative to the main build output
+ let gn_out_path = root.join(format!("target/{}", gn_mode.clone()));
+ let gn_out_dir = normalize_path(&gn_out_path);
+
+ // Tell Cargo when to re-run this file. We do this first, so these directives
+ // can take effect even if something goes wrong later in the build process.
+ println!("cargo:rerun-if-env-changed=DENO_BUILD_PATH");
+
+ // This helps Rust source files locate the snapshot, source map etc.
+ println!("cargo:rustc-env=GN_OUT_DIR={}", gn_out_dir);
+
+ // Detect if we're being invoked by the rust language server (RLS).
+ // Unfortunately we can't detect whether we're being run by `cargo check`.
+ let check_only = env::var_os("CARGO")
+ .map(PathBuf::from)
+ .as_ref()
+ .and_then(|p| p.file_stem())
+ .and_then(|f| f.to_str())
+ .map(|s| s.starts_with("rls"))
+ .unwrap_or(false);
+
+ if check_only {
+ // Enable the 'check_only' feature, which enables some workarounds in the
+ // rust source code to compile successfully without a bundle and snapshot
+ println!("cargo:rustc-cfg=feature=\"check-only\"");
+ }
+
+ Build {
+ gn_out_dir,
+ gn_out_path,
+ check_only,
+ gn_mode,
+ root,
+ }
+ }
+
+ pub fn run(&self, gn_target: &str) {
+ if !self.gn_out_path.join("build.ninja").exists() {
+ let mut cmd = Command::new("python");
+ cmd.env("DENO_BUILD_PATH", &self.gn_out_dir);
+ cmd.env("DENO_BUILD_MODE", &self.gn_mode);
+ cmd.env("DEPOT_TOOLS_WIN_TOOLCHAIN", "0");
+ cmd.arg("./tools/setup.py");
+ if env::var_os("DENO_NO_BINARY_DOWNLOAD").is_some() {
+ cmd.arg("--no-binary-download");
+ }
+ let status = cmd.status().expect("setup.py failed");
+ assert!(status.success());
+ }
+
+ let mut ninja = Command::new("third_party/depot_tools/ninja");
+ let ninja = if !cfg!(target_os = "windows") {
+ &mut ninja
+ } else {
+ // Windows needs special configuration. This is similar to the function of
+ // python_env() in //tools/util.py.
+ let python_path: Vec<String> = vec![
+ "third_party/python_packages",
+ "third_party/python_packages/win32",
+ "third_party/python_packages/win32/lib",
+ "third_party/python_packages/Pythonwin",
+ ]
+ .into_iter()
+ .map(|p| self.root.join(p).into_os_string().into_string().unwrap())
+ .collect();
+ let orig_path = String::from(";")
+ + &env::var_os("PATH").unwrap().into_string().unwrap();
+ let path = self
+ .root
+ .join("third_party/python_packages/pywin32_system32")
+ .into_os_string()
+ .into_string()
+ .unwrap();
+ ninja
+ .env("PYTHONPATH", python_path.join(";"))
+ .env("PATH", path + &orig_path)
+ .env("DEPOT_TOOLS_WIN_TOOLCHAIN", "0")
+ };
+
+ let status = ninja
+ .arg(gn_target)
+ .arg("-C")
+ .arg(&self.gn_out_dir)
+ .status()
+ .expect("ninja failed");
+ assert!(status.success());
+ }
+ }
+
+ // Utility function to make a path absolute, normalizing it to use forward
+ // slashes only. The returned value is an owned String, otherwise panics.
+ fn normalize_path<T: AsRef<Path>>(path: T) -> String {
+ path
+ .as_ref()
+ .to_str()
+ .unwrap()
+ .to_owned()
+ .chars()
+ .map(|c| if path::is_separator(c) { '/' } else { c })
+ .collect()
+ }
+}
diff --git a/core/examples/http_bench.js b/core/examples/http_bench.js
new file mode 100644
index 000000000..a7142b09d
--- /dev/null
+++ b/core/examples/http_bench.js
@@ -0,0 +1,139 @@
+// This is not a real HTTP server. We read blindly one time into 'requestBuf',
+// then write this fixed 'responseBuf'. The point of this benchmark is to
+// exercise the event loop in a simple yet semi-realistic way.
+const requestBuf = new Uint8Array(64 * 1024);
+const responseBuf = new Uint8Array(
+ "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
+ .split("")
+ .map(c => c.charCodeAt(0))
+);
+const promiseMap = new Map();
+let nextPromiseId = 1;
+
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+
+function createResolvable() {
+ let methods;
+ const promise = new Promise((resolve, reject) => {
+ methods = { resolve, reject };
+ });
+ return Object.assign(promise, methods);
+}
+
+const scratch32 = new Int32Array(3);
+const scratchBytes = new Uint8Array(
+ scratch32.buffer,
+ scratch32.byteOffset,
+ scratch32.byteLength
+);
+assert(scratchBytes.byteLength === 3 * 4);
+
+function send(promiseId, opId, arg, zeroCopy = null) {
+ scratch32[0] = promiseId;
+ scratch32[1] = arg;
+ scratch32[2] = -1;
+ return Deno.core.dispatch(opId, scratchBytes, zeroCopy);
+}
+
+/** Returns Promise<number> */
+function sendAsync(opId, arg, zeroCopy = null) {
+ const promiseId = nextPromiseId++;
+ const p = createResolvable();
+ promiseMap.set(promiseId, p);
+ send(promiseId, opId, arg, zeroCopy);
+ return p;
+}
+
+function recordFromBuf(buf) {
+ assert(buf.byteLength === 3 * 4);
+ const buf32 = new Int32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
+ return {
+ promiseId: buf32[0],
+ arg: buf32[1],
+ result: buf32[2]
+ };
+}
+
+/** Returns i32 number */
+function sendSync(opId, arg) {
+ const buf = send(0, opId, arg);
+ const record = recordFromBuf(buf);
+ return record.result;
+}
+
+function handleAsyncMsgFromRust(opId, buf) {
+ const record = recordFromBuf(buf);
+ const { promiseId, result } = record;
+ const p = promiseMap.get(promiseId);
+ promiseMap.delete(promiseId);
+ p.resolve(result);
+}
+
+/** Listens on 0.0.0.0:4500, returns rid. */
+function listen() {
+ return sendSync(ops["listen"], -1);
+}
+
+/** Accepts a connection, returns rid. */
+async function accept(rid) {
+ return await sendAsync(ops["accept"], rid);
+}
+
+/**
+ * Reads a packet from the rid, presumably an http request. data is ignored.
+ * Returns bytes read.
+ */
+async function read(rid, data) {
+ return await sendAsync(ops["read"], rid, data);
+}
+
+/** Writes a fixed HTTP response to the socket rid. Returns bytes written. */
+async function write(rid, data) {
+ return await sendAsync(ops["write"], rid, data);
+}
+
+function close(rid) {
+ return sendSync(ops["close"], rid);
+}
+
+async function serve(rid) {
+ while (true) {
+ const nread = await read(rid, requestBuf);
+ if (nread <= 0) {
+ break;
+ }
+
+ const nwritten = await write(rid, responseBuf);
+ if (nwritten < 0) {
+ break;
+ }
+ }
+ close(rid);
+}
+
+let ops;
+
+async function main() {
+ Deno.core.setAsyncHandler(handleAsyncMsgFromRust);
+ ops = Deno.core.ops();
+
+ Deno.core.print("http_bench.js start\n");
+
+ const listenerRid = listen();
+ Deno.core.print(`listening http://127.0.0.1:4544/ rid = ${listenerRid}\n`);
+ while (true) {
+ const rid = await accept(listenerRid);
+ // Deno.core.print(`accepted ${rid}`);
+ if (rid < 0) {
+ Deno.core.print(`accept error ${rid}`);
+ return;
+ }
+ serve(rid);
+ }
+}
+
+main();
diff --git a/core/examples/http_bench.rs b/core/examples/http_bench.rs
new file mode 100644
index 000000000..c019d8a11
--- /dev/null
+++ b/core/examples/http_bench.rs
@@ -0,0 +1,302 @@
+/// To run this benchmark:
+///
+/// > DENO_BUILD_MODE=release ./tools/build.py && \
+/// ./target/release/deno_core_http_bench --multi-thread
+extern crate deno;
+extern crate futures;
+extern crate libc;
+extern crate tokio;
+
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate lazy_static;
+
+use deno::*;
+use futures::future::lazy;
+use std::collections::HashMap;
+use std::env;
+use std::net::SocketAddr;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::sync::Mutex;
+use tokio::prelude::*;
+
+static LOGGER: Logger = Logger;
+struct Logger;
+impl log::Log for Logger {
+ fn enabled(&self, metadata: &log::Metadata) -> bool {
+ metadata.level() <= log::max_level()
+ }
+ fn log(&self, record: &log::Record) {
+ if self.enabled(record.metadata()) {
+ println!("{} - {}", record.level(), record.args());
+ }
+ }
+ fn flush(&self) {}
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Record {
+ pub promise_id: i32,
+ pub arg: i32,
+ pub result: i32,
+}
+
+impl Into<Buf> for Record {
+ fn into(self) -> Buf {
+ let buf32 = vec![self.promise_id, self.arg, self.result].into_boxed_slice();
+ let ptr = Box::into_raw(buf32) as *mut [u8; 3 * 4];
+ unsafe { Box::from_raw(ptr) }
+ }
+}
+
+impl From<&[u8]> for Record {
+ fn from(s: &[u8]) -> Record {
+ #[allow(clippy::cast_ptr_alignment)]
+ let ptr = s.as_ptr() as *const i32;
+ let ints = unsafe { std::slice::from_raw_parts(ptr, 3) };
+ Record {
+ promise_id: ints[0],
+ arg: ints[1],
+ result: ints[2],
+ }
+ }
+}
+
+impl From<Buf> for Record {
+ fn from(buf: Buf) -> Record {
+ assert_eq!(buf.len(), 3 * 4);
+ #[allow(clippy::cast_ptr_alignment)]
+ let ptr = Box::into_raw(buf) as *mut [i32; 3];
+ let ints: Box<[i32]> = unsafe { Box::from_raw(ptr) };
+ assert_eq!(ints.len(), 3);
+ Record {
+ promise_id: ints[0],
+ arg: ints[1],
+ result: ints[2],
+ }
+ }
+}
+
+#[test]
+fn test_record_from() {
+ let r = Record {
+ promise_id: 1,
+ arg: 3,
+ result: 4,
+ };
+ let expected = r.clone();
+ let buf: Buf = r.into();
+ #[cfg(target_endian = "little")]
+ assert_eq!(
+ buf,
+ vec![1u8, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0].into_boxed_slice()
+ );
+ let actual = Record::from(buf);
+ assert_eq!(actual, expected);
+ // TODO test From<&[u8]> for Record
+}
+
+pub type HttpOp = dyn Future<Item = i32, Error = std::io::Error> + Send;
+
+pub type HttpOpHandler =
+ fn(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp>;
+
+fn http_op(
+ handler: HttpOpHandler,
+) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp {
+ move |control: &[u8], zero_copy_buf: Option<PinnedBuf>| -> CoreOp {
+ let record = Record::from(control);
+ let is_sync = record.promise_id == 0;
+ let op = handler(record.clone(), zero_copy_buf);
+
+ let mut record_a = record.clone();
+ let mut record_b = record.clone();
+
+ let fut = Box::new(
+ op.and_then(move |result| {
+ record_a.result = result;
+ Ok(record_a)
+ })
+ .or_else(|err| -> Result<Record, ()> {
+ eprintln!("unexpected err {}", err);
+ record_b.result = -1;
+ Ok(record_b)
+ })
+ .then(|result| -> Result<Buf, ()> {
+ let record = result.unwrap();
+ Ok(record.into())
+ }),
+ );
+
+ if is_sync {
+ Op::Sync(fut.wait().unwrap())
+ } else {
+ Op::Async(fut)
+ }
+ }
+}
+
+fn main() {
+ let main_future = lazy(move || {
+ // TODO currently isolate.execute() must be run inside tokio, hence the
+ // lazy(). It would be nice to not have that contraint. Probably requires
+ // using v8::MicrotasksPolicy::kExplicit
+
+ let js_source = include_str!("http_bench.js");
+
+ let startup_data = StartupData::Script(Script {
+ source: js_source,
+ filename: "http_bench.js",
+ });
+
+ let mut isolate = deno::Isolate::new(startup_data, false);
+ isolate.register_op("listen", http_op(op_listen));
+ isolate.register_op("accept", http_op(op_accept));
+ isolate.register_op("read", http_op(op_read));
+ isolate.register_op("write", http_op(op_write));
+ isolate.register_op("close", http_op(op_close));
+
+ isolate.then(|r| {
+ js_check(r);
+ Ok(())
+ })
+ });
+
+ let args: Vec<String> = env::args().collect();
+ // NOTE: `--help` arg will display V8 help and exit
+ let args = deno::v8_set_flags(args);
+
+ log::set_logger(&LOGGER).unwrap();
+ log::set_max_level(if args.iter().any(|a| a == "-D") {
+ log::LevelFilter::Debug
+ } else {
+ log::LevelFilter::Warn
+ });
+
+ if args.iter().any(|a| a == "--multi-thread") {
+ println!("multi-thread");
+ tokio::run(main_future);
+ } else {
+ println!("single-thread");
+ tokio::runtime::current_thread::run(main_future);
+ }
+}
+
+enum Repr {
+ TcpListener(tokio::net::TcpListener),
+ TcpStream(tokio::net::TcpStream),
+}
+
+type ResourceTable = HashMap<i32, Repr>;
+lazy_static! {
+ static ref RESOURCE_TABLE: Mutex<ResourceTable> = Mutex::new(HashMap::new());
+ static ref NEXT_RID: AtomicUsize = AtomicUsize::new(3);
+}
+
+fn new_rid() -> i32 {
+ let rid = NEXT_RID.fetch_add(1, Ordering::SeqCst);
+ rid as i32
+}
+
+fn op_accept(record: Record, _zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
+ let listener_rid = record.arg;
+ debug!("accept {}", listener_rid);
+ Box::new(
+ futures::future::poll_fn(move || {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let maybe_repr = table.get_mut(&listener_rid);
+ match maybe_repr {
+ Some(Repr::TcpListener(ref mut listener)) => listener.poll_accept(),
+ _ => panic!("bad rid {}", listener_rid),
+ }
+ })
+ .and_then(move |(stream, addr)| {
+ debug!("accept success {}", addr);
+ let rid = new_rid();
+
+ let mut guard = RESOURCE_TABLE.lock().unwrap();
+ guard.insert(rid, Repr::TcpStream(stream));
+
+ Ok(rid as i32)
+ }),
+ )
+}
+
+fn op_listen(
+ _record: Record,
+ _zero_copy_buf: Option<PinnedBuf>,
+) -> Box<HttpOp> {
+ debug!("listen");
+ Box::new(lazy(move || {
+ let addr = "127.0.0.1:4544".parse::<SocketAddr>().unwrap();
+ let listener = tokio::net::TcpListener::bind(&addr).unwrap();
+ let rid = new_rid();
+
+ let mut guard = RESOURCE_TABLE.lock().unwrap();
+ guard.insert(rid, Repr::TcpListener(listener));
+ futures::future::ok(rid)
+ }))
+}
+
+fn op_close(record: Record, _zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
+ debug!("close");
+ let rid = record.arg;
+ Box::new(lazy(move || {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let r = table.remove(&rid);
+ let result = if r.is_some() { 0 } else { -1 };
+ futures::future::ok(result)
+ }))
+}
+
+fn op_read(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
+ let rid = record.arg;
+ debug!("read rid={}", rid);
+ let mut zero_copy_buf = zero_copy_buf.unwrap();
+ Box::new(
+ futures::future::poll_fn(move || {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let maybe_repr = table.get_mut(&rid);
+ match maybe_repr {
+ Some(Repr::TcpStream(ref mut stream)) => {
+ stream.poll_read(&mut zero_copy_buf)
+ }
+ _ => panic!("bad rid"),
+ }
+ })
+ .and_then(move |nread| {
+ debug!("read success {}", nread);
+ Ok(nread as i32)
+ }),
+ )
+}
+
+fn op_write(record: Record, zero_copy_buf: Option<PinnedBuf>) -> Box<HttpOp> {
+ let rid = record.arg;
+ debug!("write rid={}", rid);
+ let zero_copy_buf = zero_copy_buf.unwrap();
+ Box::new(
+ futures::future::poll_fn(move || {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let maybe_repr = table.get_mut(&rid);
+ match maybe_repr {
+ Some(Repr::TcpStream(ref mut stream)) => {
+ stream.poll_write(&zero_copy_buf)
+ }
+ _ => panic!("bad rid"),
+ }
+ })
+ .and_then(move |nwritten| {
+ debug!("write success {}", nwritten);
+ Ok(nwritten as i32)
+ }),
+ )
+}
+
+fn js_check(r: Result<(), ErrBox>) {
+ if let Err(e) = r {
+ panic!(e.to_string());
+ }
+}
diff --git a/core/flags.rs b/core/flags.rs
new file mode 100644
index 000000000..e1429ab9a
--- /dev/null
+++ b/core/flags.rs
@@ -0,0 +1,44 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+//! This module wraps libdeno::deno_set_v8_flags
+use crate::libdeno::deno_set_v8_flags;
+use libc::c_char;
+use libc::c_int;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::vec::Vec;
+
+/// Pass the command line arguments to v8.
+/// Returns a vector of command line arguments that V8 did not understand.
+pub fn v8_set_flags(args: Vec<String>) -> Vec<String> {
+ // deno_set_v8_flags(int* argc, char** argv) mutates argc and argv to remove
+ // flags that v8 understands.
+
+ // Make a new array, that can be modified by V8::SetFlagsFromCommandLine(),
+ // containing mutable raw pointers to the individual command line args.
+ let mut raw_argv = args
+ .iter()
+ .map(|arg| CString::new(arg.as_str()).unwrap().into_bytes_with_nul())
+ .collect::<Vec<_>>();
+ let mut c_argv = raw_argv
+ .iter_mut()
+ .map(|arg| arg.as_mut_ptr() as *mut c_char)
+ .collect::<Vec<_>>();
+
+ // Store the length of the c_argv array in a local variable. We'll pass
+ // a pointer to this local variable to deno_set_v8_flags(), which then
+ // updates its value.
+ let mut c_argv_len = c_argv.len() as c_int;
+ // Let v8 parse the arguments it recognizes and remove them from c_argv.
+ unsafe { deno_set_v8_flags(&mut c_argv_len, c_argv.as_mut_ptr()) };
+ // If c_argv_len was updated we have to change the length of c_argv to match.
+ c_argv.truncate(c_argv_len as usize);
+ // Copy the modified arguments list into a proper rust vec and return it.
+ c_argv
+ .iter()
+ .map(|ptr| unsafe {
+ let cstr = CStr::from_ptr(*ptr as *const c_char);
+ let slice = cstr.to_str().unwrap();
+ slice.to_string()
+ })
+ .collect()
+}
diff --git a/core/isolate.rs b/core/isolate.rs
new file mode 100644
index 000000000..2f544a20a
--- /dev/null
+++ b/core/isolate.rs
@@ -0,0 +1,1348 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+
+// Do not add any dependency to modules.rs!
+// modules.rs is complex and should remain decoupled from isolate.rs to keep the
+// Isolate struct from becoming too bloating for users who do not need
+// asynchronous module loading.
+
+use crate::any_error::ErrBox;
+use crate::js_errors::CoreJSError;
+use crate::js_errors::V8Exception;
+use crate::libdeno;
+use crate::libdeno::deno_buf;
+use crate::libdeno::deno_dyn_import_id;
+use crate::libdeno::deno_mod;
+use crate::libdeno::deno_pinned_buf;
+use crate::libdeno::PinnedBuf;
+use crate::libdeno::Snapshot1;
+use crate::libdeno::Snapshot2;
+use crate::ops::*;
+use crate::shared_queue::SharedQueue;
+use crate::shared_queue::RECOMMENDED_SIZE;
+use futures::stream::FuturesUnordered;
+use futures::stream::Stream;
+use futures::stream::StreamFuture;
+use futures::task;
+use futures::Async::*;
+use futures::Future;
+use futures::Poll;
+use libc::c_char;
+use libc::c_void;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::fmt;
+use std::ptr::null;
+use std::sync::{Arc, Mutex, Once};
+
+/// Stores a script used to initalize a Isolate
+pub struct Script<'a> {
+ pub source: &'a str,
+ pub filename: &'a str,
+}
+
+/// Represent result of fetching the source code of a module. Found module URL
+/// might be different from specified URL used for loading due to redirections
+/// (like HTTP 303). E.G. Both https://example.com/a.ts and
+/// https://example.com/b.ts may point to https://example.com/c.ts
+/// By keeping track of specified and found URL we can alias modules and avoid
+/// recompiling the same code 3 times.
+#[derive(Debug, Eq, PartialEq)]
+pub struct SourceCodeInfo {
+ pub code: String,
+ pub module_url_specified: String,
+ pub module_url_found: String,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum RecursiveLoadEvent {
+ Fetch(SourceCodeInfo),
+ Instantiate(deno_mod),
+}
+
+pub trait ImportStream: Stream {
+ fn register(
+ &mut self,
+ source_code_info: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox>;
+}
+
+type DynImportStream =
+ Box<dyn ImportStream<Item = RecursiveLoadEvent, Error = ErrBox> + Send>;
+
+type DynImportFn = dyn Fn(deno_dyn_import_id, &str, &str) -> DynImportStream;
+
+/// Wraps DynImportStream to include the deno_dyn_import_id, so that it doesn't
+/// need to be exposed.
+#[derive(Debug)]
+struct DynImport {
+ pub id: deno_dyn_import_id,
+ pub inner: DynImportStream,
+}
+
+impl fmt::Debug for DynImportStream {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "DynImportStream(..)")
+ }
+}
+
+impl Stream for DynImport {
+ type Item = (deno_dyn_import_id, RecursiveLoadEvent);
+ type Error = (deno_dyn_import_id, ErrBox);
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ match self.inner.poll() {
+ Ok(Ready(Some(event))) => Ok(Ready(Some((self.id, event)))),
+ Ok(Ready(None)) => unreachable!(),
+ Err(e) => Err((self.id, e)),
+ Ok(NotReady) => Ok(NotReady),
+ }
+ }
+}
+
+impl ImportStream for DynImport {
+ fn register(
+ &mut self,
+ source_code_info: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox> {
+ self.inner.register(source_code_info, isolate)
+ }
+}
+
+// TODO(ry) It's ugly that we have both Script and OwnedScript. Ideally we
+// wouldn't expose such twiddly complexity.
+struct OwnedScript {
+ pub source: String,
+ pub filename: String,
+}
+
+impl From<Script<'_>> for OwnedScript {
+ fn from(s: Script) -> OwnedScript {
+ OwnedScript {
+ source: s.source.to_string(),
+ filename: s.filename.to_string(),
+ }
+ }
+}
+
+/// Represents data used to initialize isolate at startup
+/// either a binary snapshot or a javascript source file
+/// in the form of the StartupScript struct.
+pub enum StartupData<'a> {
+ Script(Script<'a>),
+ Snapshot(&'a [u8]),
+ LibdenoSnapshot(Snapshot1<'a>),
+ None,
+}
+
+type JSErrorCreateFn = dyn Fn(V8Exception) -> ErrBox;
+
+/// A single execution context of JavaScript. Corresponds roughly to the "Web
+/// Worker" concept in the DOM. An Isolate is a Future that can be used with
+/// Tokio. The Isolate future complete when there is an error or when all
+/// pending ops have completed.
+///
+/// Ops are created in JavaScript by calling Deno.core.dispatch(), and in Rust
+/// by implementing dispatcher function that takes control buffer and optional zero copy buffer
+/// as arguments. An async Op corresponds exactly to a Promise in JavaScript.
+pub struct Isolate {
+ libdeno_isolate: *const libdeno::isolate,
+ shared_libdeno_isolate: Arc<Mutex<Option<*const libdeno::isolate>>>,
+ dyn_import: Option<Arc<DynImportFn>>,
+ js_error_create: Arc<JSErrorCreateFn>,
+ needs_init: bool,
+ shared: SharedQueue,
+ pending_ops: FuturesUnordered<PendingOpFuture>,
+ pending_dyn_imports: FuturesUnordered<StreamFuture<DynImport>>,
+ have_unpolled_ops: bool,
+ startup_script: Option<OwnedScript>,
+ op_registry: OpRegistry,
+}
+
+unsafe impl Send for Isolate {}
+
+impl Drop for Isolate {
+ fn drop(&mut self) {
+ // remove shared_libdeno_isolate reference
+ *self.shared_libdeno_isolate.lock().unwrap() = None;
+
+ unsafe { libdeno::deno_delete(self.libdeno_isolate) }
+ }
+}
+
+static DENO_INIT: Once = Once::new();
+
+impl Isolate {
+ /// startup_data defines the snapshot or script used at startup to initialize
+ /// the isolate.
+ pub fn new(startup_data: StartupData, will_snapshot: bool) -> Self {
+ DENO_INIT.call_once(|| {
+ unsafe { libdeno::deno_init() };
+ });
+
+ let shared = SharedQueue::new(RECOMMENDED_SIZE);
+
+ let needs_init = true;
+
+ let mut libdeno_config = libdeno::deno_config {
+ will_snapshot: will_snapshot.into(),
+ load_snapshot: Snapshot2::empty(),
+ shared: shared.as_deno_buf(),
+ recv_cb: Self::pre_dispatch,
+ dyn_import_cb: Self::dyn_import,
+ };
+
+ let mut startup_script: Option<OwnedScript> = None;
+
+ // Separate into Option values for each startup type
+ match startup_data {
+ StartupData::Script(d) => {
+ startup_script = Some(d.into());
+ }
+ StartupData::Snapshot(d) => {
+ libdeno_config.load_snapshot = d.into();
+ }
+ StartupData::LibdenoSnapshot(d) => {
+ libdeno_config.load_snapshot = d;
+ }
+ StartupData::None => {}
+ };
+
+ let libdeno_isolate = unsafe { libdeno::deno_new(libdeno_config) };
+
+ Self {
+ libdeno_isolate,
+ shared_libdeno_isolate: Arc::new(Mutex::new(Some(libdeno_isolate))),
+ dyn_import: None,
+ js_error_create: Arc::new(CoreJSError::from_v8_exception),
+ shared,
+ needs_init,
+ pending_ops: FuturesUnordered::new(),
+ have_unpolled_ops: false,
+ pending_dyn_imports: FuturesUnordered::new(),
+ startup_script,
+ op_registry: OpRegistry::new(),
+ }
+ }
+
+ /// Defines the how Deno.core.dispatch() acts.
+ /// Called whenever Deno.core.dispatch() is called in JavaScript. zero_copy_buf
+ /// corresponds to the second argument of Deno.core.dispatch().
+ ///
+ /// Requires runtime to explicitly ask for op ids before using any of the ops.
+ pub fn register_op<F>(&mut self, name: &str, op: F) -> OpId
+ where
+ F: Fn(&[u8], Option<PinnedBuf>) -> CoreOp + Send + Sync + 'static,
+ {
+ self.op_registry.register(name, op)
+ }
+
+ pub fn set_dyn_import<F>(&mut self, f: F)
+ where
+ F: Fn(deno_dyn_import_id, &str, &str) -> DynImportStream
+ + Send
+ + Sync
+ + 'static,
+ {
+ self.dyn_import = Some(Arc::new(f));
+ }
+
+ /// Allows a callback to be set whenever a V8 exception is made. This allows
+ /// the caller to wrap the V8Exception into an error. By default this callback
+ /// is set to CoreJSError::from_v8_exception.
+ pub fn set_js_error_create<F>(&mut self, f: F)
+ where
+ F: Fn(V8Exception) -> ErrBox + 'static,
+ {
+ self.js_error_create = Arc::new(f);
+ }
+
+ /// Get a thread safe handle on the isolate.
+ pub fn shared_isolate_handle(&mut self) -> IsolateHandle {
+ IsolateHandle {
+ shared_libdeno_isolate: self.shared_libdeno_isolate.clone(),
+ }
+ }
+
+ /// Executes a bit of built-in JavaScript to provide Deno.sharedQueue.
+ fn shared_init(&mut self) {
+ if self.needs_init {
+ self.needs_init = false;
+ js_check(
+ self.execute("shared_queue.js", include_str!("shared_queue.js")),
+ );
+ // Maybe execute the startup script.
+ if let Some(s) = self.startup_script.take() {
+ self.execute(&s.filename, &s.source).unwrap()
+ }
+ }
+ }
+
+ extern "C" fn dyn_import(
+ user_data: *mut c_void,
+ specifier: *const c_char,
+ referrer: *const c_char,
+ id: deno_dyn_import_id,
+ ) {
+ assert_ne!(user_data, std::ptr::null_mut());
+ let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
+ let specifier = unsafe { CStr::from_ptr(specifier).to_str().unwrap() };
+ let referrer = unsafe { CStr::from_ptr(referrer).to_str().unwrap() };
+ debug!("dyn_import specifier {} referrer {} ", specifier, referrer);
+
+ if let Some(ref f) = isolate.dyn_import {
+ let inner = f(id, specifier, referrer);
+ let stream = DynImport { inner, id };
+ task::current().notify();
+ isolate.pending_dyn_imports.push(stream.into_future());
+ } else {
+ panic!("dyn_import callback not set")
+ }
+ }
+
+ extern "C" fn pre_dispatch(
+ user_data: *mut c_void,
+ op_id: OpId,
+ control_buf: deno_buf,
+ zero_copy_buf: deno_pinned_buf,
+ ) {
+ let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
+
+ let op = isolate.op_registry.call(
+ op_id,
+ control_buf.as_ref(),
+ PinnedBuf::new(zero_copy_buf),
+ );
+
+ debug_assert_eq!(isolate.shared.size(), 0);
+ match op {
+ Op::Sync(buf) => {
+ // For sync messages, we always return the response via Deno.core.send's
+ // return value. Sync messages ignore the op_id.
+ let op_id = 0;
+ isolate
+ .respond(Some((op_id, &buf)))
+ // Because this is a sync op, deno_respond() does not actually call
+ // into JavaScript. We should not get an error here.
+ .expect("unexpected error");
+ }
+ Op::Async(fut) => {
+ let fut2 = fut.map(move |buf| (op_id, buf));
+ isolate.pending_ops.push(Box::new(fut2));
+ isolate.have_unpolled_ops = true;
+ }
+ }
+ }
+
+ #[inline]
+ unsafe fn from_raw_ptr<'a>(ptr: *const c_void) -> &'a mut Self {
+ let ptr = ptr as *mut _;
+ &mut *ptr
+ }
+
+ #[inline]
+ fn as_raw_ptr(&self) -> *const c_void {
+ self as *const _ as *const c_void
+ }
+
+ /// Executes traditional JavaScript code (traditional = not ES modules)
+ ///
+ /// ErrBox can be downcast to a type that exposes additional information about
+ /// the V8 exception. By default this type is CoreJSError, however it may be a
+ /// different type if Isolate::set_js_error_create() has been used.
+ pub fn execute(
+ &mut self,
+ js_filename: &str,
+ js_source: &str,
+ ) -> Result<(), ErrBox> {
+ self.shared_init();
+ let filename = CString::new(js_filename).unwrap();
+ let source = CString::new(js_source).unwrap();
+ unsafe {
+ libdeno::deno_execute(
+ self.libdeno_isolate,
+ self.as_raw_ptr(),
+ filename.as_ptr(),
+ source.as_ptr(),
+ )
+ };
+ self.check_last_exception()
+ }
+
+ fn check_last_exception(&self) -> Result<(), ErrBox> {
+ let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) };
+ if ptr.is_null() {
+ Ok(())
+ } else {
+ let js_error_create = &*self.js_error_create;
+ let cstr = unsafe { CStr::from_ptr(ptr) };
+ let json_str = cstr.to_str().unwrap();
+ let v8_exception = V8Exception::from_json(json_str).unwrap();
+ let js_error = js_error_create(v8_exception);
+ Err(js_error)
+ }
+ }
+
+ fn check_promise_errors(&self) {
+ unsafe {
+ libdeno::deno_check_promise_errors(self.libdeno_isolate);
+ }
+ }
+
+ fn respond(
+ &mut self,
+ maybe_buf: Option<(OpId, &[u8])>,
+ ) -> Result<(), ErrBox> {
+ let (op_id, buf) = match maybe_buf {
+ None => (0, deno_buf::empty()),
+ Some((op_id, r)) => (op_id, deno_buf::from(r)),
+ };
+ unsafe {
+ libdeno::deno_respond(self.libdeno_isolate, self.as_raw_ptr(), op_id, buf)
+ }
+ self.check_last_exception()
+ }
+
+ /// Low-level module creation.
+ pub fn mod_new(
+ &self,
+ main: bool,
+ name: &str,
+ source: &str,
+ ) -> Result<deno_mod, ErrBox> {
+ let name_ = CString::new(name.to_string()).unwrap();
+ let name_ptr = name_.as_ptr() as *const libc::c_char;
+
+ let source_ = CString::new(source.to_string()).unwrap();
+ let source_ptr = source_.as_ptr() as *const libc::c_char;
+
+ let id = unsafe {
+ libdeno::deno_mod_new(self.libdeno_isolate, main, name_ptr, source_ptr)
+ };
+
+ self.check_last_exception().map(|_| id)
+ }
+
+ pub fn mod_get_imports(&self, id: deno_mod) -> Vec<String> {
+ let len =
+ unsafe { libdeno::deno_mod_imports_len(self.libdeno_isolate, id) };
+ let mut out = Vec::new();
+ for i in 0..len {
+ let specifier_ptr =
+ unsafe { libdeno::deno_mod_imports_get(self.libdeno_isolate, id, i) };
+ let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) };
+ let specifier: &str = specifier_c.to_str().unwrap();
+
+ out.push(specifier.to_string());
+ }
+ out
+ }
+
+ /// Takes a snapshot. The isolate should have been created with will_snapshot
+ /// set to true.
+ ///
+ /// ErrBox can be downcast to a type that exposes additional information about
+ /// the V8 exception. By default this type is CoreJSError, however it may be a
+ /// different type if Isolate::set_js_error_create() has been used.
+ pub fn snapshot(&self) -> Result<Snapshot1<'static>, ErrBox> {
+ let snapshot = unsafe { libdeno::deno_snapshot_new(self.libdeno_isolate) };
+ match self.check_last_exception() {
+ Ok(..) => Ok(snapshot),
+ Err(err) => {
+ assert_eq!(snapshot.data_ptr, null());
+ assert_eq!(snapshot.data_len, 0);
+ Err(err)
+ }
+ }
+ }
+
+ fn dyn_import_done(
+ &self,
+ id: libdeno::deno_dyn_import_id,
+ result: Result<deno_mod, Option<String>>,
+ ) -> Result<(), ErrBox> {
+ debug!("dyn_import_done {} {:?}", id, result);
+ let (mod_id, maybe_err_str) = match result {
+ Ok(mod_id) => (mod_id, None),
+ Err(None) => (0, None),
+ Err(Some(err_str)) => (0, Some(CString::new(err_str).unwrap())),
+ };
+ let err_str_ptr = match maybe_err_str {
+ Some(ref err_str) => err_str.as_ptr(),
+ None => std::ptr::null(),
+ };
+ unsafe {
+ libdeno::deno_dyn_import_done(
+ self.libdeno_isolate,
+ self.as_raw_ptr(),
+ id,
+ mod_id,
+ err_str_ptr,
+ )
+ };
+ self.check_last_exception()
+ }
+
+ fn poll_dyn_imports(&mut self) -> Poll<(), ErrBox> {
+ use RecursiveLoadEvent::*;
+ loop {
+ match self.pending_dyn_imports.poll() {
+ Ok(NotReady) | Ok(Ready(None)) => {
+ // There are no active dynamic import loaders, or none are ready.
+ return Ok(futures::Async::Ready(()));
+ }
+ Ok(Ready(Some((
+ Some((dyn_import_id, Fetch(source_code_info))),
+ mut stream,
+ )))) => {
+ // A module (not necessarily the one dynamically imported) has been
+ // fetched. Create and register it, and if successful, poll for the
+ // next recursive-load event related to this dynamic import.
+ match stream.register(source_code_info, self) {
+ Ok(()) => self.pending_dyn_imports.push(stream.into_future()),
+ Err(err) => {
+ self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))?
+ }
+ }
+ }
+ Ok(Ready(Some((Some((dyn_import_id, Instantiate(module_id))), _)))) => {
+ // The top-level module from a dynamic import has been instantiated.
+ match self.mod_evaluate(module_id) {
+ Ok(()) => self.dyn_import_done(dyn_import_id, Ok(module_id))?,
+ Err(..) => self.dyn_import_done(dyn_import_id, Err(None))?,
+ }
+ }
+ Err(((dyn_import_id, err), _)) => {
+ // A non-javascript error occurred; this could be due to a an invalid
+ // module specifier, or a problem with the source map, or a failure
+ // to fetch the module source code.
+ self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))?
+ }
+ Ok(Ready(Some((None, _)))) => unreachable!(),
+ }
+ }
+ }
+}
+
+/// Called during mod_instantiate() to resolve imports.
+type ResolveFn<'a> = dyn FnMut(&str, deno_mod) -> deno_mod + 'a;
+
+/// Used internally by Isolate::mod_instantiate to wrap ResolveFn and
+/// encapsulate pointer casts.
+struct ResolveContext<'a> {
+ resolve_fn: &'a mut ResolveFn<'a>,
+}
+
+impl<'a> ResolveContext<'a> {
+ #[inline]
+ fn as_raw_ptr(&mut self) -> *mut c_void {
+ self as *mut _ as *mut c_void
+ }
+
+ #[inline]
+ unsafe fn from_raw_ptr(ptr: *mut c_void) -> &'a mut Self {
+ &mut *(ptr as *mut _)
+ }
+}
+
+impl Isolate {
+ /// Instanciates a ES module
+ ///
+ /// ErrBox can be downcast to a type that exposes additional information about
+ /// the V8 exception. By default this type is CoreJSError, however it may be a
+ /// different type if Isolate::set_js_error_create() has been used.
+ pub fn mod_instantiate(
+ &mut self,
+ id: deno_mod,
+ resolve_fn: &mut ResolveFn,
+ ) -> Result<(), ErrBox> {
+ let libdeno_isolate = self.libdeno_isolate;
+ let mut ctx = ResolveContext { resolve_fn };
+ unsafe {
+ libdeno::deno_mod_instantiate(
+ libdeno_isolate,
+ ctx.as_raw_ptr(),
+ id,
+ Self::resolve_cb,
+ )
+ };
+ self.check_last_exception()
+ }
+
+ /// Called during mod_instantiate() only.
+ extern "C" fn resolve_cb(
+ user_data: *mut libc::c_void,
+ specifier_ptr: *const libc::c_char,
+ referrer: deno_mod,
+ ) -> deno_mod {
+ let ResolveContext { resolve_fn } =
+ unsafe { ResolveContext::from_raw_ptr(user_data) };
+ let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) };
+ let specifier: &str = specifier_c.to_str().unwrap();
+
+ resolve_fn(specifier, referrer)
+ }
+
+ /// Evaluates an already instantiated ES module.
+ ///
+ /// ErrBox can be downcast to a type that exposes additional information about
+ /// the V8 exception. By default this type is CoreJSError, however it may be a
+ /// different type if Isolate::set_js_error_create() has been used.
+ pub fn mod_evaluate(&mut self, id: deno_mod) -> Result<(), ErrBox> {
+ self.shared_init();
+ unsafe {
+ libdeno::deno_mod_evaluate(self.libdeno_isolate, self.as_raw_ptr(), id)
+ };
+ self.check_last_exception()
+ }
+}
+
+struct LockerScope {
+ libdeno_isolate: *const libdeno::isolate,
+}
+
+impl LockerScope {
+ fn new(libdeno_isolate: *const libdeno::isolate) -> LockerScope {
+ unsafe { libdeno::deno_lock(libdeno_isolate) }
+ LockerScope { libdeno_isolate }
+ }
+}
+
+impl Drop for LockerScope {
+ fn drop(&mut self) {
+ unsafe { libdeno::deno_unlock(self.libdeno_isolate) }
+ }
+}
+
+impl Future for Isolate {
+ type Item = ();
+ type Error = ErrBox;
+
+ fn poll(&mut self) -> Poll<(), ErrBox> {
+ self.shared_init();
+
+ let mut overflow_response: Option<(OpId, Buf)> = None;
+
+ loop {
+ // If there are any pending dyn_import futures, do those first.
+ if !self.pending_dyn_imports.is_empty() {
+ self.poll_dyn_imports()?;
+ }
+
+ // Now handle actual ops.
+ self.have_unpolled_ops = false;
+ #[allow(clippy::match_wild_err_arm)]
+ match self.pending_ops.poll() {
+ Err(_) => panic!("unexpected op error"),
+ Ok(Ready(None)) => break,
+ Ok(NotReady) => break,
+ Ok(Ready(Some((op_id, buf)))) => {
+ let successful_push = self.shared.push(op_id, &buf);
+ if !successful_push {
+ // If we couldn't push the response to the shared queue, because
+ // there wasn't enough size, we will return the buffer via the
+ // legacy route, using the argument of deno_respond.
+ overflow_response = Some((op_id, buf));
+ break;
+ }
+ }
+ }
+ }
+
+ if self.shared.size() > 0 {
+ // Lock the current thread for V8.
+ let locker = LockerScope::new(self.libdeno_isolate);
+ self.respond(None)?;
+ // The other side should have shifted off all the messages.
+ assert_eq!(self.shared.size(), 0);
+ drop(locker);
+ }
+
+ if overflow_response.is_some() {
+ // Lock the current thread for V8.
+ let locker = LockerScope::new(self.libdeno_isolate);
+ let (op_id, buf) = overflow_response.take().unwrap();
+ self.respond(Some((op_id, &buf)))?;
+ drop(locker);
+ }
+
+ self.check_promise_errors();
+ self.check_last_exception()?;
+
+ // We're idle if pending_ops is empty.
+ if self.pending_ops.is_empty() && self.pending_dyn_imports.is_empty() {
+ Ok(futures::Async::Ready(()))
+ } else {
+ if self.have_unpolled_ops {
+ task::current().notify();
+ }
+ Ok(futures::Async::NotReady)
+ }
+ }
+}
+
+/// IsolateHandle is a thread safe handle on an Isolate. It exposed thread safe V8 functions.
+#[derive(Clone)]
+pub struct IsolateHandle {
+ shared_libdeno_isolate: Arc<Mutex<Option<*const libdeno::isolate>>>,
+}
+
+unsafe impl Send for IsolateHandle {}
+
+impl IsolateHandle {
+ /// Terminate the execution of any currently running javascript.
+ /// After terminating execution it is probably not wise to continue using
+ /// the isolate.
+ pub fn terminate_execution(&self) {
+ unsafe {
+ if let Some(isolate) = *self.shared_libdeno_isolate.lock().unwrap() {
+ libdeno::deno_terminate_execution(isolate)
+ }
+ }
+ }
+}
+
+pub fn js_check<T>(r: Result<T, ErrBox>) -> T {
+ if let Err(e) = r {
+ panic!(e.to_string());
+ }
+ r.unwrap()
+}
+
+#[cfg(test)]
+pub mod tests {
+ use super::*;
+ use futures::executor::spawn;
+ use futures::future::lazy;
+ use futures::future::ok;
+ use futures::Async;
+ use std::io;
+ use std::ops::FnOnce;
+ use std::sync::atomic::{AtomicUsize, Ordering};
+
+ pub fn run_in_task<F, R>(f: F) -> R
+ where
+ F: FnOnce() -> R,
+ {
+ spawn(lazy(move || ok::<R, ()>(f()))).wait_future().unwrap()
+ }
+
+ fn poll_until_ready<F>(
+ future: &mut F,
+ max_poll_count: usize,
+ ) -> Result<F::Item, F::Error>
+ where
+ F: Future,
+ {
+ for _ in 0..max_poll_count {
+ match future.poll() {
+ Ok(NotReady) => continue,
+ Ok(Ready(val)) => return Ok(val),
+ Err(err) => return Err(err),
+ }
+ }
+ panic!(
+ "Isolate still not ready after polling {} times.",
+ max_poll_count
+ )
+ }
+
+ pub enum Mode {
+ AsyncImmediate,
+ OverflowReqSync,
+ OverflowResSync,
+ OverflowReqAsync,
+ OverflowResAsync,
+ }
+
+ pub fn setup(mode: Mode) -> (Isolate, Arc<AtomicUsize>) {
+ let dispatch_count = Arc::new(AtomicUsize::new(0));
+ let dispatch_count_ = dispatch_count.clone();
+
+ let mut isolate = Isolate::new(StartupData::None, false);
+
+ let dispatcher =
+ move |control: &[u8], _zero_copy: Option<PinnedBuf>| -> CoreOp {
+ dispatch_count_.fetch_add(1, Ordering::Relaxed);
+ match mode {
+ Mode::AsyncImmediate => {
+ assert_eq!(control.len(), 1);
+ assert_eq!(control[0], 42);
+ let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
+ Op::Async(Box::new(futures::future::ok(buf)))
+ }
+ Mode::OverflowReqSync => {
+ assert_eq!(control.len(), 100 * 1024 * 1024);
+ let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
+ Op::Sync(buf)
+ }
+ Mode::OverflowResSync => {
+ assert_eq!(control.len(), 1);
+ assert_eq!(control[0], 42);
+ let mut vec = Vec::<u8>::new();
+ vec.resize(100 * 1024 * 1024, 0);
+ vec[0] = 99;
+ let buf = vec.into_boxed_slice();
+ Op::Sync(buf)
+ }
+ Mode::OverflowReqAsync => {
+ assert_eq!(control.len(), 100 * 1024 * 1024);
+ let buf = vec![43u8, 0, 0, 0].into_boxed_slice();
+ Op::Async(Box::new(futures::future::ok(buf)))
+ }
+ Mode::OverflowResAsync => {
+ assert_eq!(control.len(), 1);
+ assert_eq!(control[0], 42);
+ let mut vec = Vec::<u8>::new();
+ vec.resize(100 * 1024 * 1024, 0);
+ vec[0] = 4;
+ let buf = vec.into_boxed_slice();
+ Op::Async(Box::new(futures::future::ok(buf)))
+ }
+ }
+ };
+
+ isolate.register_op("test", dispatcher);
+
+ js_check(isolate.execute(
+ "setup.js",
+ r#"
+ function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+ }
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ (isolate, dispatch_count)
+ }
+
+ #[test]
+ fn test_dispatch() {
+ let (mut isolate, dispatch_count) = setup(Mode::AsyncImmediate);
+ js_check(isolate.execute(
+ "filename.js",
+ r#"
+ let control = new Uint8Array([42]);
+ Deno.core.send(1, control);
+ async function main() {
+ Deno.core.send(1, control);
+ }
+ main();
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
+ }
+
+ #[test]
+ fn test_mods() {
+ let (mut isolate, dispatch_count) = setup(Mode::AsyncImmediate);
+ let mod_a = isolate
+ .mod_new(
+ true,
+ "a.js",
+ r#"
+ import { b } from 'b.js'
+ if (b() != 'b') throw Error();
+ let control = new Uint8Array([42]);
+ Deno.core.send(1, control);
+ "#,
+ )
+ .unwrap();
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+
+ let imports = isolate.mod_get_imports(mod_a);
+ assert_eq!(imports, vec!["b.js".to_string()]);
+
+ let mod_b = isolate
+ .mod_new(false, "b.js", "export function b() { return 'b' }")
+ .unwrap();
+ let imports = isolate.mod_get_imports(mod_b);
+ assert_eq!(imports.len(), 0);
+
+ let resolve_count = Arc::new(AtomicUsize::new(0));
+ let resolve_count_ = resolve_count.clone();
+
+ let mut resolve = move |specifier: &str, _referrer: deno_mod| -> deno_mod {
+ resolve_count_.fetch_add(1, Ordering::SeqCst);
+ assert_eq!(specifier, "b.js");
+ mod_b
+ };
+
+ js_check(isolate.mod_instantiate(mod_b, &mut resolve));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(resolve_count.load(Ordering::SeqCst), 0);
+
+ js_check(isolate.mod_instantiate(mod_a, &mut resolve));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ assert_eq!(resolve_count.load(Ordering::SeqCst), 1);
+
+ js_check(isolate.mod_evaluate(mod_a));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ assert_eq!(resolve_count.load(Ordering::SeqCst), 1);
+ }
+
+ #[test]
+ fn test_poll_async_immediate_ops() {
+ run_in_task(|| {
+ let (mut isolate, dispatch_count) = setup(Mode::AsyncImmediate);
+
+ js_check(isolate.execute(
+ "setup2.js",
+ r#"
+ let nrecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => {
+ nrecv++;
+ });
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
+ js_check(isolate.execute(
+ "check1.js",
+ r#"
+ assert(nrecv == 0);
+ let control = new Uint8Array([42]);
+ Deno.core.send(1, control);
+ assert(nrecv == 0);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ assert_eq!(Async::Ready(()), isolate.poll().unwrap());
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ js_check(isolate.execute(
+ "check2.js",
+ r#"
+ assert(nrecv == 1);
+ Deno.core.send(1, control);
+ assert(nrecv == 1);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
+ assert_eq!(Async::Ready(()), isolate.poll().unwrap());
+ js_check(isolate.execute("check3.js", "assert(nrecv == 2)"));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
+ // We are idle, so the next poll should be the last.
+ assert_eq!(Async::Ready(()), isolate.poll().unwrap());
+ });
+ }
+
+ struct MockImportStream(Vec<Result<RecursiveLoadEvent, ErrBox>>);
+
+ impl Stream for MockImportStream {
+ type Item = RecursiveLoadEvent;
+ type Error = ErrBox;
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ let event = if self.0.is_empty() {
+ None
+ } else {
+ Some(self.0.remove(0)?)
+ };
+ Ok(Ready(event))
+ }
+ }
+
+ impl ImportStream for MockImportStream {
+ fn register(
+ &mut self,
+ module_data: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox> {
+ let id = isolate.mod_new(
+ false,
+ &module_data.module_url_found,
+ &module_data.code,
+ )?;
+ println!(
+ "MockImportStream register {} {}",
+ id, module_data.module_url_found
+ );
+ Ok(())
+ }
+ }
+
+ #[test]
+ fn dyn_import_err() {
+ // Test an erroneous dynamic import where the specified module isn't found.
+ run_in_task(|| {
+ let count = Arc::new(AtomicUsize::new(0));
+ let count_ = count.clone();
+ let mut isolate = Isolate::new(StartupData::None, false);
+ isolate.set_dyn_import(move |_, specifier, referrer| {
+ count_.fetch_add(1, Ordering::Relaxed);
+ assert_eq!(specifier, "foo.js");
+ assert_eq!(referrer, "dyn_import2.js");
+ let err = io::Error::from(io::ErrorKind::NotFound);
+ let stream = MockImportStream(vec![Err(err.into())]);
+ Box::new(stream)
+ });
+ js_check(isolate.execute(
+ "dyn_import2.js",
+ r#"
+ (async () => {
+ await import("foo.js");
+ })();
+ "#,
+ ));
+ assert_eq!(count.load(Ordering::Relaxed), 1);
+
+ // We should get an error here.
+ let result = isolate.poll();
+ assert!(result.is_err());
+ })
+ }
+
+ #[test]
+ fn dyn_import_err2() {
+ use std::convert::TryInto;
+ // Import multiple modules to demonstrate that after failed dynamic import
+ // another dynamic import can still be run
+ run_in_task(|| {
+ let count = Arc::new(AtomicUsize::new(0));
+ let count_ = count.clone();
+ let mut isolate = Isolate::new(StartupData::None, false);
+ isolate.set_dyn_import(move |_, specifier, referrer| {
+ let c = count_.fetch_add(1, Ordering::Relaxed);
+ match c {
+ 0 => assert_eq!(specifier, "foo1.js"),
+ 1 => assert_eq!(specifier, "foo2.js"),
+ 2 => assert_eq!(specifier, "foo3.js"),
+ _ => unreachable!(),
+ }
+ assert_eq!(referrer, "dyn_import_error.js");
+
+ let source_code_info = SourceCodeInfo {
+ module_url_specified: specifier.to_owned(),
+ module_url_found: specifier.to_owned(),
+ code: "# not valid JS".to_owned(),
+ };
+ let stream = MockImportStream(vec![
+ Ok(RecursiveLoadEvent::Fetch(source_code_info)),
+ Ok(RecursiveLoadEvent::Instantiate(c.try_into().unwrap())),
+ ]);
+ Box::new(stream)
+ });
+
+ js_check(isolate.execute(
+ "dyn_import_error.js",
+ r#"
+ (async () => {
+ await import("foo1.js");
+ })();
+ (async () => {
+ await import("foo2.js");
+ })();
+ (async () => {
+ await import("foo3.js");
+ })();
+ "#,
+ ));
+
+ assert_eq!(count.load(Ordering::Relaxed), 3);
+ // Now each poll should return error
+ assert!(isolate.poll().is_err());
+ assert!(isolate.poll().is_err());
+ assert!(isolate.poll().is_err());
+ })
+ }
+
+ #[test]
+ fn dyn_import_ok() {
+ run_in_task(|| {
+ let count = Arc::new(AtomicUsize::new(0));
+ let count_ = count.clone();
+
+ // Sometimes Rust is really annoying.
+ let mod_b = Arc::new(Mutex::new(0));
+ let mod_b2 = mod_b.clone();
+
+ let mut isolate = Isolate::new(StartupData::None, false);
+ isolate.set_dyn_import(move |_id, specifier, referrer| {
+ let c = count_.fetch_add(1, Ordering::Relaxed);
+ match c {
+ 0 => assert_eq!(specifier, "foo1.js"),
+ 1 => assert_eq!(specifier, "foo2.js"),
+ _ => unreachable!(),
+ }
+ assert_eq!(referrer, "dyn_import3.js");
+ let mod_id = *mod_b2.lock().unwrap();
+ let source_code_info = SourceCodeInfo {
+ module_url_specified: "foo.js".to_owned(),
+ module_url_found: "foo.js".to_owned(),
+ code: "".to_owned(),
+ };
+ let stream = MockImportStream(vec![
+ Ok(RecursiveLoadEvent::Fetch(source_code_info)),
+ Ok(RecursiveLoadEvent::Instantiate(mod_id)),
+ ]);
+ Box::new(stream)
+ });
+
+ // Instantiate mod_b
+ {
+ let mut mod_id = mod_b.lock().unwrap();
+ *mod_id = isolate
+ .mod_new(false, "b.js", "export function b() { return 'b' }")
+ .unwrap();
+ let mut resolve = move |_specifier: &str,
+ _referrer: deno_mod|
+ -> deno_mod { unreachable!() };
+ js_check(isolate.mod_instantiate(*mod_id, &mut resolve));
+ }
+ // Dynamically import mod_b
+ js_check(isolate.execute(
+ "dyn_import3.js",
+ r#"
+ (async () => {
+ let mod = await import("foo1.js");
+ if (mod.b() !== 'b') {
+ throw Error("bad1");
+ }
+ // And again!
+ mod = await import("foo2.js");
+ if (mod.b() !== 'b') {
+ throw Error("bad2");
+ }
+ })();
+ "#,
+ ));
+
+ assert_eq!(count.load(Ordering::Relaxed), 1);
+ assert_eq!(Ready(()), isolate.poll().unwrap());
+ assert_eq!(count.load(Ordering::Relaxed), 2);
+ assert_eq!(Ready(()), isolate.poll().unwrap());
+ assert_eq!(count.load(Ordering::Relaxed), 2);
+ })
+ }
+
+ #[test]
+ fn terminate_execution() {
+ let (tx, rx) = std::sync::mpsc::channel::<bool>();
+ let tx_clone = tx.clone();
+
+ let (mut isolate, _dispatch_count) = setup(Mode::AsyncImmediate);
+ let shared = isolate.shared_isolate_handle();
+
+ let t1 = std::thread::spawn(move || {
+ // allow deno to boot and run
+ std::thread::sleep(std::time::Duration::from_millis(100));
+
+ // terminate execution
+ shared.terminate_execution();
+
+ // allow shutdown
+ std::thread::sleep(std::time::Duration::from_millis(100));
+
+ // unless reported otherwise the test should fail after this point
+ tx_clone.send(false).ok();
+ });
+
+ let t2 = std::thread::spawn(move || {
+ // run an infinite loop
+ let res = isolate.execute(
+ "infinite_loop.js",
+ r#"
+ let i = 0;
+ while (true) { i++; }
+ "#,
+ );
+
+ // execute() terminated, which means terminate_execution() was successful.
+ tx.send(true).ok();
+
+ if let Err(e) = res {
+ assert_eq!(e.to_string(), "Uncaught Error: execution terminated");
+ } else {
+ panic!("should return an error");
+ }
+
+ // make sure the isolate is still unusable
+ let res = isolate.execute("simple.js", "1+1;");
+ if let Err(e) = res {
+ assert_eq!(e.to_string(), "Uncaught Error: execution terminated");
+ } else {
+ panic!("should return an error");
+ }
+ });
+
+ if !rx.recv().unwrap() {
+ panic!("should have terminated")
+ }
+
+ t1.join().unwrap();
+ t2.join().unwrap();
+ }
+
+ #[test]
+ fn dangling_shared_isolate() {
+ let shared = {
+ // isolate is dropped at the end of this block
+ let (mut isolate, _dispatch_count) = setup(Mode::AsyncImmediate);
+ isolate.shared_isolate_handle()
+ };
+
+ // this should not SEGFAULT
+ shared.terminate_execution();
+ }
+
+ #[test]
+ fn overflow_req_sync() {
+ let (mut isolate, dispatch_count) = setup(Mode::OverflowReqSync);
+ js_check(isolate.execute(
+ "overflow_req_sync.js",
+ r#"
+ let asyncRecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => { asyncRecv++ });
+ // Large message that will overflow the shared space.
+ let control = new Uint8Array(100 * 1024 * 1024);
+ let response = Deno.core.dispatch(1, control);
+ assert(response instanceof Uint8Array);
+ assert(response.length == 4);
+ assert(response[0] == 43);
+ assert(asyncRecv == 0);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ }
+
+ #[test]
+ fn overflow_res_sync() {
+ // TODO(ry) This test is quite slow due to memcpy-ing 100MB into JS. We
+ // should optimize this.
+ let (mut isolate, dispatch_count) = setup(Mode::OverflowResSync);
+ js_check(isolate.execute(
+ "overflow_res_sync.js",
+ r#"
+ let asyncRecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => { asyncRecv++ });
+ // Large message that will overflow the shared space.
+ let control = new Uint8Array([42]);
+ let response = Deno.core.dispatch(1, control);
+ assert(response instanceof Uint8Array);
+ assert(response.length == 100 * 1024 * 1024);
+ assert(response[0] == 99);
+ assert(asyncRecv == 0);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ }
+
+ #[test]
+ fn overflow_req_async() {
+ run_in_task(|| {
+ let (mut isolate, dispatch_count) = setup(Mode::OverflowReqAsync);
+ js_check(isolate.execute(
+ "overflow_req_async.js",
+ r#"
+ let asyncRecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => {
+ assert(opId == 1);
+ assert(buf.byteLength === 4);
+ assert(buf[0] === 43);
+ asyncRecv++;
+ });
+ // Large message that will overflow the shared space.
+ let control = new Uint8Array(100 * 1024 * 1024);
+ let response = Deno.core.dispatch(1, control);
+ // Async messages always have null response.
+ assert(response == null);
+ assert(asyncRecv == 0);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ assert_eq!(Async::Ready(()), js_check(isolate.poll()));
+ js_check(isolate.execute("check.js", "assert(asyncRecv == 1);"));
+ });
+ }
+
+ #[test]
+ fn overflow_res_async() {
+ run_in_task(|| {
+ // TODO(ry) This test is quite slow due to memcpy-ing 100MB into JS. We
+ // should optimize this.
+ let (mut isolate, dispatch_count) = setup(Mode::OverflowResAsync);
+ js_check(isolate.execute(
+ "overflow_res_async.js",
+ r#"
+ let asyncRecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => {
+ assert(opId == 1);
+ assert(buf.byteLength === 100 * 1024 * 1024);
+ assert(buf[0] === 4);
+ asyncRecv++;
+ });
+ // Large message that will overflow the shared space.
+ let control = new Uint8Array([42]);
+ let response = Deno.core.dispatch(1, control);
+ assert(response == null);
+ assert(asyncRecv == 0);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
+ poll_until_ready(&mut isolate, 3).unwrap();
+ js_check(isolate.execute("check.js", "assert(asyncRecv == 1);"));
+ });
+ }
+
+ #[test]
+ fn overflow_res_multiple_dispatch_async() {
+ // TODO(ry) This test is quite slow due to memcpy-ing 100MB into JS. We
+ // should optimize this.
+ run_in_task(|| {
+ let (mut isolate, dispatch_count) = setup(Mode::OverflowResAsync);
+ js_check(isolate.execute(
+ "overflow_res_multiple_dispatch_async.js",
+ r#"
+ let asyncRecv = 0;
+ Deno.core.setAsyncHandler((opId, buf) => {
+ assert(opId === 1);
+ assert(buf.byteLength === 100 * 1024 * 1024);
+ assert(buf[0] === 4);
+ asyncRecv++;
+ });
+ // Large message that will overflow the shared space.
+ let control = new Uint8Array([42]);
+ let response = Deno.core.dispatch(1, control);
+ assert(response == null);
+ assert(asyncRecv == 0);
+ // Dispatch another message to verify that pending ops
+ // are done even if shared space overflows
+ Deno.core.dispatch(1, control);
+ "#,
+ ));
+ assert_eq!(dispatch_count.load(Ordering::Relaxed), 2);
+ poll_until_ready(&mut isolate, 3).unwrap();
+ js_check(isolate.execute("check.js", "assert(asyncRecv == 2);"));
+ });
+ }
+
+ #[test]
+ fn test_js() {
+ run_in_task(|| {
+ let (mut isolate, _dispatch_count) = setup(Mode::AsyncImmediate);
+ js_check(
+ isolate.execute(
+ "shared_queue_test.js",
+ include_str!("shared_queue_test.js"),
+ ),
+ );
+ assert_eq!(Async::Ready(()), isolate.poll().unwrap());
+ });
+ }
+
+ #[test]
+ fn will_snapshot() {
+ let snapshot = {
+ let mut isolate = Isolate::new(StartupData::None, true);
+ js_check(isolate.execute("a.js", "a = 1 + 2"));
+ let s = isolate.snapshot().unwrap();
+ drop(isolate);
+ s
+ };
+
+ let startup_data = StartupData::LibdenoSnapshot(snapshot);
+ let mut isolate2 = Isolate::new(startup_data, false);
+ js_check(isolate2.execute("check.js", "if (a != 3) throw Error('x')"));
+ }
+}
diff --git a/core/js_errors.rs b/core/js_errors.rs
new file mode 100644
index 000000000..3656242e0
--- /dev/null
+++ b/core/js_errors.rs
@@ -0,0 +1,416 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// Note that source_map_mappings requires 0-indexed line and column numbers but
+// V8 Exceptions are 1-indexed.
+
+// TODO: This currently only applies to uncaught exceptions. It would be nice to
+// also have source maps for situations like this:
+// const err = new Error("Boo!");
+// console.log(err.stack);
+// It would require calling into Rust from Error.prototype.prepareStackTrace.
+
+use crate::any_error::ErrBox;
+use serde_json;
+use serde_json::value::Value;
+use std::error::Error;
+use std::fmt;
+use std::str;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct StackFrame {
+ pub line: i64, // zero indexed
+ pub column: i64, // zero indexed
+ pub script_name: String,
+ pub function_name: String,
+ pub is_eval: bool,
+ pub is_constructor: bool,
+ pub is_wasm: bool,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct V8Exception {
+ pub message: String,
+
+ pub source_line: Option<String>,
+ pub script_resource_name: Option<String>,
+ pub line_number: Option<i64>,
+ pub start_position: Option<i64>,
+ pub end_position: Option<i64>,
+ pub error_level: Option<i64>,
+ pub start_column: Option<i64>,
+ pub end_column: Option<i64>,
+
+ pub frames: Vec<StackFrame>,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct CoreJSError(V8Exception);
+
+impl StackFrame {
+ // TODO Maybe use serde_derive?
+ fn from_json_value(v: &serde_json::Value) -> Option<Self> {
+ if !v.is_object() {
+ return None;
+ }
+ let obj = v.as_object().unwrap();
+
+ let line_v = &obj["line"];
+ if !line_v.is_u64() {
+ return None;
+ }
+ let line = line_v.as_u64().unwrap() as i64;
+
+ let column_v = &obj["column"];
+ if !column_v.is_u64() {
+ return None;
+ }
+ let column = column_v.as_u64().unwrap() as i64;
+
+ let script_name_v = &obj["scriptName"];
+ if !script_name_v.is_string() {
+ return None;
+ }
+ let script_name = String::from(script_name_v.as_str().unwrap());
+
+ // Optional fields. See EncodeExceptionAsJSON() in libdeno.
+ // Sometimes V8 doesn't provide all the frame information.
+
+ let mut function_name = String::from(""); // default
+ if obj.contains_key("functionName") {
+ let function_name_v = &obj["functionName"];
+ if function_name_v.is_string() {
+ function_name = String::from(function_name_v.as_str().unwrap());
+ }
+ }
+
+ let mut is_eval = false; // default
+ if obj.contains_key("isEval") {
+ let is_eval_v = &obj["isEval"];
+ if is_eval_v.is_boolean() {
+ is_eval = is_eval_v.as_bool().unwrap();
+ }
+ }
+
+ let mut is_constructor = false; // default
+ if obj.contains_key("isConstructor") {
+ let is_constructor_v = &obj["isConstructor"];
+ if is_constructor_v.is_boolean() {
+ is_constructor = is_constructor_v.as_bool().unwrap();
+ }
+ }
+
+ let mut is_wasm = false; // default
+ if obj.contains_key("isWasm") {
+ let is_wasm_v = &obj["isWasm"];
+ if is_wasm_v.is_boolean() {
+ is_wasm = is_wasm_v.as_bool().unwrap();
+ }
+ }
+
+ Some(StackFrame {
+ line: line - 1,
+ column: column - 1,
+ script_name,
+ function_name,
+ is_eval,
+ is_constructor,
+ is_wasm,
+ })
+ }
+}
+
+impl V8Exception {
+ /// Creates a new V8Exception by parsing the raw exception JSON string from V8.
+ pub fn from_json(json_str: &str) -> Option<Self> {
+ let v = serde_json::from_str::<serde_json::Value>(json_str);
+ if v.is_err() {
+ return None;
+ }
+ let v = v.unwrap();
+ Self::from_json_value(v)
+ }
+
+ pub fn from_json_value(v: serde_json::Value) -> Option<Self> {
+ if !v.is_object() {
+ return None;
+ }
+ let obj = v.as_object().unwrap();
+
+ let message_v = &obj["message"];
+ if !message_v.is_string() {
+ return None;
+ }
+ let message = String::from(message_v.as_str().unwrap());
+
+ let source_line = obj
+ .get("sourceLine")
+ .and_then(|v| v.as_str().map(String::from));
+ let script_resource_name = obj
+ .get("scriptResourceName")
+ .and_then(|v| v.as_str().map(String::from));
+ let line_number = obj.get("lineNumber").and_then(Value::as_i64);
+ let start_position = obj.get("startPosition").and_then(Value::as_i64);
+ let end_position = obj.get("endPosition").and_then(Value::as_i64);
+ let error_level = obj.get("errorLevel").and_then(Value::as_i64);
+ let start_column = obj.get("startColumn").and_then(Value::as_i64);
+ let end_column = obj.get("endColumn").and_then(Value::as_i64);
+
+ let frames_v = &obj["frames"];
+ if !frames_v.is_array() {
+ return None;
+ }
+ let frame_values = frames_v.as_array().unwrap();
+
+ let mut frames = Vec::<StackFrame>::new();
+ for frame_v in frame_values {
+ match StackFrame::from_json_value(frame_v) {
+ None => return None,
+ Some(frame) => frames.push(frame),
+ }
+ }
+
+ Some(V8Exception {
+ message,
+ source_line,
+ script_resource_name,
+ line_number,
+ start_position,
+ end_position,
+ error_level,
+ start_column,
+ end_column,
+ frames,
+ })
+ }
+}
+
+impl CoreJSError {
+ pub fn from_v8_exception(v8_exception: V8Exception) -> ErrBox {
+ let error = Self(v8_exception);
+ ErrBox::from(error)
+ }
+}
+
+fn format_source_loc(script_name: &str, line: i64, column: i64) -> String {
+ // TODO match this style with how typescript displays errors.
+ let line = line + 1;
+ let column = column + 1;
+ format!("{}:{}:{}", script_name, line, column)
+}
+
+fn format_stack_frame(frame: &StackFrame) -> String {
+ // Note when we print to string, we change from 0-indexed to 1-indexed.
+ let source_loc =
+ format_source_loc(&frame.script_name, frame.line, frame.column);
+
+ if !frame.function_name.is_empty() {
+ format!(" at {} ({})", frame.function_name, source_loc)
+ } else if frame.is_eval {
+ format!(" at eval ({})", source_loc)
+ } else {
+ format!(" at {}", source_loc)
+ }
+}
+
+impl fmt::Display for CoreJSError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.0.script_resource_name.is_some() {
+ let script_resource_name = self.0.script_resource_name.as_ref().unwrap();
+ if self.0.line_number.is_some() && self.0.start_column.is_some() {
+ assert!(self.0.line_number.is_some());
+ assert!(self.0.start_column.is_some());
+ let source_loc = format_source_loc(
+ script_resource_name,
+ self.0.line_number.unwrap() - 1,
+ self.0.start_column.unwrap() - 1,
+ );
+ write!(f, "{}", source_loc)?;
+ }
+ if self.0.source_line.is_some() {
+ write!(f, "\n{}\n", self.0.source_line.as_ref().unwrap())?;
+ let mut s = String::new();
+ for i in 0..self.0.end_column.unwrap() {
+ if i >= self.0.start_column.unwrap() {
+ s.push('^');
+ } else {
+ s.push(' ');
+ }
+ }
+ writeln!(f, "{}", s)?;
+ }
+ }
+
+ write!(f, "{}", self.0.message)?;
+
+ for frame in &self.0.frames {
+ write!(f, "\n{}", format_stack_frame(frame))?;
+ }
+ Ok(())
+ }
+}
+
+impl Error for CoreJSError {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn error1() -> V8Exception {
+ V8Exception {
+ 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,
+ },
+ ],
+ }
+ }
+
+ #[test]
+ fn stack_frame_from_json_value_1() {
+ let v = serde_json::from_str::<serde_json::Value>(
+ r#"{
+ "line":2,
+ "column":11,
+ "functionName":"foo",
+ "scriptName":"/Users/rld/src/deno/tests/error_001.ts",
+ "isEval":true,
+ "isConstructor":false,
+ "isWasm":false
+ }"#,
+ )
+ .unwrap();
+ let r = StackFrame::from_json_value(&v);
+ assert_eq!(
+ r,
+ Some(StackFrame {
+ line: 1,
+ column: 10,
+ script_name: "/Users/rld/src/deno/tests/error_001.ts".to_string(),
+ function_name: "foo".to_string(),
+ is_eval: true,
+ is_constructor: false,
+ is_wasm: false,
+ })
+ );
+ }
+
+ #[test]
+ fn stack_frame_from_json_value_2() {
+ let v = serde_json::from_str::<serde_json::Value>(
+ r#"{
+ "scriptName": "/Users/rld/src/deno/tests/error_001.ts",
+ "line": 2,
+ "column": 11
+ }"#,
+ )
+ .unwrap();
+ let r = StackFrame::from_json_value(&v);
+ assert!(r.is_some());
+ let f = r.unwrap();
+ assert_eq!(f.line, 1);
+ assert_eq!(f.column, 10);
+ assert_eq!(f.script_name, "/Users/rld/src/deno/tests/error_001.ts");
+ }
+
+ #[test]
+ fn v8_exception_from_json() {
+ let r = V8Exception::from_json(
+ r#"{
+ "message":"Uncaught Error: bad",
+ "frames":[
+ {
+ "line":2,
+ "column":11,
+ "functionName":"foo",
+ "scriptName":"/Users/rld/src/deno/tests/error_001.ts",
+ "isEval":true,
+ "isConstructor":false,
+ "isWasm":false
+ }, {
+ "line":5,
+ "column":5,
+ "functionName":"bar",
+ "scriptName":"/Users/rld/src/deno/tests/error_001.ts",
+ "isEval":true,
+ "isConstructor":false,
+ "isWasm":false
+ }
+ ]}"#,
+ );
+ assert!(r.is_some());
+ let e = r.unwrap();
+ assert_eq!(e.message, "Uncaught Error: bad");
+ assert_eq!(e.frames.len(), 2);
+ assert_eq!(
+ e.frames[0],
+ StackFrame {
+ line: 1,
+ column: 10,
+ script_name: "/Users/rld/src/deno/tests/error_001.ts".to_string(),
+ function_name: "foo".to_string(),
+ is_eval: true,
+ is_constructor: false,
+ is_wasm: false,
+ }
+ )
+ }
+
+ #[test]
+ fn v8_exception_from_json_2() {
+ let r = V8Exception::from_json(
+ "{\"message\":\"Error: boo\",\"sourceLine\":\"throw Error('boo');\",\"scriptResourceName\":\"a.js\",\"lineNumber\":3,\"startPosition\":8,\"endPosition\":9,\"errorLevel\":8,\"startColumn\":6,\"endColumn\":7,\"isSharedCrossOrigin\":false,\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":7,\"functionName\":\"\",\"scriptName\":\"a.js\",\"isEval\":false,\"isConstructor\":false,\"isWasm\":false}]}"
+ );
+ assert!(r.is_some());
+ let e = r.unwrap();
+ assert_eq!(e.message, "Error: boo");
+ assert_eq!(e.source_line, Some("throw Error('boo');".to_string()));
+ assert_eq!(e.script_resource_name, Some("a.js".to_string()));
+ assert_eq!(e.line_number, Some(3));
+ assert_eq!(e.start_position, Some(8));
+ assert_eq!(e.end_position, Some(9));
+ assert_eq!(e.error_level, Some(8));
+ assert_eq!(e.start_column, Some(6));
+ assert_eq!(e.end_column, Some(7));
+ assert_eq!(e.frames.len(), 1);
+ }
+
+ #[test]
+ fn js_error_to_string() {
+ let e = CoreJSError(error1());
+ let expected = "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";
+ assert_eq!(expected, &e.to_string());
+ }
+}
diff --git a/core/lib.rs b/core/lib.rs
new file mode 100644
index 000000000..42a692f1a
--- /dev/null
+++ b/core/lib.rs
@@ -0,0 +1,38 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#[macro_use]
+extern crate log;
+extern crate futures;
+extern crate libc;
+
+mod any_error;
+mod flags;
+mod isolate;
+mod js_errors;
+mod libdeno;
+mod module_specifier;
+mod modules;
+mod ops;
+mod shared_queue;
+
+pub use crate::any_error::*;
+pub use crate::flags::v8_set_flags;
+pub use crate::isolate::*;
+pub use crate::js_errors::*;
+pub use crate::libdeno::deno_mod;
+pub use crate::libdeno::OpId;
+pub use crate::libdeno::PinnedBuf;
+pub use crate::module_specifier::*;
+pub use crate::modules::*;
+pub use crate::ops::*;
+
+pub fn v8_version() -> &'static str {
+ use std::ffi::CStr;
+ let version = unsafe { libdeno::deno_v8_version() };
+ let c_str = unsafe { CStr::from_ptr(version) };
+ c_str.to_str().unwrap()
+}
+
+#[test]
+fn test_v8_version() {
+ assert!(v8_version().len() > 3);
+}
diff --git a/core/libdeno.rs b/core/libdeno.rs
new file mode 100644
index 000000000..c46880f1f
--- /dev/null
+++ b/core/libdeno.rs
@@ -0,0 +1,329 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+use libc::c_char;
+use libc::c_int;
+use libc::c_void;
+use libc::size_t;
+use std::convert::From;
+use std::marker::PhantomData;
+use std::ops::{Deref, DerefMut};
+use std::option::Option;
+use std::ptr::null;
+use std::ptr::NonNull;
+use std::slice;
+
+pub type OpId = u32;
+
+// TODO(F001): change this definition to `extern { pub type isolate; }`
+// After RFC 1861 is stablized. See https://github.com/rust-lang/rust/issues/43467.
+#[repr(C)]
+pub struct isolate {
+ _unused: [u8; 0],
+}
+
+/// This type represents a borrowed slice.
+#[repr(C)]
+pub struct deno_buf {
+ data_ptr: *const u8,
+ data_len: usize,
+}
+
+/// `deno_buf` can not clone, and there is no interior mutability.
+/// This type satisfies Send bound.
+unsafe impl Send for deno_buf {}
+
+impl deno_buf {
+ #[inline]
+ pub fn empty() -> Self {
+ Self {
+ data_ptr: null(),
+ data_len: 0,
+ }
+ }
+
+ #[inline]
+ pub unsafe fn from_raw_parts(ptr: *const u8, len: usize) -> Self {
+ Self {
+ data_ptr: ptr,
+ data_len: len,
+ }
+ }
+}
+
+/// Converts Rust &Buf to libdeno `deno_buf`.
+impl<'a> From<&'a [u8]> for deno_buf {
+ #[inline]
+ fn from(x: &'a [u8]) -> Self {
+ Self {
+ data_ptr: x.as_ref().as_ptr(),
+ data_len: x.len(),
+ }
+ }
+}
+
+impl Deref for deno_buf {
+ type Target = [u8];
+ #[inline]
+ fn deref(&self) -> &[u8] {
+ unsafe { std::slice::from_raw_parts(self.data_ptr, self.data_len) }
+ }
+}
+
+impl AsRef<[u8]> for deno_buf {
+ #[inline]
+ fn as_ref(&self) -> &[u8] {
+ &*self
+ }
+}
+
+/// A PinnedBuf encapsulates a slice that's been borrowed from a JavaScript
+/// ArrayBuffer object. JavaScript objects can normally be garbage collected,
+/// but the existence of a PinnedBuf inhibits this until it is dropped. It
+/// behaves much like an Arc<[u8]>, although a PinnedBuf currently can't be
+/// cloned.
+#[repr(C)]
+pub struct PinnedBuf {
+ data_ptr: NonNull<u8>,
+ data_len: usize,
+ pin: NonNull<c_void>,
+}
+
+#[repr(C)]
+pub struct PinnedBufRaw {
+ data_ptr: *mut u8,
+ data_len: usize,
+ pin: *mut c_void,
+}
+
+unsafe impl Send for PinnedBuf {}
+unsafe impl Send for PinnedBufRaw {}
+
+impl PinnedBuf {
+ pub fn new(raw: PinnedBufRaw) -> Option<Self> {
+ NonNull::new(raw.data_ptr).map(|data_ptr| PinnedBuf {
+ data_ptr,
+ data_len: raw.data_len,
+ pin: NonNull::new(raw.pin).unwrap(),
+ })
+ }
+}
+
+impl Drop for PinnedBuf {
+ fn drop(&mut self) {
+ unsafe {
+ let raw = &mut *(self as *mut PinnedBuf as *mut PinnedBufRaw);
+ deno_pinned_buf_delete(raw);
+ }
+ }
+}
+
+impl Deref for PinnedBuf {
+ type Target = [u8];
+ fn deref(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.data_ptr.as_ptr(), self.data_len) }
+ }
+}
+
+impl DerefMut for PinnedBuf {
+ fn deref_mut(&mut self) -> &mut [u8] {
+ unsafe { slice::from_raw_parts_mut(self.data_ptr.as_ptr(), self.data_len) }
+ }
+}
+
+impl AsRef<[u8]> for PinnedBuf {
+ fn as_ref(&self) -> &[u8] {
+ &*self
+ }
+}
+
+impl AsMut<[u8]> for PinnedBuf {
+ fn as_mut(&mut self) -> &mut [u8] {
+ &mut *self
+ }
+}
+
+pub use PinnedBufRaw as deno_pinned_buf;
+
+#[repr(C)]
+pub struct deno_snapshot<'a> {
+ pub data_ptr: *const u8,
+ pub data_len: usize,
+ _marker: PhantomData<&'a [u8]>,
+}
+
+/// `deno_snapshot` can not clone, and there is no interior mutability.
+/// This type satisfies Send bound.
+unsafe impl Send for deno_snapshot<'_> {}
+
+// TODO(ry) Snapshot1 and Snapshot2 are not very good names and need to be
+// reconsidered. The entire snapshotting interface is still under construction.
+
+/// The type returned from deno_snapshot_new. Needs to be dropped.
+pub type Snapshot1<'a> = deno_snapshot<'a>;
+
+/// The type created from slice. Used for loading.
+pub type Snapshot2<'a> = deno_snapshot<'a>;
+
+/// Converts Rust &Buf to libdeno `deno_buf`.
+impl<'a> From<&'a [u8]> for Snapshot2<'a> {
+ #[inline]
+ fn from(x: &'a [u8]) -> Self {
+ Self {
+ data_ptr: x.as_ref().as_ptr(),
+ data_len: x.len(),
+ _marker: PhantomData,
+ }
+ }
+}
+
+impl Snapshot2<'_> {
+ #[inline]
+ pub fn empty() -> Self {
+ Self {
+ data_ptr: null(),
+ data_len: 0,
+ _marker: PhantomData,
+ }
+ }
+}
+
+#[allow(non_camel_case_types)]
+type deno_recv_cb = unsafe extern "C" fn(
+ user_data: *mut c_void,
+ op_id: OpId,
+ control_buf: deno_buf,
+ zero_copy_buf: deno_pinned_buf,
+);
+
+/// Called when dynamic import is called in JS: import('foo')
+/// Embedder must call deno_dyn_import_done() with the specified id and
+/// the module.
+#[allow(non_camel_case_types)]
+type deno_dyn_import_cb = unsafe extern "C" fn(
+ user_data: *mut c_void,
+ specifier: *const c_char,
+ referrer: *const c_char,
+ id: deno_dyn_import_id,
+);
+
+#[allow(non_camel_case_types)]
+pub type deno_mod = i32;
+
+#[allow(non_camel_case_types)]
+pub type deno_dyn_import_id = i32;
+
+#[allow(non_camel_case_types)]
+type deno_resolve_cb = unsafe extern "C" fn(
+ user_data: *mut c_void,
+ specifier: *const c_char,
+ referrer: deno_mod,
+) -> deno_mod;
+
+#[repr(C)]
+pub struct deno_config<'a> {
+ pub will_snapshot: c_int,
+ pub load_snapshot: Snapshot2<'a>,
+ pub shared: deno_buf,
+ pub recv_cb: deno_recv_cb,
+ pub dyn_import_cb: deno_dyn_import_cb,
+}
+
+#[cfg(not(windows))]
+#[link(name = "deno")]
+extern "C" {}
+
+#[cfg(any(target_os = "macos", target_os = "freebsd"))]
+#[link(name = "c++")]
+extern "C" {}
+
+#[cfg(windows)]
+#[link(name = "libdeno")]
+extern "C" {}
+
+#[cfg(windows)]
+#[link(name = "shlwapi")]
+extern "C" {}
+
+#[cfg(windows)]
+#[link(name = "winmm")]
+extern "C" {}
+
+#[cfg(windows)]
+#[link(name = "ws2_32")]
+extern "C" {}
+
+#[cfg(windows)]
+#[link(name = "dbghelp")]
+extern "C" {}
+
+extern "C" {
+ pub fn deno_init();
+ pub fn deno_v8_version() -> *const c_char;
+ pub fn deno_set_v8_flags(argc: *mut c_int, argv: *mut *mut c_char);
+ pub fn deno_new(config: deno_config) -> *const isolate;
+ pub fn deno_delete(i: *const isolate);
+ pub fn deno_last_exception(i: *const isolate) -> *const c_char;
+ pub fn deno_check_promise_errors(i: *const isolate);
+ pub fn deno_lock(i: *const isolate);
+ pub fn deno_unlock(i: *const isolate);
+ pub fn deno_respond(
+ i: *const isolate,
+ user_data: *const c_void,
+ op_id: OpId,
+ buf: deno_buf,
+ );
+ pub fn deno_pinned_buf_delete(buf: &mut deno_pinned_buf);
+ pub fn deno_execute(
+ i: *const isolate,
+ user_data: *const c_void,
+ js_filename: *const c_char,
+ js_source: *const c_char,
+ );
+ pub fn deno_terminate_execution(i: *const isolate);
+ #[allow(dead_code)]
+ pub fn deno_run_microtasks(i: *const isolate, user_data: *const c_void);
+
+ // Modules
+
+ pub fn deno_mod_new(
+ i: *const isolate,
+ main: bool,
+ name: *const c_char,
+ source: *const c_char,
+ ) -> deno_mod;
+
+ pub fn deno_mod_imports_len(i: *const isolate, id: deno_mod) -> size_t;
+
+ pub fn deno_mod_imports_get(
+ i: *const isolate,
+ id: deno_mod,
+ index: size_t,
+ ) -> *const c_char;
+
+ pub fn deno_mod_instantiate(
+ i: *const isolate,
+ user_data: *const c_void,
+ id: deno_mod,
+ resolve_cb: deno_resolve_cb,
+ );
+
+ pub fn deno_mod_evaluate(
+ i: *const isolate,
+ user_data: *const c_void,
+ id: deno_mod,
+ );
+
+ /// Call exactly once for every deno_dyn_import_cb.
+ pub fn deno_dyn_import_done(
+ i: *const isolate,
+ user_data: *const c_void,
+ id: deno_dyn_import_id,
+ mod_id: deno_mod,
+ error_str: *const c_char,
+ );
+
+ pub fn deno_snapshot_new(i: *const isolate) -> Snapshot1<'static>;
+
+ #[allow(dead_code)]
+ pub fn deno_snapshot_delete(s: &mut deno_snapshot);
+}
diff --git a/core/libdeno/.gn b/core/libdeno/.gn
new file mode 100644
index 000000000..7916c47a7
--- /dev/null
+++ b/core/libdeno/.gn
@@ -0,0 +1,60 @@
+# This file is used by the GN meta build system to find the root of the source
+# tree and to set startup options. For documentation on the values set in this
+# file, run "gn help dotfile" at the command line.
+
+# The location of the build configuration file.
+buildconfig = "//build/config/BUILDCONFIG.gn"
+
+# These are the targets to check headers for by default. The files in targets
+# matching these patterns (see "gn help label_pattern" for format) will have
+# their includes checked for proper dependencies when you run either
+# "gn check" or "gn gen --check".
+check_targets = []
+
+default_args = {
+ # Various global chrome args that are unrelated to deno.
+ proprietary_codecs = false
+ safe_browsing_mode = 0
+ toolkit_views = false
+ use_aura = false
+ use_dbus = false
+ use_gio = false
+ use_glib = false
+ use_ozone = false
+ use_udev = false
+
+ # To disable "use_atk" and other features that we don't need.
+ is_desktop_linux = false
+
+ # TODO(ry) We may want to turn on CFI at some point. Disabling for simplicity
+ # for now. See http://clang.llvm.org/docs/ControlFlowIntegrity.html
+ is_cfi = false
+
+ # TODO(ry) Remove this so debug builds can link faster. Currently removing
+ # this breaks cargo build in debug mode in OSX.
+ is_component_build = false
+
+ # Enable Jumbo build for a faster build.
+ # https://chromium.googlesource.com/chromium/src/+/master/docs/jumbo.md
+ use_jumbo_build = true
+
+ symbol_level = 1
+ treat_warnings_as_errors = true
+
+ # https://cs.chromium.org/chromium/src/docs/ccache_mac.md
+ clang_use_chrome_plugins = false
+
+ v8_enable_gdbjit = false
+ v8_enable_i18n_support = false
+ v8_enable_shared_ro_heap = false # See #2624
+ v8_imminent_deprecation_warnings = false
+ v8_monolithic = false
+ v8_untrusted_code_mitigations = false
+ v8_use_external_startup_data = false
+ v8_use_snapshot = true
+ v8_postmortem_support = true # for https://github.com/nodejs/llnode/
+
+ # We don't want to require downloading the binary executable
+ # tools/clang/dsymutil.
+ enable_dsyms = false
+}
diff --git a/core/libdeno/BUILD.gn b/core/libdeno/BUILD.gn
new file mode 100644
index 000000000..d2322c678
--- /dev/null
+++ b/core/libdeno/BUILD.gn
@@ -0,0 +1,98 @@
+# Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import("//v8/gni/v8.gni")
+
+group("default") {
+ testonly = true
+ deps = [
+ ":libdeno_static_lib",
+ ":libdeno_test",
+ ":v8",
+ ]
+}
+
+config("deno_config") {
+ include_dirs = [ "//v8" ] # This allows us to v8/src/base/ libraries.
+ configs = [ "//v8:external_config" ]
+ cflags = []
+
+ if (is_debug) {
+ defines = [ "DEBUG" ]
+ }
+
+ if (is_clang) {
+ cflags += [
+ "-fcolor-diagnostics",
+ "-fansi-escape-codes",
+ ]
+ }
+
+ if (is_debug && is_clang && !is_win) {
+ cflags += [ "-glldb" ]
+ }
+
+ if (is_win) {
+ # The `/Zl` ("omit default library name") flag makes the compiler produce
+ # object files that can link with both the static and dynamic CRT.
+ cflags += [ "/Zl" ]
+ }
+}
+
+v8_source_set("v8") {
+ deps = [
+ "//v8:v8",
+ "//v8:v8_libbase",
+ "//v8:v8_libplatform",
+ "//v8:v8_libsampler",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+# Only functionality needed for libdeno_test and snapshot_creator
+# In particular no assets, no rust, no msg handlers.
+# Because snapshots are slow, it's important that snapshot_creator's
+# dependencies are minimal.
+v8_source_set("libdeno") {
+ sources = [
+ "api.cc",
+ "binding.cc",
+ "buffer.h",
+ "deno.h",
+ "exceptions.cc",
+ "exceptions.h",
+ "internal.h",
+ "modules.cc",
+ ]
+ deps = [
+ ":v8",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+# The cargo-driven build links with libdeno to pull in all non-rust code.
+v8_static_library("libdeno_static_lib") {
+ output_name = "libdeno"
+ deps = [
+ ":libdeno",
+ "//build/config:shared_library_deps",
+ ]
+ configs = [ ":deno_config" ]
+}
+
+v8_executable("libdeno_test") {
+ testonly = true
+ sources = [
+ "libdeno_test.cc",
+ "modules_test.cc",
+ "test.cc",
+ ]
+ deps = [
+ ":libdeno",
+ "//testing/gtest:gtest",
+ ]
+ data = [
+ "libdeno_test.js",
+ ]
+ js_path = rebase_path(data[0])
+ defines = [ "JS_PATH=\"$js_path\"" ]
+ configs = [ ":deno_config" ]
+}
diff --git a/core/libdeno/api.cc b/core/libdeno/api.cc
new file mode 100644
index 000000000..18dc1d43e
--- /dev/null
+++ b/core/libdeno/api.cc
@@ -0,0 +1,246 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+#include <string>
+
+#include "v8/include/libplatform/libplatform.h"
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+#include "deno.h"
+#include "exceptions.h"
+#include "internal.h"
+
+extern "C" {
+
+Deno* deno_new_snapshotter(deno_config config) {
+ CHECK(config.will_snapshot);
+ // TODO(ry) Support loading snapshots before snapshotting.
+ CHECK_NULL(config.load_snapshot.data_ptr);
+ auto* creator = new v8::SnapshotCreator(deno::external_references);
+ auto* isolate = creator->GetIsolate();
+ auto* d = new deno::DenoIsolate(config);
+ d->snapshot_creator_ = creator;
+ d->AddIsolate(isolate);
+ {
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = v8::Context::New(isolate);
+ d->context_.Reset(isolate, context);
+
+ creator->SetDefaultContext(context,
+ v8::SerializeInternalFieldsCallback(
+ deno::SerializeInternalFields, nullptr));
+ deno::InitializeContext(isolate, context);
+ }
+ return reinterpret_cast<Deno*>(d);
+}
+
+Deno* deno_new(deno_config config) {
+ if (config.will_snapshot) {
+ return deno_new_snapshotter(config);
+ }
+ deno::DenoIsolate* d = new deno::DenoIsolate(config);
+ v8::Isolate::CreateParams params;
+ params.array_buffer_allocator = &deno::ArrayBufferAllocator::global();
+ params.external_references = deno::external_references;
+
+ if (config.load_snapshot.data_ptr) {
+ params.snapshot_blob = &d->snapshot_;
+ }
+
+ v8::Isolate* isolate = v8::Isolate::New(params);
+ d->AddIsolate(isolate);
+
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ {
+ v8::HandleScope handle_scope(isolate);
+ auto context =
+ v8::Context::New(isolate, nullptr, v8::MaybeLocal<v8::ObjectTemplate>(),
+ v8::MaybeLocal<v8::Value>(),
+ v8::DeserializeInternalFieldsCallback(
+ deno::DeserializeInternalFields, nullptr));
+ if (!config.load_snapshot.data_ptr) {
+ // If no snapshot is provided, we initialize the context with empty
+ // main source code and source maps.
+ deno::InitializeContext(isolate, context);
+ }
+ d->context_.Reset(isolate, context);
+ }
+
+ return reinterpret_cast<Deno*>(d);
+}
+
+deno::DenoIsolate* unwrap(Deno* d_) {
+ return reinterpret_cast<deno::DenoIsolate*>(d_);
+}
+
+void deno_lock(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NULL(d->locker_);
+ d->locker_ = new v8::Locker(d->isolate_);
+}
+
+void deno_unlock(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NOT_NULL(d->locker_);
+ delete d->locker_;
+ d->locker_ = nullptr;
+}
+
+deno_snapshot deno_snapshot_new(Deno* d_) {
+ auto* d = unwrap(d_);
+ CHECK_NOT_NULL(d->snapshot_creator_);
+ d->ClearModules();
+ d->context_.Reset();
+
+ auto blob = d->snapshot_creator_->CreateBlob(
+ v8::SnapshotCreator::FunctionCodeHandling::kKeep);
+ d->has_snapshotted_ = true;
+ return {reinterpret_cast<uint8_t*>(const_cast<char*>(blob.data)),
+ blob.raw_size};
+}
+
+void deno_snapshot_delete(deno_snapshot snapshot) {
+ delete[] snapshot.data_ptr;
+}
+
+static std::unique_ptr<v8::Platform> platform;
+
+void deno_init() {
+ if (platform.get() == nullptr) {
+ platform = v8::platform::NewDefaultPlatform();
+ v8::V8::InitializePlatform(platform.get());
+ v8::V8::Initialize();
+ // TODO(ry) This makes WASM compile synchronously. Eventually we should
+ // remove this to make it work asynchronously too. But that requires getting
+ // PumpMessageLoop and RunMicrotasks setup correctly.
+ // See https://github.com/denoland/deno/issues/2544
+ const char* argv[3] = {"", "--no-wasm-async-compilation",
+ "--harmony-top-level-await"};
+ int argc = 3;
+ v8::V8::SetFlagsFromCommandLine(&argc, const_cast<char**>(argv), false);
+ }
+}
+
+const char* deno_v8_version() { return v8::V8::GetVersion(); }
+
+void deno_set_v8_flags(int* argc, char** argv) {
+ v8::V8::SetFlagsFromCommandLine(argc, argv, true);
+}
+
+const char* deno_last_exception(Deno* d_) {
+ auto* d = unwrap(d_);
+ if (d->last_exception_.length() > 0) {
+ return d->last_exception_.c_str();
+ } else {
+ return nullptr;
+ }
+}
+
+void deno_execute(Deno* d_, void* user_data, const char* js_filename,
+ const char* js_source) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+ auto* isolate = d->isolate_;
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ CHECK(!context.IsEmpty());
+ deno::Execute(context, js_filename, js_source);
+}
+
+void deno_pinned_buf_delete(deno_pinned_buf* buf) {
+ // The PinnedBuf destructor implicitly releases the ArrayBuffer reference.
+ auto _ = deno::PinnedBuf(buf);
+}
+
+void deno_respond(Deno* d_, void* user_data, deno_op_id op_id, deno_buf buf) {
+ auto* d = unwrap(d_);
+ if (d->current_args_ != nullptr) {
+ // Synchronous response.
+ // Note op_id is not passed back in the case of synchronous response.
+ if (buf.data_ptr != nullptr) {
+ auto ab = deno::ImportBuf(d, buf);
+ d->current_args_->GetReturnValue().Set(ab);
+ }
+ d->current_args_ = nullptr;
+ return;
+ }
+
+ // Asynchronous response.
+ deno::UserDataScope user_data_scope(d, user_data);
+ v8::Isolate::Scope isolate_scope(d->isolate_);
+ v8::HandleScope handle_scope(d->isolate_);
+
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(d->isolate_);
+
+ auto recv_ = d->recv_.Get(d->isolate_);
+ if (recv_.IsEmpty()) {
+ d->last_exception_ = "Deno.core.recv has not been called.";
+ return;
+ }
+
+ v8::Local<v8::Value> args[2];
+ int argc = 0;
+
+ if (buf.data_ptr != nullptr) {
+ args[0] = v8::Integer::New(d->isolate_, op_id);
+ args[1] = deno::ImportBuf(d, buf);
+ argc = 2;
+ }
+
+ auto v = recv_->Call(context, context->Global(), argc, args);
+
+ if (try_catch.HasCaught()) {
+ CHECK(v.IsEmpty());
+ deno::HandleException(context, try_catch.Exception());
+ }
+}
+
+void deno_check_promise_errors(Deno* d_) {
+ auto* d = unwrap(d_);
+ if (d->pending_promise_map_.size() > 0) {
+ auto* isolate = d->isolate_;
+ v8::Locker locker(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto it = d->pending_promise_map_.begin();
+ while (it != d->pending_promise_map_.end()) {
+ auto error = it->second.Get(isolate);
+ deno::HandleException(context, error);
+ it = d->pending_promise_map_.erase(it);
+ }
+ }
+}
+
+void deno_delete(Deno* d_) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+ delete d;
+}
+
+void deno_terminate_execution(Deno* d_) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+ d->isolate_->TerminateExecution();
+}
+
+void deno_run_microtasks(Deno* d_, void* user_data) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+
+ deno::UserDataScope user_data_scope(d, user_data);
+ v8::Locker locker(d->isolate_);
+ v8::Isolate::Scope isolate_scope(d->isolate_);
+ d->isolate_->RunMicrotasks();
+}
+}
diff --git a/core/libdeno/binding.cc b/core/libdeno/binding.cc
new file mode 100644
index 000000000..911c61499
--- /dev/null
+++ b/core/libdeno/binding.cc
@@ -0,0 +1,597 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <iostream>
+#include <string>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <io.h>
+#include <windows.h>
+#endif // _WIN32
+
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+#include "deno.h"
+#include "exceptions.h"
+#include "internal.h"
+
+#define GLOBAL_IMPORT_BUF_SIZE 1024
+
+namespace deno {
+
+std::vector<InternalFieldData*> deserialized_data;
+
+void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
+ v8::StartupData payload, void* data) {
+ DCHECK_NULL(data);
+ if (payload.raw_size == 0) {
+ holder->SetAlignedPointerInInternalField(index, nullptr);
+ return;
+ }
+ InternalFieldData* embedder_field = new InternalFieldData{0};
+ memcpy(embedder_field, payload.data, payload.raw_size);
+ holder->SetAlignedPointerInInternalField(index, embedder_field);
+ deserialized_data.push_back(embedder_field);
+}
+
+v8::StartupData SerializeInternalFields(v8::Local<v8::Object> holder, int index,
+ void* data) {
+ DCHECK_NULL(data);
+ InternalFieldData* embedder_field = static_cast<InternalFieldData*>(
+ holder->GetAlignedPointerFromInternalField(index));
+ if (embedder_field == nullptr) return {nullptr, 0};
+ int size = sizeof(*embedder_field);
+ char* payload = new char[size];
+ // We simply use memcpy to serialize the content.
+ memcpy(payload, embedder_field, size);
+ return {payload, size};
+}
+
+// Extracts a C string from a v8::V8 Utf8Value.
+const char* ToCString(const v8::String::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+}
+
+void PromiseRejectCallback(v8::PromiseRejectMessage promise_reject_message) {
+ auto* isolate = v8::Isolate::GetCurrent();
+ DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0));
+ DCHECK_EQ(d->isolate_, isolate);
+ v8::HandleScope handle_scope(d->isolate_);
+ auto error = promise_reject_message.GetValue();
+ auto context = d->context_.Get(d->isolate_);
+ auto promise = promise_reject_message.GetPromise();
+
+ v8::Context::Scope context_scope(context);
+
+ int promise_id = promise->GetIdentityHash();
+ switch (promise_reject_message.GetEvent()) {
+ case v8::kPromiseRejectWithNoHandler:
+ // Insert the error into the pending_promise_map_ using the promise's id
+ // as the key.
+ d->pending_promise_map_.emplace(std::piecewise_construct,
+ std::make_tuple(promise_id),
+ std::make_tuple(d->isolate_, error));
+ break;
+
+ case v8::kPromiseHandlerAddedAfterReject:
+ d->pending_promise_map_.erase(promise_id);
+ break;
+
+ case v8::kPromiseRejectAfterResolved:
+ break;
+
+ case v8::kPromiseResolveAfterResolved:
+ // Should not warn. See #1272
+ break;
+
+ default:
+ CHECK(false && "unreachable");
+ }
+}
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ auto* isolate = args.GetIsolate();
+ int argsLen = args.Length();
+ if (argsLen < 1 || argsLen > 2) {
+ ThrowInvalidArgument(isolate);
+ }
+ v8::HandleScope handle_scope(isolate);
+ bool is_err = args.Length() >= 2 ? args[1]->BooleanValue(isolate) : false;
+ FILE* file = is_err ? stderr : stdout;
+
+#ifdef _WIN32
+ int fd = _fileno(file);
+ if (fd < 0) return;
+
+ HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (h == INVALID_HANDLE_VALUE) return;
+
+ DWORD mode;
+ if (GetConsoleMode(h, &mode)) {
+ // Print to Windows console. Since the Windows API generally doesn't support
+ // UTF-8 encoded text, we have to use `WriteConsoleW()` which uses UTF-16.
+ v8::String::Value str(isolate, args[0]);
+ auto str_len = static_cast<size_t>(str.length());
+ auto str_wchars = reinterpret_cast<WCHAR*>(*str);
+
+ // WriteConsoleW has some limit to how many characters can be written at
+ // once, which is unspecified but low enough to be encountered in practice.
+ // Therefore we break up the write into chunks of 8kb if necessary.
+ size_t chunk_start = 0;
+ while (chunk_start < str_len) {
+ size_t chunk_end = std::min(chunk_start + 8192, str_len);
+
+ // Do not break in the middle of a surrogate pair. Note that `chunk_end`
+ // points to the start of the next chunk, so we check whether it contains
+ // the second half of a surrogate pair (a.k.a. "low surrogate").
+ if (chunk_end < str_len && str_wchars[chunk_end] >= 0xdc00 &&
+ str_wchars[chunk_end] <= 0xdfff) {
+ --chunk_end;
+ }
+
+ // Write to the console.
+ DWORD chunk_len = static_cast<DWORD>(chunk_end - chunk_start);
+ DWORD _;
+ WriteConsoleW(h, &str_wchars[chunk_start], chunk_len, &_, nullptr);
+
+ chunk_start = chunk_end;
+ }
+ return;
+ }
+#endif // _WIN32
+
+ v8::String::Utf8Value str(isolate, args[0]);
+ fwrite(*str, sizeof(**str), str.length(), file);
+ fflush(file);
+}
+
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK_EQ(args.Length(), 1);
+ auto* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::HandleScope handle_scope(isolate);
+ auto json_string = EncodeExceptionAsJSON(context, args[0]);
+ args.GetReturnValue().Set(v8_str(json_string.c_str()));
+}
+
+v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf) {
+ if (buf.data_ptr == nullptr) {
+ return v8::Local<v8::Uint8Array>();
+ }
+
+ // To avoid excessively allocating new ArrayBuffers, we try to reuse a single
+ // global ArrayBuffer. The caveat is that users must extract data from it
+ // before the next tick. We only do this for ArrayBuffers less than 1024
+ // bytes.
+ v8::Local<v8::ArrayBuffer> ab;
+ void* data;
+ if (buf.data_len > GLOBAL_IMPORT_BUF_SIZE) {
+ // Simple case. We allocate a new ArrayBuffer for this.
+ ab = v8::ArrayBuffer::New(d->isolate_, buf.data_len);
+ data = ab->GetContents().Data();
+ } else {
+ // Fast case. We reuse the global ArrayBuffer.
+ if (d->global_import_buf_.IsEmpty()) {
+ // Lazily initialize it.
+ DCHECK_NULL(d->global_import_buf_ptr_);
+ ab = v8::ArrayBuffer::New(d->isolate_, GLOBAL_IMPORT_BUF_SIZE);
+ d->global_import_buf_.Reset(d->isolate_, ab);
+ d->global_import_buf_ptr_ = ab->GetContents().Data();
+ } else {
+ DCHECK(d->global_import_buf_ptr_);
+ ab = d->global_import_buf_.Get(d->isolate_);
+ }
+ data = d->global_import_buf_ptr_;
+ }
+ memcpy(data, buf.data_ptr, buf.data_len);
+ auto view = v8::Uint8Array::New(ab, 0, buf.data_len);
+ return view;
+}
+
+// Sets the recv_ callback.
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+
+ v8::HandleScope handle_scope(isolate);
+
+ if (!d->recv_.IsEmpty()) {
+ isolate->ThrowException(v8_str("Deno.core.recv already called."));
+ return;
+ }
+
+ v8::Local<v8::Value> v = args[0];
+ CHECK(v->IsFunction());
+ v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
+
+ d->recv_.Reset(isolate, func);
+}
+
+void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+
+ v8::HandleScope handle_scope(isolate);
+
+ deno_buf control = {nullptr, 0};
+
+ int32_t op_id = 0;
+ if (args[0]->IsInt32()) {
+ auto context = d->context_.Get(isolate);
+ op_id = args[0]->Int32Value(context).FromJust();
+ }
+
+ if (args[1]->IsArrayBufferView()) {
+ auto view = v8::Local<v8::ArrayBufferView>::Cast(args[1]);
+ auto data =
+ reinterpret_cast<uint8_t*>(view->Buffer()->GetContents().Data());
+ control = {data + view->ByteOffset(), view->ByteLength()};
+ }
+
+ PinnedBuf zero_copy =
+ args[2]->IsArrayBufferView()
+ ? PinnedBuf(v8::Local<v8::ArrayBufferView>::Cast(args[2]))
+ : PinnedBuf();
+
+ DCHECK_NULL(d->current_args_);
+ d->current_args_ = &args;
+
+ d->recv_cb_(d->user_data_, op_id, control, zero_copy.IntoRaw());
+
+ if (d->current_args_ == nullptr) {
+ // This indicates that deno_repond() was called already.
+ } else {
+ // Asynchronous.
+ d->current_args_ = nullptr;
+ }
+}
+
+v8::ScriptOrigin ModuleOrigin(v8::Isolate* isolate,
+ v8::Local<v8::Value> resource_name) {
+ return v8::ScriptOrigin(resource_name, v8::Local<v8::Integer>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Boolean>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Value>(),
+ v8::Local<v8::Boolean>(), v8::Local<v8::Boolean>(),
+ v8::True(isolate));
+}
+
+deno_mod DenoIsolate::RegisterModule(bool main, const char* name,
+ const char* source) {
+ v8::Isolate::Scope isolate_scope(isolate_);
+ v8::Locker locker(isolate_);
+ v8::HandleScope handle_scope(isolate_);
+ auto context = context_.Get(isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::String> name_str = v8_str(name);
+ v8::Local<v8::String> source_str = v8_str(source);
+
+ auto origin = ModuleOrigin(isolate_, name_str);
+ v8::ScriptCompiler::Source source_(source_str, origin);
+
+ v8::TryCatch try_catch(isolate_);
+
+ auto maybe_module = v8::ScriptCompiler::CompileModule(isolate_, &source_);
+
+ if (try_catch.HasCaught()) {
+ CHECK(maybe_module.IsEmpty());
+ HandleException(context, try_catch.Exception());
+ return 0;
+ }
+
+ auto module = maybe_module.ToLocalChecked();
+
+ int id = module->GetIdentityHash();
+
+ std::vector<std::string> import_specifiers;
+
+ for (int i = 0; i < module->GetModuleRequestsLength(); ++i) {
+ v8::Local<v8::String> specifier = module->GetModuleRequest(i);
+ v8::String::Utf8Value specifier_utf8(isolate_, specifier);
+ import_specifiers.push_back(*specifier_utf8);
+ }
+
+ mods_.emplace(
+ std::piecewise_construct, std::make_tuple(id),
+ std::make_tuple(isolate_, module, main, name, import_specifiers));
+ mods_by_name_[name] = id;
+
+ return id;
+}
+
+void Shared(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info) {
+ v8::Isolate* isolate = info.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ DCHECK_EQ(d->isolate_, isolate);
+ v8::Locker locker(d->isolate_);
+ v8::EscapableHandleScope handle_scope(isolate);
+ if (d->shared_.data_ptr == nullptr) {
+ return;
+ }
+ v8::Local<v8::SharedArrayBuffer> ab;
+ if (d->shared_ab_.IsEmpty()) {
+ // Lazily initialize the persistent external ArrayBuffer.
+ ab = v8::SharedArrayBuffer::New(isolate, d->shared_.data_ptr,
+ d->shared_.data_len,
+ v8::ArrayBufferCreationMode::kExternalized);
+ d->shared_ab_.Reset(isolate, ab);
+ }
+ auto shared_ab = d->shared_ab_.Get(isolate);
+ info.GetReturnValue().Set(shared_ab);
+}
+
+void DenoIsolate::ClearModules() {
+ for (auto it = mods_.begin(); it != mods_.end(); it++) {
+ it->second.handle.Reset();
+ }
+ mods_.clear();
+ mods_by_name_.clear();
+}
+
+bool Execute(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source) {
+ auto* isolate = context->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto source = v8_str(js_source);
+ auto name = v8_str(js_filename);
+
+ v8::TryCatch try_catch(isolate);
+
+ v8::ScriptOrigin origin(name);
+
+ auto script = v8::Script::Compile(context, source, &origin);
+
+ if (script.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return false;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ DCHECK(try_catch.HasCaught());
+ HandleException(context, try_catch.Exception());
+ return false;
+ }
+
+ return true;
+}
+
+static inline v8::Local<v8::Boolean> v8_bool(bool v) {
+ return v8::Boolean::New(v8::Isolate::GetCurrent(), v);
+}
+
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::EscapableHandleScope handleScope(isolate);
+ auto context = d->context_.Get(isolate);
+ v8::Context::Scope context_scope(context);
+
+ if (!(args[0]->IsString())) {
+ ThrowInvalidArgument(isolate);
+ return;
+ }
+
+ auto source = args[0].As<v8::String>();
+
+ auto output = v8::Array::New(isolate, 2);
+ /**
+ * output[0] = result
+ * output[1] = ErrorInfo | null
+ * ErrorInfo = {
+ * thrown: Error | any,
+ * isNativeError: boolean,
+ * isCompileError: boolean,
+ * }
+ */
+
+ v8::TryCatch try_catch(isolate);
+
+ auto name = v8_str("<unknown>");
+ v8::ScriptOrigin origin(name);
+ auto script = v8::Script::Compile(context, source, &origin);
+
+ if (script.IsEmpty()) {
+ CHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ CHECK(output->Set(context, 0, v8::Null(isolate)).FromJust());
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ CHECK(errinfo_obj->Set(context, v8_str("isCompileError"), v8_bool(true))
+ .FromJust());
+ CHECK(errinfo_obj
+ ->Set(context, v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()))
+ .FromJust());
+ CHECK(errinfo_obj->Set(context, v8_str("thrown"), exception).FromJust());
+
+ CHECK(output->Set(context, 1, errinfo_obj).FromJust());
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ auto result = script.ToLocalChecked()->Run(context);
+
+ if (result.IsEmpty()) {
+ CHECK(try_catch.HasCaught());
+ auto exception = try_catch.Exception();
+
+ CHECK(output->Set(context, 0, v8::Null(isolate)).FromJust());
+
+ auto errinfo_obj = v8::Object::New(isolate);
+ CHECK(errinfo_obj->Set(context, v8_str("isCompileError"), v8_bool(false))
+ .FromJust());
+ CHECK(errinfo_obj
+ ->Set(context, v8_str("isNativeError"),
+ v8_bool(exception->IsNativeError()))
+ .FromJust());
+ CHECK(errinfo_obj->Set(context, v8_str("thrown"), exception).FromJust());
+
+ CHECK(output->Set(context, 1, errinfo_obj).FromJust());
+
+ args.GetReturnValue().Set(output);
+ return;
+ }
+
+ CHECK(output->Set(context, 0, result.ToLocalChecked()).FromJust());
+ CHECK(output->Set(context, 1, v8::Null(isolate)).FromJust());
+ args.GetReturnValue().Set(output);
+}
+
+void QueueMicrotask(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ v8::Isolate* isolate = args.GetIsolate();
+
+ if (!(args[0]->IsFunction())) {
+ ThrowInvalidArgument(isolate);
+ return;
+ }
+ isolate->EnqueueMicrotask(args[0].As<v8::Function>());
+}
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto global = context->Global();
+
+ auto deno_val = v8::Object::New(isolate);
+ CHECK(global->Set(context, deno::v8_str("Deno"), deno_val).FromJust());
+
+ auto core_val = v8::Object::New(isolate);
+ CHECK(deno_val->Set(context, deno::v8_str("core"), core_val).FromJust());
+
+ auto print_tmpl = v8::FunctionTemplate::New(isolate, Print);
+ auto print_val = print_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("print"), print_val).FromJust());
+
+ auto recv_tmpl = v8::FunctionTemplate::New(isolate, Recv);
+ auto recv_val = recv_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("recv"), recv_val).FromJust());
+
+ auto send_tmpl = v8::FunctionTemplate::New(isolate, Send);
+ auto send_val = send_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("send"), send_val).FromJust());
+
+ auto eval_context_tmpl = v8::FunctionTemplate::New(isolate, EvalContext);
+ auto eval_context_val =
+ eval_context_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("evalContext"), eval_context_val)
+ .FromJust());
+
+ auto error_to_json_tmpl = v8::FunctionTemplate::New(isolate, ErrorToJSON);
+ auto error_to_json_val =
+ error_to_json_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(core_val->Set(context, deno::v8_str("errorToJSON"), error_to_json_val)
+ .FromJust());
+
+ CHECK(core_val->SetAccessor(context, deno::v8_str("shared"), Shared)
+ .FromJust());
+
+ // Direct bindings on `window`.
+ auto queue_microtask_tmpl =
+ v8::FunctionTemplate::New(isolate, QueueMicrotask);
+ auto queue_microtask_val =
+ queue_microtask_tmpl->GetFunction(context).ToLocalChecked();
+ CHECK(
+ global->Set(context, deno::v8_str("queueMicrotask"), queue_microtask_val)
+ .FromJust());
+}
+
+void MessageCallback(v8::Local<v8::Message> message,
+ v8::Local<v8::Value> data) {
+ auto* isolate = message->GetIsolate();
+ DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0));
+
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(isolate);
+ HandleExceptionMessage(context, message);
+}
+
+void HostInitializeImportMetaObjectCallback(v8::Local<v8::Context> context,
+ v8::Local<v8::Module> module,
+ v8::Local<v8::Object> meta) {
+ auto* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+
+ CHECK(!module.IsEmpty());
+
+ deno_mod id = module->GetIdentityHash();
+ CHECK_NE(id, 0);
+
+ auto* info = d->GetModuleInfo(id);
+
+ const char* url = info->name.c_str();
+ const bool main = info->main;
+
+ meta->CreateDataProperty(context, v8_str("url"), v8_str(url)).ToChecked();
+ meta->CreateDataProperty(context, v8_str("main"), v8_bool(main)).ToChecked();
+}
+
+v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(
+ v8::Local<v8::Context> context, v8::Local<v8::ScriptOrModule> referrer,
+ v8::Local<v8::String> specifier) {
+ auto* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Context::Scope context_scope(context);
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ v8::String::Utf8Value specifier_str(isolate, specifier);
+
+ auto referrer_name = referrer->GetResourceName();
+ v8::String::Utf8Value referrer_name_str(isolate, referrer_name);
+
+ // TODO(ry) I'm not sure what HostDefinedOptions is for or if we're ever going
+ // to use it. For now we check that it is not used. This check may need to be
+ // changed in the future.
+ auto host_defined_options = referrer->GetHostDefinedOptions();
+ CHECK_EQ(host_defined_options->Length(), 0);
+
+ v8::Local<v8::Promise::Resolver> resolver =
+ v8::Promise::Resolver::New(context).ToLocalChecked();
+
+ deno_dyn_import_id import_id = d->next_dyn_import_id_++;
+
+ d->dyn_import_map_.emplace(std::piecewise_construct,
+ std::make_tuple(import_id),
+ std::make_tuple(d->isolate_, resolver));
+
+ d->dyn_import_cb_(d->user_data_, *specifier_str, *referrer_name_str,
+ import_id);
+
+ auto promise = resolver->GetPromise();
+ return handle_scope.Escape(promise);
+}
+
+void DenoIsolate::AddIsolate(v8::Isolate* isolate) {
+ isolate_ = isolate;
+ isolate_->SetCaptureStackTraceForUncaughtExceptions(
+ true, 10, v8::StackTrace::kDetailed);
+ isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback);
+ isolate_->SetData(0, this);
+ isolate_->AddMessageListener(MessageCallback);
+ isolate->SetHostInitializeImportMetaObjectCallback(
+ HostInitializeImportMetaObjectCallback);
+ isolate->SetHostImportModuleDynamicallyCallback(
+ HostImportModuleDynamicallyCallback);
+}
+
+} // namespace deno
diff --git a/core/libdeno/buffer.h b/core/libdeno/buffer.h
new file mode 100644
index 000000000..9a6e3acf7
--- /dev/null
+++ b/core/libdeno/buffer.h
@@ -0,0 +1,140 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef BUFFER_H_
+#define BUFFER_H_
+
+// Cpplint bans the use of <mutex> because it duplicates functionality in
+// chromium //base. However Deno doensn't use that, so suppress that lint.
+#include <memory>
+#include <mutex> // NOLINT
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+namespace deno {
+
+class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
+ public:
+ static ArrayBufferAllocator& global() {
+ static ArrayBufferAllocator global_instance;
+ return global_instance;
+ }
+
+ void* Allocate(size_t length) override { return new uint8_t[length](); }
+
+ void* AllocateUninitialized(size_t length) override {
+ return new uint8_t[length];
+ }
+
+ void Free(void* data, size_t length) override { Unref(data); }
+
+ private:
+ friend class PinnedBuf;
+
+ void Ref(void* data) {
+ std::lock_guard<std::mutex> lock(ref_count_map_mutex_);
+ // Note:
+ // - `unordered_map::insert(make_pair(key, value))` returns the existing
+ // item if the key, already exists in the map, otherwise it creates an
+ // new entry with `value`.
+ // - Buffers not in the map have an implicit reference count of one.
+ auto entry = ref_count_map_.insert(std::make_pair(data, 1)).first;
+ ++entry->second;
+ }
+
+ void Unref(void* data) {
+ {
+ std::lock_guard<std::mutex> lock(ref_count_map_mutex_);
+ auto entry = ref_count_map_.find(data);
+ if (entry == ref_count_map_.end()) {
+ // Buffers not in the map have an implicit ref count of one. After
+ // dereferencing there are no references left, so we delete the buffer.
+ } else if (--entry->second == 0) {
+ // The reference count went to zero, so erase the map entry and free the
+ // buffer.
+ ref_count_map_.erase(entry);
+ } else {
+ // After decreasing the reference count the buffer still has references
+ // left, so we leave the pin in place.
+ return;
+ }
+ delete[] reinterpret_cast<uint8_t*>(data);
+ }
+ }
+
+ private:
+ ArrayBufferAllocator() {}
+
+ ~ArrayBufferAllocator() {
+ // TODO(pisciaureus): Enable this check. It currently fails sometimes
+ // because the compiler worker isolate never actually exits, so when the
+ // process exits this isolate still holds on to some buffers.
+ // CHECK(ref_count_map_.empty());
+ }
+
+ std::unordered_map<void*, size_t> ref_count_map_;
+ std::mutex ref_count_map_mutex_;
+};
+
+class PinnedBuf {
+ struct Unref {
+ // This callback gets called from the Pin destructor.
+ void operator()(void* ptr) { ArrayBufferAllocator::global().Unref(ptr); }
+ };
+ // The Pin is a unique (non-copyable) smart pointer which automatically
+ // unrefs the referenced ArrayBuffer in its destructor.
+ using Pin = std::unique_ptr<void, Unref>;
+
+ uint8_t* data_ptr_;
+ size_t data_len_;
+ Pin pin_;
+
+ public:
+ // PinnedBuf::Raw is a POD struct with the same memory layout as the PinBuf
+ // itself. It is used to move a PinnedBuf between C and Rust.
+ struct Raw {
+ uint8_t* data_ptr;
+ size_t data_len;
+ void* pin;
+ };
+
+ PinnedBuf() : data_ptr_(nullptr), data_len_(0), pin_() {}
+
+ explicit PinnedBuf(v8::Local<v8::ArrayBufferView> view) {
+ auto buf = view->Buffer()->GetContents().Data();
+ ArrayBufferAllocator::global().Ref(buf);
+
+ data_ptr_ = reinterpret_cast<uint8_t*>(buf) + view->ByteOffset();
+ data_len_ = view->ByteLength();
+ pin_ = Pin(buf);
+ }
+
+ // This constructor recreates a PinnedBuf that has previously been converted
+ // to a PinnedBuf::Raw using the IntoRaw() method. This is a move operation;
+ // the Raw struct is emptied in the process.
+ explicit PinnedBuf(Raw* raw)
+ : data_ptr_(raw->data_ptr), data_len_(raw->data_len), pin_(raw->pin) {
+ raw->data_ptr = nullptr;
+ raw->data_len = 0;
+ raw->pin = nullptr;
+ }
+
+ // The IntoRaw() method converts the PinnedBuf to a PinnedBuf::Raw so it's
+ // ownership can be moved to Rust. The source PinnedBuf is emptied in the
+ // process, but the pinned ArrayBuffer is not dereferenced. In order to not
+ // leak it, the raw struct must eventually be turned back into a PinnedBuf
+ // using the constructor above.
+ Raw IntoRaw() {
+ Raw raw{
+ .data_ptr = data_ptr_, .data_len = data_len_, .pin = pin_.release()};
+ data_ptr_ = nullptr;
+ data_len_ = 0;
+ return raw;
+ }
+};
+
+} // namespace deno
+
+#endif // BUFFER_H_
diff --git a/core/libdeno/build b/core/libdeno/build
new file mode 160000
+Subproject 6af664c48ed657b89e99a9a8692dc15d7f7a6d9
diff --git a/core/libdeno/build_overrides b/core/libdeno/build_overrides
new file mode 120000
index 000000000..f1aca1a07
--- /dev/null
+++ b/core/libdeno/build_overrides
@@ -0,0 +1 @@
+v8/build_overrides \ No newline at end of file
diff --git a/core/libdeno/buildtools b/core/libdeno/buildtools
new file mode 120000
index 000000000..c5b1c451c
--- /dev/null
+++ b/core/libdeno/buildtools
@@ -0,0 +1 @@
+v8/buildtools \ No newline at end of file
diff --git a/core/libdeno/deno.h b/core/libdeno/deno.h
new file mode 100644
index 000000000..0bdd31f50
--- /dev/null
+++ b/core/libdeno/deno.h
@@ -0,0 +1,157 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef DENO_H_
+#define DENO_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "buffer.h"
+
+// Neither Rust nor Go support calling directly into C++ functions, therefore
+// the public interface to libdeno is done in C.
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef deno::PinnedBuf::Raw deno_pinned_buf;
+
+// Data that gets transmitted.
+typedef struct {
+ uint8_t* data_ptr;
+ size_t data_len;
+} deno_buf;
+
+typedef struct {
+ uint8_t* data_ptr;
+ size_t data_len;
+} deno_snapshot;
+
+typedef struct deno_s Deno;
+
+typedef uint32_t deno_op_id;
+
+// A callback to receive a message from a Deno.core.send() javascript call.
+// control_buf is valid for only for the lifetime of this callback.
+// data_buf is valid until deno_respond() is called.
+//
+// op_id corresponds to the first argument of Deno.core.send().
+// op_id is an extra user-defined integer valued which is not interpreted by
+// libdeno.
+//
+// control_buf corresponds to the second argument of Deno.core.send().
+//
+// zero_copy_buf corresponds to the third argument of Deno.core.send().
+// The user must call deno_pinned_buf_delete on each zero_copy_buf received.
+typedef void (*deno_recv_cb)(void* user_data, deno_op_id op_id,
+ deno_buf control_buf,
+ deno_pinned_buf zero_copy_buf);
+
+typedef int deno_dyn_import_id;
+// Called when dynamic import is called in JS: import('foo')
+// Embedder must call deno_dyn_import_done() with the specified id and
+// the module.
+typedef void (*deno_dyn_import_cb)(void* user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id id);
+
+void deno_init();
+const char* deno_v8_version();
+void deno_set_v8_flags(int* argc, char** argv);
+
+typedef struct {
+ int will_snapshot; // Default 0. If calling deno_snapshot_new 1.
+ deno_snapshot load_snapshot; // A startup snapshot to use.
+ deno_buf shared; // Shared buffer to be mapped to libdeno.shared
+ deno_recv_cb recv_cb; // Maps to Deno.core.send() calls.
+ deno_dyn_import_cb dyn_import_cb;
+} deno_config;
+
+// Create a new deno isolate.
+// Warning: If config.will_snapshot is set, deno_snapshot_new() must be called
+// or an error will result.
+Deno* deno_new(deno_config config);
+void deno_delete(Deno* d);
+
+// Generate a snapshot. The resulting buf can be used in as the load_snapshot
+// member in deno_confg.
+// When calling this function, the caller must have created the isolate "d" with
+// "will_snapshot" set to 1.
+// The caller must free the returned data with deno_snapshot_delete().
+deno_snapshot deno_snapshot_new(Deno* d);
+
+// Only for use with data returned from deno_snapshot_new.
+void deno_snapshot_delete(deno_snapshot);
+
+void deno_lock(Deno* d);
+void deno_unlock(Deno* d);
+
+// Compile and execute a traditional JavaScript script that does not use
+// module import statements.
+// If it succeeded deno_last_exception() will return NULL.
+void deno_execute(Deno* d, void* user_data, const char* js_filename,
+ const char* js_source);
+
+// deno_respond sends one message back for every deno_recv_cb made.
+//
+// If this is called during deno_recv_cb, the issuing Deno.core.send() in
+// javascript will synchronously return the specified buf as an ArrayBuffer (or
+// null if buf is empty).
+//
+// If this is called after deno_recv_cb has returned, the deno_respond
+// will call into the JS callback specified by Deno.core.recv().
+//
+// (Ideally, but not currently: After calling deno_respond(), the caller no
+// longer owns `buf` and must not use it; deno_respond() is responsible for
+// releasing its memory.)
+//
+// op_id is an extra user-defined integer valued which is not currently
+// interpreted by libdeno. But it should probably correspond to the op_id in
+// deno_recv_cb.
+//
+// If a JS exception was encountered, deno_last_exception() will be non-NULL.
+void deno_respond(Deno* d, void* user_data, deno_op_id op_id, deno_buf buf);
+
+// consumes zero_copy
+void deno_pinned_buf_delete(deno_pinned_buf* buf);
+
+void deno_check_promise_errors(Deno* d);
+
+const char* deno_last_exception(Deno* d);
+
+void deno_terminate_execution(Deno* d);
+
+void deno_run_microtasks(Deno* d, void* user_data);
+// Module API
+
+typedef int deno_mod;
+
+// Returns zero on error - check deno_last_exception().
+deno_mod deno_mod_new(Deno* d, bool main, const char* name, const char* source);
+
+size_t deno_mod_imports_len(Deno* d, deno_mod id);
+
+// Returned pointer is valid for the lifetime of the Deno isolate "d".
+const char* deno_mod_imports_get(Deno* d, deno_mod id, size_t index);
+
+typedef deno_mod (*deno_resolve_cb)(void* user_data, const char* specifier,
+ deno_mod referrer);
+
+// If it succeeded deno_last_exception() will return NULL.
+void deno_mod_instantiate(Deno* d, void* user_data, deno_mod id,
+ deno_resolve_cb cb);
+
+// If it succeeded deno_last_exception() will return NULL.
+void deno_mod_evaluate(Deno* d, void* user_data, deno_mod id);
+
+// Call exactly once for every deno_dyn_import_cb.
+// Note this call will execute JS.
+// Either mod_id is zero and error_str is not null OR mod_id is valid and
+// error_str is null.
+// TODO(ry) The errors arising from dynamic import are not exactly the same as
+// those arising from ops in Deno.
+void deno_dyn_import_done(Deno* d, void* user_data, deno_dyn_import_id id,
+ deno_mod mod_id, const char* error_str);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+#endif // DENO_H_
diff --git a/core/libdeno/exceptions.cc b/core/libdeno/exceptions.cc
new file mode 100644
index 000000000..5f4d578b6
--- /dev/null
+++ b/core/libdeno/exceptions.cc
@@ -0,0 +1,235 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "exceptions.h"
+#include <string>
+
+namespace deno {
+
+v8::Local<v8::Object> EncodeMessageAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ auto* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto stack_trace = message->GetStackTrace();
+
+ // Encode the exception into a JS object, which we will then turn into JSON.
+ auto json_obj = v8::Object::New(isolate);
+ auto exception_str = message->Get();
+ CHECK(json_obj->Set(context, v8_str("message"), exception_str).FromJust());
+
+ auto maybe_source_line = message->GetSourceLine(context);
+ if (!maybe_source_line.IsEmpty()) {
+ CHECK(json_obj
+ ->Set(context, v8_str("sourceLine"),
+ maybe_source_line.ToLocalChecked())
+ .FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("scriptResourceName"),
+ message->GetScriptResourceName())
+ .FromJust());
+
+ auto maybe_line_number = message->GetLineNumber(context);
+ if (maybe_line_number.IsJust()) {
+ CHECK(json_obj
+ ->Set(context, v8_str("lineNumber"),
+ v8::Integer::New(isolate, maybe_line_number.FromJust()))
+ .FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("startPosition"),
+ v8::Integer::New(isolate, message->GetStartPosition()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("endPosition"),
+ v8::Integer::New(isolate, message->GetEndPosition()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("errorLevel"),
+ v8::Integer::New(isolate, message->ErrorLevel()))
+ .FromJust());
+
+ auto maybe_start_column = message->GetStartColumn(context);
+ if (maybe_start_column.IsJust()) {
+ auto start_column =
+ v8::Integer::New(isolate, maybe_start_column.FromJust());
+ CHECK(
+ json_obj->Set(context, v8_str("startColumn"), start_column).FromJust());
+ }
+
+ auto maybe_end_column = message->GetEndColumn(context);
+ if (maybe_end_column.IsJust()) {
+ auto end_column = v8::Integer::New(isolate, maybe_end_column.FromJust());
+ CHECK(json_obj->Set(context, v8_str("endColumn"), end_column).FromJust());
+ }
+
+ CHECK(json_obj
+ ->Set(context, v8_str("isSharedCrossOrigin"),
+ v8::Boolean::New(isolate, message->IsSharedCrossOrigin()))
+ .FromJust());
+
+ CHECK(json_obj
+ ->Set(context, v8_str("isOpaque"),
+ v8::Boolean::New(isolate, message->IsOpaque()))
+ .FromJust());
+
+ v8::Local<v8::Array> frames;
+ if (!stack_trace.IsEmpty()) {
+ uint32_t count = static_cast<uint32_t>(stack_trace->GetFrameCount());
+ frames = v8::Array::New(isolate, count);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ auto frame = stack_trace->GetFrame(isolate, i);
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, i, frame_obj).FromJust());
+ auto line = v8::Integer::New(isolate, frame->GetLineNumber());
+ auto column = v8::Integer::New(isolate, frame->GetColumn());
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+
+ auto function_name = frame->GetFunctionName();
+ if (!function_name.IsEmpty()) {
+ CHECK(frame_obj->Set(context, v8_str("functionName"), function_name)
+ .FromJust());
+ }
+ // scriptName can be empty in special conditions e.g. eval
+ auto scriptName = frame->GetScriptNameOrSourceURL();
+ if (scriptName.IsEmpty()) {
+ scriptName = v8_str("<unknown>");
+ }
+ CHECK(
+ frame_obj->Set(context, v8_str("scriptName"), scriptName).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isEval"),
+ v8::Boolean::New(isolate, frame->IsEval()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isConstructor"),
+ v8::Boolean::New(isolate, frame->IsConstructor()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isWasm"),
+ v8::Boolean::New(isolate, frame->IsWasm()))
+ .FromJust());
+ }
+ } else {
+ // No stack trace. We only have one stack frame of info..
+ frames = v8::Array::New(isolate, 1);
+
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, 0, frame_obj).FromJust());
+
+ auto line =
+ v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
+ auto column =
+ v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
+
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("scriptName"),
+ message->GetScriptResourceName())
+ .FromJust());
+ }
+
+ CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust());
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+
+std::string EncodeMessageAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+ auto json_obj = EncodeMessageAsObject(context, message);
+ auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked();
+ v8::String::Utf8Value json_string_(isolate, json_string);
+ return std::string(ToCString(json_string_));
+}
+
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ auto* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ auto json_obj = EncodeMessageAsObject(context, message);
+ json_obj = handle_scope.Escape(json_obj);
+ return json_obj;
+}
+
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ auto* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ auto message = v8::Exception::CreateMessage(isolate, exception);
+ return EncodeMessageAsJSON(context, message);
+}
+
+void HandleException(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
+ v8::Isolate* isolate = context->GetIsolate();
+
+ // TerminateExecution was called
+ if (isolate->IsExecutionTerminating()) {
+ // cancel exception termination so that the exception can be created
+ isolate->CancelTerminateExecution();
+
+ // maybe make a new exception object
+ if (exception->IsNullOrUndefined()) {
+ exception = v8::Exception::Error(v8_str("execution terminated"));
+ }
+
+ // handle the exception as if it is a regular exception
+ HandleException(context, exception);
+
+ // re-enable exception termination
+ isolate->TerminateExecution();
+ return;
+ }
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ std::string json_str = EncodeExceptionAsJSON(context, exception);
+ CHECK_NOT_NULL(d);
+ d->last_exception_ = json_str;
+ d->last_exception_handle_.Reset(isolate, exception);
+}
+
+void HandleExceptionMessage(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message) {
+ v8::Isolate* isolate = context->GetIsolate();
+
+ // TerminateExecution was called
+ if (isolate->IsExecutionTerminating()) {
+ HandleException(context, v8::Undefined(isolate));
+ return;
+ }
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ std::string json_str = EncodeMessageAsJSON(context, message);
+ CHECK_NOT_NULL(d);
+ d->last_exception_ = json_str;
+}
+
+void ClearException(v8::Local<v8::Context> context) {
+ v8::Isolate* isolate = context->GetIsolate();
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+ CHECK_NOT_NULL(d);
+
+ d->last_exception_.clear();
+ d->last_exception_handle_.Reset();
+}
+
+void ThrowInvalidArgument(v8::Isolate* isolate) {
+ isolate->ThrowException(v8::Exception::TypeError(v8_str("Invalid Argument")));
+}
+
+} // namespace deno
diff --git a/core/libdeno/exceptions.h b/core/libdeno/exceptions.h
new file mode 100644
index 000000000..d8852f544
--- /dev/null
+++ b/core/libdeno/exceptions.h
@@ -0,0 +1,27 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef EXCEPTIONS_H_
+#define EXCEPTIONS_H_
+
+#include <string>
+#include "v8/include/v8.h"
+
+namespace deno {
+
+v8::Local<v8::Object> EncodeExceptionAsObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+void HandleException(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception);
+
+void HandleExceptionMessage(v8::Local<v8::Context> context,
+ v8::Local<v8::Message> message);
+
+void ClearException(v8::Local<v8::Context> context);
+
+void ThrowInvalidArgument(v8::Isolate* isolate);
+} // namespace deno
+
+#endif // EXCEPTIONS_H_
diff --git a/core/libdeno/internal.h b/core/libdeno/internal.h
new file mode 100644
index 000000000..89c746d04
--- /dev/null
+++ b/core/libdeno/internal.h
@@ -0,0 +1,200 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef INTERNAL_H_
+#define INTERNAL_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "buffer.h"
+#include "deno.h"
+#include "v8/include/v8.h"
+#include "v8/src/base/logging.h"
+
+namespace deno {
+
+struct ModuleInfo {
+ bool main;
+ std::string name;
+ v8::Persistent<v8::Module> handle;
+ std::vector<std::string> import_specifiers;
+
+ ModuleInfo(v8::Isolate* isolate, v8::Local<v8::Module> module, bool main_,
+ const char* name_, std::vector<std::string> import_specifiers_)
+ : main(main_), name(name_), import_specifiers(import_specifiers_) {
+ handle.Reset(isolate, module);
+ }
+};
+
+// deno_s = Wrapped Isolate.
+class DenoIsolate {
+ public:
+ explicit DenoIsolate(deno_config config)
+ : isolate_(nullptr),
+ locker_(nullptr),
+ shared_(config.shared),
+ current_args_(nullptr),
+ snapshot_creator_(nullptr),
+ global_import_buf_ptr_(nullptr),
+ recv_cb_(config.recv_cb),
+ user_data_(nullptr),
+ resolve_cb_(nullptr),
+ next_dyn_import_id_(0),
+ dyn_import_cb_(config.dyn_import_cb),
+ has_snapshotted_(false) {
+ if (config.load_snapshot.data_ptr) {
+ snapshot_.data =
+ reinterpret_cast<const char*>(config.load_snapshot.data_ptr);
+ snapshot_.raw_size = static_cast<int>(config.load_snapshot.data_len);
+ }
+ }
+
+ ~DenoIsolate() {
+ last_exception_handle_.Reset();
+ shared_ab_.Reset();
+ if (locker_) {
+ delete locker_;
+ }
+ if (snapshot_creator_) {
+ // TODO(ry) V8 has a strange assert which prevents a SnapshotCreator from
+ // being deallocated if it hasn't created a snapshot yet.
+ // https://github.com/v8/v8/blob/73212783fbd534fac76cc4b66aac899c13f71fc8/src/api.cc#L603
+ // If that assert is removed, this if guard could be removed.
+ // WARNING: There may be false positive LSAN errors here.
+ if (has_snapshotted_) {
+ delete snapshot_creator_;
+ }
+ } else {
+ isolate_->Dispose();
+ }
+ }
+
+ static inline DenoIsolate* FromIsolate(v8::Isolate* isolate) {
+ return static_cast<DenoIsolate*>(isolate->GetData(0));
+ }
+
+ void AddIsolate(v8::Isolate* isolate);
+
+ deno_mod RegisterModule(bool main, const char* name, const char* source);
+ void ClearModules();
+
+ ModuleInfo* GetModuleInfo(deno_mod id) {
+ if (id == 0) {
+ return nullptr;
+ }
+ auto it = mods_.find(id);
+ if (it != mods_.end()) {
+ return &it->second;
+ } else {
+ return nullptr;
+ }
+ }
+
+ v8::Isolate* isolate_;
+ v8::Locker* locker_;
+ deno_buf shared_;
+ const v8::FunctionCallbackInfo<v8::Value>* current_args_;
+ v8::SnapshotCreator* snapshot_creator_;
+ void* global_import_buf_ptr_;
+ deno_recv_cb recv_cb_;
+ void* user_data_;
+
+ std::map<deno_mod, ModuleInfo> mods_;
+ std::map<std::string, deno_mod> mods_by_name_;
+ deno_resolve_cb resolve_cb_;
+
+ deno_dyn_import_id next_dyn_import_id_;
+ deno_dyn_import_cb dyn_import_cb_;
+ std::map<deno_dyn_import_id, v8::Persistent<v8::Promise::Resolver>>
+ dyn_import_map_;
+
+ v8::Persistent<v8::Context> context_;
+ std::map<int, v8::Persistent<v8::Value>> pending_promise_map_;
+ std::string last_exception_;
+ v8::Persistent<v8::Value> last_exception_handle_;
+ v8::Persistent<v8::Function> recv_;
+ v8::StartupData snapshot_;
+ v8::Persistent<v8::ArrayBuffer> global_import_buf_;
+ v8::Persistent<v8::SharedArrayBuffer> shared_ab_;
+ bool has_snapshotted_;
+};
+
+class UserDataScope {
+ DenoIsolate* deno_;
+ void* prev_data_;
+ void* data_; // Not necessary; only for sanity checking.
+
+ public:
+ UserDataScope(DenoIsolate* deno, void* data) : deno_(deno), data_(data) {
+ CHECK(deno->user_data_ == nullptr || deno->user_data_ == data_);
+ prev_data_ = deno->user_data_;
+ deno->user_data_ = data;
+ }
+
+ ~UserDataScope() {
+ CHECK(deno_->user_data_ == data_);
+ deno_->user_data_ = prev_data_;
+ }
+};
+
+struct InternalFieldData {
+ uint32_t data;
+};
+
+static inline v8::Local<v8::String> v8_str(const char* x) {
+ return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), x,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+}
+
+void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Send(const v8::FunctionCallbackInfo<v8::Value>& args);
+void EvalContext(const v8::FunctionCallbackInfo<v8::Value>& args);
+void ErrorToJSON(const v8::FunctionCallbackInfo<v8::Value>& args);
+void Shared(v8::Local<v8::Name> property,
+ const v8::PropertyCallbackInfo<v8::Value>& info);
+void MessageCallback(v8::Local<v8::Message> message, v8::Local<v8::Value> data);
+void QueueMicrotask(const v8::FunctionCallbackInfo<v8::Value>& args);
+static intptr_t external_references[] = {
+ reinterpret_cast<intptr_t>(Print),
+ reinterpret_cast<intptr_t>(Recv),
+ reinterpret_cast<intptr_t>(Send),
+ reinterpret_cast<intptr_t>(EvalContext),
+ reinterpret_cast<intptr_t>(ErrorToJSON),
+ reinterpret_cast<intptr_t>(Shared),
+ reinterpret_cast<intptr_t>(MessageCallback),
+ reinterpret_cast<intptr_t>(QueueMicrotask),
+ 0};
+
+static const deno_buf empty_buf = {nullptr, 0};
+static const deno_snapshot empty_snapshot = {nullptr, 0};
+
+Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb);
+
+void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context);
+
+void DeserializeInternalFields(v8::Local<v8::Object> holder, int index,
+ v8::StartupData payload, void* data);
+
+v8::StartupData SerializeInternalFields(v8::Local<v8::Object> holder, int index,
+ void* data);
+
+v8::Local<v8::Uint8Array> ImportBuf(DenoIsolate* d, deno_buf buf);
+
+bool Execute(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source);
+bool ExecuteMod(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source, bool resolve_only);
+
+} // namespace deno
+
+extern "C" {
+// This is just to workaround the linker.
+struct deno_s {
+ deno::DenoIsolate isolate;
+};
+}
+
+#endif // INTERNAL_H_
diff --git a/core/libdeno/libdeno_test.cc b/core/libdeno/libdeno_test.cc
new file mode 100644
index 000000000..a72793944
--- /dev/null
+++ b/core/libdeno/libdeno_test.cc
@@ -0,0 +1,322 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+
+TEST(LibDenoTest, InitializesCorrectly) {
+ EXPECT_NE(snapshot.data_ptr, nullptr);
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "1 + 2");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Snapshotter) {
+ Deno* d1 = deno_new(deno_config{1, empty_snapshot, empty, nullptr, nullptr});
+ deno_execute(d1, nullptr, "a.js", "a = 1 + 2");
+ EXPECT_EQ(nullptr, deno_last_exception(d1));
+ deno_snapshot test_snapshot = deno_snapshot_new(d1);
+ deno_delete(d1);
+
+ Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr, nullptr});
+ deno_execute(d2, nullptr, "b.js", "if (a != 3) throw Error('x');");
+ EXPECT_EQ(nullptr, deno_last_exception(d2));
+ deno_delete(d2);
+
+ deno_snapshot_delete(test_snapshot);
+}
+
+TEST(LibDenoTest, CanCallFunction) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_lock(d);
+ deno_execute(d, nullptr, "a.js",
+ "if (CanCallFunction() != 'foo') throw Error();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_unlock(d);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ErrorsCorrectly) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "throw Error()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+void assert_null(deno_pinned_buf b) {
+ EXPECT_EQ(b.data_ptr, nullptr);
+ EXPECT_EQ(b.data_len, 0u);
+ EXPECT_EQ(b.pin, nullptr);
+}
+
+TEST(LibDenoTest, RecvReturnEmpty) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, nullptr, "a.js", "RecvReturnEmpty()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, BasicRecv) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, auto buf,
+ auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ // auto d = reinterpret_cast<Deno*>(user_data);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 1);
+ EXPECT_EQ(buf.data_ptr[1], 2);
+ EXPECT_EQ(buf.data_ptr[2], 3);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "BasicRecv()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ {
+ deno_lock(d);
+ uint8_t response[] = {'b', 'a', 'r'};
+ deno_respond(d, nullptr, 43, {response, sizeof response});
+ deno_unlock(d);
+ }
+ EXPECT_EQ(count, 2);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, RecvReturnBar) {
+ static int count = 0;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, auto buf,
+ auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ auto d = reinterpret_cast<Deno*>(user_data);
+ assert_null(zero_copy_buf);
+ count++;
+ EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 'a');
+ EXPECT_EQ(buf.data_ptr[1], 'b');
+ EXPECT_EQ(buf.data_ptr[2], 'c');
+ uint8_t response[] = {'b', 'a', 'r'};
+ deno_respond(d, user_data, op_id, {response, sizeof response});
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "RecvReturnBar()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, DoubleRecvFails) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "DoubleRecvFails()");
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, TypedArraySnapshots) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "TypedArraySnapshots()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SnapshotBug) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "SnapshotBug()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, GlobalErrorHandling) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()");
+ std::string expected =
+ "{\"message\":\"Uncaught ReferenceError: notdefined is not defined\","
+ "\"sourceLine\":\" "
+ "notdefined()\",\"scriptResourceName\":\"helloworld.js\","
+ "\"lineNumber\":3,\"startPosition\":3,\"endPosition\":4,\"errorLevel\":8,"
+ "\"startColumn\":1,\"endColumn\":2,\"isSharedCrossOrigin\":false,"
+ "\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":2,"
+ "\"functionName\":\"eval\",\"scriptName\":\"helloworld.js\",\"isEval\":"
+ "true,"
+ "\"isConstructor\":false,\"isWasm\":false},";
+ std::string actual(deno_last_exception(d), 0, expected.length());
+ EXPECT_STREQ(expected.c_str(), actual.c_str());
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, ZeroCopyBuf) {
+ static int count = 0;
+ static deno_pinned_buf zero_copy_buf2;
+ auto recv_cb = [](auto user_data, deno_op_id op_id, deno_buf buf,
+ deno_pinned_buf zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ count++;
+ EXPECT_NE(zero_copy_buf.pin, nullptr);
+ zero_copy_buf.data_ptr[0] = 4;
+ zero_copy_buf.data_ptr[1] = 2;
+ zero_copy_buf2 = zero_copy_buf;
+ EXPECT_EQ(2u, buf.data_len);
+ EXPECT_EQ(2u, zero_copy_buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 1);
+ EXPECT_EQ(buf.data_ptr[1], 2);
+ // Note zero_copy_buf won't actually be freed here because in
+ // libdeno_test.js zeroCopyBuf is a rooted global. We just want to exercise
+ // the API here.
+ deno_pinned_buf_delete(&zero_copy_buf);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ deno_execute(d, d, "a.js", "ZeroCopyBuf()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ // zero_copy_buf was subsequently changed in JS, let's check that our copy
+ // reflects that.
+ EXPECT_EQ(zero_copy_buf2.data_ptr[0], 9);
+ EXPECT_EQ(zero_copy_buf2.data_ptr[1], 8);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, CheckPromiseErrors) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ count++;
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(count, 1);
+ // We caught the exception. So still no errors after calling
+ // deno_check_promise_errors().
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LastException) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "\n\nthrow Error('boo');\n\n");
+ EXPECT_STREQ(deno_last_exception(d),
+ "{\"message\":\"Uncaught Error: boo\",\"sourceLine\":\"throw "
+ "Error('boo');\",\"scriptResourceName\":\"a.js\",\"lineNumber\":"
+ "3,\"startPosition\":8,\"endPosition\":9,\"errorLevel\":8,"
+ "\"startColumn\":6,\"endColumn\":7,\"isSharedCrossOrigin\":"
+ "false,\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":7,"
+ "\"scriptName\":\"a.js\",\"isEval\":false,"
+ "\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, EncodeErrorBug) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "eval('a')");
+ EXPECT_STREQ(
+ deno_last_exception(d),
+ "{\"message\":\"Uncaught ReferenceError: a is not "
+ "defined\",\"sourceLine\":\"a\",\"lineNumber\":1,\"startPosition\":0,"
+ "\"endPosition\":1,\"errorLevel\":8,\"startColumn\":0,\"endColumn\":1,"
+ "\"isSharedCrossOrigin\":false,\"isOpaque\":false,\"frames\":[{\"line\":"
+ "1,\"column\":1,\"functionName\":\"eval\",\"scriptName\":\"<unknown>\","
+ "\"isEval\":true,\"isConstructor\":false,\"isWasm\":false},{\"line\":1,"
+ "\"column\":1,\"scriptName\":\"a.js\",\"isEval\":"
+ "false,\"isConstructor\":false,\"isWasm\":false}]}");
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Shared) {
+ uint8_t s[] = {0, 1, 2};
+ deno_buf shared = {s, sizeof s};
+ Deno* d = deno_new(deno_config{0, snapshot, shared, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "Shared()");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(s[0], 42);
+ EXPECT_EQ(s[1], 43);
+ EXPECT_EQ(s[2], 44);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, Utf8Bug) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, nullptr, nullptr});
+ // The following is a valid UTF-8 javascript which just defines a string
+ // literal. We had a bug where libdeno would choke on this.
+ deno_execute(d, nullptr, "a.js", "x = \"\xEF\xBF\xBD\"");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContext) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContext();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContextError) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextError();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoEvalContextInvalidArgument) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoEvalContextInvalidArgument();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, LibDenoPrintInvalidArgument) {
+ Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js", "LibDenoPrintInvalidArgument();");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, SharedAtomics) {
+ int32_t s[] = {0, 1, 2};
+ deno_buf shared = {reinterpret_cast<uint8_t*>(s), sizeof s};
+ Deno* d = deno_new(deno_config{0, empty_snapshot, shared, nullptr, nullptr});
+ deno_execute(d, nullptr, "a.js",
+ "Atomics.add(new Int32Array(Deno.core.shared), 0, 1)");
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(s[0], 1);
+ EXPECT_EQ(s[1], 1);
+ EXPECT_EQ(s[2], 2);
+ deno_delete(d);
+}
+
+TEST(LibDenoTest, WasmInstantiate) {
+ static int count = 0;
+ auto recv_cb = [](auto _, deno_op_id op_id, auto buf, auto zero_copy_buf) {
+ EXPECT_EQ(op_id, 42u);
+ EXPECT_EQ(buf.data_len, 1u);
+ EXPECT_EQ(buf.data_ptr[0], 42);
+ count++;
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_execute(d, nullptr, "a.js", "WasmInstantiate()");
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(count, 3);
+
+ deno_delete(d);
+}
diff --git a/core/libdeno/libdeno_test.js b/core/libdeno/libdeno_test.js
new file mode 100644
index 000000000..779762cfd
--- /dev/null
+++ b/core/libdeno/libdeno_test.js
@@ -0,0 +1,271 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// A simple runtime that doesn't involve typescript or protobufs to test
+// libdeno. Invoked by libdeno_test.cc
+
+// eslint-disable-next-line @typescript-eslint/no-this-alias
+const global = this;
+
+function assert(cond) {
+ if (!cond) throw Error("libdeno_test.js assert failed");
+}
+
+global.CanCallFunction = () => {
+ Deno.core.print("Hello world from foo");
+ return "foo";
+};
+
+// This object is created to test snapshotting.
+// See DeserializeInternalFieldsCallback and SerializeInternalFieldsCallback.
+const snapshotted = new Uint8Array([1, 3, 3, 7]);
+
+global.TypedArraySnapshots = () => {
+ assert(snapshotted[0] === 1);
+ assert(snapshotted[1] === 3);
+ assert(snapshotted[2] === 3);
+ assert(snapshotted[3] === 7);
+};
+
+global.RecvReturnEmpty = () => {
+ const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const m2 = m1.slice();
+ const r1 = Deno.core.send(42, m1);
+ assert(r1 == null);
+ const r2 = Deno.core.send(42, m2);
+ assert(r2 == null);
+};
+
+global.BasicRecv = () => {
+ const m = new Uint8Array([1, 2, 3]);
+ Deno.core.recv((opId, buf) => {
+ assert(opId === 43);
+ assert(buf instanceof Uint8Array);
+ assert(buf.byteLength === 3);
+ const s = String.fromCharCode(...buf);
+ assert(s === "bar");
+ const r = Deno.core.send(42, m);
+ assert(!r); // async
+ });
+ const r = Deno.core.send(42, m);
+ assert(!r); // async
+};
+
+global.RecvReturnBar = () => {
+ const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
+ const r = Deno.core.send(42, m);
+ assert(r instanceof Uint8Array);
+ assert(r.byteLength === 3);
+ const rstr = String.fromCharCode(...r);
+ assert(rstr === "bar");
+};
+
+global.DoubleRecvFails = () => {
+ // Deno.core.recv is an internal function and should only be called once from the
+ // runtime.
+ Deno.core.recv((_channel, _msg) => assert(false));
+ Deno.core.recv((_channel, _msg) => assert(false));
+};
+
+global.SendRecvSlice = () => {
+ const abLen = 1024;
+ let buf = new Uint8Array(abLen);
+ for (let i = 0; i < 5; i++) {
+ // Set first and last byte, for verification by the native side.
+ buf[0] = 100 + i;
+ buf[buf.length - 1] = 100 - i;
+ // On the native side, the slice is shortened by 19 bytes.
+ buf = Deno.core.send(42, buf);
+ assert(buf.byteOffset === i * 11);
+ assert(buf.byteLength === abLen - i * 30 - 19);
+ assert(buf.buffer.byteLength == abLen);
+ // Look for values written by the backend.
+ assert(buf[0] === 200 + i);
+ assert(buf[buf.length - 1] === 200 - i);
+ // On the JS side, the start of the slice is moved up by 11 bytes.
+ buf = buf.subarray(11);
+ assert(buf.byteOffset === (i + 1) * 11);
+ assert(buf.byteLength === abLen - (i + 1) * 30);
+ }
+};
+
+global.JSSendArrayBufferViewTypes = () => {
+ // Test that ArrayBufferView slices are transferred correctly.
+ // Send Uint8Array.
+ const ab1 = new ArrayBuffer(4321);
+ const u8 = new Uint8Array(ab1, 2468, 1000);
+ u8[0] = 1;
+ Deno.core.send(42, u8);
+ // Send Uint32Array.
+ const ab2 = new ArrayBuffer(4321);
+ const u32 = new Uint32Array(ab2, 2468, 1000 / Uint32Array.BYTES_PER_ELEMENT);
+ u32[0] = 0x02020202;
+ Deno.core.send(42, u32);
+ // Send DataView.
+ const ab3 = new ArrayBuffer(4321);
+ const dv = new DataView(ab3, 2468, 1000);
+ dv.setUint8(0, 3);
+ Deno.core.send(42, dv);
+};
+
+// The following join has caused SnapshotBug to segfault when using kKeep.
+[].join("");
+
+global.SnapshotBug = () => {
+ assert("1,2,3" === String([1, 2, 3]));
+};
+
+global.GlobalErrorHandling = () => {
+ eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
+};
+
+// Allocate this buf at the top level to avoid GC.
+const zeroCopyBuf = new Uint8Array([3, 4]);
+
+global.ZeroCopyBuf = () => {
+ const a = new Uint8Array([1, 2]);
+ const b = zeroCopyBuf;
+ // The second parameter of send should modified by the
+ // privileged side.
+ const r = Deno.core.send(42, a, b);
+ assert(r == null);
+ // b is different.
+ assert(b[0] === 4);
+ assert(b[1] === 2);
+ // Now we modify it again.
+ b[0] = 9;
+ b[1] = 8;
+};
+
+global.CheckPromiseErrors = () => {
+ async function fn() {
+ throw new Error("message");
+ }
+
+ (async () => {
+ try {
+ await fn();
+ } catch (e) {
+ Deno.core.send(42, new Uint8Array([42]));
+ }
+ })();
+};
+
+global.Shared = () => {
+ const ab = Deno.core.shared;
+ assert(ab instanceof SharedArrayBuffer);
+ assert(Deno.core.shared != undefined);
+ assert(ab.byteLength === 3);
+ const ui8 = new Uint8Array(ab);
+ assert(ui8[0] === 0);
+ assert(ui8[1] === 1);
+ assert(ui8[2] === 2);
+ ui8[0] = 42;
+ ui8[1] = 43;
+ ui8[2] = 44;
+};
+
+global.LibDenoEvalContext = () => {
+ const [result, errInfo] = Deno.core.evalContext("let a = 1; a");
+ assert(result === 1);
+ assert(!errInfo);
+ const [result2, errInfo2] = Deno.core.evalContext("a = a + 1; a");
+ assert(result2 === 2);
+ assert(!errInfo2);
+};
+
+global.LibDenoEvalContextError = () => {
+ const [result, errInfo] = Deno.core.evalContext("not_a_variable");
+ assert(!result);
+ assert(!!errInfo);
+ assert(errInfo.isNativeError); // is a native error (ReferenceError)
+ assert(!errInfo.isCompileError); // is NOT a compilation error
+ assert(errInfo.thrown.message === "not_a_variable is not defined");
+
+ const [result2, errInfo2] = Deno.core.evalContext("throw 1");
+ assert(!result2);
+ assert(!!errInfo2);
+ assert(!errInfo2.isNativeError); // is NOT a native error
+ assert(!errInfo2.isCompileError); // is NOT a compilation error
+ assert(errInfo2.thrown === 1);
+
+ const [result3, errInfo3] = Deno.core.evalContext(
+ "class AError extends Error {}; throw new AError('e')"
+ );
+ assert(!result3);
+ assert(!!errInfo3);
+ assert(errInfo3.isNativeError); // extend from native error, still native error
+ assert(!errInfo3.isCompileError); // is NOT a compilation error
+ assert(errInfo3.thrown.message === "e");
+
+ const [result4, errInfo4] = Deno.core.evalContext("{");
+ assert(!result4);
+ assert(!!errInfo4);
+ assert(errInfo4.isNativeError); // is a native error (SyntaxError)
+ assert(errInfo4.isCompileError); // is a compilation error! (braces not closed)
+ assert(errInfo4.thrown.message === "Unexpected end of input");
+
+ const [result5, errInfo5] = Deno.core.evalContext("eval('{')");
+ assert(!result5);
+ assert(!!errInfo5);
+ assert(errInfo5.isNativeError); // is a native error (SyntaxError)
+ assert(!errInfo5.isCompileError); // is NOT a compilation error! (just eval)
+ assert(errInfo5.thrown.message === "Unexpected end of input");
+};
+
+global.LibDenoEvalContextInvalidArgument = () => {
+ try {
+ Deno.core.evalContext();
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+};
+
+global.LibDenoPrintInvalidArgument = () => {
+ try {
+ Deno.core.print();
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+ try {
+ Deno.core.print(2, 3, 4);
+ } catch (e) {
+ assert(e instanceof TypeError);
+ assert(e.message === "Invalid Argument");
+ }
+};
+
+global.WasmInstantiate = () => {
+ // The following blob can be created by taking the following s-expr and pass
+ // it through wat2wasm.
+ // (module
+ // (func $add (param $a i32) (param $b i32) (result i32)
+ // local.get $a
+ // local.get $b
+ // i32.add)
+ // (export "add" (func $add))
+ // )
+ // prettier-ignore
+ const bytes = new Uint8Array([
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,
+ 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01,
+ 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20,
+ 0x00, 0x20, 0x01, 0x6a, 0x0b
+ ]);
+
+ (async () => {
+ Deno.core.send(42, new Uint8Array([42]));
+
+ const wasm = await WebAssembly.instantiate(bytes);
+
+ Deno.core.send(42, new Uint8Array([42]));
+
+ const result = wasm.instance.exports.add(1, 3);
+ if (result != 4) {
+ throw Error("bad");
+ }
+ // To signal success, we send back a fixed buffer.
+ Deno.core.send(42, new Uint8Array([42]));
+ })();
+};
diff --git a/core/libdeno/modules.cc b/core/libdeno/modules.cc
new file mode 100644
index 000000000..5293fc95f
--- /dev/null
+++ b/core/libdeno/modules.cc
@@ -0,0 +1,223 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+#include "exceptions.h"
+#include "internal.h"
+
+using deno::ClearException;
+using deno::DenoIsolate;
+using deno::HandleException;
+using v8::Boolean;
+using v8::Context;
+using v8::EscapableHandleScope;
+using v8::HandleScope;
+using v8::Integer;
+using v8::Isolate;
+using v8::Local;
+using v8::Locker;
+using v8::Module;
+using v8::Object;
+using v8::ScriptCompiler;
+using v8::ScriptOrigin;
+using v8::String;
+using v8::Value;
+
+v8::MaybeLocal<v8::Module> ResolveCallback(Local<Context> context,
+ Local<String> specifier,
+ Local<Module> referrer) {
+ auto* isolate = context->GetIsolate();
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+
+ DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
+
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ deno_mod referrer_id = referrer->GetIdentityHash();
+ auto* referrer_info = d->GetModuleInfo(referrer_id);
+ CHECK_NOT_NULL(referrer_info);
+
+ for (int i = 0; i < referrer->GetModuleRequestsLength(); i++) {
+ Local<String> req = referrer->GetModuleRequest(i);
+
+ if (req->Equals(context, specifier).ToChecked()) {
+ v8::String::Utf8Value req_utf8(isolate, req);
+ std::string req_str(*req_utf8);
+
+ deno_mod id = d->resolve_cb_(d->user_data_, req_str.c_str(), referrer_id);
+
+ // Note: id might be zero, in which case GetModuleInfo will return
+ // nullptr.
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr) {
+ char buf[64 * 1024];
+ snprintf(buf, sizeof(buf), "Cannot resolve module \"%s\" from \"%s\"",
+ req_str.c_str(), referrer_info->name.c_str());
+ isolate->ThrowException(deno::v8_str(buf));
+ break;
+ } else {
+ Local<Module> child_mod = info->handle.Get(isolate);
+ return handle_scope.Escape(child_mod);
+ }
+ }
+ }
+
+ return v8::MaybeLocal<v8::Module>(); // Error
+}
+
+extern "C" {
+
+deno_mod deno_mod_new(Deno* d_, bool main, const char* name_cstr,
+ const char* source_cstr) {
+ auto* d = unwrap(d_);
+ return d->RegisterModule(main, name_cstr, source_cstr);
+}
+
+const char* deno_mod_name(Deno* d_, deno_mod id) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ return info->name.c_str();
+}
+
+size_t deno_mod_imports_len(Deno* d_, deno_mod id) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ return info->import_specifiers.size();
+}
+
+const char* deno_mod_imports_get(Deno* d_, deno_mod id, size_t index) {
+ auto* d = unwrap(d_);
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr || index >= info->import_specifiers.size()) {
+ return nullptr;
+ } else {
+ return info->import_specifiers[index].c_str();
+ }
+}
+
+void deno_mod_instantiate(Deno* d_, void* user_data, deno_mod id,
+ deno_resolve_cb cb) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(isolate);
+ {
+ CHECK_NULL(d->resolve_cb_);
+ d->resolve_cb_ = cb;
+ {
+ auto* info = d->GetModuleInfo(id);
+ if (info == nullptr) {
+ return;
+ }
+ Local<Module> module = info->handle.Get(isolate);
+ if (module->GetStatus() == Module::kErrored) {
+ return;
+ }
+ auto maybe_ok = module->InstantiateModule(context, ResolveCallback);
+ CHECK(maybe_ok.IsJust() || try_catch.HasCaught());
+ }
+ d->resolve_cb_ = nullptr;
+ }
+
+ if (try_catch.HasCaught()) {
+ HandleException(context, try_catch.Exception());
+ }
+}
+
+void deno_mod_evaluate(Deno* d_, void* user_data, deno_mod id) {
+ auto* d = unwrap(d_);
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto* info = d->GetModuleInfo(id);
+ auto module = info->handle.Get(isolate);
+ auto status = module->GetStatus();
+
+ if (status == Module::kInstantiated) {
+ bool ok = !module->Evaluate(context).IsEmpty();
+ status = module->GetStatus(); // Update status after evaluating.
+ if (ok) {
+ // Note status can still be kErrored even if we get ok.
+ CHECK(status == Module::kEvaluated || status == Module::kErrored);
+ } else {
+ CHECK_EQ(status, Module::kErrored);
+ }
+ }
+
+ switch (status) {
+ case Module::kEvaluated:
+ ClearException(context);
+ break;
+ case Module::kErrored:
+ HandleException(context, module->GetException());
+ break;
+ default:
+ FATAL("Unexpected module status: %d", static_cast<int>(status));
+ }
+}
+
+void deno_dyn_import_done(Deno* d_, void* user_data,
+ deno_dyn_import_id import_id, deno_mod mod_id,
+ const char* error_str) {
+ auto* d = unwrap(d_);
+ CHECK((mod_id == 0 && error_str != nullptr) ||
+ (mod_id != 0 && error_str == nullptr) ||
+ (mod_id == 0 && !d->last_exception_handle_.IsEmpty()));
+ deno::UserDataScope user_data_scope(d, user_data);
+
+ auto* isolate = d->isolate_;
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::Locker locker(isolate);
+ v8::HandleScope handle_scope(isolate);
+ auto context = d->context_.Get(d->isolate_);
+ v8::Context::Scope context_scope(context);
+
+ auto it = d->dyn_import_map_.find(import_id);
+ if (it == d->dyn_import_map_.end()) {
+ CHECK(false); // TODO(ry) error on bad import_id.
+ return;
+ }
+
+ /// Resolve.
+ auto persistent_promise = &it->second;
+ auto promise = persistent_promise->Get(isolate);
+
+ auto* info = d->GetModuleInfo(mod_id);
+
+ // Do the following callback into JS?? Is user_data_scope needed?
+ persistent_promise->Reset();
+ d->dyn_import_map_.erase(it);
+
+ if (info == nullptr) {
+ // Resolution error.
+ if (error_str != nullptr) {
+ auto msg = deno::v8_str(error_str);
+ auto exception = v8::Exception::TypeError(msg);
+ promise->Reject(context, exception).ToChecked();
+ } else {
+ auto e = d->last_exception_handle_.Get(isolate);
+ ClearException(context);
+ promise->Reject(context, e).ToChecked();
+ }
+ } else {
+ // Resolution success
+ Local<Module> module = info->handle.Get(isolate);
+ CHECK_EQ(module->GetStatus(), v8::Module::kEvaluated);
+ Local<Value> module_namespace = module->GetModuleNamespace();
+ promise->Resolve(context, module_namespace).ToChecked();
+ }
+ d->isolate_->RunMicrotasks();
+}
+
+} // extern "C"
diff --git a/core/libdeno/modules_test.cc b/core/libdeno/modules_test.cc
new file mode 100644
index 000000000..e11231528
--- /dev/null
+++ b/core/libdeno/modules_test.cc
@@ -0,0 +1,426 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+
+static int exec_count = 0;
+void recv_cb(void* user_data, deno_op_id op_id, deno_buf buf,
+ deno_pinned_buf zero_copy_buf) {
+ // We use this to check that scripts have executed.
+ EXPECT_EQ(1u, buf.data_len);
+ EXPECT_EQ(42u, op_id);
+ EXPECT_EQ(buf.data_ptr[0], 4);
+ EXPECT_EQ(zero_copy_buf.data_ptr, nullptr);
+ EXPECT_EQ(zero_copy_buf.data_len, 0u);
+ EXPECT_EQ(zero_copy_buf.pin, nullptr);
+ exec_count++;
+}
+
+TEST(ModulesTest, Resolution) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a = deno_mod_new(d, true, "a.js",
+ "import { b } from 'b.js'\n"
+ "if (b() != 'b') throw Error();\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ const char* b_src = "export function b() { return 'b' }";
+ static deno_mod b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(0, exec_count);
+
+ EXPECT_EQ(1u, deno_mod_imports_len(d, a));
+ EXPECT_EQ(0u, deno_mod_imports_len(d, b));
+
+ EXPECT_STREQ("b.js", deno_mod_imports_get(d, a, 0));
+ EXPECT_EQ(nullptr, deno_mod_imports_get(d, a, 1));
+ EXPECT_EQ(nullptr, deno_mod_imports_get(d, b, 0));
+
+ static int resolve_count = 0;
+ auto resolve_cb = [](void* user_data, const char* specifier,
+ deno_mod referrer) {
+ EXPECT_EQ(referrer, a);
+ EXPECT_STREQ(specifier, "b.js");
+ resolve_count++;
+ return b;
+ };
+
+ deno_mod_instantiate(d, d, b, resolve_cb);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(0, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_instantiate(d, d, a, resolve_cb);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(1, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, ResolutionError) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a = deno_mod_new(d, true, "a.js",
+ "import 'bad'\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ EXPECT_EQ(0, exec_count);
+
+ EXPECT_EQ(1u, deno_mod_imports_len(d, a));
+ EXPECT_STREQ("bad", deno_mod_imports_get(d, a, 0));
+
+ static int resolve_count = 0;
+ auto resolve_cb = [](void* user_data, const char* specifier,
+ deno_mod referrer) {
+ EXPECT_EQ(referrer, a);
+ EXPECT_STREQ(specifier, "bad");
+ resolve_count++;
+ return 0;
+ };
+
+ deno_mod_instantiate(d, d, a, resolve_cb);
+ EXPECT_NE(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, resolve_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, ImportMetaUrl) {
+ exec_count = 0; // Reset
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+ EXPECT_EQ(0, exec_count);
+
+ static deno_mod a =
+ deno_mod_new(d, true, "a.js",
+ "if ('a.js' != import.meta.url) throw 'hmm'\n"
+ "Deno.core.send(42, new Uint8Array([4]));");
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(0, exec_count);
+
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ EXPECT_EQ(1, exec_count);
+}
+
+TEST(ModulesTest, ImportMetaMain) {
+ Deno* d = deno_new(deno_config{0, empty_snapshot, empty, recv_cb, nullptr});
+
+ const char* throw_not_main_src = "if (!import.meta.main) throw 'err'";
+ static deno_mod throw_not_main =
+ deno_mod_new(d, true, "a.js", throw_not_main_src);
+ EXPECT_NE(throw_not_main, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, throw_not_main, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_evaluate(d, d, throw_not_main);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ const char* throw_main_src = "if (import.meta.main) throw 'err'";
+ static deno_mod throw_main = deno_mod_new(d, false, "b.js", throw_main_src);
+ EXPECT_NE(throw_main, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_instantiate(d, d, throw_main, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_mod_evaluate(d, d, throw_main);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportSuccess) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // Send a message to signify that we're done.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ const char* b_src = "export function b() { return 'b' }";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, b);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+ EXPECT_EQ(1, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+}
+
+TEST(ModulesTest, DynamicImportError) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ // We indicate there was an error resolving by returning mod_id 0.
+ deno_dyn_import_done(d, d, import_id, 0, "foo not found");
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ // The following should be unreachable.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ // No error when evaluating, because it's an async error.
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ // Now we should get an error.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ std::string e(deno_last_exception(d));
+ EXPECT_NE(e.find("Uncaught TypeError: foo not found"), std::string::npos);
+ deno_delete(d);
+ EXPECT_EQ(0, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+}
+
+TEST(ModulesTest, DynamicImportAsync) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ static std::vector<deno_dyn_import_id> import_ids = {};
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ // auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "foo");
+ EXPECT_STREQ(referrer, "a.js");
+ // We don't call deno_dyn_import_done until later.
+ import_ids.push_back(import_id);
+ };
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // AGAIN!
+ " mod = await import('foo'); \n"
+ " assert(mod.b() === 'b'); \n"
+ // Send a message to signify that we're done.
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Evaluate. We check that there are no errors, and Deno.core.send has not
+ // been called.
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ EXPECT_EQ(0, exec_count);
+ EXPECT_EQ(1, dyn_import_count);
+
+ // Instantiate b.js
+ const char* b_src = "export function b() { return 'b' }";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, b);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Now we resolve the import.
+ EXPECT_EQ(1u, import_ids.size());
+ auto import_id = import_ids.back();
+ import_ids.pop_back();
+
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ EXPECT_EQ(1u, import_ids.size());
+ EXPECT_EQ(2, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ import_id = import_ids.back();
+ import_ids.pop_back();
+ deno_dyn_import_done(d, d, import_id, b, nullptr);
+
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // We still have to resolve the second one
+ EXPECT_EQ(2, dyn_import_count);
+ EXPECT_EQ(1, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportThrows) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ static deno_mod b = 0;
+ static std::vector<deno_dyn_import_id> import_ids = {};
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "b.js");
+ EXPECT_STREQ(referrer, "a.js");
+ // We don't call deno_dyn_import_done until later.
+ import_ids.push_back(import_id);
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+
+ // Instantiate and evaluate the root module. This should succeed.
+ const char* a_src =
+ "(async () => { \n"
+ " let mod = await import('b.js'); \n"
+ // unreachable
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ static deno_mod a = deno_mod_new(d, true, "a.js", a_src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // Instantiate b.js, which should succeed.
+ const char* b_src = "throw new Error('foo')";
+ b = deno_mod_new(d, false, "b.js", b_src);
+ EXPECT_NE(b, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, b, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // Evaluate b.js. It throws in the global scope, so deno_last_exception()
+ // should be non-null afterwards.
+ deno_mod_evaluate(d, d, b);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+
+ // Resolve the dynamic import of b.js. Since deno_mod_evaluate() failed,
+ // we indicate failure to deno_dyn_import_done() by setting mod_id to 0.
+ // The last error should be picked up and cleared by deno_dyn_import_done().
+ EXPECT_EQ(1u, import_ids.size());
+ auto import_id = import_ids.back();
+ import_ids.pop_back();
+ deno_dyn_import_done(d, d, import_id, 0, nullptr);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+
+ // Since the dynamically imported module threw an error,
+ // it should show up as an unhandled promise rejection.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ std::string e(deno_last_exception(d));
+ EXPECT_NE(e.find("Uncaught Error: foo"), std::string::npos);
+
+ EXPECT_EQ(1, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
+
+TEST(ModulesTest, DynamicImportSyntaxError) {
+ exec_count = 0;
+ static int dyn_import_count = 0;
+ auto dyn_import_cb = [](auto user_data, const char* specifier,
+ const char* referrer, deno_dyn_import_id import_id) {
+ auto d = reinterpret_cast<Deno*>(user_data);
+ dyn_import_count++;
+ EXPECT_STREQ(specifier, "b.js");
+ EXPECT_STREQ(referrer, "a.js");
+
+ // Compile b.js, which should fail because of the syntax error.
+ deno_mod b = deno_mod_new(d, false, "b.js", "syntax error");
+ EXPECT_EQ(b, 0);
+ EXPECT_NE(nullptr, deno_last_exception(d));
+
+ // `deno_dyn_import_done` should consume the last exception, and use it
+ // to reject the dynamic import promise.
+ deno_dyn_import_done(d, d, import_id, 0, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ };
+ Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, dyn_import_cb});
+
+ // Instantiate and evaluate the root module. This should succeed.
+ const char* src =
+ "(async () => { \n"
+ " let mod = await import('b.js'); \n"
+ // unreachable
+ " Deno.core.send(42, new Uint8Array([4])); \n"
+ "})(); \n";
+ static deno_mod a = deno_mod_new(d, true, "a.js", src);
+ EXPECT_NE(a, 0);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_instantiate(d, d, a, nullptr);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+ deno_mod_evaluate(d, d, a);
+ EXPECT_EQ(nullptr, deno_last_exception(d));
+
+ // The failed dynamic import should cause an unhandled promise rejection.
+ deno_check_promise_errors(d);
+ EXPECT_NE(deno_last_exception(d), nullptr);
+ EXPECT_NE(std::string(deno_last_exception(d)).find("Syntax"),
+ std::string::npos);
+
+ EXPECT_EQ(1, dyn_import_count);
+ EXPECT_EQ(0, exec_count);
+
+ deno_delete(d);
+}
diff --git a/core/libdeno/test.cc b/core/libdeno/test.cc
new file mode 100644
index 000000000..0bfe374ef
--- /dev/null
+++ b/core/libdeno/test.cc
@@ -0,0 +1,47 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#include "test.h"
+#include <fstream>
+#include <string>
+#include "internal.h"
+
+deno_snapshot snapshot = {nullptr, 0};
+
+bool ReadFileToString(const char* fn, std::string* contents) {
+ std::ifstream file(fn, std::ios::binary);
+ if (file.fail()) {
+ return false;
+ }
+ contents->assign(std::istreambuf_iterator<char>{file}, {});
+ return !file.fail();
+}
+
+int main(int argc, char** argv) {
+ // All of the JS code in libdeno_test.js is tested after being snapshotted.
+ // We create that snapshot now at runtime, rather than at compile time to
+ // simplify the build process. So we load and execute the libdeno_test.js
+ // file, without running any of the tests and store the result in the global
+ // "snapshot" variable, which will be used later in the tests.
+ std::string js_fn = JS_PATH;
+ std::string js_source;
+ CHECK(ReadFileToString(js_fn.c_str(), &js_source));
+
+ deno_init();
+ deno_config config = {1, deno::empty_snapshot, deno::empty_buf, nullptr,
+ nullptr};
+ Deno* d = deno_new(config);
+
+ deno_execute(d, nullptr, js_fn.c_str(), js_source.c_str());
+ if (deno_last_exception(d) != nullptr) {
+ std::cerr << "Snapshot Exception " << std::endl;
+ std::cerr << deno_last_exception(d) << std::endl;
+ deno_delete(d);
+ return 1;
+ }
+
+ snapshot = deno_snapshot_new(d);
+
+ testing::InitGoogleTest(&argc, argv);
+ deno_init();
+ deno_set_v8_flags(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/core/libdeno/test.h b/core/libdeno/test.h
new file mode 100644
index 000000000..4ae83f810
--- /dev/null
+++ b/core/libdeno/test.h
@@ -0,0 +1,12 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+#ifndef TEST_H_
+#define TEST_H_
+
+#include "deno.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+extern deno_snapshot snapshot; // Loaded in libdeno/test.cc
+const deno_buf empty = {nullptr, 0};
+const deno_snapshot empty_snapshot = {nullptr, 0};
+
+#endif // TEST_H_
diff --git a/core/libdeno/testing b/core/libdeno/testing
new file mode 120000
index 000000000..ed34f4824
--- /dev/null
+++ b/core/libdeno/testing
@@ -0,0 +1 @@
+v8/testing \ No newline at end of file
diff --git a/core/libdeno/third_party b/core/libdeno/third_party
new file mode 120000
index 000000000..48370cbe7
--- /dev/null
+++ b/core/libdeno/third_party
@@ -0,0 +1 @@
+v8/third_party \ No newline at end of file
diff --git a/core/libdeno/tools b/core/libdeno/tools
new file mode 120000
index 000000000..7b6c53ae9
--- /dev/null
+++ b/core/libdeno/tools
@@ -0,0 +1 @@
+v8/tools \ No newline at end of file
diff --git a/core/libdeno/v8 b/core/libdeno/v8
new file mode 120000
index 000000000..efdcb44da
--- /dev/null
+++ b/core/libdeno/v8
@@ -0,0 +1 @@
+../../third_party/v8 \ No newline at end of file
diff --git a/core/module_specifier.rs b/core/module_specifier.rs
new file mode 100644
index 000000000..dbab0ce9b
--- /dev/null
+++ b/core/module_specifier.rs
@@ -0,0 +1,473 @@
+use std::env::current_dir;
+use std::error::Error;
+use std::fmt;
+use std::path::PathBuf;
+use url::ParseError;
+use url::Url;
+
+/// Error indicating the reason resolving a module specifier failed.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum ModuleResolutionError {
+ InvalidUrl(ParseError),
+ InvalidBaseUrl(ParseError),
+ InvalidPath(PathBuf),
+ ImportPrefixMissing(String),
+}
+use ModuleResolutionError::*;
+
+impl Error for ModuleResolutionError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ InvalidUrl(ref err) | InvalidBaseUrl(ref err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Display for ModuleResolutionError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ InvalidUrl(ref err) => write!(f, "invalid URL: {}", err),
+ InvalidBaseUrl(ref err) => {
+ write!(f, "invalid base URL for relative import: {}", err)
+ }
+ InvalidPath(ref path) => write!(f, "invalid module path: {:?}", path),
+ ImportPrefixMissing(ref specifier) => write!(
+ f,
+ "relative import path \"{}\" not prefixed with / or ./ or ../",
+ specifier
+ ),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, Hash, PartialEq)]
+/// Resolved module specifier
+pub struct ModuleSpecifier(Url);
+
+impl ModuleSpecifier {
+ fn is_dummy_specifier(specifier: &str) -> bool {
+ specifier == "<unknown>"
+ }
+
+ pub fn as_url(&self) -> &Url {
+ &self.0
+ }
+
+ pub fn as_str(&self) -> &str {
+ self.0.as_str()
+ }
+
+ /// Resolves module using this algorithm:
+ /// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
+ pub fn resolve_import(
+ specifier: &str,
+ base: &str,
+ ) -> Result<ModuleSpecifier, ModuleResolutionError> {
+ let url = match Url::parse(specifier) {
+ // 1. Apply the URL parser to specifier.
+ // If the result is not failure, return he result.
+ Ok(url) => url,
+
+ // 2. If specifier does not start with the character U+002F SOLIDUS (/),
+ // the two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./),
+ // or the three-character sequence U+002E FULL STOP, U+002E FULL STOP,
+ // U+002F SOLIDUS (../), return failure.
+ Err(ParseError::RelativeUrlWithoutBase)
+ if !(specifier.starts_with('/')
+ || specifier.starts_with("./")
+ || specifier.starts_with("../")) =>
+ {
+ return Err(ImportPrefixMissing(specifier.to_string()))
+ }
+
+ // 3. Return the result of applying the URL parser to specifier with base
+ // URL as the base URL.
+ Err(ParseError::RelativeUrlWithoutBase) => {
+ let base = if ModuleSpecifier::is_dummy_specifier(base) {
+ // Handle <unknown> case, happening under e.g. repl.
+ // Use CWD for such case.
+
+ // Forcefully join base to current dir.
+ // Otherwise, later joining in Url would be interpreted in
+ // the parent directory (appending trailing slash does not work)
+ let path = current_dir().unwrap().join(base);
+ Url::from_file_path(path).unwrap()
+ } else {
+ Url::parse(base).map_err(InvalidBaseUrl)?
+ };
+ base.join(&specifier).map_err(InvalidUrl)?
+ }
+
+ // If parsing the specifier as a URL failed for a different reason than
+ // it being relative, always return the original error. We don't want to
+ // return `ImportPrefixMissing` or `InvalidBaseUrl` if the real
+ // problem lies somewhere else.
+ Err(err) => return Err(InvalidUrl(err)),
+ };
+
+ Ok(ModuleSpecifier(url))
+ }
+
+ /// Converts a string representing an absulute URL into a ModuleSpecifier.
+ pub fn resolve_url(
+ url_str: &str,
+ ) -> Result<ModuleSpecifier, ModuleResolutionError> {
+ Url::parse(url_str)
+ .map(ModuleSpecifier)
+ .map_err(ModuleResolutionError::InvalidUrl)
+ }
+
+ /// Takes a string representing either an absolute URL or a file path,
+ /// as it may be passed to deno as a command line argument.
+ /// The string is interpreted as a URL if it starts with a valid URI scheme,
+ /// e.g. 'http:' or 'file:' or 'git+ssh:'. If not, it's interpreted as a
+ /// file path; if it is a relative path it's resolved relative to the current
+ /// working directory.
+ pub fn resolve_url_or_path(
+ specifier: &str,
+ ) -> Result<ModuleSpecifier, ModuleResolutionError> {
+ if Self::specifier_has_uri_scheme(specifier) {
+ Self::resolve_url(specifier)
+ } else {
+ Self::resolve_path(specifier)
+ }
+ }
+
+ /// Converts a string representing a relative or absolute path into a
+ /// ModuleSpecifier. A relative path is considered relative to the current
+ /// working directory.
+ fn resolve_path(
+ path_str: &str,
+ ) -> Result<ModuleSpecifier, ModuleResolutionError> {
+ let path = current_dir().unwrap().join(path_str);
+ Url::from_file_path(path.clone())
+ .map(ModuleSpecifier)
+ .map_err(|()| ModuleResolutionError::InvalidPath(path))
+ }
+
+ /// Returns true if the input string starts with a sequence of characters
+ /// that could be a valid URI scheme, like 'https:', 'git+ssh:' or 'data:'.
+ ///
+ /// According to RFC 3986 (https://tools.ietf.org/html/rfc3986#section-3.1),
+ /// a valid scheme has the following format:
+ /// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ ///
+ /// We additionally require the scheme to be at least 2 characters long,
+ /// because otherwise a windows path like c:/foo would be treated as a URL,
+ /// while no schemes with a one-letter name actually exist.
+ fn specifier_has_uri_scheme(specifier: &str) -> bool {
+ let mut chars = specifier.chars();
+ let mut len = 0usize;
+ // THe first character must be a letter.
+ match chars.next() {
+ Some(c) if c.is_ascii_alphabetic() => len += 1,
+ _ => return false,
+ }
+ // Second and following characters must be either a letter, number,
+ // plus sign, minus sign, or dot.
+ loop {
+ match chars.next() {
+ Some(c) if c.is_ascii_alphanumeric() || "+-.".contains(c) => len += 1,
+ Some(':') if len >= 2 => return true,
+ _ => return false,
+ }
+ }
+ }
+}
+
+impl fmt::Display for ModuleSpecifier {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl From<Url> for ModuleSpecifier {
+ fn from(url: Url) -> Self {
+ ModuleSpecifier(url)
+ }
+}
+
+impl PartialEq<String> for ModuleSpecifier {
+ fn eq(&self, other: &String) -> bool {
+ &self.to_string() == other
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_resolve_import() {
+ let tests = vec![
+ (
+ "./005_more_imports.ts",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "http://deno.land/core/tests/005_more_imports.ts",
+ ),
+ (
+ "../005_more_imports.ts",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "http://deno.land/core/005_more_imports.ts",
+ ),
+ (
+ "http://deno.land/core/tests/005_more_imports.ts",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "http://deno.land/core/tests/005_more_imports.ts",
+ ),
+ (
+ "data:text/javascript,export default 'grapes';",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "data:text/javascript,export default 'grapes';",
+ ),
+ (
+ "blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f",
+ ),
+ (
+ "javascript:export default 'artichokes';",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "javascript:export default 'artichokes';",
+ ),
+ (
+ "data:text/plain,export default 'kale';",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "data:text/plain,export default 'kale';",
+ ),
+ (
+ "/dev/core/tests/005_more_imports.ts",
+ "file:///home/yeti",
+ "file:///dev/core/tests/005_more_imports.ts",
+ ),
+ (
+ "//zombo.com/1999.ts",
+ "https://cherry.dev/its/a/thing",
+ "https://zombo.com/1999.ts",
+ ),
+ (
+ "http://deno.land/this/url/is/valid",
+ "base is clearly not a valid url",
+ "http://deno.land/this/url/is/valid",
+ ),
+ (
+ "//server/some/dir/file",
+ "file:///home/yeti/deno",
+ "file://server/some/dir/file",
+ ),
+ // This test is disabled because the url crate does not follow the spec,
+ // dropping the server part from the final result.
+ // (
+ // "/another/path/at/the/same/server",
+ // "file://server/some/dir/file",
+ // "file://server/another/path/at/the/same/server",
+ // ),
+ ];
+
+ for (specifier, base, expected_url) in tests {
+ let url = ModuleSpecifier::resolve_import(specifier, base)
+ .unwrap()
+ .to_string();
+ assert_eq!(url, expected_url);
+ }
+ }
+
+ #[test]
+ fn test_resolve_import_error() {
+ use url::ParseError::*;
+ use ModuleResolutionError::*;
+
+ let tests = vec![
+ (
+ "005_more_imports.ts",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ ImportPrefixMissing("005_more_imports.ts".to_string()),
+ ),
+ (
+ ".tomato",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ ImportPrefixMissing(".tomato".to_string()),
+ ),
+ (
+ "..zucchini.mjs",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ ImportPrefixMissing("..zucchini.mjs".to_string()),
+ ),
+ (
+ r".\yam.es",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ ImportPrefixMissing(r".\yam.es".to_string()),
+ ),
+ (
+ r"..\yam.es",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ ImportPrefixMissing(r"..\yam.es".to_string()),
+ ),
+ (
+ "https://eggplant:b/c",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ InvalidUrl(InvalidPort),
+ ),
+ (
+ "https://eggplant@/c",
+ "http://deno.land/core/tests/006_url_imports.ts",
+ InvalidUrl(EmptyHost),
+ ),
+ (
+ "./foo.ts",
+ "/relative/base/url",
+ InvalidBaseUrl(RelativeUrlWithoutBase),
+ ),
+ ];
+
+ for (specifier, base, expected_err) in tests {
+ let err = ModuleSpecifier::resolve_import(specifier, base).unwrap_err();
+ assert_eq!(err, expected_err);
+ }
+ }
+
+ #[test]
+ fn test_resolve_url_or_path() {
+ // Absolute URL.
+ let mut tests: Vec<(&str, String)> = vec![
+ (
+ "http://deno.land/core/tests/006_url_imports.ts",
+ "http://deno.land/core/tests/006_url_imports.ts".to_string(),
+ ),
+ (
+ "https://deno.land/core/tests/006_url_imports.ts",
+ "https://deno.land/core/tests/006_url_imports.ts".to_string(),
+ ),
+ ];
+
+ // The local path tests assume that the cwd is the deno repo root.
+ let cwd = current_dir().unwrap();
+ let cwd_str = cwd.to_str().unwrap();
+
+ if cfg!(target_os = "windows") {
+ // Absolute local path.
+ let expected_url = "file:///C:/deno/tests/006_url_imports.ts";
+ tests.extend(vec![
+ (
+ r"C:/deno/tests/006_url_imports.ts",
+ expected_url.to_string(),
+ ),
+ (
+ r"C:\deno\tests\006_url_imports.ts",
+ expected_url.to_string(),
+ ),
+ (
+ r"\\?\C:\deno\tests\006_url_imports.ts",
+ expected_url.to_string(),
+ ),
+ // Not supported: `Url::from_file_path()` fails.
+ // (r"\\.\C:\deno\tests\006_url_imports.ts", expected_url.to_string()),
+ // Not supported: `Url::from_file_path()` performs the wrong conversion.
+ // (r"//./C:/deno/tests/006_url_imports.ts", expected_url.to_string()),
+ ]);
+
+ // Rooted local path without drive letter.
+ let expected_url = format!(
+ "file:///{}:/deno/tests/006_url_imports.ts",
+ cwd_str.get(..1).unwrap(),
+ );
+ tests.extend(vec![
+ (r"/deno/tests/006_url_imports.ts", expected_url.to_string()),
+ (r"\deno\tests\006_url_imports.ts", expected_url.to_string()),
+ ]);
+
+ // Relative local path.
+ let expected_url = format!(
+ "file:///{}/tests/006_url_imports.ts",
+ cwd_str.replace("\\", "/")
+ );
+ tests.extend(vec![
+ (r"tests/006_url_imports.ts", expected_url.to_string()),
+ (r"tests\006_url_imports.ts", expected_url.to_string()),
+ (r"./tests/006_url_imports.ts", expected_url.to_string()),
+ (r".\tests\006_url_imports.ts", expected_url.to_string()),
+ ]);
+
+ // UNC network path.
+ let expected_url = "file://server/share/deno/cool";
+ tests.extend(vec![
+ (r"\\server\share\deno\cool", expected_url.to_string()),
+ (r"\\server/share/deno/cool", expected_url.to_string()),
+ // Not supported: `Url::from_file_path()` performs the wrong conversion.
+ // (r"//server/share/deno/cool", expected_url.to_string()),
+ ]);
+ } else {
+ // Absolute local path.
+ let expected_url = "file:///deno/tests/006_url_imports.ts";
+ tests.extend(vec![
+ ("/deno/tests/006_url_imports.ts", expected_url.to_string()),
+ ("//deno/tests/006_url_imports.ts", expected_url.to_string()),
+ ]);
+
+ // Relative local path.
+ let expected_url = format!("file://{}/tests/006_url_imports.ts", cwd_str);
+ tests.extend(vec![
+ ("tests/006_url_imports.ts", expected_url.to_string()),
+ ("./tests/006_url_imports.ts", expected_url.to_string()),
+ ]);
+ }
+
+ for (specifier, expected_url) in tests {
+ let url = ModuleSpecifier::resolve_url_or_path(specifier)
+ .unwrap()
+ .to_string();
+ assert_eq!(url, expected_url);
+ }
+ }
+
+ #[test]
+ fn test_resolve_url_or_path_error() {
+ use url::ParseError::*;
+ use ModuleResolutionError::*;
+
+ let mut tests = vec![
+ ("https://eggplant:b/c", InvalidUrl(InvalidPort)),
+ ("https://:8080/a/b/c", InvalidUrl(EmptyHost)),
+ ];
+ if cfg!(target_os = "windows") {
+ let p = r"\\.\c:/stuff/deno/script.ts";
+ tests.push((p, InvalidPath(PathBuf::from(p))));
+ }
+
+ for (specifier, expected_err) in tests {
+ let err = ModuleSpecifier::resolve_url_or_path(specifier).unwrap_err();
+ assert_eq!(err, expected_err);
+ }
+ }
+
+ #[test]
+ fn test_specifier_has_uri_scheme() {
+ let tests = vec![
+ ("http://foo.bar/etc", true),
+ ("HTTP://foo.bar/etc", true),
+ ("http:ftp:", true),
+ ("http:", true),
+ ("hTtP:", true),
+ ("ftp:", true),
+ ("mailto:spam@please.me", true),
+ ("git+ssh://git@github.com/denoland/deno", true),
+ ("blob:https://whatwg.org/mumbojumbo", true),
+ ("abc.123+DEF-ghi:", true),
+ ("abc.123+def-ghi:@", true),
+ ("", false),
+ (":not", false),
+ ("http", false),
+ ("c:dir", false),
+ ("X:", false),
+ ("./http://not", false),
+ ("1abc://kinda/but/no", false),
+ ("schluẞ://no/more", false),
+ ];
+
+ for (specifier, expected) in tests {
+ let result = ModuleSpecifier::specifier_has_uri_scheme(specifier);
+ assert_eq!(result, expected);
+ }
+ }
+}
diff --git a/core/modules.rs b/core/modules.rs
new file mode 100644
index 000000000..5956a7317
--- /dev/null
+++ b/core/modules.rs
@@ -0,0 +1,1086 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+
+// Implementation note: one could imagine combining this module with Isolate to
+// provide a more intuitive high-level API. However, due to the complexity
+// inherent in asynchronous module loading, we would like the Isolate to remain
+// small and simple for users who do not use modules or if they do can load them
+// synchronously. The isolate.rs module should never depend on this module.
+
+use crate::any_error::ErrBox;
+use crate::isolate::ImportStream;
+use crate::isolate::Isolate;
+use crate::isolate::RecursiveLoadEvent as Event;
+use crate::isolate::SourceCodeInfo;
+use crate::libdeno::deno_dyn_import_id;
+use crate::libdeno::deno_mod;
+use crate::module_specifier::ModuleSpecifier;
+use futures::future::loop_fn;
+use futures::future::Loop;
+use futures::stream::FuturesUnordered;
+use futures::stream::Stream;
+use futures::Async::*;
+use futures::Future;
+use futures::Poll;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::fmt;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+pub type SourceCodeInfoFuture =
+ dyn Future<Item = SourceCodeInfo, Error = ErrBox> + Send;
+
+pub trait Loader: Send + Sync {
+ /// Returns an absolute URL.
+ /// When implementing an spec-complaint VM, this should be exactly the
+ /// algorithm described here:
+ /// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ is_main: bool,
+ is_dyn_import: bool,
+ ) -> Result<ModuleSpecifier, ErrBox>;
+
+ /// Given ModuleSpecifier, load its source code.
+ fn load(
+ &self,
+ module_specifier: &ModuleSpecifier,
+ ) -> Box<SourceCodeInfoFuture>;
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum Kind {
+ Main,
+ DynamicImport(deno_dyn_import_id),
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum State {
+ ResolveMain(String), // specifier
+ ResolveImport(String, String), // specifier, referrer
+ LoadingRoot,
+ LoadingImports(deno_mod),
+ Instantiated(deno_mod),
+}
+
+/// This future is used to implement parallel async module loading without
+/// complicating the Isolate API.
+/// TODO: RecursiveLoad desperately needs to be merged with Modules.
+pub struct RecursiveLoad<L: Loader> {
+ kind: Kind,
+ state: State,
+ loader: L,
+ modules: Arc<Mutex<Modules>>,
+ pending: FuturesUnordered<Box<SourceCodeInfoFuture>>,
+ is_pending: HashSet<ModuleSpecifier>,
+}
+
+impl<L: Loader> RecursiveLoad<L> {
+ /// Starts a new parallel load of the given URL of the main module.
+ pub fn main(
+ specifier: &str,
+ loader: L,
+ modules: Arc<Mutex<Modules>>,
+ ) -> Self {
+ let kind = Kind::Main;
+ let state = State::ResolveMain(specifier.to_owned());
+ Self::new(kind, state, loader, modules)
+ }
+
+ pub fn dynamic_import(
+ id: deno_dyn_import_id,
+ specifier: &str,
+ referrer: &str,
+ loader: L,
+ modules: Arc<Mutex<Modules>>,
+ ) -> Self {
+ let kind = Kind::DynamicImport(id);
+ let state = State::ResolveImport(specifier.to_owned(), referrer.to_owned());
+ Self::new(kind, state, loader, modules)
+ }
+
+ pub fn dyn_import_id(&self) -> Option<deno_dyn_import_id> {
+ match self.kind {
+ Kind::Main => None,
+ Kind::DynamicImport(id) => Some(id),
+ }
+ }
+
+ fn new(
+ kind: Kind,
+ state: State,
+ loader: L,
+ modules: Arc<Mutex<Modules>>,
+ ) -> Self {
+ Self {
+ kind,
+ state,
+ loader,
+ modules,
+ pending: FuturesUnordered::new(),
+ is_pending: HashSet::new(),
+ }
+ }
+
+ fn add_root(&mut self) -> Result<(), ErrBox> {
+ let module_specifier = match self.state {
+ State::ResolveMain(ref specifier) => self.loader.resolve(
+ specifier,
+ ".",
+ true,
+ self.dyn_import_id().is_some(),
+ )?,
+ State::ResolveImport(ref specifier, ref referrer) => self
+ .loader
+ .resolve(specifier, referrer, false, self.dyn_import_id().is_some())?,
+ _ => unreachable!(),
+ };
+
+ // We deliberately do not check if this module is already present in the
+ // module map. That's because the module map doesn't track whether a
+ // a module's dependencies have been loaded and whether it's been
+ // instantiated, so if we did find this module in the module map and used
+ // its id, this could lead to a crash.
+ //
+ // For the time being code and metadata for a module specifier is fetched
+ // multiple times, register() uses only the first result, and assigns the
+ // same module id to all instances.
+ //
+ // TODO: this is very ugly. The module map and recursive loader should be
+ // integrated into one thing.
+ self
+ .pending
+ .push(Box::new(self.loader.load(&module_specifier)));
+ self.state = State::LoadingRoot;
+
+ Ok(())
+ }
+
+ fn add_import(
+ &mut self,
+ specifier: &str,
+ referrer: &str,
+ parent_id: deno_mod,
+ ) -> Result<(), ErrBox> {
+ let module_specifier = self.loader.resolve(
+ specifier,
+ referrer,
+ false,
+ self.dyn_import_id().is_some(),
+ )?;
+ let module_name = module_specifier.as_str();
+
+ let mut modules = self.modules.lock().unwrap();
+
+ modules.add_child(parent_id, module_name);
+
+ if !modules.is_registered(module_name)
+ && !self.is_pending.contains(&module_specifier)
+ {
+ self
+ .pending
+ .push(Box::new(self.loader.load(&module_specifier)));
+ self.is_pending.insert(module_specifier);
+ }
+
+ Ok(())
+ }
+
+ /// Returns a future that resolves to the final module id of the root module.
+ /// This future needs to take ownership of the isolate.
+ pub fn get_future(
+ self,
+ isolate: Arc<Mutex<Isolate>>,
+ ) -> impl Future<Item = deno_mod, Error = ErrBox> {
+ loop_fn(self, move |load| {
+ let isolate = isolate.clone();
+ load.into_future().map_err(|(e, _)| e).and_then(
+ move |(event, mut load)| {
+ Ok(match event.unwrap() {
+ Event::Fetch(info) => {
+ let mut isolate = isolate.lock().unwrap();
+ load.register(info, &mut isolate)?;
+ Loop::Continue(load)
+ }
+ Event::Instantiate(id) => Loop::Break(id),
+ })
+ },
+ )
+ })
+ }
+}
+
+impl<L: Loader> ImportStream for RecursiveLoad<L> {
+ // TODO: this should not be part of RecursiveLoad.
+ fn register(
+ &mut self,
+ source_code_info: SourceCodeInfo,
+ isolate: &mut Isolate,
+ ) -> Result<(), ErrBox> {
+ // #A There are 3 cases to handle at this moment:
+ // 1. Source code resolved result have the same module name as requested
+ // and is not yet registered
+ // -> register
+ // 2. Source code resolved result have a different name as requested:
+ // 2a. The module with resolved module name has been registered
+ // -> alias
+ // 2b. The module with resolved module name has not yet been registerd
+ // -> register & alias
+ let SourceCodeInfo {
+ code,
+ module_url_specified,
+ module_url_found,
+ } = source_code_info;
+
+ let is_main = self.kind == Kind::Main && self.state == State::LoadingRoot;
+
+ let module_id = {
+ let mut modules = self.modules.lock().unwrap();
+
+ // If necessary, register an alias.
+ if module_url_specified != module_url_found {
+ modules.alias(&module_url_specified, &module_url_found);
+ }
+
+ match modules.get_id(&module_url_found) {
+ // Module has already been registered.
+ Some(id) => {
+ debug!(
+ "Already-registered module fetched again: {}",
+ module_url_found
+ );
+ id
+ }
+ // Module not registered yet, do it now.
+ None => {
+ let id = isolate.mod_new(is_main, &module_url_found, &code)?;
+ modules.register(id, &module_url_found);
+ id
+ }
+ }
+ };
+
+ // Now we must iterate over all imports of the module and load them.
+ let imports = isolate.mod_get_imports(module_id);
+ for import in imports {
+ self.add_import(&import, &module_url_found, module_id)?;
+ }
+
+ // If we just finished loading the root module, store the root module id.
+ match self.state {
+ State::LoadingRoot => self.state = State::LoadingImports(module_id),
+ State::LoadingImports(..) => {}
+ _ => unreachable!(),
+ };
+
+ // If all imports have been loaded, instantiate the root module.
+ if self.pending.is_empty() {
+ let root_id = match self.state {
+ State::LoadingImports(mod_id) => mod_id,
+ _ => unreachable!(),
+ };
+
+ let mut resolve_cb =
+ |specifier: &str, referrer_id: deno_mod| -> deno_mod {
+ let modules = self.modules.lock().unwrap();
+ let referrer = modules.get_name(referrer_id).unwrap();
+ match self.loader.resolve(
+ specifier,
+ &referrer,
+ is_main,
+ self.dyn_import_id().is_some(),
+ ) {
+ Ok(specifier) => modules.get_id(specifier.as_str()).unwrap_or(0),
+ // We should have already resolved and Ready this module, so
+ // resolve() will not fail this time.
+ Err(..) => unreachable!(),
+ }
+ };
+ isolate.mod_instantiate(root_id, &mut resolve_cb)?;
+
+ self.state = State::Instantiated(root_id);
+ }
+
+ Ok(())
+ }
+}
+
+impl<L: Loader> Stream for RecursiveLoad<L> {
+ type Item = Event;
+ type Error = ErrBox;
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ Ok(match self.state {
+ State::ResolveMain(..) | State::ResolveImport(..) => {
+ self.add_root()?;
+ self.poll()?
+ }
+ State::LoadingRoot | State::LoadingImports(..) => {
+ match self.pending.poll()? {
+ Ready(None) => unreachable!(),
+ Ready(Some(info)) => Ready(Some(Event::Fetch(info))),
+ NotReady => NotReady,
+ }
+ }
+ State::Instantiated(id) => Ready(Some(Event::Instantiate(id))),
+ })
+ }
+}
+
+struct ModuleInfo {
+ name: String,
+ children: Vec<String>,
+}
+
+impl ModuleInfo {
+ fn has_child(&self, child_name: &str) -> bool {
+ for c in self.children.iter() {
+ if c == child_name {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+/// A symbolic module entity.
+enum SymbolicModule {
+ /// This module is an alias to another module.
+ /// This is useful such that multiple names could point to
+ /// the same underlying module (particularly due to redirects).
+ Alias(String),
+ /// This module associates with a V8 module by id.
+ Mod(deno_mod),
+}
+
+#[derive(Default)]
+/// Alias-able module name map
+struct ModuleNameMap {
+ inner: HashMap<String, SymbolicModule>,
+}
+
+impl ModuleNameMap {
+ pub fn new() -> Self {
+ ModuleNameMap {
+ inner: HashMap::new(),
+ }
+ }
+
+ /// Get the id of a module.
+ /// If this module is internally represented as an alias,
+ /// follow the alias chain to get the final module id.
+ pub fn get(&self, name: &str) -> Option<deno_mod> {
+ let mut mod_name = name;
+ loop {
+ let cond = self.inner.get(mod_name);
+ match cond {
+ Some(SymbolicModule::Alias(target)) => {
+ mod_name = target;
+ }
+ Some(SymbolicModule::Mod(mod_id)) => {
+ return Some(*mod_id);
+ }
+ _ => {
+ return None;
+ }
+ }
+ }
+ }
+
+ /// Insert a name assocated module id.
+ pub fn insert(&mut self, name: String, id: deno_mod) {
+ self.inner.insert(name, SymbolicModule::Mod(id));
+ }
+
+ /// Create an alias to another module.
+ pub fn alias(&mut self, name: String, target: String) {
+ self.inner.insert(name, SymbolicModule::Alias(target));
+ }
+
+ /// Check if a name is an alias to another module.
+ pub fn is_alias(&self, name: &str) -> bool {
+ let cond = self.inner.get(name);
+ match cond {
+ Some(SymbolicModule::Alias(_)) => true,
+ _ => false,
+ }
+ }
+}
+
+/// A collection of JS modules.
+#[derive(Default)]
+pub struct Modules {
+ info: HashMap<deno_mod, ModuleInfo>,
+ by_name: ModuleNameMap,
+}
+
+impl Modules {
+ pub fn new() -> Modules {
+ Self {
+ info: HashMap::new(),
+ by_name: ModuleNameMap::new(),
+ }
+ }
+
+ pub fn get_id(&self, name: &str) -> Option<deno_mod> {
+ self.by_name.get(name)
+ }
+
+ pub fn get_children(&self, id: deno_mod) -> Option<&Vec<String>> {
+ self.info.get(&id).map(|i| &i.children)
+ }
+
+ pub fn get_children2(&self, name: &str) -> Option<&Vec<String>> {
+ self.get_id(name).and_then(|id| self.get_children(id))
+ }
+
+ 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 add_child(&mut self, parent_id: deno_mod, child_name: &str) -> bool {
+ self
+ .info
+ .get_mut(&parent_id)
+ .map(move |i| {
+ if !i.has_child(&child_name) {
+ i.children.push(child_name.to_string());
+ }
+ })
+ .is_some()
+ }
+
+ pub fn register(&mut self, id: deno_mod, name: &str) {
+ let name = String::from(name);
+ debug!("register_complete {}", name);
+
+ self.by_name.insert(name.clone(), id);
+ self.info.insert(
+ id,
+ ModuleInfo {
+ name,
+ children: Vec::new(),
+ },
+ );
+ }
+
+ pub fn alias(&mut self, name: &str, target: &str) {
+ self.by_name.alias(name.to_owned(), target.to_owned());
+ }
+
+ pub fn is_alias(&self, name: &str) -> bool {
+ self.by_name.is_alias(name)
+ }
+
+ pub fn deps(&self, url: &str) -> Option<Deps> {
+ Deps::new(self, url)
+ }
+}
+
+/// This is a tree structure representing the dependencies of a given module.
+/// Use Modules::deps to construct it. The 'deps' member is None if this module
+/// was already seen elsewher in the tree.
+#[derive(Debug, PartialEq)]
+pub struct Deps {
+ pub name: String,
+ pub deps: Option<Vec<Deps>>,
+ prefix: String,
+ is_last: bool,
+}
+
+impl Deps {
+ fn new(modules: &Modules, module_name: &str) -> Option<Deps> {
+ let mut seen = HashSet::new();
+ Self::helper(&mut seen, "".to_string(), true, modules, module_name)
+ }
+
+ fn helper(
+ seen: &mut HashSet<String>,
+ prefix: String,
+ is_last: bool,
+ modules: &Modules,
+ name: &str, // TODO(ry) rename url
+ ) -> Option<Deps> {
+ if seen.contains(name) {
+ Some(Deps {
+ name: name.to_string(),
+ prefix,
+ deps: None,
+ is_last,
+ })
+ } else {
+ let children = modules.get_children2(name)?;
+ seen.insert(name.to_string());
+ let child_count = children.len();
+ let deps: Vec<Deps> = children
+ .iter()
+ .enumerate()
+ .map(|(index, dep_name)| {
+ 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_name)
+ })
+ // If any of the children are missing, return None.
+ .collect::<Option<_>>()?;
+
+ Some(Deps {
+ name: name.to_string(),
+ prefix,
+ deps: Some(deps),
+ is_last,
+ })
+ }
+ }
+
+ pub fn to_json(&self) -> String {
+ let mut children = "[".to_string();
+
+ if let Some(ref deps) = self.deps {
+ for d in deps {
+ children.push_str(&d.to_json());
+ if !d.is_last {
+ children.push_str(",");
+ }
+ }
+ }
+ children.push_str("]");
+
+ format!("[\"{}\",{}]", self.name, children)
+ }
+}
+
+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(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::isolate::js_check;
+ use crate::isolate::tests::*;
+ use futures::Async;
+ use std::error::Error;
+ use std::fmt;
+
+ struct MockLoader {
+ pub loads: Arc<Mutex<Vec<String>>>,
+ pub isolate: Arc<Mutex<Isolate>>,
+ pub modules: Arc<Mutex<Modules>>,
+ }
+
+ impl MockLoader {
+ fn new() -> Self {
+ let modules = Modules::new();
+ let (isolate, _dispatch_count) = setup(Mode::AsyncImmediate);
+ Self {
+ loads: Arc::new(Mutex::new(Vec::new())),
+ isolate: Arc::new(Mutex::new(isolate)),
+ modules: Arc::new(Mutex::new(modules)),
+ }
+ }
+ }
+
+ fn mock_source_code(url: &str) -> Option<(&'static str, &'static str)> {
+ // (code, real_module_name)
+ let spec: Vec<&str> = url.split("file://").collect();
+ match spec[1] {
+ "/a.js" => Some((A_SRC, "file:///a.js")),
+ "/b.js" => Some((B_SRC, "file:///b.js")),
+ "/c.js" => Some((C_SRC, "file:///c.js")),
+ "/d.js" => Some((D_SRC, "file:///d.js")),
+ "/circular1.js" => Some((CIRCULAR1_SRC, "file:///circular1.js")),
+ "/circular2.js" => Some((CIRCULAR2_SRC, "file:///circular2.js")),
+ "/circular3.js" => Some((CIRCULAR3_SRC, "file:///circular3.js")),
+ "/redirect1.js" => Some((REDIRECT1_SRC, "file:///redirect1.js")),
+ // pretend redirect - real module name is different than one requested
+ "/redirect2.js" => Some((REDIRECT2_SRC, "file:///dir/redirect2.js")),
+ "/dir/redirect3.js" => Some((REDIRECT3_SRC, "file:///redirect3.js")),
+ "/slow.js" => Some((SLOW_SRC, "file:///slow.js")),
+ "/never_ready.js" => {
+ Some(("should never be Ready", "file:///never_ready.js"))
+ }
+ "/main.js" => Some((MAIN_SRC, "file:///main.js")),
+ "/bad_import.js" => Some((BAD_IMPORT_SRC, "file:///bad_import.js")),
+ _ => None,
+ }
+ }
+
+ #[derive(Debug, PartialEq)]
+ enum MockError {
+ ResolveErr,
+ LoadErr,
+ }
+
+ impl fmt::Display for MockError {
+ fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
+ unimplemented!()
+ }
+ }
+
+ impl Error for MockError {
+ fn cause(&self) -> Option<&dyn Error> {
+ unimplemented!()
+ }
+ }
+
+ struct DelayedSourceCodeFuture {
+ url: String,
+ counter: u32,
+ }
+
+ impl Future for DelayedSourceCodeFuture {
+ type Item = SourceCodeInfo;
+ type Error = ErrBox;
+
+ fn poll(&mut self) -> Poll<Self::Item, ErrBox> {
+ self.counter += 1;
+ if self.url == "file:///never_ready.js" {
+ return Ok(Async::NotReady);
+ }
+ if self.url == "file:///slow.js" && self.counter < 2 {
+ // TODO(ry) Hopefully in the future we can remove current task
+ // notification. See comment above run_in_task.
+ futures::task::current().notify();
+ return Ok(Async::NotReady);
+ }
+ match mock_source_code(&self.url) {
+ Some(src) => Ok(Async::Ready(SourceCodeInfo {
+ code: src.0.to_owned(),
+ module_url_specified: self.url.clone(),
+ module_url_found: src.1.to_owned(),
+ })),
+ None => Err(MockError::LoadErr.into()),
+ }
+ }
+ }
+
+ impl Loader for MockLoader {
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &str,
+ _is_root: bool,
+ _is_dyn_import: bool,
+ ) -> Result<ModuleSpecifier, ErrBox> {
+ let referrer = if referrer == "." {
+ "file:///"
+ } else {
+ referrer
+ };
+
+ eprintln!(">> RESOLVING, S: {}, R: {}", specifier, referrer);
+
+ let output_specifier =
+ match ModuleSpecifier::resolve_import(specifier, referrer) {
+ Ok(specifier) => specifier,
+ Err(..) => return Err(MockError::ResolveErr.into()),
+ };
+
+ if mock_source_code(&output_specifier.to_string()).is_some() {
+ Ok(output_specifier)
+ } else {
+ Err(MockError::ResolveErr.into())
+ }
+ }
+
+ fn load(
+ &self,
+ module_specifier: &ModuleSpecifier,
+ ) -> Box<SourceCodeInfoFuture> {
+ let mut loads = self.loads.lock().unwrap();
+ loads.push(module_specifier.to_string());
+ let url = module_specifier.to_string();
+ Box::new(DelayedSourceCodeFuture { url, counter: 0 })
+ }
+ }
+
+ const A_SRC: &str = r#"
+ import { b } from "/b.js";
+ import { c } from "/c.js";
+ if (b() != 'b') throw Error();
+ if (c() != 'c') throw Error();
+ if (!import.meta.main) throw Error();
+ if (import.meta.url != 'file:///a.js') throw Error();
+ "#;
+
+ const B_SRC: &str = r#"
+ import { c } from "/c.js";
+ if (c() != 'c') throw Error();
+ export function b() { return 'b'; }
+ if (import.meta.main) throw Error();
+ if (import.meta.url != 'file:///b.js') throw Error();
+ "#;
+
+ const C_SRC: &str = r#"
+ import { d } from "/d.js";
+ export function c() { return 'c'; }
+ if (d() != 'd') throw Error();
+ if (import.meta.main) throw Error();
+ if (import.meta.url != 'file:///c.js') throw Error();
+ "#;
+
+ const D_SRC: &str = r#"
+ export function d() { return 'd'; }
+ if (import.meta.main) throw Error();
+ if (import.meta.url != 'file:///d.js') throw Error();
+ "#;
+
+ // TODO(ry) Sadly FuturesUnordered requires the current task to be set. So
+ // even though we are only using poll() in these tests and not Tokio, we must
+ // nevertheless run it in the tokio executor. Ideally run_in_task can be
+ // removed in the future.
+ use crate::isolate::tests::run_in_task;
+
+ #[test]
+ fn test_recursive_load() {
+ run_in_task(|| {
+ let loader = MockLoader::new();
+ let modules = loader.modules.clone();
+ let modules_ = modules.clone();
+ let isolate = loader.isolate.clone();
+ let isolate_ = isolate.clone();
+ let loads = loader.loads.clone();
+ let mut recursive_load = RecursiveLoad::main("/a.js", loader, modules);
+
+ let a_id = loop {
+ match recursive_load.poll() {
+ Ok(Ready(Some(Event::Fetch(info)))) => {
+ let mut isolate = isolate.lock().unwrap();
+ recursive_load.register(info, &mut isolate).unwrap();
+ }
+ Ok(Ready(Some(Event::Instantiate(id)))) => break id,
+ _ => panic!("unexpected result"),
+ };
+ };
+
+ let mut isolate = isolate_.lock().unwrap();
+ js_check(isolate.mod_evaluate(a_id));
+
+ let l = loads.lock().unwrap();
+ assert_eq!(
+ l.to_vec(),
+ vec![
+ "file:///a.js",
+ "file:///b.js",
+ "file:///c.js",
+ "file:///d.js"
+ ]
+ );
+
+ let modules = modules_.lock().unwrap();
+
+ assert_eq!(modules.get_id("file:///a.js"), Some(a_id));
+ let b_id = modules.get_id("file:///b.js").unwrap();
+ let c_id = modules.get_id("file:///c.js").unwrap();
+ let d_id = modules.get_id("file:///d.js").unwrap();
+
+ assert_eq!(
+ modules.get_children(a_id),
+ Some(&vec![
+ "file:///b.js".to_string(),
+ "file:///c.js".to_string()
+ ])
+ );
+ assert_eq!(
+ modules.get_children(b_id),
+ Some(&vec!["file:///c.js".to_string()])
+ );
+ assert_eq!(
+ modules.get_children(c_id),
+ Some(&vec!["file:///d.js".to_string()])
+ );
+ assert_eq!(modules.get_children(d_id), Some(&vec![]));
+ })
+ }
+
+ const CIRCULAR1_SRC: &str = r#"
+ import "/circular2.js";
+ Deno.core.print("circular1");
+ "#;
+
+ const CIRCULAR2_SRC: &str = r#"
+ import "/circular3.js";
+ Deno.core.print("circular2");
+ "#;
+
+ const CIRCULAR3_SRC: &str = r#"
+ import "/circular1.js";
+ import "/circular2.js";
+ Deno.core.print("circular3");
+ "#;
+
+ #[test]
+ fn test_circular_load() {
+ run_in_task(|| {
+ let loader = MockLoader::new();
+ let isolate = loader.isolate.clone();
+ let isolate_ = isolate.clone();
+ let modules = loader.modules.clone();
+ let modules_ = modules.clone();
+ let loads = loader.loads.clone();
+ let recursive_load =
+ RecursiveLoad::main("/circular1.js", loader, modules);
+ let result = recursive_load.get_future(isolate.clone()).poll();
+ assert!(result.is_ok());
+ if let Async::Ready(circular1_id) = result.ok().unwrap() {
+ let mut isolate = isolate_.lock().unwrap();
+ js_check(isolate.mod_evaluate(circular1_id));
+
+ let l = loads.lock().unwrap();
+ assert_eq!(
+ l.to_vec(),
+ vec![
+ "file:///circular1.js",
+ "file:///circular2.js",
+ "file:///circular3.js"
+ ]
+ );
+
+ let modules = modules_.lock().unwrap();
+
+ assert_eq!(modules.get_id("file:///circular1.js"), Some(circular1_id));
+ let circular2_id = modules.get_id("file:///circular2.js").unwrap();
+
+ assert_eq!(
+ modules.get_children(circular1_id),
+ Some(&vec!["file:///circular2.js".to_string()])
+ );
+
+ assert_eq!(
+ modules.get_children(circular2_id),
+ Some(&vec!["file:///circular3.js".to_string()])
+ );
+
+ assert!(modules.get_id("file:///circular3.js").is_some());
+ let circular3_id = modules.get_id("file:///circular3.js").unwrap();
+ assert_eq!(
+ modules.get_children(circular3_id),
+ Some(&vec![
+ "file:///circular1.js".to_string(),
+ "file:///circular2.js".to_string()
+ ])
+ );
+ } else {
+ unreachable!();
+ }
+ })
+ }
+
+ const REDIRECT1_SRC: &str = r#"
+ import "./redirect2.js";
+ Deno.core.print("redirect1");
+ "#;
+
+ const REDIRECT2_SRC: &str = r#"
+ import "./redirect3.js";
+ Deno.core.print("redirect2");
+ "#;
+
+ const REDIRECT3_SRC: &str = r#"
+ Deno.core.print("redirect3");
+ "#;
+
+ #[test]
+ fn test_redirect_load() {
+ run_in_task(|| {
+ let loader = MockLoader::new();
+ let isolate = loader.isolate.clone();
+ let isolate_ = isolate.clone();
+ let modules = loader.modules.clone();
+ let modules_ = modules.clone();
+ let loads = loader.loads.clone();
+ let recursive_load =
+ RecursiveLoad::main("/redirect1.js", loader, modules);
+ let result = recursive_load.get_future(isolate.clone()).poll();
+ println!(">> result {:?}", result);
+ assert!(result.is_ok());
+ if let Async::Ready(redirect1_id) = result.ok().unwrap() {
+ let mut isolate = isolate_.lock().unwrap();
+ js_check(isolate.mod_evaluate(redirect1_id));
+ let l = loads.lock().unwrap();
+ assert_eq!(
+ l.to_vec(),
+ vec![
+ "file:///redirect1.js",
+ "file:///redirect2.js",
+ "file:///dir/redirect3.js"
+ ]
+ );
+
+ let modules = modules_.lock().unwrap();
+
+ assert_eq!(modules.get_id("file:///redirect1.js"), Some(redirect1_id));
+
+ let redirect2_id = modules.get_id("file:///dir/redirect2.js").unwrap();
+ assert!(modules.is_alias("file:///redirect2.js"));
+ assert!(!modules.is_alias("file:///dir/redirect2.js"));
+ assert_eq!(modules.get_id("file:///redirect2.js"), Some(redirect2_id));
+
+ let redirect3_id = modules.get_id("file:///redirect3.js").unwrap();
+ assert!(modules.is_alias("file:///dir/redirect3.js"));
+ assert!(!modules.is_alias("file:///redirect3.js"));
+ assert_eq!(
+ modules.get_id("file:///dir/redirect3.js"),
+ Some(redirect3_id)
+ );
+ } else {
+ unreachable!();
+ }
+ })
+ }
+
+ // main.js
+ const MAIN_SRC: &str = r#"
+ // never_ready.js never loads.
+ import "/never_ready.js";
+ // slow.js resolves after one tick.
+ import "/slow.js";
+ "#;
+
+ // slow.js
+ const SLOW_SRC: &str = r#"
+ // Circular import of never_ready.js
+ // Does this trigger two Loader calls? It shouldn't.
+ import "/never_ready.js";
+ import "/a.js";
+ "#;
+
+ #[test]
+ fn slow_never_ready_modules() {
+ run_in_task(|| {
+ let loader = MockLoader::new();
+ let isolate = loader.isolate.clone();
+ let modules = loader.modules.clone();
+ let loads = loader.loads.clone();
+ let mut recursive_load =
+ RecursiveLoad::main("/main.js", loader, modules).get_future(isolate);
+
+ let result = recursive_load.poll();
+ assert!(result.is_ok());
+ assert!(result.ok().unwrap().is_not_ready());
+
+ // TODO(ry) Arguably the first time we poll only the following modules
+ // should be loaded:
+ // "file:///main.js",
+ // "file:///never_ready.js",
+ // "file:///slow.js"
+ // But due to current task notification in DelayedSourceCodeFuture they
+ // all get loaded in a single poll. Also see the comment above
+ // run_in_task.
+
+ for _ in 0..10 {
+ let result = recursive_load.poll();
+ assert!(result.is_ok());
+ assert!(result.ok().unwrap().is_not_ready());
+ let l = loads.lock().unwrap();;
+ assert_eq!(
+ l.to_vec(),
+ vec![
+ "file:///main.js",
+ "file:///never_ready.js",
+ "file:///slow.js",
+ "file:///a.js",
+ "file:///b.js",
+ "file:///c.js",
+ "file:///d.js"
+ ]
+ );
+ }
+ })
+ }
+
+ // bad_import.js
+ const BAD_IMPORT_SRC: &str = r#"
+ import "foo";
+ "#;
+
+ #[test]
+ fn loader_disappears_after_error() {
+ run_in_task(|| {
+ let loader = MockLoader::new();
+ let isolate = loader.isolate.clone();
+ let modules = loader.modules.clone();
+ let recursive_load =
+ RecursiveLoad::main("/bad_import.js", loader, modules);
+ let result = recursive_load.get_future(isolate).poll();
+ assert!(result.is_err());
+ let err = result.err().unwrap();
+ assert_eq!(
+ err.downcast_ref::<MockError>().unwrap(),
+ &MockError::ResolveErr
+ );
+ })
+ }
+
+ #[test]
+ fn empty_deps() {
+ let modules = Modules::new();
+ assert!(modules.deps("foo").is_none());
+ }
+
+ #[test]
+ fn deps() {
+ // "foo" -> "bar"
+ let mut modules = Modules::new();
+ modules.register(1, "foo");
+ modules.register(2, "bar");
+ modules.add_child(1, "bar");
+ let maybe_deps = modules.deps("foo");
+ assert!(maybe_deps.is_some());
+ let mut foo_deps = maybe_deps.unwrap();
+ assert_eq!(foo_deps.name, "foo");
+ assert!(foo_deps.deps.is_some());
+ let foo_children = foo_deps.deps.take().unwrap();
+ assert_eq!(foo_children.len(), 1);
+ let bar_deps = &foo_children[0];
+ assert_eq!(bar_deps.name, "bar");
+ assert_eq!(bar_deps.deps, Some(vec![]));
+ }
+
+ #[test]
+ fn test_deps_to_json() {
+ let mut modules = Modules::new();
+ modules.register(1, "foo");
+ modules.register(2, "bar");
+ modules.register(3, "baz");
+ modules.register(4, "zuh");
+ modules.add_child(1, "bar");
+ modules.add_child(1, "baz");
+ modules.add_child(3, "zuh");
+ let maybe_deps = modules.deps("foo");
+ assert!(maybe_deps.is_some());
+ assert_eq!(
+ "[\"foo\",[[\"bar\",[]],[\"baz\",[[\"zuh\",[]]]]]]",
+ maybe_deps.unwrap().to_json()
+ );
+ }
+}
diff --git a/core/ops.rs b/core/ops.rs
new file mode 100644
index 000000000..84c15e096
--- /dev/null
+++ b/core/ops.rs
@@ -0,0 +1,111 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+pub use crate::libdeno::OpId;
+use crate::PinnedBuf;
+use futures::Future;
+use std::collections::HashMap;
+
+pub type Buf = Box<[u8]>;
+
+pub type OpAsyncFuture<E> = Box<dyn Future<Item = Buf, Error = E> + Send>;
+
+pub(crate) type PendingOpFuture =
+ Box<dyn Future<Item = (OpId, Buf), Error = CoreError> + Send>;
+
+pub type OpResult<E> = Result<Op<E>, E>;
+
+pub enum Op<E> {
+ Sync(Buf),
+ Async(OpAsyncFuture<E>),
+}
+
+pub type CoreError = ();
+
+pub type CoreOp = Op<CoreError>;
+
+/// Main type describing op
+type OpDispatcher = dyn Fn(&[u8], Option<PinnedBuf>) -> CoreOp;
+
+#[derive(Default)]
+pub struct OpRegistry {
+ dispatchers: Vec<Box<OpDispatcher>>,
+ name_to_id: HashMap<String, OpId>,
+}
+
+impl OpRegistry {
+ pub fn new() -> Self {
+ let mut registry = Self::default();
+ let op_id = registry.register("ops", |_, _| {
+ // ops is a special op which is handled in call.
+ unreachable!()
+ });
+ assert_eq!(op_id, 0);
+ registry
+ }
+
+ pub fn register<F>(&mut self, name: &str, op: F) -> OpId
+ where
+ F: Fn(&[u8], Option<PinnedBuf>) -> CoreOp + Send + Sync + 'static,
+ {
+ let op_id = self.dispatchers.len() as u32;
+
+ let existing = self.name_to_id.insert(name.to_string(), op_id);
+ assert!(
+ existing.is_none(),
+ format!("Op already registered: {}", name)
+ );
+
+ self.dispatchers.push(Box::new(op));
+ op_id
+ }
+
+ fn json_map(&self) -> Buf {
+ let op_map_json = serde_json::to_string(&self.name_to_id).unwrap();
+ op_map_json.as_bytes().to_owned().into_boxed_slice()
+ }
+
+ pub fn call(
+ &self,
+ op_id: OpId,
+ control: &[u8],
+ zero_copy_buf: Option<PinnedBuf>,
+ ) -> CoreOp {
+ // Op with id 0 has special meaning - it's a special op that is always
+ // provided to retrieve op id map. The map consists of name to `OpId`
+ // mappings.
+ if op_id == 0 {
+ return Op::Sync(self.json_map());
+ }
+
+ let d = &*self.dispatchers.get(op_id as usize).expect("Op not found!");
+ d(control, zero_copy_buf)
+ }
+}
+
+#[test]
+fn test_op_registry() {
+ use std::sync::atomic;
+ use std::sync::Arc;
+ let mut op_registry = OpRegistry::new();
+
+ let c = Arc::new(atomic::AtomicUsize::new(0));
+ let c_ = c.clone();
+
+ let test_id = op_registry.register("test", move |_, _| {
+ c_.fetch_add(1, atomic::Ordering::SeqCst);
+ CoreOp::Sync(Box::new([]))
+ });
+ assert!(test_id != 0);
+
+ let mut expected = HashMap::new();
+ expected.insert("ops".to_string(), 0);
+ expected.insert("test".to_string(), 1);
+ assert_eq!(op_registry.name_to_id, expected);
+
+ let res = op_registry.call(test_id, &[], None);
+ if let Op::Sync(buf) = res {
+ assert_eq!(buf.len(), 0);
+ } else {
+ unreachable!();
+ }
+ assert_eq!(c.load(atomic::Ordering::SeqCst), 1);
+}
diff --git a/core/shared_queue.js b/core/shared_queue.js
new file mode 100644
index 000000000..7eeb61255
--- /dev/null
+++ b/core/shared_queue.js
@@ -0,0 +1,205 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+/*
+SharedQueue Binary Layout
++-------------------------------+-------------------------------+
+| NUM_RECORDS (32) |
++---------------------------------------------------------------+
+| NUM_SHIFTED_OFF (32) |
++---------------------------------------------------------------+
+| HEAD (32) |
++---------------------------------------------------------------+
+| OFFSETS (32) |
++---------------------------------------------------------------+
+| RECORD_ENDS (*MAX_RECORDS) ...
++---------------------------------------------------------------+
+| RECORDS (*MAX_RECORDS) ...
++---------------------------------------------------------------+
+ */
+
+/* eslint-disable @typescript-eslint/no-use-before-define */
+
+(window => {
+ const GLOBAL_NAMESPACE = "Deno";
+ const CORE_NAMESPACE = "core";
+ const MAX_RECORDS = 100;
+ const INDEX_NUM_RECORDS = 0;
+ const INDEX_NUM_SHIFTED_OFF = 1;
+ const INDEX_HEAD = 2;
+ const INDEX_OFFSETS = 3;
+ const INDEX_RECORDS = INDEX_OFFSETS + 2 * MAX_RECORDS;
+ const HEAD_INIT = 4 * INDEX_RECORDS;
+
+ // Available on start due to bindings.
+ const Deno = window[GLOBAL_NAMESPACE];
+ const core = Deno[CORE_NAMESPACE];
+ // Warning: DO NOT use window.Deno after this point.
+ // It is possible that the Deno namespace has been deleted.
+ // Use the above local Deno and core variable instead.
+
+ let sharedBytes;
+ let shared32;
+ let initialized = false;
+
+ function maybeInit() {
+ if (!initialized) {
+ init();
+ initialized = true;
+ }
+ }
+
+ function init() {
+ const shared = Deno.core.shared;
+ assert(shared.byteLength > 0);
+ assert(sharedBytes == null);
+ assert(shared32 == null);
+ sharedBytes = new Uint8Array(shared);
+ shared32 = new Int32Array(shared);
+ // Callers should not call Deno.core.recv, use setAsyncHandler.
+ Deno.core.recv(handleAsyncMsgFromRust);
+ }
+
+ function ops() {
+ // op id 0 is a special value to retreive the map of registered ops.
+ const opsMapBytes = Deno.core.send(0, new Uint8Array([]), null);
+ const opsMapJson = String.fromCharCode.apply(null, opsMapBytes);
+ return JSON.parse(opsMapJson);
+ }
+
+ function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+ }
+
+ function reset() {
+ maybeInit();
+ shared32[INDEX_NUM_RECORDS] = 0;
+ shared32[INDEX_NUM_SHIFTED_OFF] = 0;
+ shared32[INDEX_HEAD] = HEAD_INIT;
+ }
+
+ function head() {
+ maybeInit();
+ return shared32[INDEX_HEAD];
+ }
+
+ function numRecords() {
+ return shared32[INDEX_NUM_RECORDS];
+ }
+
+ function size() {
+ return shared32[INDEX_NUM_RECORDS] - shared32[INDEX_NUM_SHIFTED_OFF];
+ }
+
+ function setMeta(index, end, opId) {
+ shared32[INDEX_OFFSETS + 2 * index] = end;
+ shared32[INDEX_OFFSETS + 2 * index + 1] = opId;
+ }
+
+ function getMeta(index) {
+ if (index < numRecords()) {
+ const buf = shared32[INDEX_OFFSETS + 2 * index];
+ const opId = shared32[INDEX_OFFSETS + 2 * index + 1];
+ return [opId, buf];
+ } else {
+ return null;
+ }
+ }
+
+ function getOffset(index) {
+ if (index < numRecords()) {
+ if (index == 0) {
+ return HEAD_INIT;
+ } else {
+ return shared32[INDEX_OFFSETS + 2 * (index - 1)];
+ }
+ } else {
+ return null;
+ }
+ }
+
+ function push(opId, buf) {
+ const off = head();
+ const end = off + buf.byteLength;
+ const index = numRecords();
+ if (end > shared32.byteLength || index >= MAX_RECORDS) {
+ // console.log("shared_queue.js push fail");
+ return false;
+ }
+ setMeta(index, end, opId);
+ assert(end - off == buf.byteLength);
+ sharedBytes.set(buf, off);
+ shared32[INDEX_NUM_RECORDS] += 1;
+ shared32[INDEX_HEAD] = end;
+ return true;
+ }
+
+ /// Returns null if empty.
+ function shift() {
+ const i = shared32[INDEX_NUM_SHIFTED_OFF];
+ if (size() == 0) {
+ assert(i == 0);
+ return null;
+ }
+
+ const off = getOffset(i);
+ const [opId, end] = getMeta(i);
+
+ if (size() > 1) {
+ shared32[INDEX_NUM_SHIFTED_OFF] += 1;
+ } else {
+ reset();
+ }
+
+ assert(off != null);
+ assert(end != null);
+ const buf = sharedBytes.subarray(off, end);
+ return [opId, buf];
+ }
+
+ let asyncHandler;
+ function setAsyncHandler(cb) {
+ maybeInit();
+ assert(asyncHandler == null);
+ asyncHandler = cb;
+ }
+
+ function handleAsyncMsgFromRust(opId, buf) {
+ if (buf) {
+ // This is the overflow_response case of deno::Isolate::poll().
+ asyncHandler(opId, buf);
+ } else {
+ while (true) {
+ const opIdBuf = shift();
+ if (opIdBuf == null) {
+ break;
+ }
+ asyncHandler(...opIdBuf);
+ }
+ }
+ }
+
+ function dispatch(opId, control, zeroCopy = null) {
+ maybeInit();
+ return Deno.core.send(opId, control, zeroCopy);
+ }
+
+ const denoCore = {
+ setAsyncHandler,
+ dispatch,
+ sharedQueue: {
+ MAX_RECORDS,
+ head,
+ numRecords,
+ size,
+ push,
+ reset,
+ shift
+ },
+ ops
+ };
+
+ assert(window[GLOBAL_NAMESPACE] != null);
+ assert(window[GLOBAL_NAMESPACE][CORE_NAMESPACE] != null);
+ Object.assign(core, denoCore);
+})(this);
diff --git a/core/shared_queue.rs b/core/shared_queue.rs
new file mode 100644
index 000000000..dbb738f15
--- /dev/null
+++ b/core/shared_queue.rs
@@ -0,0 +1,289 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+/*
+SharedQueue Binary Layout
++-------------------------------+-------------------------------+
+| NUM_RECORDS (32) |
++---------------------------------------------------------------+
+| NUM_SHIFTED_OFF (32) |
++---------------------------------------------------------------+
+| HEAD (32) |
++---------------------------------------------------------------+
+| OFFSETS (32) |
++---------------------------------------------------------------+
+| RECORD_ENDS (*MAX_RECORDS) ...
++---------------------------------------------------------------+
+| RECORDS (*MAX_RECORDS) ...
++---------------------------------------------------------------+
+ */
+
+use crate::libdeno::deno_buf;
+use crate::libdeno::OpId;
+
+const MAX_RECORDS: usize = 100;
+/// Total number of records added.
+const INDEX_NUM_RECORDS: usize = 0;
+/// Number of records that have been shifted off.
+const INDEX_NUM_SHIFTED_OFF: usize = 1;
+/// The head is the number of initialized bytes in SharedQueue.
+/// It grows monotonically.
+const INDEX_HEAD: usize = 2;
+const INDEX_OFFSETS: usize = 3;
+const INDEX_RECORDS: usize = INDEX_OFFSETS + 2 * MAX_RECORDS;
+/// Byte offset of where the records begin. Also where the head starts.
+const HEAD_INIT: usize = 4 * INDEX_RECORDS;
+/// A rough guess at how big we should make the shared buffer in bytes.
+pub const RECOMMENDED_SIZE: usize = 128 * MAX_RECORDS;
+
+pub struct SharedQueue {
+ bytes: Vec<u8>,
+}
+
+impl SharedQueue {
+ pub fn new(len: usize) -> Self {
+ let mut bytes = Vec::new();
+ bytes.resize(HEAD_INIT + len, 0);
+ let mut q = Self { bytes };
+ q.reset();
+ q
+ }
+
+ pub fn as_deno_buf(&self) -> deno_buf {
+ let ptr = self.bytes.as_ptr();
+ let len = self.bytes.len();
+ unsafe { deno_buf::from_raw_parts(ptr, len) }
+ }
+
+ fn reset(&mut self) {
+ debug!("rust:shared_queue:reset");
+ let s: &mut [u32] = self.as_u32_slice_mut();
+ s[INDEX_NUM_RECORDS] = 0;
+ s[INDEX_NUM_SHIFTED_OFF] = 0;
+ s[INDEX_HEAD] = HEAD_INIT as u32;
+ }
+
+ fn as_u32_slice(&self) -> &[u32] {
+ let p = self.bytes.as_ptr();
+ // Assert pointer is 32 bit aligned before casting.
+ assert_eq!((p as usize) % std::mem::align_of::<u32>(), 0);
+ #[allow(clippy::cast_ptr_alignment)]
+ let p32 = p as *const u32;
+ unsafe { std::slice::from_raw_parts(p32, self.bytes.len() / 4) }
+ }
+
+ fn as_u32_slice_mut(&mut self) -> &mut [u32] {
+ let p = self.bytes.as_mut_ptr();
+ // Assert pointer is 32 bit aligned before casting.
+ assert_eq!((p as usize) % std::mem::align_of::<u32>(), 0);
+ #[allow(clippy::cast_ptr_alignment)]
+ let p32 = p as *mut u32;
+ unsafe { std::slice::from_raw_parts_mut(p32, self.bytes.len() / 4) }
+ }
+
+ pub fn size(&self) -> usize {
+ let s = self.as_u32_slice();
+ (s[INDEX_NUM_RECORDS] - s[INDEX_NUM_SHIFTED_OFF]) as usize
+ }
+
+ fn num_records(&self) -> usize {
+ let s = self.as_u32_slice();
+ s[INDEX_NUM_RECORDS] as usize
+ }
+
+ fn head(&self) -> usize {
+ let s = self.as_u32_slice();
+ s[INDEX_HEAD] as usize
+ }
+
+ fn num_shifted_off(&self) -> usize {
+ let s = self.as_u32_slice();
+ s[INDEX_NUM_SHIFTED_OFF] as usize
+ }
+
+ fn set_meta(&mut self, index: usize, end: usize, op_id: OpId) {
+ let s = self.as_u32_slice_mut();
+ s[INDEX_OFFSETS + 2 * index] = end as u32;
+ s[INDEX_OFFSETS + 2 * index + 1] = op_id;
+ }
+
+ #[cfg(test)]
+ fn get_meta(&self, index: usize) -> Option<(OpId, usize)> {
+ if index < self.num_records() {
+ let s = self.as_u32_slice();
+ let end = s[INDEX_OFFSETS + 2 * index] as usize;
+ let op_id = s[INDEX_OFFSETS + 2 * index + 1];
+ Some((op_id, end))
+ } else {
+ None
+ }
+ }
+
+ #[cfg(test)]
+ fn get_offset(&self, index: usize) -> Option<usize> {
+ if index < self.num_records() {
+ Some(if index == 0 {
+ HEAD_INIT
+ } else {
+ let s = self.as_u32_slice();
+ s[INDEX_OFFSETS + 2 * (index - 1)] as usize
+ })
+ } else {
+ None
+ }
+ }
+
+ /// Returns none if empty.
+ #[cfg(test)]
+ pub fn shift(&mut self) -> Option<(OpId, &[u8])> {
+ let u32_slice = self.as_u32_slice();
+ let i = u32_slice[INDEX_NUM_SHIFTED_OFF] as usize;
+ if self.size() == 0 {
+ assert_eq!(i, 0);
+ return None;
+ }
+
+ let off = self.get_offset(i).unwrap();
+ let (op_id, end) = self.get_meta(i).unwrap();
+
+ if self.size() > 1 {
+ let u32_slice = self.as_u32_slice_mut();
+ u32_slice[INDEX_NUM_SHIFTED_OFF] += 1;
+ } else {
+ self.reset();
+ }
+ println!(
+ "rust:shared_queue:shift: num_records={}, num_shifted_off={}, head={}",
+ self.num_records(),
+ self.num_shifted_off(),
+ self.head()
+ );
+ Some((op_id, &self.bytes[off..end]))
+ }
+
+ /// Because JS-side may cast `record` to Int32Array it is required
+ /// that `record`'s length is divisible by 4.
+ pub fn push(&mut self, op_id: OpId, record: &[u8]) -> bool {
+ let off = self.head();
+ let end = off + record.len();
+ debug!(
+ "rust:shared_queue:pre-push: op={}, off={}, end={}, len={}",
+ op_id,
+ off,
+ end,
+ record.len()
+ );
+ assert_eq!(record.len() % 4, 0);
+ let index = self.num_records();
+ if end > self.bytes.len() || index >= MAX_RECORDS {
+ debug!("WARNING the sharedQueue overflowed");
+ return false;
+ }
+ self.set_meta(index, end, op_id);
+ assert_eq!(end - off, record.len());
+ self.bytes[off..end].copy_from_slice(record);
+ let u32_slice = self.as_u32_slice_mut();
+ u32_slice[INDEX_NUM_RECORDS] += 1;
+ u32_slice[INDEX_HEAD] = end as u32;
+ debug!(
+ "rust:shared_queue:push: num_records={}, num_shifted_off={}, head={}",
+ self.num_records(),
+ self.num_shifted_off(),
+ self.head()
+ );
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ops::Buf;
+
+ #[test]
+ fn basic() {
+ let mut q = SharedQueue::new(RECOMMENDED_SIZE);
+
+ let h = q.head();
+ assert!(h > 0);
+
+ let r = vec![1u8, 2, 3, 4].into_boxed_slice();
+ let len = r.len() + h;
+ assert!(q.push(0, &r));
+ assert_eq!(q.head(), len);
+
+ let r = vec![5, 6, 7, 8].into_boxed_slice();
+ assert!(q.push(0, &r));
+
+ let r = vec![9, 10, 11, 12].into_boxed_slice();
+ assert!(q.push(0, &r));
+ assert_eq!(q.num_records(), 3);
+ assert_eq!(q.size(), 3);
+
+ let (_op_id, r) = q.shift().unwrap();
+ assert_eq!(r, vec![1, 2, 3, 4].as_slice());
+ assert_eq!(q.num_records(), 3);
+ assert_eq!(q.size(), 2);
+
+ let (_op_id, r) = q.shift().unwrap();
+ assert_eq!(r, vec![5, 6, 7, 8].as_slice());
+ assert_eq!(q.num_records(), 3);
+ assert_eq!(q.size(), 1);
+
+ let (_op_id, r) = q.shift().unwrap();
+ assert_eq!(r, vec![9, 10, 11, 12].as_slice());
+ assert_eq!(q.num_records(), 0);
+ assert_eq!(q.size(), 0);
+
+ assert!(q.shift().is_none());
+ assert!(q.shift().is_none());
+
+ assert_eq!(q.num_records(), 0);
+ assert_eq!(q.size(), 0);
+ }
+
+ fn alloc_buf(byte_length: usize) -> Buf {
+ let mut v = Vec::new();
+ v.resize(byte_length, 0);
+ v.into_boxed_slice()
+ }
+
+ #[test]
+ fn overflow() {
+ let mut q = SharedQueue::new(RECOMMENDED_SIZE);
+ assert!(q.push(0, &alloc_buf(RECOMMENDED_SIZE - 4)));
+ assert_eq!(q.size(), 1);
+ assert!(!q.push(0, &alloc_buf(8)));
+ assert_eq!(q.size(), 1);
+ assert!(q.push(0, &alloc_buf(4)));
+ assert_eq!(q.size(), 2);
+
+ let (_op_id, buf) = q.shift().unwrap();
+ assert_eq!(buf.len(), RECOMMENDED_SIZE - 4);
+ assert_eq!(q.size(), 1);
+
+ assert!(!q.push(0, &alloc_buf(4)));
+
+ let (_op_id, buf) = q.shift().unwrap();
+ assert_eq!(buf.len(), 4);
+ assert_eq!(q.size(), 0);
+ }
+
+ #[test]
+ fn full_records() {
+ let mut q = SharedQueue::new(RECOMMENDED_SIZE);
+ for _ in 0..MAX_RECORDS {
+ assert!(q.push(0, &alloc_buf(4)))
+ }
+ assert_eq!(q.push(0, &alloc_buf(4)), false);
+ // Even if we shift one off, we still cannot push a new record.
+ let _ignored = q.shift().unwrap();
+ assert_eq!(q.push(0, &alloc_buf(4)), false);
+ }
+
+ #[test]
+ #[should_panic]
+ fn bad_buf_length() {
+ let mut q = SharedQueue::new(RECOMMENDED_SIZE);
+ // check that `record` that has length not a multiple of 4 will cause panic
+ q.push(0, &alloc_buf(3));
+ }
+}
diff --git a/core/shared_queue_test.js b/core/shared_queue_test.js
new file mode 100644
index 000000000..ff9bb4dd8
--- /dev/null
+++ b/core/shared_queue_test.js
@@ -0,0 +1,83 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+
+// Check overflow (corresponds to full_records test in rust)
+function fullRecords(q) {
+ q.reset();
+ const oneByte = new Uint8Array([42]);
+ for (let i = 0; i < q.MAX_RECORDS; i++) {
+ assert(q.push(1, oneByte));
+ }
+ assert(!q.push(1, oneByte));
+ const [opId, r] = q.shift();
+ assert(opId == 1);
+ assert(r.byteLength == 1);
+ assert(r[0] == 42);
+ // Even if we shift one off, we still cannot push a new record.
+ assert(!q.push(1, oneByte));
+}
+
+function main() {
+ const q = Deno.core.sharedQueue;
+
+ const h = q.head();
+ assert(h > 0);
+
+ let r = new Uint8Array([1, 2, 3, 4, 5]);
+ const len = r.byteLength + h;
+ assert(q.push(1, r));
+ assert(q.head() == len);
+
+ r = new Uint8Array([6, 7]);
+ assert(q.push(1, r));
+
+ r = new Uint8Array([8, 9, 10, 11]);
+ assert(q.push(1, r));
+ assert(q.numRecords() == 3);
+ assert(q.size() == 3);
+
+ let opId;
+ [opId, r] = q.shift();
+ assert(r.byteLength == 5);
+ assert(r[0] == 1);
+ assert(r[1] == 2);
+ assert(r[2] == 3);
+ assert(r[3] == 4);
+ assert(r[4] == 5);
+ assert(q.numRecords() == 3);
+ assert(q.size() == 2);
+
+ [opId, r] = q.shift();
+ assert(r.byteLength == 2);
+ assert(r[0] == 6);
+ assert(r[1] == 7);
+ assert(q.numRecords() == 3);
+ assert(q.size() == 1);
+
+ [opId, r] = q.shift();
+ assert(opId == 1);
+ assert(r.byteLength == 4);
+ assert(r[0] == 8);
+ assert(r[1] == 9);
+ assert(r[2] == 10);
+ assert(r[3] == 11);
+ assert(q.numRecords() == 0);
+ assert(q.size() == 0);
+
+ assert(q.shift() == null);
+ assert(q.shift() == null);
+ assert(q.numRecords() == 0);
+ assert(q.size() == 0);
+
+ fullRecords(q);
+
+ Deno.core.print("shared_queue_test.js ok\n");
+ q.reset();
+}
+
+main();