├── .dir-locals.el ├── .editorconfig ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── examples └── hello-world.rs ├── src ├── allocator.rs ├── context.rs ├── error.rs ├── isolate.rs ├── lib.rs ├── platform.rs ├── script.rs ├── template.rs ├── util.rs └── value.rs ├── v8-api ├── Cargo.toml └── src │ ├── bin │ └── v8-api-rs-parse.rs │ └── lib.rs └── v8-sys ├── Cargo.toml ├── build.rs └── src ├── lib.rs ├── v8-glue.cc ├── v8-glue.h └── v8-trampoline.h /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((rust-mode . ((fill-column . 100)))) 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.rs] 10 | indent_size = 4 11 | 12 | [*.js] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | dist: trusty 3 | language: rust 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - sourceline: 'ppa:pinepain/libv8-5.4' 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | # Modern compilers 12 | - gcc-6 13 | - g++-6 14 | # The V8 version that we want to bind 15 | - libv8-5.4-dev 16 | - libicu-dev 17 | # Dependencies for travis-cargo and for coverage 18 | - libcurl4-openssl-dev 19 | - libelf-dev 20 | - libdw-dev 21 | - binutils-dev 22 | 23 | matrix: 24 | include: 25 | - env: FEATURES="" CC=gcc-6 CXX=g++-6 26 | rust: stable 27 | os: linux 28 | - env: FEATURES="--features=shared" CC=gcc-6 CXX=g++-6 29 | rust: stable 30 | os: linux 31 | - env: FEATURES="--features=shared" 32 | rust: stable 33 | os: osx 34 | osx_image: xcode8 35 | - env: FEATURES="" CC=gcc-6 CXX=g++-6 36 | rust: nightly 37 | os: linux 38 | - env: FEATURES="--features=shared" CC=gcc-6 CXX=g++-6 39 | rust: nightly 40 | os: linux 41 | allow_failures: 42 | - rust: nightly 43 | 44 | before_install: 45 | - | 46 | if [ "$TRAVIS_OS_NAME" = osx ] 47 | then 48 | brew tap dflemstr/tools 49 | brew update 50 | brew install -v dflemstr/tools/v8 51 | fi 52 | 53 | before_script: 54 | - | 55 | pip install 'travis-cargo<0.2' --user && 56 | export PATH=$HOME/.local/bin:$HOME/Library/Python/2.7/bin:$PATH 57 | 58 | script: 59 | - | 60 | travis-cargo build -- $FEATURES && 61 | travis-cargo test -- $FEATURES && 62 | travis-cargo bench -- $FEATURES 63 | 64 | after_success: 65 | - | 66 | if [ -z "$FEATURES" ]; then travis-cargo --only stable doc; travis-cargo --only stable doc-upload; fi 67 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 68 | tar xzf master.tar.gz && 69 | cd kcov-master && 70 | mkdir build && 71 | cd build && 72 | cmake .. && 73 | make && 74 | sudo make install && 75 | cd ../.. && 76 | rm -rf kcov-master && 77 | RUSTFLAGS='-C link-dead-code' cargo test $FEATURES --no-run && 78 | for file in target/debug/v8-* 79 | do 80 | kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov" "$file" 81 | done && 82 | bash <(curl -s https://codecov.io/bash) && 83 | echo "Uploaded code coverage" 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["David Flemström "] 3 | description = "High-level bindings to V8, the Javascript engine" 4 | documentation = "https://dflemstr.github.io/v8-rs/v8/index.html" 5 | homepage = "https://dflemstr.github.io/v8-rs" 6 | keywords = ["v8", "javascript", "js", "ecmascript", "google"] 7 | license = "Apache-2.0" 8 | name = "v8" 9 | repository = "https://github.com/dflemstr/v8-rs" 10 | version = "0.9.6" 11 | 12 | [dependencies] 13 | error-chain = "0.9.0" 14 | lazy_static = "0.2.1" 15 | num_cpus = "1.1.0" 16 | 17 | [dependencies.v8-sys] 18 | path = "v8-sys" 19 | version = "0.14.0" 20 | 21 | [features] 22 | shared = ["v8-sys/shared"] 23 | unstable = [] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `v8-rs` [![Build Status](https://travis-ci.org/dflemstr/v8-rs.svg?branch=master)](https://travis-ci.org/dflemstr/v8-rs) [![Crates.io](https://img.shields.io/crates/v/v8.svg?maxAge=3600)](https://crates.io/crates/v8) [![codecov](https://codecov.io/gh/dflemstr/v8-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/dflemstr/v8-rs) [![Language (Rust)](https://img.shields.io/badge/powered_by-Rust-blue.svg)](http://www.rust-lang.org/) 2 | 3 | **Note:** This library is not actively maintained. I (dflemstr) have 4 | attempted keeping it up to date with the latest mainline V8 versions, 5 | but the maintenance burden is too high. The path forward would be to 6 | more directly map the C++ API of V8 via `bindgen` and go forward from 7 | there to try to automate more of the API surface mapping, but several 8 | attempts at doing so by me have failed. Pull requests welcome! 9 | 10 | This is a wrapper around the [V8](https://developers.google.com/v8/) 11 | Javascript engine, used for example in 12 | the [Google Chrome browser](https://www.google.com/chrome/browser) 13 | or [Node.js](https://nodejs.org/en/). 14 | 15 | [Documentation](https://dflemstr.github.io/v8-rs) 16 | 17 | ## Building 18 | 19 | It is quite complicated to build V8. This library has been tested 20 | against V8 5.4.x with GCC 6.x, but later versions might work. 21 | 22 | ### Static / Shared 23 | 24 | By default, this library links V8 statically. There is a feature 25 | called `shared` that builds it by linking to `libv8.so` (and related 26 | libraries like `libicu-i10n.so`) instead. There's usually little 27 | reason to link dynamically since the V8 ABI changes fairly frequently. 28 | 29 | ### Ubuntu / Travis CI 30 | 31 | The easiest way to build this library on Ubuntu or Travis CI is to use 32 | a pre-packaged version of V8. You need both `sudo` and Ubuntu Trusty 33 | or later to install a compatible one: 34 | 35 | ``` yaml 36 | sudo: true 37 | dist: trusty 38 | language: rust 39 | 40 | addons: 41 | apt: 42 | sources: 43 | - sourceline: 'ppa:pinepain/libv8-5.4' 44 | - ubuntu-toolchain-r-test 45 | packages: 46 | # Modern compilers 47 | - gcc-6 48 | - g++-6 49 | # The V8 version that we want to bind 50 | - libv8-5.4-dev 51 | - libicu-dev 52 | 53 | env: 54 | global: 55 | - CC=gcc-6 56 | - CXX=g++-6 57 | ``` 58 | 59 | ### Build tree 60 | 61 | You can build a build tree using any supported build method that uses 62 | any combination of `depot_tools`, `make`, `gyp`, `ninja` and/or `gn`, 63 | but `gn` hasn't been tested that extensively. 64 | 65 | You should set `v8_use_snapshot=false`, loading snapshots is currently 66 | not supported. 67 | 68 | You should also not disable `i10n` support; this library assumes 69 | `libicu` was built at the same time as V8 or is compatible with V8. 70 | 71 | You should build using `shared_library` if you want to build with the 72 | `shared` feature. 73 | 74 | Simply set the environment variable `V8_SOURCE` to the root of the 75 | `v8` checkout, and `V8_BUILD` to the build output in the tree (for 76 | example `$V8_SOURCE/out/Release`) and the build Should Work®. If not, 77 | please figure out how to fix it and send a PR, it'll be impossible for 78 | me to test all of the V8 build configurations :) 79 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | extern crate v8; 2 | 3 | use v8::value; 4 | 5 | fn main() { 6 | let isolate = v8::Isolate::new(); 7 | let context = v8::Context::new(&isolate); 8 | 9 | let source = value::String::from_str(&isolate, "'Hello, ' + 'World!'"); 10 | let script = v8::Script::compile(&isolate, &context, &source).unwrap(); 11 | 12 | let result = script.run(&context).unwrap(); 13 | let result_str = result.to_string(&context); 14 | 15 | println!("{}", result_str.value()); 16 | } 17 | -------------------------------------------------------------------------------- /src/allocator.rs: -------------------------------------------------------------------------------- 1 | //! Allocators for array buffers. 2 | use v8_sys as v8; 3 | 4 | use std::os; 5 | use std::mem; 6 | 7 | /// A simple array buffer allocator that guarantees that all allocated 8 | /// blocks are coercible to `Vec`s. 9 | #[derive(Debug)] 10 | pub struct Allocator(v8::ArrayBuffer_AllocatorPtr); 11 | 12 | impl Allocator { 13 | /// Creates a new allocator. 14 | pub fn new() -> Allocator { 15 | let raw = unsafe { v8::v8_ArrayBuffer_Allocator_Create(ALLOCATOR_FUNCTIONS) }; 16 | if raw.is_null() { 17 | panic!("Could not create ArrayBuffer::Allocator"); 18 | } 19 | 20 | Allocator(raw) 21 | } 22 | 23 | /// Returns the underlying raw pointer behind this allocator. 24 | pub fn as_raw(&self) -> v8::ArrayBuffer_AllocatorPtr { 25 | self.0 26 | } 27 | } 28 | 29 | impl Drop for Allocator { 30 | fn drop(&mut self) { 31 | unsafe { 32 | v8::v8_ArrayBuffer_Allocator_Destroy(self.0); 33 | } 34 | } 35 | } 36 | 37 | const ALLOCATOR_FUNCTIONS: v8::v8_AllocatorFunctions = v8::v8_AllocatorFunctions { 38 | Allocate: Some(allocate), 39 | AllocateUninitialized: Some(allocate_uninitialized), 40 | Free: Some(free), 41 | }; 42 | 43 | extern "C" fn allocate(length: usize) -> *mut os::raw::c_void { 44 | let mut data = Vec::with_capacity(length); 45 | data.resize(length, 0u8); 46 | let ptr = data.as_mut_ptr(); 47 | mem::forget(data); 48 | 49 | ptr as *mut os::raw::c_void 50 | } 51 | 52 | extern "C" fn allocate_uninitialized(length: usize) -> *mut os::raw::c_void { 53 | let mut data = Vec::with_capacity(length); 54 | 55 | unsafe { 56 | data.set_len(length); 57 | } 58 | 59 | let ptr = data.as_mut_ptr(); 60 | mem::forget(data); 61 | 62 | ptr as *mut os::raw::c_void 63 | } 64 | 65 | unsafe extern "C" fn free(data: *mut os::raw::c_void, length: usize) { 66 | // TODO: restore `cap` here? Can this possibly leak memory? 67 | drop(Vec::from_raw_parts(data, length, length)); 68 | } 69 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | //! Execution contexts and sandboxing. 2 | use v8_sys as v8; 3 | use isolate; 4 | use util; 5 | use value; 6 | 7 | /// A sandboxed execution context with its own set of built-in objects and functions. 8 | #[derive(Debug)] 9 | pub struct Context(isolate::Isolate, v8::ContextRef); 10 | 11 | /// A guard that keeps a context bound while it is in scope. 12 | #[must_use] 13 | pub struct ContextGuard<'a>(&'a Context); 14 | 15 | impl Context { 16 | /// Creates a new context and returns a handle to the newly allocated context. 17 | pub fn new(isolate: &isolate::Isolate) -> Context { 18 | unsafe { 19 | Context(isolate.clone(), 20 | util::invoke(isolate, |c| v8::v8_Context_New(c)).unwrap()) 21 | } 22 | } 23 | 24 | /// Binds the context to the current scope. 25 | /// 26 | /// Within this scope, functionality that relies on implicit contexts will work. 27 | pub fn make_current(&self) -> ContextGuard { 28 | self.enter(); 29 | ContextGuard(self) 30 | } 31 | 32 | fn enter(&self) { 33 | unsafe { util::invoke(&self.0, |c| v8::v8_Context_Enter(c, self.1)).unwrap() } 34 | } 35 | 36 | fn exit(&self) { 37 | unsafe { util::invoke(&self.0, |c| v8::v8_Context_Exit(c, self.1)).unwrap() } 38 | } 39 | 40 | /// Returns the global proxy object. 41 | /// 42 | /// Global proxy object is a thin wrapper whose prototype points to actual context's global 43 | /// object with the properties like Object, etc. This is done that way for security reasons (for 44 | /// more details see https://wiki.mozilla.org/Gecko:SplitWindow). 45 | /// 46 | /// Please note that changes to global proxy object prototype most probably would break VM---v8 47 | /// expects only global object as a prototype of global proxy object. 48 | /// 49 | pub fn global(&self) -> value::Object { 50 | unsafe { 51 | value::Object::from_raw(&self.0, 52 | util::invoke(&self.0, |c| v8::v8_Context_Global(c, self.1)) 53 | .unwrap()) 54 | } 55 | } 56 | 57 | /// Creates a context from a set of raw pointers. 58 | pub unsafe fn from_raw(isolate: &isolate::Isolate, raw: v8::ContextRef) -> Context { 59 | Context(isolate.clone(), raw) 60 | } 61 | 62 | /// Returns the underlying raw pointer behind this context. 63 | pub fn as_raw(&self) -> v8::ContextRef { 64 | self.1 65 | } 66 | } 67 | 68 | reference!(Context, v8::v8_Context_CloneRef, v8::v8_Context_DestroyRef); 69 | 70 | impl<'a> Drop for ContextGuard<'a> { 71 | fn drop(&mut self) { 72 | self.0.exit() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types and utilities. 2 | 3 | use std::fmt; 4 | use v8_sys as v8; 5 | use context; 6 | use isolate; 7 | use util; 8 | use value; 9 | 10 | error_chain! { 11 | errors { 12 | Javascript(message: String, stack_trace: CapturedStackTrace) { 13 | description("Javascript exception") 14 | display("Javascript exception: {}\n{}", message, stack_trace) 15 | } 16 | } 17 | } 18 | 19 | /// A captured stack trace, that is separated from its underlying isolate. 20 | #[derive(Clone, Debug)] 21 | pub struct CapturedStackTrace { 22 | pub frames: Vec, 23 | } 24 | 25 | /// A captured stack frame, that is separated from its underlying isolate. 26 | #[derive(Clone, Debug)] 27 | pub struct CapturedStackFrame { 28 | pub line: u32, 29 | pub column: u32, 30 | pub script_name: Option, 31 | pub function_name: Option, 32 | pub is_eval: bool, 33 | pub is_constructor: bool, 34 | } 35 | 36 | /// An error message. 37 | #[derive(Debug)] 38 | pub struct Message(isolate::Isolate, v8::MessageRef); 39 | 40 | /// A stack trace, that is bound to an isolate. 41 | #[derive(Debug)] 42 | pub struct StackTrace(isolate::Isolate, v8::StackTraceRef); 43 | 44 | /// A stack frame, that is bound to an isolate. 45 | #[derive(Debug)] 46 | pub struct StackFrame(isolate::Isolate, v8::StackFrameRef); 47 | 48 | impl Message { 49 | // TODO: pub fn get_script_origin(&self) 50 | 51 | /// The error message string. 52 | pub fn get(&self, context: &context::Context) -> value::String { 53 | let _g = context.make_current(); 54 | unsafe { 55 | value::String::from_raw(&self.0, 56 | util::invoke_ctx(&self.0, 57 | context, 58 | |c| v8::v8_Message_Get(c, self.1)) 59 | .unwrap()) 60 | } 61 | } 62 | 63 | /// The stack trace to the point where the error was generated. 64 | pub fn get_stack_trace(&self) -> StackTrace { 65 | let raw = 66 | unsafe { util::invoke(&self.0, |c| v8::v8_Message_GetStackTrace(c, self.1)).unwrap() }; 67 | 68 | StackTrace(self.0.clone(), raw) 69 | } 70 | 71 | pub unsafe fn from_raw(isolate: &isolate::Isolate, raw: v8::MessageRef) -> Message { 72 | Message(isolate.clone(), raw) 73 | } 74 | } 75 | 76 | impl StackTrace { 77 | /// The stack frames that this stack trace consists of. 78 | pub fn get_frames(&self) -> Vec { 79 | let count = 80 | unsafe { util::invoke(&self.0, |c| v8::v8_StackTrace_GetFrameCount(c, self.1)).unwrap() }; 81 | let mut result = Vec::with_capacity(count as usize); 82 | 83 | for i in 0..count { 84 | let raw_frame = unsafe { 85 | util::invoke(&self.0, |c| v8::v8_StackTrace_GetFrame(c, self.1, i as u32)).unwrap() 86 | }; 87 | let frame = StackFrame(self.0.clone(), raw_frame); 88 | result.push(frame); 89 | } 90 | 91 | result 92 | } 93 | 94 | /// Creates a captured version of this stack trace, that doesn't retain a reference to its 95 | /// isolate. 96 | pub fn to_captured(&self) -> CapturedStackTrace { 97 | CapturedStackTrace { 98 | frames: self.get_frames() 99 | .iter() 100 | .map(StackFrame::to_captured) 101 | .collect(), 102 | } 103 | } 104 | } 105 | 106 | impl StackFrame { 107 | /// The line number at which this stack frame was pushed. 108 | pub fn get_line_number(&self) -> u32 { 109 | unsafe { 110 | util::invoke(&self.0, |c| v8::v8_StackFrame_GetLineNumber(c, self.1)).unwrap() as u32 111 | } 112 | } 113 | 114 | /// The column number at which this stack frame was pushed. 115 | pub fn get_column(&self) -> u32 { 116 | unsafe { util::invoke(&self.0, |c| v8::v8_StackFrame_GetColumn(c, self.1)).unwrap() as u32 } 117 | } 118 | 119 | /// The script file name in which this stack frame was pushed. 120 | pub fn get_script_name(&self) -> Option { 121 | unsafe { 122 | let raw = util::invoke(&self.0, |c| v8::v8_StackFrame_GetScriptName(c, self.1)).unwrap(); 123 | if raw.is_null() { 124 | None 125 | } else { 126 | Some(value::String::from_raw(&self.0, raw)) 127 | } 128 | } 129 | } 130 | 131 | /// The function name in which this stack frame was pushed. 132 | pub fn get_function_name(&self) -> value::String { 133 | unsafe { 134 | let raw = util::invoke(&self.0, |c| v8::v8_StackFrame_GetFunctionName(c, self.1)).unwrap(); 135 | value::String::from_raw(&self.0, raw) 136 | } 137 | } 138 | 139 | /// Whether this stack frame is part of an eval call. 140 | pub fn is_eval(&self) -> bool { 141 | unsafe { util::invoke(&self.0, |c| v8::v8_StackFrame_IsEval(c, self.1)).unwrap() } 142 | } 143 | 144 | /// Whether this stack frame is part of a constructor call. 145 | pub fn is_constructor(&self) -> bool { 146 | unsafe { util::invoke(&self.0, |c| v8::v8_StackFrame_IsConstructor(c, self.1)).unwrap() } 147 | } 148 | 149 | /// Creates a captured version of this stack frame, that doesn't retain a reference to its 150 | /// isolate. 151 | pub fn to_captured(&self) -> CapturedStackFrame { 152 | let function_name = self.get_function_name().value(); 153 | CapturedStackFrame { 154 | line: self.get_line_number(), 155 | column: self.get_column(), 156 | script_name: self.get_script_name().map(|ref s| s.value()), 157 | function_name: if function_name.is_empty() { 158 | None 159 | } else { 160 | Some(function_name) 161 | }, 162 | is_eval: self.is_eval(), 163 | is_constructor: self.is_constructor(), 164 | } 165 | } 166 | } 167 | 168 | impl fmt::Display for CapturedStackTrace { 169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 170 | for frame in self.frames.iter() { 171 | try!(writeln!(f, "{}", frame)); 172 | } 173 | Ok(()) 174 | } 175 | } 176 | 177 | impl fmt::Display for CapturedStackFrame { 178 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 179 | try!(write!(f, " at ")); 180 | 181 | if self.is_constructor { 182 | try!(write!(f, "new ")); 183 | } 184 | 185 | if let Some(ref function_name) = self.function_name { 186 | try!(write!(f, "{} (", function_name)); 187 | 188 | if self.is_eval { 189 | try!(write!(f, "eval ")); 190 | } 191 | 192 | try!(write!(f, 193 | "{}:{}:{})", 194 | self.script_name.as_ref().map(|n| n.as_str()).unwrap_or(""), 195 | self.line, 196 | self.column)); 197 | } else { 198 | if self.is_eval { 199 | try!(write!(f, "eval ")); 200 | } 201 | try!(write!(f, 202 | "{}:{}:{}", 203 | self.script_name.as_ref().map(|n| n.as_str()).unwrap_or(""), 204 | self.line, 205 | self.column)); 206 | } 207 | 208 | Ok(()) 209 | } 210 | } 211 | 212 | reference!(Message, v8::v8_Message_CloneRef, v8::v8_Message_DestroyRef); 213 | reference!(StackTrace, 214 | v8::v8_StackTrace_CloneRef, 215 | v8::v8_StackTrace_DestroyRef); 216 | reference!(StackFrame, 217 | v8::v8_StackFrame_CloneRef, 218 | v8::v8_StackFrame_DestroyRef); 219 | -------------------------------------------------------------------------------- /src/isolate.rs: -------------------------------------------------------------------------------- 1 | //! Heap and execution isolation. 2 | //! 3 | //! # Usage 4 | //! 5 | //! Construct a new isolate with default settings by doing `Isolate::new()`. You can customize the 6 | //! isolate settings by using `Isolate::builder()`. 7 | //! 8 | //! # Foreground tasks 9 | //! 10 | //! Javascript can produce "deferred" or "time-outed" tasks that need to run on the main thread. 11 | //! Additionally, V8 has a bunch of internal tasks it wants to perform regularly (for example GC). 12 | //! The user should therefore call `isolate.run_enqueued_tasks()` regularly to allow these tasks to 13 | //! run. 14 | //! 15 | //! # Background tasks 16 | //! 17 | //! Javascript and V8 can trigger various background tasks to run. These will be run as simple 18 | //! background OS threads, while trying to keep the number of running background tasks less than the 19 | //! number of available CPUs. 20 | //! 21 | //! # Idle tasks 22 | //! 23 | //! V8 can perform various maintenance tasks if the application has nothing better to do. If the 24 | //! user wants to allow this to happen, an isolate should be constructed with 25 | //! `Isolate::builder().supports_idle_tasks(true).build()`. The user should then regularly call 26 | //! `isolate.run_idle_tasks(deadline)` to run any pending idle tasks. 27 | 28 | use std::cmp; 29 | use std::collections; 30 | use std::mem; 31 | use std::os; 32 | use std::sync; 33 | use std::time; 34 | use v8_sys as v8; 35 | use allocator; 36 | use context; 37 | use platform; 38 | 39 | static INITIALIZE: sync::Once = sync::ONCE_INIT; 40 | 41 | /// Isolate represents an isolated instance of the V8 engine. 42 | /// 43 | /// V8 isolates have completely separate states. Objects from one isolate must not be used in other 44 | /// isolates. The embedder can create multiple isolates and use them in parallel in multiple 45 | /// threads. An isolate can be entered by at most one thread at any given time. The 46 | /// Locker/Unlocker API must be used to synchronize. 47 | #[derive(Debug)] 48 | pub struct Isolate(v8::IsolatePtr); 49 | 50 | /// A builder for isolates. Can be converted into an isolate with the `build` method. 51 | pub struct Builder { 52 | supports_idle_tasks: bool, 53 | } 54 | 55 | #[derive(Debug)] 56 | struct Data { 57 | count: usize, 58 | _allocator: allocator::Allocator, 59 | task_queue: collections::BinaryHeap, 60 | idle_task_queue: Option>, 61 | } 62 | 63 | #[derive(Debug, Eq, PartialEq)] 64 | struct ScheduledTask(time::Instant, platform::Task); 65 | 66 | const DATA_PTR_SLOT: u32 = 0; 67 | 68 | impl Isolate { 69 | /// Creates a new isolate. 70 | pub fn new() -> Isolate { 71 | Isolate::builder().build() 72 | } 73 | 74 | /// Creates a new isolate builder. 75 | pub fn builder() -> Builder { 76 | Builder { supports_idle_tasks: false } 77 | } 78 | 79 | /// Creates a data from a set of raw pointers. 80 | /// 81 | /// This isolate must at some point have been created by `Isolate::new`, since this library 82 | /// expects isolates to be configured a certain way and contain embedder information. 83 | pub unsafe fn from_raw(raw: v8::IsolatePtr) -> Isolate { 84 | let result = Isolate(raw); 85 | result.get_data().count += 1; 86 | result 87 | } 88 | 89 | /// Returns the underlying raw pointer behind this isolate. 90 | pub fn as_raw(&self) -> v8::IsolatePtr { 91 | self.0 92 | } 93 | 94 | /// Returns the context bound to the current thread for this isolate. 95 | /// 96 | /// A context will be bound by for example `Context::make_current`, or while inside of a 97 | /// function callback. 98 | pub fn current_context(&self) -> Option { 99 | unsafe { 100 | let raw = v8::v8_Isolate_GetCurrentContext(self.as_raw()).as_mut(); 101 | raw.map(|r| context::Context::from_raw(self, r)) 102 | } 103 | } 104 | 105 | /// Runs all enqueued tasks until there are no more tasks available. 106 | pub fn run_enqueued_tasks(&self) { 107 | while self.run_enqueued_task() {} 108 | } 109 | 110 | /// Runs a single enqueued task, if there is one. Returns `true` if a task was executed, and 111 | /// `false` if there are no pending tasks to run. 112 | pub fn run_enqueued_task(&self) -> bool { 113 | let data = unsafe { self.get_data() }; 114 | let now = time::Instant::now(); 115 | 116 | if data.task_queue.peek().map(|t| t.0 > now).unwrap_or(false) { 117 | let task = data.task_queue.pop().unwrap().1; 118 | task.run(); 119 | true 120 | } else { 121 | false 122 | } 123 | } 124 | 125 | /// Runs as many idle tasks as possible within the specified deadline. It is not guaranteed 126 | /// that the execution of the tasks will take less time than the specified deadline. 127 | pub fn run_idle_tasks(&self, deadline: time::Duration) { 128 | let deadline = time::Instant::now() + deadline; 129 | 130 | loop { 131 | let now = time::Instant::now(); 132 | 133 | if now > deadline { 134 | break; 135 | } 136 | 137 | self.run_idle_task(deadline - now); 138 | } 139 | } 140 | 141 | /// Runs a single idle task within the specified deadline. It is not guaranteed that the 142 | /// execution of the task will take less time than the specified deadline. Returns `true` if a 143 | /// task was executed, and `false` if there are no pending tasks to run. 144 | pub fn run_idle_task(&self, deadline: time::Duration) -> bool { 145 | let data = unsafe { self.get_data() }; 146 | 147 | if let Some(idle_task) = data.idle_task_queue 148 | .as_mut() 149 | .map(|q| q.pop_front()) 150 | .unwrap_or(None) { 151 | idle_task.run(deadline); 152 | true 153 | } else { 154 | false 155 | } 156 | } 157 | 158 | /// Enqueues the specified task to run as soon as possible. 159 | pub fn enqueue_task(&self, task: platform::Task) { 160 | let scheduled_task = ScheduledTask(time::Instant::now(), task); 161 | unsafe { self.get_data() }.task_queue.push(scheduled_task); 162 | } 163 | 164 | /// Enqueues the specified task to run after the specified delay has passed. 165 | pub fn enqueue_delayed_task(&self, delay: time::Duration, task: platform::Task) { 166 | let scheduled_task = ScheduledTask(time::Instant::now() + delay, task); 167 | unsafe { self.get_data() }.task_queue.push(scheduled_task); 168 | } 169 | 170 | /// Enqueues a task to be run when the isolate is considered to be "idle." 171 | pub fn enqueue_idle_task(&self, idle_task: platform::IdleTask) { 172 | unsafe { self.get_data() }.idle_task_queue.as_mut().unwrap().push_back(idle_task); 173 | } 174 | 175 | /// Whether this isolate was configured to support idle tasks. 176 | pub fn supports_idle_tasks(&self) -> bool { 177 | unsafe { self.get_data() }.idle_task_queue.is_some() 178 | } 179 | 180 | unsafe fn get_data_ptr(&self) -> *mut Data { 181 | v8::v8_Isolate_GetData(self.0, DATA_PTR_SLOT) as *mut Data 182 | } 183 | 184 | unsafe fn get_data(&self) -> &mut Data { 185 | self.get_data_ptr().as_mut().unwrap() 186 | } 187 | } 188 | 189 | impl Clone for Isolate { 190 | fn clone(&self) -> Isolate { 191 | unsafe { 192 | self.get_data().count += 1; 193 | } 194 | Isolate(self.0) 195 | } 196 | } 197 | 198 | impl Drop for Isolate { 199 | fn drop(&mut self) { 200 | unsafe { 201 | let ref mut count = self.get_data().count; 202 | *count -= 1; 203 | 204 | if *count == 0 { 205 | drop(Box::from_raw(self.get_data_ptr())); 206 | v8::v8_Isolate_Dispose(self.0); 207 | } 208 | } 209 | } 210 | } 211 | 212 | impl Builder { 213 | /// Whether the isolate should support idle tasks; i.e. whether the user will call 214 | /// `run_idle_tasks` regularly. 215 | pub fn supports_idle_tasks(mut self, value: bool) -> Builder { 216 | self.supports_idle_tasks = value; 217 | self 218 | } 219 | 220 | /// Constructs a new `Isolate` based on this builder. 221 | pub fn build(self) -> Isolate { 222 | ensure_initialized(); 223 | 224 | let allocator = allocator::Allocator::new(); 225 | 226 | let raw = unsafe { v8::v8_Isolate_New(allocator.as_raw()) }; 227 | if raw.is_null() { 228 | panic!("Could not create Isolate"); 229 | } 230 | 231 | unsafe { 232 | assert!(v8::v8_Isolate_GetNumberOfDataSlots(raw) > 0); 233 | } 234 | 235 | let idle_task_queue = if self.supports_idle_tasks { 236 | Some(collections::VecDeque::new()) 237 | } else { 238 | None 239 | }; 240 | 241 | let data = Data { 242 | count: 1, 243 | _allocator: allocator, 244 | task_queue: collections::BinaryHeap::new(), 245 | idle_task_queue: idle_task_queue, 246 | }; 247 | let data_ptr: *mut Data = Box::into_raw(Box::new(data)); 248 | 249 | unsafe { 250 | v8::v8_Isolate_SetData(raw, DATA_PTR_SLOT, data_ptr as *mut os::raw::c_void); 251 | v8::v8_Isolate_SetCaptureStackTraceForUncaughtExceptions_Detailed(raw, true, 1024); 252 | } 253 | 254 | Isolate(raw) 255 | } 256 | } 257 | 258 | impl PartialOrd for ScheduledTask { 259 | fn partial_cmp(&self, other: &ScheduledTask) -> Option { 260 | Some(self.cmp(other)) 261 | } 262 | } 263 | 264 | impl Ord for ScheduledTask { 265 | fn cmp(&self, other: &ScheduledTask) -> cmp::Ordering { 266 | self.0.cmp(&other.0).reverse() 267 | } 268 | } 269 | 270 | fn ensure_initialized() { 271 | INITIALIZE.call_once(|| { 272 | unsafe { 273 | v8::v8_V8_InitializeICU(); 274 | 275 | let platform = platform::Platform::new(); 276 | v8::v8_V8_InitializePlatform(platform.as_raw()); 277 | // TODO: implement some form of cleanup 278 | mem::forget(platform); 279 | 280 | v8::v8_V8_Initialize(); 281 | } 282 | }); 283 | } 284 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A high-level wrapper around the [V8 Javascript engine][1]. 2 | //! 3 | //! # Usage 4 | //! 5 | //! First, you need to create an [`Isolate`](isolate/struct.Isolate.html). An isolate is a VM 6 | //! instance with its own heap. You should most likely create one per thread and re-use it as much 7 | //! as possible. 8 | //! 9 | //! Then, you need to create a [`Context`](context/struct.Context.html). A context is an execution 10 | //! environment that allows separate, unrelated, JavaScript code to run in a single instance of V8. 11 | //! You must explicitly specify the context in which you want any JavaScript code to be run. You 12 | //! should keep track of the context of a script manually as part of your application. 13 | //! 14 | //! # Example 15 | //! 16 | //! ``` 17 | //! use v8::{self, value}; 18 | //! 19 | //! // Create a V8 heap 20 | //! let isolate = v8::Isolate::new(); 21 | //! // Create a new context of execution 22 | //! let context = v8::Context::new(&isolate); 23 | //! 24 | //! // Load the source code that we want to evaluate 25 | //! let source = value::String::from_str(&isolate, "'Hello, ' + 'World!'"); 26 | //! 27 | //! // Compile the source code. `unwrap()` panics if the code is invalid, 28 | //! // e.g. if there is a syntax error. 29 | //! let script = v8::Script::compile(&isolate, &context, &source).unwrap(); 30 | //! 31 | //! // Run the compiled script. `unwrap()` panics if the code threw an 32 | //! // exception. 33 | //! let result = script.run(&context).unwrap(); 34 | //! 35 | //! // Convert the result to a value::String. 36 | //! let result_str = result.to_string(&context); 37 | //! 38 | //! // Success! 39 | //! assert_eq!("Hello, World!", result_str.value()); 40 | //! ``` 41 | //! 42 | //! # Isolate foreground tasks 43 | //! 44 | //! Javascript can produce "deferred" or "time-outed" tasks that need to run on the main thread. 45 | //! Additionally, V8 has a bunch of internal tasks it wants to perform regularly (for example GC). 46 | //! The user should therefore call `isolate.run_enqueued_tasks()` regularly to allow these tasks to 47 | //! run. 48 | //! 49 | //! [1]: https://developers.google.com/v8/ 50 | 51 | #![cfg_attr(all(feature="unstable", test), feature(test))] 52 | 53 | #[macro_use] 54 | extern crate error_chain; 55 | #[macro_use] 56 | extern crate lazy_static; 57 | extern crate num_cpus; 58 | extern crate v8_sys; 59 | 60 | mod allocator; 61 | mod platform; 62 | #[macro_use] 63 | mod util; 64 | 65 | pub mod context; 66 | pub mod error; 67 | pub mod isolate; 68 | pub mod script; 69 | pub mod template; 70 | pub mod value; 71 | 72 | pub use context::Context; 73 | pub use isolate::Isolate; 74 | pub use script::Script; 75 | pub use value::Value; 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | 81 | fn eval(source: &str) -> error::Result<(Isolate, Context, Value)> { 82 | let isolate = Isolate::new(); 83 | let context = Context::new(&isolate); 84 | let name = value::String::from_str(&isolate, "test.js"); 85 | let source = value::String::from_str(&isolate, source); 86 | let script = try!(Script::compile_with_name(&isolate, &context, &name, &source)); 87 | let result = try!(script.run(&context)); 88 | Ok((isolate, context, result)) 89 | } 90 | 91 | #[test] 92 | fn hello_world() { 93 | let (_, _, v) = eval("'Hello, ' + 'World!'").unwrap(); 94 | assert!(v.is_string()); 95 | let result = v.into_string().unwrap().value(); 96 | assert_eq!("Hello, World!", result); 97 | } 98 | 99 | #[test] 100 | fn eval_undefined() { 101 | let (_, _, v) = eval("undefined").unwrap(); 102 | assert!(v.is_undefined()); 103 | } 104 | 105 | #[test] 106 | fn eval_null() { 107 | let (_, _, v) = eval("null").unwrap(); 108 | assert!(v.is_null()); 109 | } 110 | 111 | #[test] 112 | fn eval_bool_false() { 113 | let (_, c, v) = eval("false").unwrap(); 114 | assert!(v.is_boolean()); 115 | assert!(v.is_false()); 116 | assert_eq!(false, v.boolean_value(&c)); 117 | let v = v.into_boolean().unwrap(); 118 | assert_eq!(false, v.value()); 119 | } 120 | 121 | #[test] 122 | fn eval_bool_true() { 123 | let (_, c, v) = eval("true").unwrap(); 124 | assert!(v.is_boolean()); 125 | assert!(v.is_true()); 126 | assert_eq!(true, v.boolean_value(&c)); 127 | let v = v.into_boolean().unwrap(); 128 | assert_eq!(true, v.value()); 129 | } 130 | 131 | #[test] 132 | fn eval_string() { 133 | let (_, c, v) = eval("'foo'").unwrap(); 134 | assert!(v.is_string()); 135 | assert_eq!("foo", v.to_string(&c).value()); 136 | let v = v.into_string().unwrap(); 137 | assert_eq!("foo", v.value()); 138 | } 139 | 140 | #[test] 141 | fn eval_string_length() { 142 | let (_, _, v) = eval("'foo'").unwrap(); 143 | assert!(v.is_string()); 144 | let v = v.into_string().unwrap(); 145 | assert_eq!(3, v.length()); 146 | } 147 | 148 | #[test] 149 | fn eval_string_utf8_length_1() { 150 | let (_, _, v) = eval("'a'").unwrap(); 151 | assert!(v.is_string()); 152 | let v = v.into_string().unwrap(); 153 | assert_eq!(1, v.utf8_length()); 154 | } 155 | 156 | #[test] 157 | fn eval_string_utf8_length_2() { 158 | let (_, _, v) = eval("'ä'").unwrap(); 159 | assert!(v.is_string()); 160 | let v = v.into_string().unwrap(); 161 | assert_eq!(2, v.utf8_length()); 162 | } 163 | 164 | #[test] 165 | fn eval_string_utf8_length_3() { 166 | let (_, _, v) = eval("'௵'").unwrap(); 167 | assert!(v.is_string()); 168 | let v = v.into_string().unwrap(); 169 | assert_eq!(3, v.utf8_length()); 170 | } 171 | 172 | #[test] 173 | fn eval_string_utf8_length_4() { 174 | let (_, _, v) = eval("'𒀰'").unwrap(); 175 | assert!(v.is_string()); 176 | let v = v.into_string().unwrap(); 177 | assert_eq!(4, v.utf8_length()); 178 | } 179 | 180 | #[test] 181 | fn eval_string_edge_cases() { 182 | let (_, _, v) = eval(r#"'foo\u0000\uffff௵𒀰\uD808\uDC30'"#).unwrap(); 183 | assert!(v.is_string()); 184 | let v = v.into_string().unwrap(); 185 | assert_eq!("foo\u{0000}\u{ffff}௵𒀰𒀰", v.value()); 186 | } 187 | 188 | #[test] 189 | fn eval_uint32() { 190 | let (_, c, v) = eval("42").unwrap(); 191 | assert!(v.is_number()); 192 | assert!(v.is_uint32()); 193 | assert_eq!(42, v.uint32_value(&c)); 194 | let v = v.into_uint32().unwrap(); 195 | assert_eq!(42, v.value()); 196 | } 197 | 198 | #[test] 199 | fn eval_int32() { 200 | let (_, c, v) = eval("-42").unwrap(); 201 | assert!(v.is_number()); 202 | assert!(v.is_int32()); 203 | assert_eq!(-42, v.int32_value(&c)); 204 | let v = v.into_int32().unwrap(); 205 | assert_eq!(-42, v.value()); 206 | } 207 | 208 | #[test] 209 | fn eval_integer() { 210 | // Use largest possible integer n such that all values of 0..n 211 | // can be represented in Javascript 212 | let (_, c, v) = eval("9007199254740992").unwrap(); 213 | assert!(v.is_number()); 214 | assert_eq!(9007199254740992, v.integer_value(&c)); 215 | } 216 | 217 | #[test] 218 | fn eval_function() { 219 | let (i, c, v) = eval("(function(a, b) { return a + b; })").unwrap(); 220 | let a = value::Integer::new(&i, 3); 221 | let b = value::Integer::new(&i, 4); 222 | let f = v.into_function().unwrap(); 223 | let r = f.call(&c, &[&a, &b]).unwrap(); 224 | assert!(r.is_int32()); 225 | assert_eq!(7, r.int32_value(&c)); 226 | } 227 | 228 | #[test] 229 | fn eval_equals_true() { 230 | let (i, c, v) = eval("({a: '', b: []})").unwrap(); 231 | assert!(v.is_object()); 232 | let v = v.into_object().unwrap(); 233 | let a_key = value::String::from_str(&i, "a"); 234 | let b_key = value::String::from_str(&i, "b"); 235 | assert!(v.get(&c, &a_key).equals(&c, &v.get(&c, &b_key))); 236 | } 237 | 238 | #[test] 239 | fn eval_equals_false() { 240 | let (i, c, v) = eval("({a: '', b: 1})").unwrap(); 241 | assert!(v.is_object()); 242 | let v = v.into_object().unwrap(); 243 | let a_key = value::String::from_str(&i, "a"); 244 | let b_key = value::String::from_str(&i, "b"); 245 | assert!(!v.get(&c, &a_key).equals(&c, &v.get(&c, &b_key))); 246 | } 247 | 248 | #[test] 249 | fn eval_strict_equals_true() { 250 | let (i, c, v) = eval("({a: 2, b: 2})").unwrap(); 251 | assert!(v.is_object()); 252 | let v = v.into_object().unwrap(); 253 | let a_key = value::String::from_str(&i, "a"); 254 | let b_key = value::String::from_str(&i, "b"); 255 | assert!(v.get(&c, &a_key).strict_equals(&v.get(&c, &b_key))); 256 | } 257 | 258 | #[test] 259 | fn eval_strict_equals_false() { 260 | let (i, c, v) = eval("({a: '', b: []})").unwrap(); 261 | assert!(v.is_object()); 262 | let v = v.into_object().unwrap(); 263 | let a_key = value::String::from_str(&i, "a"); 264 | let b_key = value::String::from_str(&i, "b"); 265 | assert!(!v.get(&c, &a_key).strict_equals(&v.get(&c, &b_key))); 266 | } 267 | 268 | #[test] 269 | fn eval_same_value_true() { 270 | let (i, c, v) = eval("(function() { var a = {}; return {a: a, b: a}; })()").unwrap(); 271 | assert!(v.is_object()); 272 | let v = v.into_object().unwrap(); 273 | let a_key = value::String::from_str(&i, "a"); 274 | let b_key = value::String::from_str(&i, "b"); 275 | assert!(v.get(&c, &a_key).same_value(&v.get(&c, &b_key))); 276 | } 277 | 278 | #[test] 279 | fn eval_same_value_false() { 280 | let (i, c, v) = eval("({a: {}, b: {}})").unwrap(); 281 | assert!(v.is_object()); 282 | let v = v.into_object().unwrap(); 283 | let a_key = value::String::from_str(&i, "a"); 284 | let b_key = value::String::from_str(&i, "b"); 285 | assert!(!v.get(&c, &a_key).same_value(&v.get(&c, &b_key))); 286 | } 287 | 288 | #[test] 289 | fn eval_function_then_call() { 290 | let (i, c, v) = eval("(function(a) { return a + a; })").unwrap(); 291 | assert!(v.is_function()); 292 | let f = v.into_function().unwrap(); 293 | let s = value::String::from_str(&i, "123"); 294 | let r = f.call(&c, &[&s]).unwrap(); 295 | assert!(r.is_string()); 296 | assert_eq!("123123", r.into_string().unwrap().value()); 297 | } 298 | 299 | #[test] 300 | fn eval_function_then_call_with_this() { 301 | let (i, c, v) = eval("(function() { return this.length; })").unwrap(); 302 | assert!(v.is_function()); 303 | let f = v.into_function().unwrap(); 304 | let s = value::String::from_str(&i, "123"); 305 | let r = f.call_with_this(&c, &s, &[]).unwrap(); 306 | assert!(r.is_int32()); 307 | assert_eq!(3, r.int32_value(&c)); 308 | } 309 | 310 | #[test] 311 | fn eval_function_then_construct() { 312 | let (i, c, v) = eval("(function ctor(a) { this.a = a; })").unwrap(); 313 | assert!(v.is_function()); 314 | let f = v.into_function().unwrap(); 315 | let a_key = value::String::from_str(&i, "a"); 316 | let s = value::String::from_str(&i, "123"); 317 | let r = f.call_as_constructor(&c, &[&s]).unwrap(); 318 | assert!(r.is_object()); 319 | let r = r.into_object().unwrap(); 320 | let r = r.get(&c, &a_key); 321 | assert!(r.is_string()); 322 | let r = r.to_string(&c); 323 | assert_eq!("123", r.value()); 324 | } 325 | 326 | #[test] 327 | fn eval_array() { 328 | let (_, c, v) = eval("[1, true, null]").unwrap(); 329 | assert!(v.is_array()); 330 | let v = v.into_object().unwrap(); 331 | assert!(v.get_index(&c, 0).is_number()); 332 | assert!(v.get_index(&c, 1).is_boolean()); 333 | assert!(v.get_index(&c, 2).is_null()); 334 | } 335 | 336 | #[test] 337 | fn eval_object() { 338 | let (i, c, v) = eval("({a: 2, b: true})").unwrap(); 339 | assert!(v.is_object()); 340 | let result = v.into_object().unwrap(); 341 | let a_key = value::String::from_str(&i, "a"); 342 | let b_key = value::String::from_str(&i, "b"); 343 | assert_eq!(2, result.get(&c, &a_key).integer_value(&c)); 344 | assert_eq!(true, result.get(&c, &b_key).boolean_value(&c)); 345 | } 346 | 347 | #[test] 348 | fn eval_date() { 349 | let (_, _, v) = eval("new Date(0)").unwrap(); 350 | assert!(v.is_date()); 351 | } 352 | 353 | #[test] 354 | fn eval_arguments_object() { 355 | let (_, _, v) = eval("(function() { return arguments; })()").unwrap(); 356 | assert!(v.is_arguments_object()); 357 | } 358 | 359 | #[test] 360 | fn eval_boolean_object() { 361 | let (_, _, v) = eval("new Boolean(true)").unwrap(); 362 | assert!(v.is_boolean_object()); 363 | } 364 | 365 | #[test] 366 | fn eval_number_object() { 367 | let (_, _, v) = eval("new Number(42)").unwrap(); 368 | assert!(v.is_number_object()); 369 | } 370 | 371 | #[test] 372 | fn eval_string_object() { 373 | let (_, _, v) = eval("new String('abc')").unwrap(); 374 | assert!(v.is_string_object()); 375 | } 376 | 377 | #[test] 378 | fn eval_symbol_object() { 379 | let (_, _, v) = eval("Object(Symbol('abc'))").unwrap(); 380 | assert!(v.is_symbol_object()); 381 | } 382 | 383 | #[test] 384 | fn eval_native_error() { 385 | let (_, _, v) = eval("new Error()").unwrap(); 386 | assert!(v.is_native_error()); 387 | } 388 | 389 | #[test] 390 | fn eval_reg_exp() { 391 | let (_, _, v) = eval("/./").unwrap(); 392 | assert!(v.is_reg_exp()); 393 | } 394 | 395 | #[test] 396 | fn eval_generator_function() { 397 | let (_, _, v) = eval("(function* () {})").unwrap(); 398 | assert!(v.is_generator_function()); 399 | } 400 | 401 | #[test] 402 | fn eval_generator_object() { 403 | let (_, _, v) = eval("(function* () {})()").unwrap(); 404 | assert!(v.is_generator_object()); 405 | } 406 | 407 | #[test] 408 | fn eval_promise() { 409 | let (_, _, v) = eval("new Promise(function() {})").unwrap(); 410 | assert!(v.is_promise()); 411 | } 412 | 413 | #[test] 414 | fn eval_map() { 415 | let (_, _, v) = eval("new Map()").unwrap(); 416 | assert!(v.is_map()); 417 | } 418 | 419 | #[test] 420 | fn eval_set() { 421 | let (_, _, v) = eval("new Set()").unwrap(); 422 | assert!(v.is_set()); 423 | } 424 | 425 | #[test] 426 | fn eval_map_iterator() { 427 | // TODO: how? 428 | } 429 | 430 | #[test] 431 | fn eval_set_iterator() { 432 | // TODO: how? 433 | } 434 | 435 | #[test] 436 | fn eval_syntax_error() { 437 | let result = eval("("); 438 | 439 | let error = result.unwrap_err(); 440 | match error.kind() { 441 | &error::ErrorKind::Javascript(ref msg, _) => { 442 | assert_eq!("Uncaught SyntaxError: Unexpected end of input", msg); 443 | } 444 | x => panic!("Unexpected error kind: {:?}", x), 445 | } 446 | } 447 | 448 | #[test] 449 | fn eval_exception() { 450 | let result = eval("throw 'x';"); 451 | 452 | let error = result.unwrap_err(); 453 | match error.kind() { 454 | &error::ErrorKind::Javascript(ref msg, _) => { 455 | assert_eq!("Uncaught x", msg); 456 | } 457 | x => panic!("Unexpected error kind: {:?}", x), 458 | } 459 | } 460 | 461 | #[test] 462 | fn eval_exception_stack() { 463 | let result = eval(r#" 464 | (function() { 465 | function x() { 466 | y(); 467 | } 468 | function y() { 469 | eval("z()"); 470 | } 471 | function z() { 472 | new w(); 473 | } 474 | function w() { 475 | throw new Error('x'); 476 | } 477 | x(); 478 | })(); 479 | "#); 480 | 481 | let error = result.unwrap_err(); 482 | match error.kind() { 483 | &error::ErrorKind::Javascript(ref msg, ref stack_trace) => { 484 | assert_eq!("Uncaught Error: x", msg); 485 | assert_eq!(" at new w (test.js:13:11)\n at z (test.js:10:5)\n at eval \ 486 | :1:1\n at y (test.js:7:5)\n at x (test.js:4:5)\n at \ 487 | test.js:15:3\n at test.js:16:3\n", 488 | format!("{}", stack_trace)); 489 | } 490 | x => panic!("Unexpected error kind: {:?}", x), 491 | } 492 | } 493 | 494 | #[test] 495 | fn run_native_function_call() { 496 | let isolate = Isolate::new(); 497 | let context = Context::new(&isolate); 498 | 499 | let function = value::Function::new(&isolate, 500 | &context, 501 | 1, 502 | Box::new(|mut info| Ok(info.args.remove(0)))); 503 | let param = value::Integer::new(&isolate, 42); 504 | 505 | let result = function.call(&context, &[¶m]).unwrap(); 506 | assert_eq!(42, result.uint32_value(&context)); 507 | } 508 | 509 | #[test] 510 | fn run_defined_function() { 511 | let i = Isolate::new(); 512 | let c = Context::new(&i); 513 | 514 | let fi = i.clone(); 515 | let fc = c.clone(); 516 | let f = value::Function::new(&i, 517 | &c, 518 | 2, 519 | Box::new(move |info| { 520 | assert_eq!(2, info.length); 521 | let ref a = info.args[0]; 522 | assert!(a.is_int32()); 523 | let a = a.int32_value(&fc); 524 | let ref b = info.args[1]; 525 | assert!(b.is_int32()); 526 | let b = b.int32_value(&fc); 527 | 528 | Ok(value::Integer::new(&fi, a + b).into()) 529 | })); 530 | 531 | let k = value::String::from_str(&i, "f"); 532 | c.global().set(&c, &k, &f); 533 | 534 | let name = value::String::from_str(&i, "test.js"); 535 | let source = value::String::from_str(&i, "f(2, 3)"); 536 | let script = Script::compile_with_name(&i, &c, &name, &source).unwrap(); 537 | let result = script.run(&c).unwrap(); 538 | 539 | assert!(result.is_int32()); 540 | assert_eq!(5, result.int32_value(&c)); 541 | } 542 | 543 | fn test_function(info: value::FunctionCallbackInfo) -> Result { 544 | let i = info.isolate; 545 | let c = i.current_context().unwrap(); 546 | 547 | assert_eq!(2, info.length); 548 | let ref a = info.args[0]; 549 | assert!(a.is_int32()); 550 | let a = a.int32_value(&c); 551 | let ref b = info.args[1]; 552 | assert!(b.is_int32()); 553 | let b = b.int32_value(&c); 554 | 555 | Ok(value::Integer::new(&i, a + b).into()) 556 | } 557 | 558 | #[test] 559 | fn run_defined_static_function() { 560 | let i = Isolate::new(); 561 | let c = Context::new(&i); 562 | let f = value::Function::new(&i, &c, 2, Box::new(test_function)); 563 | 564 | let k = value::String::from_str(&i, "f"); 565 | c.global().set(&c, &k, &f); 566 | 567 | let name = value::String::from_str(&i, "test.js"); 568 | let source = value::String::from_str(&i, "f(2, 3)"); 569 | let script = Script::compile_with_name(&i, &c, &name, &source).unwrap(); 570 | let result = script.run(&c).unwrap(); 571 | 572 | assert!(result.is_int32()); 573 | assert_eq!(5, result.int32_value(&c)); 574 | } 575 | 576 | #[test] 577 | fn run_defined_function_template_instance() { 578 | let i = Isolate::new(); 579 | let c = Context::new(&i); 580 | let ft = template::FunctionTemplate::new(&i, &c, Box::new(test_function)); 581 | let f = ft.get_function(&c); 582 | 583 | let k = value::String::from_str(&i, "f"); 584 | c.global().set(&c, &k, &f); 585 | 586 | let name = value::String::from_str(&i, "test.js"); 587 | let source = value::String::from_str(&i, "f(2, 3)"); 588 | let script = Script::compile_with_name(&i, &c, &name, &source).unwrap(); 589 | let result = script.run(&c).unwrap(); 590 | 591 | assert!(result.is_int32()); 592 | assert_eq!(5, result.int32_value(&c)); 593 | } 594 | 595 | #[test] 596 | fn create_object_instance() { 597 | let i = Isolate::new(); 598 | let c = Context::new(&i); 599 | value::Object::new(&i, &c); 600 | } 601 | 602 | #[test] 603 | fn create_array_instance() { 604 | let i = Isolate::new(); 605 | let c = Context::new(&i); 606 | value::Array::new(&i, &c, 42); 607 | } 608 | 609 | #[test] 610 | fn create_object_template_instance() { 611 | let i = Isolate::new(); 612 | let c = Context::new(&i); 613 | let ot = template::ObjectTemplate::new(&i); 614 | ot.set("test", &value::Integer::new(&i, 5)); 615 | 616 | let o = ot.new_instance(&c); 617 | let k = value::String::from_str(&i, "o"); 618 | c.global().set(&c, &k, &o); 619 | 620 | let name = value::String::from_str(&i, "test.js"); 621 | let source = value::String::from_str(&i, "o.test"); 622 | let script = Script::compile_with_name(&i, &c, &name, &source).unwrap(); 623 | let result = script.run(&c).unwrap(); 624 | 625 | assert!(result.is_int32()); 626 | assert_eq!(5, result.int32_value(&c)); 627 | } 628 | 629 | #[test] 630 | fn run_object_template_instance_function() { 631 | let i = Isolate::new(); 632 | let c = Context::new(&i); 633 | let ot = template::ObjectTemplate::new(&i); 634 | let ft = template::FunctionTemplate::new(&i, &c, Box::new(test_function)); 635 | ot.set("f", &ft); 636 | 637 | let o = ot.new_instance(&c); 638 | let k = value::String::from_str(&i, "o"); 639 | c.global().set(&c, &k, &o); 640 | 641 | let name = value::String::from_str(&i, "test.js"); 642 | let source = value::String::from_str(&i, "o.f(2, 3)"); 643 | let script = Script::compile_with_name(&i, &c, &name, &source).unwrap(); 644 | let result = script.run(&c).unwrap(); 645 | 646 | assert!(result.is_int32()); 647 | assert_eq!(5, result.int32_value(&c)); 648 | } 649 | 650 | #[test] 651 | fn isolate_rc() { 652 | let (f, c, p) = { 653 | let isolate = Isolate::new(); 654 | let context = Context::new(&isolate); 655 | let param = value::Integer::new(&isolate, 42); 656 | 657 | let function = value::Function::new(&isolate, 658 | &context, 659 | 1, 660 | Box::new(|mut info| Ok(info.args.remove(0)))); 661 | (function, context, param) 662 | }; 663 | 664 | let result = f.call(&c, &[&p]).unwrap(); 665 | assert_eq!(42, result.uint32_value(&c)); 666 | } 667 | 668 | #[test] 669 | #[should_panic] 670 | fn isolate_exception() { 671 | let isolate = Isolate::new(); 672 | let context = Context::new(&isolate); 673 | 674 | let closure_isolate = isolate.clone(); 675 | let f = value::Function::new(&isolate, 676 | &context, 677 | 0, 678 | Box::new(move |_| { 679 | let msg = value::String::from_str(&closure_isolate, "FooBar"); 680 | let ex = value::Exception::error(&closure_isolate, &msg); 681 | Err(ex) 682 | })); 683 | 684 | let name = value::String::from_str(&isolate, "f"); 685 | context.global().set(&context, &name, &f); 686 | 687 | let source = value::String::from_str(&isolate, "f();"); 688 | let script = Script::compile(&isolate, &context, &source).unwrap(); 689 | script.run(&context).unwrap(); 690 | } 691 | 692 | #[test] 693 | fn closure_lifetime() { 694 | struct Foo { 695 | msg: String, 696 | } 697 | 698 | let isolate = Isolate::new(); 699 | let context = Context::new(&isolate); 700 | 701 | let f = { 702 | let foo = Foo { msg: "Hello, World!".into() }; 703 | 704 | let closure_isolate = isolate.clone(); 705 | value::Function::new(&isolate, 706 | &context, 707 | 0, 708 | Box::new(move |_| { 709 | assert_eq!("Hello, World!", &foo.msg); 710 | Ok(value::undefined(&closure_isolate).into()) 711 | })) 712 | }; 713 | 714 | let bar = Foo { msg: "Goodbye, World!".into() }; 715 | let name = value::String::from_str(&isolate, "f"); 716 | context.global().set(&context, &name, &f); 717 | 718 | let source = value::String::from_str(&isolate, "f();"); 719 | let script = Script::compile(&isolate, &context, &source).unwrap(); 720 | let result = script.run(&context).unwrap(); 721 | 722 | assert_eq!("Goodbye, World!", bar.msg); 723 | assert!(result.is_undefined()); 724 | } 725 | 726 | #[test] 727 | #[should_panic="You dun goofed"] 728 | fn propagate_panic() { 729 | let isolate = Isolate::new(); 730 | let context = Context::new(&isolate); 731 | 732 | let f = value::Function::new(&isolate, 733 | &context, 734 | 0, 735 | Box::new(|_| panic!("You dun goofed"))); 736 | 737 | f.call(&context, &[]).unwrap(); 738 | } 739 | 740 | #[test] 741 | fn catch_panic() { 742 | let isolate = Isolate::new(); 743 | let context = Context::new(&isolate); 744 | 745 | let f = value::Function::new(&isolate, 746 | &context, 747 | 0, 748 | Box::new(|_| panic!("Something: {}", 42))); 749 | 750 | let f_key = value::String::from_str(&isolate, "f"); 751 | context.global().set(&context, &f_key, &f); 752 | 753 | let source = value::String::from_str(&isolate, 754 | "(function() { try { f(); } catch (e) { return \ 755 | e.message; } })()"); 756 | let script = Script::compile(&isolate, &context, &source).unwrap(); 757 | let result = script.run(&context).unwrap(); 758 | 759 | let result = result.into_string().unwrap(); 760 | 761 | assert_eq!("Rust panic: Something: 42", result.value()); 762 | } 763 | } 764 | 765 | #[cfg(all(feature="unstable", test))] 766 | mod benches { 767 | extern crate test; 768 | 769 | use super::*; 770 | 771 | #[bench] 772 | fn js_function_call(bencher: &mut test::Bencher) { 773 | let isolate = Isolate::new(); 774 | let context = Context::new(&isolate); 775 | let name = value::String::from_str(&isolate, "test.js"); 776 | let source = value::String::from_str(&isolate, "(function(a) { return a; })"); 777 | let script = Script::compile_with_name(&isolate, &context, &name, &source).unwrap(); 778 | let result = script.run(&context).unwrap(); 779 | 780 | let function = result.into_function().unwrap(); 781 | let param = value::Integer::new(&isolate, 42); 782 | 783 | bencher.iter(|| function.call(&context, &[¶m]).unwrap()); 784 | } 785 | 786 | #[bench] 787 | fn native_function_call(bencher: &mut test::Bencher) { 788 | let isolate = Isolate::new(); 789 | let context = Context::new(&isolate); 790 | 791 | let function = value::Function::new(&isolate, 792 | &context, 793 | 1, 794 | Box::new(|mut info| Ok(info.args.remove(0)))); 795 | let param = value::Integer::new(&isolate, 42); 796 | 797 | bencher.iter(|| function.call(&context, &[¶m]).unwrap()); 798 | } 799 | } 800 | -------------------------------------------------------------------------------- /src/platform.rs: -------------------------------------------------------------------------------- 1 | use v8_sys as v8; 2 | use std::thread; 3 | use std::time; 4 | use num_cpus; 5 | use isolate; 6 | 7 | lazy_static! { 8 | static ref START_TIME: time::Instant = { 9 | time::Instant::now() 10 | }; 11 | } 12 | 13 | /// A simple platform implementation that uses global OS threads for 14 | /// scheduling. 15 | // TODO: make this use some kind of main loop/work stealing queue 16 | // instead. 17 | #[derive(Debug)] 18 | pub struct Platform(v8::PlatformPtr); 19 | 20 | #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] 21 | pub struct Task(v8::TaskPtr); 22 | 23 | unsafe impl Send for Task {} 24 | 25 | #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] 26 | pub struct IdleTask(v8::IdleTaskPtr); 27 | 28 | impl Platform { 29 | pub fn new() -> Platform { 30 | let raw = unsafe { v8::v8_Platform_Create(PLATFORM_FUNCTIONS) }; 31 | 32 | if raw.is_null() { 33 | panic!("Could not create Platform") 34 | } 35 | 36 | Platform(raw) 37 | } 38 | 39 | pub fn as_raw(&self) -> v8::PlatformPtr { 40 | self.0 41 | } 42 | } 43 | 44 | impl Drop for Platform { 45 | fn drop(&mut self) { 46 | unsafe { 47 | v8::v8_Platform_Destroy(self.0); 48 | } 49 | } 50 | } 51 | 52 | impl Task { 53 | pub fn run(&self) { 54 | unsafe { 55 | v8::v8_Task_Run(self.0); 56 | } 57 | } 58 | } 59 | 60 | impl Drop for Task { 61 | fn drop(&mut self) { 62 | unsafe { 63 | v8::v8_Task_Destroy(self.0); 64 | } 65 | } 66 | } 67 | 68 | impl IdleTask { 69 | pub fn run(&self, deadline: time::Duration) { 70 | unsafe { 71 | v8::v8_IdleTask_Run(self.0, duration_to_seconds(deadline)); 72 | } 73 | } 74 | } 75 | 76 | impl Drop for IdleTask { 77 | fn drop(&mut self) { 78 | unsafe { 79 | v8::v8_IdleTask_Destroy(self.0); 80 | } 81 | } 82 | } 83 | 84 | const PLATFORM_FUNCTIONS: v8::v8_PlatformFunctions = v8::v8_PlatformFunctions { 85 | Destroy: Some(destroy_platform), 86 | NumberOfAvailableBackgroundThreads: Some(number_of_available_background_threads), 87 | CallOnBackgroundThread: Some(call_on_background_thread), 88 | CallOnForegroundThread: Some(call_on_foreground_thread), 89 | CallDelayedOnForegroundThread: Some(call_delayed_on_foreground_thread), 90 | CallIdleOnForegroundThread: Some(call_idle_on_foreground_thread), 91 | IdleTasksEnabled: Some(idle_tasks_enabled), 92 | MonotonicallyIncreasingTime: Some(monotonically_increasing_time), 93 | }; 94 | 95 | extern "C" fn destroy_platform() { 96 | // No-op 97 | } 98 | 99 | extern "C" fn number_of_available_background_threads() -> usize { 100 | num_cpus::get() 101 | } 102 | 103 | extern "C" fn call_on_background_thread(task: v8::TaskPtr, 104 | _expected_runtime: v8::v8_ExpectedRuntime) { 105 | let task = Task(task); 106 | thread::spawn(move || { 107 | unsafe { 108 | v8::v8_Task_Run(task.0); 109 | } 110 | }); 111 | } 112 | 113 | extern "C" fn call_on_foreground_thread(isolate: v8::IsolatePtr, task: v8::TaskPtr) { 114 | let task = Task(task); 115 | let isolate = unsafe { isolate::Isolate::from_raw(isolate) }; 116 | 117 | isolate.enqueue_task(task); 118 | } 119 | 120 | extern "C" fn call_delayed_on_foreground_thread(isolate: v8::IsolatePtr, 121 | task: v8::TaskPtr, 122 | delay_in_seconds: f64) { 123 | let task = Task(task); 124 | let isolate = unsafe { isolate::Isolate::from_raw(isolate) }; 125 | let duration = duration_from_seconds(delay_in_seconds); 126 | 127 | isolate.enqueue_delayed_task(duration, task); 128 | } 129 | 130 | extern "C" fn call_idle_on_foreground_thread(isolate: v8::IsolatePtr, idle_task: v8::IdleTaskPtr) { 131 | let idle_task = IdleTask(idle_task); 132 | let isolate = unsafe { isolate::Isolate::from_raw(isolate) }; 133 | 134 | isolate.enqueue_idle_task(idle_task); 135 | } 136 | 137 | extern "C" fn idle_tasks_enabled(isolate: v8::IsolatePtr) -> bool { 138 | let isolate = unsafe { isolate::Isolate::from_raw(isolate) }; 139 | 140 | isolate.supports_idle_tasks() 141 | } 142 | 143 | extern "C" fn monotonically_increasing_time() -> f64 { 144 | let start = *START_TIME; 145 | let d = time::Instant::now().duration_since(start); 146 | duration_to_seconds(d) 147 | } 148 | 149 | fn duration_to_seconds(duration: time::Duration) -> f64 { 150 | (duration.subsec_nanos() as f64).mul_add(1e-9, duration.as_secs() as f64) 151 | } 152 | 153 | fn duration_from_seconds(seconds: f64) -> time::Duration { 154 | time::Duration::new(seconds as u64, (seconds.fract() * 1e9) as u32) 155 | } 156 | -------------------------------------------------------------------------------- /src/script.rs: -------------------------------------------------------------------------------- 1 | //! Script and source code compilation, execution, origins and management. 2 | use v8_sys as v8; 3 | 4 | use context; 5 | use error; 6 | use isolate; 7 | use value; 8 | use util; 9 | 10 | /// A compiled JavaScript script, tied to a Context which was active when the script was compiled. 11 | #[derive(Debug)] 12 | pub struct Script(isolate::Isolate, v8::ScriptRef); 13 | 14 | impl Script { 15 | /// Compiles the specified source code into a compiled script. 16 | pub fn compile(isolate: &isolate::Isolate, 17 | context: &context::Context, 18 | source: &value::String) 19 | -> error::Result