├── .editorconfig ├── .gitignore ├── Cargo.toml ├── README.md ├── chakracore-sys ├── Cargo.toml ├── README.md ├── build.rs └── src │ └── lib.rs ├── chakracore ├── Cargo.toml ├── README.md └── src │ ├── context.rs │ ├── error.rs │ ├── lib.rs │ ├── macros.rs │ ├── property.rs │ ├── runtime.rs │ ├── script.rs │ ├── util.rs │ └── value │ ├── array.rs │ ├── boolean.rs │ ├── error.rs │ ├── external.rs │ ├── function.rs │ ├── macros.rs │ ├── mod.rs │ ├── number.rs │ ├── object.rs │ ├── promise.rs │ ├── string.rs │ └── value.rs └── rustfmt.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.rs] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | **/*.bk 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "chakracore-sys", 4 | "chakracore", 5 | ] 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NOTE: This project is not currently maintained, due to ChakraCore itself is no longer being actively developed. 2 | 3 |
4 | 5 | # `chakracore-rs` 6 | 7 | [![crates.io version][crate-shield]][crate] 8 | [![Documentation][docs-shield]][docs] 9 | [![Language (Rust)][rust-shield]][rust] 10 | 11 |
12 | 13 | **chakracore-rs is an iditiomatic wrapper for 14 | [ChakraCore](https://github.com/Microsoft/ChakraCore), written in Rust.** 15 | 16 | This repository contains two crates: 17 | - [chakracore-sys](#chakracore-sys) - raw bindings to the JavaScript 18 | Runtime. 19 | - [chakracore](#chakracore) - an idiomatic wrapper, built on the 20 | chakracore-sys crate. 21 | 22 | ## chakracore 23 | 24 | This is a wrapper around the [JavaScript Runtime (JSRT)](https://goo.gl/1F6Gi1), 25 | used in [Microsoft Edge](https://www.microsoft.com/en-us/windows/microsoft-edge) 26 | and [node-chakracore](https://github.com/nodejs/node-chakracore). The library is 27 | still in pre-release and is not yet stable. The tests try to cover as much 28 | functionality as possible but memory leaks and segfaults may occur. If you want 29 | a more stable library, use the underlying API directly; 30 | [chakracore-sys](#chakracore-sys). 31 | 32 | ### Installation 33 | 34 | Add this to your `Cargo.toml`: 35 | 36 | ```toml 37 | [dependencies] 38 | chakracore = "0.2" 39 | ``` 40 | 41 | ... and this to your crate root: 42 | 43 | ```rust 44 | extern crate chakracore as js; 45 | ``` 46 | 47 | *NOTE: See additional build instructions for [chakracore-sys](#chakracore-sys)* 48 | 49 | ### Examples 50 | 51 | #### Hello World 52 | 53 | ```rust 54 | extern crate chakracore as js; 55 | 56 | fn main() { 57 | let runtime = js::Runtime::new().unwrap(); 58 | let context = js::Context::new(&runtime).unwrap(); 59 | let guard = context.make_current().unwrap(); 60 | 61 | let result = js::script::eval(&guard, "5 + 5").unwrap(); 62 | assert_eq!(result.to_integer(&guard), 10); 63 | } 64 | ``` 65 | 66 | #### Function - Multiply 67 | 68 | ```rust 69 | extern crate chakracore as js; 70 | 71 | fn main() { 72 | let runtime = js::Runtime::new().unwrap(); 73 | let context = js::Context::new(&runtime).unwrap(); 74 | let guard = context.make_current().unwrap(); 75 | 76 | let multiply = js::value::Function::new(&guard, Box::new(|guard, info| { 77 | let result = info.arguments[0].to_integer(guard) 78 | * info.arguments[1].to_integer(guard); 79 | Ok(js::value::Number::new(guard, result).into()) 80 | })); 81 | 82 | let result = multiply.call(&guard, &[ 83 | &js::value::Number::new(&guard, 191).into(), 84 | &js::value::Number::new(&guard, 7).into(), 85 | ]).unwrap(); 86 | 87 | assert_eq!(result.to_integer(&guard), 1337); 88 | } 89 | ``` 90 | 91 | ## chakracore-sys 92 | 93 | This library handles the static and dynamic linking of the JavaScript 94 | Runtime. The rust bindings are generated (on the fly) for the interface, 95 | therefore the entire API is exposed and accessable. 96 | 97 | A *Hello World* example can be found in 98 | [src/lib.rs](./chakracore-sys/src/lib.rs). 99 | 100 | An example of the generated bindings can be found 101 | [here](https://gist.github.com/darfink/d519756ad88efcddfbfe895439cf9451). 102 | 103 | ### Requirements 104 | 105 | This library builds the ChakraCore component in the source tree. It is cloned 106 | by the build script and built in test-mode (same as release, but includes 107 | more runtime checks). If custom build settings are desired, ChakraCore can be 108 | built manually, out of tree, and specified using two environment variables: 109 | 110 | * `CHAKRA_SOURCE`: The root of the ChakraCore checkout. 111 | * `CHAKRA_BUILD`: The `bin` directory of the build. 112 | - Default on Windows: `%CHAKRA_SOURCE%\Build\VcBuild\bin\{BUILD_TYPE}`. 113 | - Default on Unix: `$CHAKRA_SOURCE/BuildLinux/{BUILD_TYPE}`. 114 | 115 | This script has not been tested with the `--embed-icu` option. 116 | 117 | #### Static/Shared 118 | 119 | By default, this library links ChakraCore dynamically. There is a feature 120 | called `static` that builds it by linking to the generated archive instead. 121 | On windows, only shared library builds are available as of this time (see 122 | [#279](https://github.com/Microsoft/ChakraCore/issues/279)). 123 | 124 | #### Prerequisites 125 | 126 | The library naturally shares all of ChakraCore's dependencies. Beyond this, 127 | [rust-bindgen](https://github.com/servo/rust-bindgen) is used in the build 128 | script, which requires `clang-3.8` or later. On Unix `pkg-config` is required as 129 | well. 130 | 131 | ##### Windows 132 | 133 | * Visual Studio 2013/2015/2017 with: 134 | - Windows SDK 8.1 135 | - C++ support 136 | * `clang-3.8` or later. Downloads can be found 137 | [here](http://llvm.org/releases/download.html). 138 | Remember to add LLVM directories to `PATH` during installation. 139 | * Rust MSVC toolchain (i.e `rustup install stable-msvc`). 140 | This is required since ChakraCore uses the MSVC ABI. 141 | * If building for ARM: [Windows 10 SDK (July 142 | 2015)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive) 143 | 144 | ##### macOS 145 | 146 | ``` 147 | $ brew install cmake icu4c llvm38 pkg-config 148 | ``` 149 | 150 | ##### Debian-based linuxes 151 | 152 | ``` 153 | # apt-get install -y build-essential cmake clang libunwind8-dev \ 154 | # libicu-dev llvm-3.8-dev libclang-3.8-dev pkg-config liblzma-dev 155 | ``` 156 | 157 | #### Building 158 | 159 | - ##### Windows 160 | 161 | Ensure that you are running in a Visual Studio command line environment, 162 | either by sourcing `vcvarsall.bat`, or by building from the Visual 163 | Studio Command Prompt. 164 | 165 | ``` 166 | $ cargo test -vv 167 | ``` 168 | 169 | - ##### Unix 170 | 171 | ``` 172 | $ cargo test -vv [--features static] 173 | ``` 174 | 175 | In case you find yourself stuck in the build process, open an 176 | [issue](https://github.com/darfink/chakracore-rs/issues/new). 177 | 178 | #### Status 179 | 180 | This library has been built on `macOS 10.12 x86_64`, `Ubuntu 16.10 x86_64` and 181 | `Windows 10 x86_x64`. 182 | 183 | [crate-shield]: https://img.shields.io/crates/v/chakracore.svg?style=flat-square 184 | [crate]: https://crates.io/crates/chakracore 185 | [rust-shield]: https://img.shields.io/badge/powered%20by-rust-blue.svg?style=flat-square 186 | [rust]: https://www.rust-lang.org 187 | [docs-shield]: https://img.shields.io/badge/docs-crates-green.svg?style=flat-square 188 | [docs]: https://darfink.github.io/chakracore-rs/chakracore/index.html 189 | -------------------------------------------------------------------------------- /chakracore-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Elliott Linder "] 3 | name = "chakracore-sys" 4 | version = "0.2.3" 5 | build = "build.rs" 6 | description = "Low-level bindings to JSRT, the ChakraCore API" 7 | documentation = "https://docs.rs/chakracore-sys" 8 | homepage = "https://github.com/darfink/chakracore-rs/tree/master/chakracore-sys" 9 | repository = "https://github.com/darfink/chakracore-rs/tree/master/chakracore-sys" 10 | include = ["build.rs", "src/**/*", "Cargo.toml"] 11 | keywords = ["jsrt", "javascript", "js", "ecmascript", "chakracore"] 12 | license = "MIT" 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [dependencies] 18 | libc = "0.2" 19 | 20 | [build-dependencies] 21 | bindgen = "0.25" 22 | clang-sys = "0.18.0" 23 | pkg-config = "0.3.8" 24 | regex = "0.2" 25 | 26 | [features] 27 | static = [] 28 | -------------------------------------------------------------------------------- /chakracore-sys/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /chakracore-sys/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | extern crate clang_sys; 3 | extern crate pkg_config; 4 | extern crate regex; 5 | 6 | use std::path::{Path, PathBuf}; 7 | use std::{env, fs}; 8 | 9 | const LIBRARY: &'static str = "ChakraCore"; 10 | const REPOSITORY: &'static str = "https://github.com/Microsoft/ChakraCore.git"; 11 | const VERSION: &'static str = "1.11.10"; 12 | const LIBS: [(&'static str, &'static str); 1] = [("lib", "ChakraCoreStatic")]; 13 | 14 | macro_rules! get(($name:expr) => (env::var($name).unwrap())); 15 | macro_rules! log { 16 | ($fmt:expr) => (println!(concat!("chakracore-sys/build.rs:{}: ", $fmt), line!())); 17 | ($fmt:expr, $($arg:tt)*) => (println!(concat!("chakracore-sys/build.rs:{}: ", $fmt), line!(), $($arg)*)); 18 | } 19 | 20 | fn main() { 21 | println!("cargo:rerun-if-env-changed=CHAKRA_SOURCE"); 22 | println!("cargo:rerun-if-env-changed=CHAKRA_BUILD"); 23 | 24 | if util::has_target("windows") { 25 | if cfg!(feature = "static") { 26 | // This is related to ChakraCore (see #279) 27 | panic!("Windows build does not support static linkage"); 28 | } 29 | 30 | if !util::has_target("msvc") { 31 | // The runtime errors are very cryptic, so be explicit 32 | panic!("Only MSVC toolchain is compatible with ChakraCore"); 33 | } 34 | } else { 35 | // This build relies heavily on pkg-config 36 | util::run_command("which", &["pkg-config"], None); 37 | } 38 | 39 | // If both these are set, they will override the default build settings 40 | let overrides = [env::var("CHAKRA_SOURCE"), env::var("CHAKRA_BUILD")]; 41 | 42 | assert!( 43 | overrides.iter().filter(|var| var.is_ok()).count() != 1, 44 | "Only one of $CHAKRA_SOURCE/BUILD variable was set" 45 | ); 46 | 47 | let (src_dir, lib_dirs) = if overrides.iter().all(|var| var.is_ok()) { 48 | log!("Using custom ChakraCore build"); 49 | setup_custom() 50 | } else { 51 | log!("Using default ChakraCore build (version: {})", VERSION); 52 | setup_default() 53 | }; 54 | 55 | binding::generate(&src_dir); 56 | linking::setup(&lib_dirs); 57 | } 58 | 59 | fn setup_custom() -> (PathBuf, Vec) { 60 | // Use the user supplied build directory 61 | let build_dir = PathBuf::from(get!("CHAKRA_BUILD")); 62 | let src_dir = PathBuf::from(get!("CHAKRA_SOURCE")); 63 | 64 | ( 65 | src_dir, 66 | LIBS.iter().map(|&(dir, _)| build_dir.join(dir)).collect(), 67 | ) 68 | } 69 | 70 | fn setup_default() -> (PathBuf, Vec) { 71 | let out_dir = PathBuf::from(&get!("OUT_DIR")); 72 | let lib_dir = out_dir.join(format!("lib-{}", VERSION)); 73 | 74 | // Directory where all sources are stored 75 | let src_dir = 76 | PathBuf::from(&get!("CARGO_MANIFEST_DIR")).join(format!("target/source-{}", VERSION)); 77 | 78 | if !lib_dir.exists() { 79 | log!("Creating directory '{:?}'", lib_dir); 80 | fs::create_dir(&lib_dir).expect("Could not create library directory"); 81 | } 82 | 83 | if !Path::new(&src_dir.join(".git")).exists() { 84 | // Clone the repository for local access 85 | util::run_command( 86 | "git", 87 | &[ 88 | "clone", 89 | &format!("--branch=v{}", VERSION), 90 | REPOSITORY, 91 | src_dir.to_str().unwrap(), 92 | ], 93 | None, 94 | ); 95 | } 96 | 97 | let has_required_libs = if cfg!(feature = "static") { 98 | // The static archives make up three different files, all required 99 | LIBS 100 | .iter() 101 | .all(|&(_, name)| lib_dir.join(linking::format_lib(name)).exists()) 102 | } else { 103 | // The dynamic library is only a single file 104 | lib_dir.join(linking::format_lib(LIBRARY)).exists() 105 | }; 106 | 107 | if !has_required_libs { 108 | log!("Building ChakraCore from source, brace yourself"); 109 | let build_dir = build::compile(&src_dir); 110 | build::copy_libs(&build_dir, &lib_dir); 111 | } else { 112 | log!("Binaries already built, using existing..."); 113 | } 114 | 115 | // Return the source and lib directory 116 | (src_dir, vec![lib_dir]) 117 | } 118 | 119 | mod build { 120 | use pkg_config; 121 | use std::path::{Path, PathBuf}; 122 | use std::{env, fs}; 123 | use {linking, util, LIBRARY, LIBS}; 124 | 125 | /// Builds the ChakraCore project. 126 | pub fn compile(src_dir: &Path) -> PathBuf { 127 | let arch = util::get_arch(&get!("TARGET")); 128 | 129 | let is_debug = util::is_debug(); 130 | let config = if is_debug { "Debug" } else { "Test" }; 131 | 132 | if util::has_target("windows") { 133 | // This requires `vcvars` to be sourced 134 | util::run_command( 135 | "msbuild", 136 | &[ 137 | "/m", 138 | &format!("/p:Configuration={}", config), 139 | &format!("/p:Platform={:?}", arch), 140 | r"Build\Chakra.Core.sln", 141 | ], 142 | Some(&src_dir), 143 | ); 144 | 145 | src_dir.join(format!("Build/VcBuild/bin/{:?}_{}", arch, config)) 146 | } else { 147 | // The ICU directory must be configued using pkg-config 148 | let icu_include = pkg_config::get_variable("icu-i18n", "includedir") 149 | .expect("No library includes for 'icu-i18n' found"); 150 | 151 | // These need to live long enough 152 | let arg_icu = format!("--icu={}", icu_include); 153 | let arg_jobs = format!("--jobs={}", get!("NUM_JOBS")); 154 | 155 | let mut arguments = vec![ 156 | #[cfg(feature = "static")] 157 | "--static", 158 | if is_debug { "--debug" } else { "--test-build" }, 159 | &arg_jobs, 160 | &arg_icu, 161 | ]; 162 | 163 | match arch { 164 | util::Architecture::arm => panic!("ARM is only supported on Windows"), 165 | util::Architecture::x86 => arguments.push("--arch=TARGETSx86"), 166 | util::Architecture::x64 => 167 | // This is the default 168 | { 169 | () 170 | }, 171 | } 172 | 173 | // Use the build script bundled in ChakraCore 174 | util::run_command("./build.sh", &arguments, Some(&src_dir)); 175 | 176 | // Hopefully this directory won't change 177 | src_dir.join(format!("out/{}", config)) 178 | } 179 | } 180 | 181 | /// Copies all binaries to the local 'libs' folder. 182 | pub fn copy_libs(build_dir: &Path, lib_dir: &Path) { 183 | let build_dir = build_dir.to_path_buf(); 184 | 185 | let deps = if cfg!(feature = "static") { 186 | LIBS 187 | .iter() 188 | .map(|&(dir, name)| build_dir.join(dir).join(linking::format_lib(name))) 189 | .collect() 190 | } else { 191 | vec![ 192 | // Windows requires an import library as well 193 | #[cfg(windows)] 194 | build_dir.join(format!("{}.lib", LIBRARY)), 195 | build_dir.join(linking::format_lib(LIBRARY)), 196 | ] 197 | }; 198 | 199 | for dependency in deps { 200 | let file_name = dependency.file_name().unwrap(); 201 | fs::copy(&dependency, lib_dir.join(file_name)).expect(&format!( 202 | "Failed to copy '{:?}' to target directory", 203 | dependency 204 | )); 205 | } 206 | } 207 | } 208 | 209 | mod linking { 210 | use pkg_config; 211 | use std::fs; 212 | use std::path::{Path, PathBuf}; 213 | use {util, LIBS}; 214 | 215 | /// Provides Cargo with linker configuration. 216 | pub fn setup(search_paths: &[PathBuf]) { 217 | for path in search_paths { 218 | add_path(path); 219 | } 220 | 221 | if cfg!(feature = "static") { 222 | // Statically link all ChakraCore libraries 223 | link_manually( 224 | "static", 225 | &LIBS.iter().map(|&(_, name)| name).collect::>(), 226 | ); 227 | 228 | if util::has_target("apple") { 229 | link_manually("framework", &["Security", "Foundation"]); 230 | } else if util::has_target("linux") { 231 | // TODO: Support for builds without ptrace 232 | link_library("libunwind-ptrace", true); 233 | link_library("liblzma", true); 234 | } 235 | 236 | // Use 'libstdc++' on all Unixes (ChakraCore does this) 237 | link_manually("dylib", &["stdc++"]); 238 | link_library("icu-i18n", true); 239 | } else { 240 | // The dynamic library is completely self-contained 241 | link_manually("dylib", &["ChakraCore"]); 242 | } 243 | } 244 | 245 | /// Returns a library filename in OS specific format. 246 | pub fn format_lib(name: &str) -> String { 247 | if cfg!(windows) { 248 | format!("{}.dll", name) 249 | } else if cfg!(feature = "static") { 250 | format!("lib{}.a", name) 251 | } else if cfg!(target_os = "macos") { 252 | format!("lib{}.dylib", name) 253 | } else { 254 | format!("lib{}.so", name) 255 | } 256 | } 257 | 258 | /// Adds a library search path. 259 | fn add_path

(dir: P) 260 | where 261 | P: AsRef, 262 | { 263 | let dir = dir.as_ref(); 264 | assert!( 265 | fs::metadata(dir).map(|m| m.is_dir()).unwrap_or(false), 266 | format!("Library search path '{:?}' does not exist", dir) 267 | ); 268 | println!("cargo:rustc-link-search=native={}", dir.to_string_lossy()); 269 | } 270 | 271 | fn link_library(name: &str, statik: bool) { 272 | pkg_config::Config::new() 273 | .statik(statik) 274 | .probe(name) 275 | .ok() 276 | .expect(&format!("No package configuration for '{}' found", name)); 277 | } 278 | 279 | fn link_manually(linkage: &str, libs: &[&str]) { 280 | for lib in libs.iter() { 281 | println!("cargo:rustc-link-lib={}={}", linkage, lib); 282 | } 283 | } 284 | } 285 | 286 | mod binding { 287 | use bindgen; 288 | use clang_sys::support::Clang; 289 | use regex::Regex; 290 | use std::env; 291 | use std::path::Path; 292 | use util; 293 | 294 | pub fn generate(src_dir: &Path) { 295 | let clang = Clang::find(None).expect("No clang found, is it installed?"); 296 | 297 | // Some default includes are not found without this (e.g 'stddef.h') 298 | let mut builder = clang 299 | .c_search_paths 300 | .iter() 301 | .fold(bindgen::builder(), |builder, paths| { 302 | // Ensure all potential system paths are searched 303 | paths.iter().fold(builder, |builder, path| { 304 | builder 305 | .clang_arg("-idirafter") 306 | .clang_arg(path.to_str().unwrap()) 307 | }) 308 | }); 309 | 310 | if util::has_target("windows") { 311 | // Clang is not aware of 'uint8_t' and its cousins by default 312 | builder = ["-include", "stdint.h", "-Wno-pragma-once-outside-header"] 313 | .iter() 314 | .fold(builder, |builder, carg| builder.clang_arg(*carg)); 315 | } 316 | 317 | // Convert 'ChakraCore.h' → 'ffi.rs' 318 | let binding = builder 319 | // Source contains 'nullptr' 320 | .clang_arg("-xc++") 321 | .clang_arg("--std=c++11") 322 | // This must be after the arguments to Clang 323 | .header(src_dir.join("lib/Jsrt").join("ChakraCore.h").to_str().unwrap()) 324 | // Only include JSRT associated types (i.e not STL types) 325 | .whitelisted_function("^Js.+") 326 | .whitelisted_type("^Js.+") 327 | // These are not detected as dependencies 328 | .whitelisted_type("ChakraBytePtr") 329 | .whitelisted_type("TTDOpenResourceStreamCallback") 330 | // Some enums are used as bitfields 331 | .bitfield_enum(r"\w+Attributes") 332 | .bitfield_enum(r"\w+Modes") 333 | .ctypes_prefix("libc") 334 | .generate() 335 | .expect("Failed to generate binding") 336 | .to_string(); 337 | 338 | // Make the binding Rust friendly and platform agnostic 339 | let binding = sanitize_interface(binding); 340 | 341 | let out_dir_str = env::var_os("OUT_DIR").expect("No $OUT_DIR specified"); 342 | let out_dir_path = Path::new(&out_dir_str); 343 | 344 | // Write the generated binding to file 345 | util::write_file_contents(&out_dir_path.join("ffi.rs"), &binding); 346 | } 347 | 348 | fn sanitize_interface(mut content: String) -> String { 349 | // Change calling convention from C → system 350 | regex_replace(&mut content, "extern \"C\"", "extern \"system\""); 351 | 352 | // Normalize all bitflags (by removing the prepended enum name) 353 | regex_replace(&mut content, r"_\w+_(?P\w+):", "$name:"); 354 | 355 | // Ensure safety by making all void handles strongly typed, wrapping the 356 | // pointer in a struct. Also derive sensible defaults and add a constructor 357 | // that initializes the handle with a null pointer. 358 | regex_replace( 359 | &mut content, 360 | r"pub type (?P\w+).+(?P\*mut.+c_void);", 361 | &[ 362 | "#[repr(C)]", 363 | "#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]", 364 | "pub struct $name(pub $type);", 365 | "impl $name {", 366 | "pub fn new() -> Self { $name(::std::ptr::null_mut()) }", 367 | "}", 368 | ] 369 | .join("\n"), 370 | ); 371 | 372 | // Enums are scoped in Rust, but they are not in C/C++. This leads to 373 | // verbose and cumbersome code (e.g 'JsMemoryType::JsMemoryTypeAlloc'). To 374 | // prevent this, remove a specific prefix of all enum values. By default the 375 | // enum name (and some edge cases where the values do not match the name). 376 | let mut prefixes_to_remove = regex_find(&content, r"enum (\w+)"); 377 | 378 | // These prefixes do not correspond to the enum name 379 | prefixes_to_remove.extend( 380 | [ 381 | "JsError", 382 | "JsArrayType", 383 | "JsModuleHostInfo", 384 | "JsMemory", 385 | "Js", 386 | ] 387 | .iter() 388 | .map(|s| s.to_string()), 389 | ); 390 | 391 | for prefix in prefixes_to_remove.iter() { 392 | let ident = format!(r"{}_?(?P\w+) = (?P\d+)", prefix); 393 | regex_replace(&mut content, &ident, "$name = $value"); 394 | } 395 | 396 | content 397 | } 398 | 399 | /// Replaces all occurences with a specified replacement. 400 | fn regex_replace(source: &mut String, ident: &str, replacement: &str) { 401 | let regex = Regex::new(ident).expect("Replacement regex has invalid syntax"); 402 | *source = regex.replace_all(&source, replacement).into(); 403 | } 404 | 405 | /// Returns a collection of the first capture group. 406 | fn regex_find(source: &str, ident: &str) -> Vec { 407 | Regex::new(ident) 408 | .expect("Find regex has invalid syntax") 409 | .captures_iter(source) 410 | .map(|cap| cap[1].to_string()) 411 | .collect() 412 | } 413 | } 414 | 415 | mod util { 416 | use std::io::Write; 417 | use std::path::Path; 418 | use std::process::Command; 419 | use std::{env, fs}; 420 | 421 | pub fn write_file_contents(path: &Path, content: &str) { 422 | let mut handle = fs::File::create(path).expect("Failed to create file"); 423 | handle 424 | .write_all(content.as_bytes()) 425 | .expect("Failed to write to file"); 426 | } 427 | 428 | #[derive(Debug)] 429 | #[allow(non_camel_case_types)] 430 | pub enum Architecture { 431 | x86, 432 | x64, 433 | arm, 434 | } 435 | 436 | /// Returns the architecture in a build script format. 437 | pub fn get_arch(target: &str) -> Architecture { 438 | if target.starts_with("x86_64") { 439 | Architecture::x64 440 | } else if target.starts_with("i686") || target.starts_with("i586") { 441 | Architecture::x86 442 | } else if target.starts_with("arm") { 443 | Architecture::arm 444 | } else { 445 | panic!("Unknown target architecture"); 446 | } 447 | } 448 | 449 | /// Runs a command in a working directory, and panics if it fails. 450 | pub fn run_command(name: &str, arguments: &[&str], directory: Option<&Path>) { 451 | let mut command = Command::new(name); 452 | if let Some(path) = directory { 453 | command.current_dir(path); 454 | } 455 | 456 | for argument in arguments { 457 | command.arg(argument); 458 | } 459 | 460 | if !command.status().ok().map_or(false, |res| res.success()) { 461 | panic!(format!( 462 | "Failed to run command '{} {}'", 463 | name, 464 | arguments.join(" ") 465 | )); 466 | } 467 | } 468 | 469 | pub fn has_target(target: &str) -> bool { 470 | env::var("TARGET") 471 | .expect("No $TARGET specified") 472 | .contains(target) 473 | } 474 | 475 | pub fn is_debug() -> bool { 476 | match &env::var("PROFILE").expect("No $PROFILE specified")[..] { 477 | "bench" | "release" => false, 478 | _ => true, 479 | } 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /chakracore-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_upper_case_globals)] 4 | extern crate libc; 5 | 6 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 7 | 8 | unsafe impl Send for JsRuntimeHandle {} 9 | unsafe impl Send for JsRef {} 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use super::*; 14 | use std::ptr; 15 | use std::str; 16 | 17 | macro_rules! js { 18 | ($e: expr) => { 19 | let result = $e; 20 | if result != JsErrorCode::NoError { 21 | panic!("JavaScript failed error: {:?}", result); 22 | } 23 | }; 24 | } 25 | 26 | #[test] 27 | fn it_works() { 28 | unsafe { 29 | let mut runtime = JsRuntimeHandle::new(); 30 | js!(JsCreateRuntime(JsRuntimeAttributeNone, None, &mut runtime)); 31 | 32 | // Create an execution context. 33 | let mut context = JsContextRef::new(); 34 | js!(JsCreateContext(runtime, &mut context)); 35 | 36 | // Now set the current execution context. 37 | js!(JsSetCurrentContext(context)); 38 | 39 | let mut script = String::from("5 + 5"); 40 | let vector = script.as_mut_vec(); 41 | 42 | let mut script_buffer = JsValueRef::new(); 43 | js!(JsCreateExternalArrayBuffer( 44 | vector.as_mut_ptr() as *mut _, 45 | vector.len() as usize as _, 46 | None, 47 | ptr::null_mut(), 48 | &mut script_buffer 49 | )); 50 | 51 | let name = "test"; 52 | let mut name_value = JsValueRef::new(); 53 | js!(JsCreateString( 54 | name.as_ptr() as *const libc::c_char, 55 | name.len(), 56 | &mut name_value 57 | )); 58 | 59 | // Run the script. 60 | let mut result = JsValueRef::new(); 61 | let source_context = 1; 62 | js!(JsRun( 63 | script_buffer, 64 | source_context, 65 | name_value, 66 | JsParseScriptAttributeNone, 67 | &mut result 68 | )); 69 | 70 | // Convert your script result to String in JavaScript; redundant if your 71 | // script returns a String 72 | let mut result_as_string = JsValueRef::new(); 73 | js!(JsConvertValueToString(result, &mut result_as_string)); 74 | 75 | // Project script result back to Rust 76 | let mut size = 0; 77 | let mut buffer: Vec = vec![0; 100]; 78 | js!(JsCopyString( 79 | result_as_string, 80 | buffer.as_mut_ptr() as *mut libc::c_char, 81 | buffer.len(), 82 | &mut size 83 | )); 84 | buffer.truncate(size); 85 | 86 | println!("Output: {}", str::from_utf8_unchecked(&buffer)); 87 | 88 | // Dispose runtime 89 | js!(JsSetCurrentContext(JsValueRef::new())); 90 | js!(JsDisposeRuntime(runtime)); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /chakracore/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Elliott Linder "] 3 | description = "High-level bindings to JSRT, the ChakraCore API" 4 | documentation = "https://docs.rs/chakracore" 5 | homepage = "https://github.com/darfink/chakracore-rs" 6 | keywords = ["jsrt", "javascript", "js", "ecmascript", "chakracore"] 7 | license = "MIT" 8 | name = "chakracore" 9 | repository = "https://github.com/darfink/chakracore-rs" 10 | version = "0.2.0" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | anymap = "0.12.1" 15 | boolinator = "2.4.0" 16 | chakracore-sys = { version = "0.2", path = "../chakracore-sys" } 17 | libc = "0.2" 18 | 19 | [dev-dependencies] 20 | matches = "0.1.8" 21 | 22 | [features] 23 | static = ["chakracore-sys/static"] 24 | unstable = [] 25 | -------------------------------------------------------------------------------- /chakracore/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /chakracore/src/context.rs: -------------------------------------------------------------------------------- 1 | //! Execution contexts and sandboxing. 2 | use crate::{util::jstry, value, Result, Runtime}; 3 | use anymap::AnyMap; 4 | use boolinator::Boolinator; 5 | use chakracore_sys::*; 6 | use std::{marker::PhantomData, ptr}; 7 | 8 | /// Used for holding context instance data. 9 | struct ContextData { 10 | promise_queue: Vec, 11 | user_data: AnyMap, 12 | } 13 | 14 | /// A sandboxed execution context with its own set of built-in objects and 15 | /// functions. 16 | /// 17 | /// The majority of APIs require an active context. 18 | /// 19 | /// In a browser or Node.JS environment, the task of executing promises is 20 | /// handled by the runtime. This is not the case with **ChakraCore**. To run 21 | /// promise chains, `execute_tasks` must be called at a regular interval. This 22 | /// is done using the `ContextGuard`. 23 | #[derive(Debug, PartialEq)] 24 | pub struct Context(JsContextRef); 25 | 26 | // TODO: Should context lifetime explicitly depend on runtime? 27 | impl Context { 28 | /// Creates a new context and returns a handle to it. 29 | pub fn new(runtime: &Runtime) -> Result { 30 | let mut reference = JsContextRef::new(); 31 | unsafe { 32 | jstry(JsCreateContext(runtime.as_raw(), &mut reference))?; 33 | jstry(JsSetObjectBeforeCollectCallback( 34 | reference, 35 | ptr::null_mut(), 36 | Some(Self::collect), 37 | ))?; 38 | 39 | let context = Self::from_raw(reference); 40 | context.set_data(Box::new(ContextData { 41 | promise_queue: Vec::new(), 42 | user_data: AnyMap::new(), 43 | }))?; 44 | 45 | // Promise continuation callback requires an active context 46 | context 47 | .exec_with(|_| { 48 | let data = context.get_data() as *mut _ as *mut _; 49 | jstry(JsSetPromiseContinuationCallback( 50 | Some(Self::promise_handler), 51 | data, 52 | )) 53 | }) 54 | .expect("activating promise continuation callback") 55 | .map(|_| context) 56 | } 57 | } 58 | 59 | /// Binds the context to the current scope. 60 | pub fn make_current<'a>(&'a self) -> Result> { 61 | // Preserve the previous context so it can be restored later 62 | let current = unsafe { Self::get_current().map(|guard| guard.current.clone()) }; 63 | 64 | self.enter().map(|_| ContextGuard::<'a> { 65 | previous: current, 66 | current: self.clone(), 67 | phantom: PhantomData, 68 | drop: true, 69 | }) 70 | } 71 | 72 | /// Returns the active context in the current thread. 73 | /// 74 | /// This is unsafe because there should be little reason to use it in 75 | /// idiomatic code. 76 | /// 77 | /// Usage patterns should utilize `ContextGuard` or 78 | /// `exec_with_current` instead. 79 | /// 80 | /// This `ContextGuard` does not reset the current context upon destruction, 81 | /// in contrast to a normally allocated `ContextGuard`. This is merely a 82 | /// hollow reference. 83 | pub unsafe fn get_current<'a>() -> Option> { 84 | let mut reference = JsContextRef::new(); 85 | jsassert!(JsGetCurrentContext(&mut reference)); 86 | 87 | // The JSRT API returns null instead of an error code 88 | reference.0.as_ref().map(|_| ContextGuard { 89 | previous: None, 90 | current: Self::from_raw(reference), 91 | phantom: PhantomData, 92 | drop: false, 93 | }) 94 | } 95 | 96 | /// Binds the context to the closure's scope. 97 | /// 98 | /// ```c 99 | /// let result = context.exec_with(|guard| script::eval(guard, "1 + 1")).unwrap(); 100 | /// ``` 101 | pub fn exec_with Ret>(&self, callback: T) -> Result { 102 | self.make_current().map(|guard| callback(&guard)) 103 | } 104 | 105 | /// Executes a closure with the thread's active context. 106 | /// 107 | /// This is a safe alternative to `get_current`. It will either return the 108 | /// closures result wrapped in `Some`, or `None`, if no context is currently 109 | /// active. 110 | pub fn exec_with_current Ret>(callback: T) -> Option { 111 | unsafe { Self::get_current().as_ref().map(callback) } 112 | } 113 | 114 | /// Executes a closure with a value's associated context. 115 | /// 116 | /// - The active context will only be changed if it differs from the value's. 117 | /// - If the switch fails, an error will be returned. 118 | /// - Due to the fact that this relies on `from_value`, it suffers from the 119 | /// same limitations and should be avoided. 120 | /// - If the value has no associated context, `None` will be returned. 121 | pub(crate) fn exec_with_value(value: &value::Value, callback: T) -> Result> 122 | where 123 | T: FnOnce(&ContextGuard) -> Ret, 124 | { 125 | Context::from_value(value).map_or(Ok(None), |context| unsafe { 126 | // In case there is no active context, or if it differs from the 127 | // value's context, temporarily change the context. 128 | let guard = Context::get_current() 129 | .and_then(|guard| (guard.context() == context).as_some(guard)) 130 | .map_or_else(|| context.make_current(), Ok); 131 | guard.map(|guard| Some(callback(&guard))) 132 | }) 133 | } 134 | 135 | /// Set user data associated with the context. 136 | /// 137 | /// - Only one value per type. 138 | /// - The internal implementation uses `AnyMap`. 139 | /// - Returns a previous value if applicable. 140 | /// - The data will live as long as the runtime keeps the context. 141 | pub fn insert_user_data(&self, value: T) -> Option 142 | where 143 | T: Send + 'static, 144 | { 145 | unsafe { self.get_data().user_data.insert(value) } 146 | } 147 | 148 | /// Remove user data associated with the context. 149 | pub fn remove_user_data(&self) -> Option 150 | where 151 | T: Send + 'static, 152 | { 153 | unsafe { self.get_data().user_data.remove::() } 154 | } 155 | 156 | /// Get user data associated with the context. 157 | pub fn get_user_data(&self) -> Option<&T> 158 | where 159 | T: Send + 'static, 160 | { 161 | unsafe { self.get_data().user_data.get::() } 162 | } 163 | 164 | /// Get mutable user data associated with the context. 165 | pub fn get_user_data_mut(&self) -> Option<&mut T> 166 | where 167 | T: Send + 'static, 168 | { 169 | unsafe { self.get_data().user_data.get_mut::() } 170 | } 171 | 172 | /// Returns a recyclable value's associated context. 173 | /// 174 | /// This is unreliable, because types that have an associated context is 175 | /// implementation defined (by the underlying runtime), based on whether they 176 | /// are recyclable or not, therefore it should be avoided. 177 | fn from_value(value: &value::Value) -> Option { 178 | let mut reference = JsContextRef::new(); 179 | unsafe { 180 | jstry(JsGetContextOfObject(value.as_raw(), &mut reference)) 181 | .ok() 182 | .map(|_| Self::from_raw(reference)) 183 | } 184 | } 185 | 186 | /// Sets the internal data of the context. 187 | unsafe fn set_data(&self, data: Box) -> Result<()> { 188 | jstry(JsSetContextData( 189 | self.as_raw(), 190 | Box::into_raw(data) as *mut _, 191 | )) 192 | } 193 | 194 | /// Gets the internal data of the context. 195 | unsafe fn get_data<'a>(&'a self) -> &'a mut ContextData { 196 | let mut data = ptr::null_mut(); 197 | jsassert!(JsGetContextData(self.as_raw(), &mut data)); 198 | (data as *mut ContextData) 199 | .as_mut() 200 | .expect("retrieving context data") 201 | } 202 | 203 | /// Sets the current context. 204 | fn enter(&self) -> Result<()> { 205 | jstry(unsafe { JsSetCurrentContext(self.as_raw()) }) 206 | } 207 | 208 | /// Unsets the current context. 209 | fn exit(&self, previous: Option<&Context>) -> Result<()> { 210 | jstry(unsafe { 211 | let next = previous 212 | .map(|context| context.as_raw()) 213 | .unwrap_or_else(JsValueRef::new); 214 | JsSetCurrentContext(next) 215 | }) 216 | } 217 | 218 | /// A promise handler, triggered whenever a promise method is used. 219 | unsafe extern "system" fn promise_handler(task: JsValueRef, data: *mut ::libc::c_void) { 220 | let data = (data as *mut ContextData) 221 | .as_mut() 222 | .expect("retrieving promise handler stack"); 223 | data.promise_queue.push(value::Function::from_raw(task)); 224 | } 225 | 226 | /// A collect callback, triggered before the context is destroyed. 227 | unsafe extern "system" fn collect(context: JsContextRef, _: *mut ::libc::c_void) { 228 | let context = Self::from_raw(context); 229 | Box::from_raw(context.get_data()); 230 | } 231 | } 232 | 233 | reference!(Context); 234 | 235 | /// A guard that keeps a context active while it is in scope. 236 | #[must_use] 237 | #[derive(Debug)] 238 | pub struct ContextGuard<'a> { 239 | previous: Option, 240 | current: Context, 241 | phantom: PhantomData<&'a Context>, 242 | drop: bool, 243 | } 244 | 245 | impl<'a> ContextGuard<'a> { 246 | /// Returns the guard's associated context. 247 | pub fn context(&self) -> Context { 248 | self.current.clone() 249 | } 250 | 251 | /// Returns the active context's global object. 252 | pub fn global(&self) -> value::Object { 253 | let mut value = JsValueRef::new(); 254 | unsafe { 255 | jsassert!(JsGetGlobalObject(&mut value)); 256 | value::Object::from_raw(value) 257 | } 258 | } 259 | 260 | /// Executes all the context's queued promise tasks. 261 | pub fn execute_tasks(&self) { 262 | let data = unsafe { self.current.get_data() }; 263 | while let Some(task) = data.promise_queue.pop() { 264 | task.call(self, &[]).expect("executing promise task"); 265 | } 266 | } 267 | } 268 | 269 | impl<'a> Drop for ContextGuard<'a> { 270 | /// Resets the currently active context. 271 | fn drop(&mut self) { 272 | if self.drop { 273 | assert!(self.current.exit(self.previous.as_ref()).is_ok()) 274 | } 275 | } 276 | } 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use crate::{script, test, value, Context, Property}; 281 | 282 | #[test] 283 | fn global() { 284 | test::run_with_context(|guard| { 285 | let global = guard.global(); 286 | let dirname = Property::new(guard, "__dirname"); 287 | 288 | global.set(guard, &dirname, value::String::new(guard, "FooBar")); 289 | global.set_index(guard, 2, value::Number::new(guard, 1337)); 290 | 291 | let result1 = script::eval(guard, "__dirname").unwrap(); 292 | let result2 = script::eval(guard, "this[2]").unwrap(); 293 | 294 | assert_eq!(result1.to_string(guard), "FooBar"); 295 | assert_eq!(result2.to_integer(guard), 1337); 296 | }); 297 | } 298 | 299 | #[test] 300 | fn stack() { 301 | let (runtime, context) = test::setup_env(); 302 | { 303 | let get_current = || unsafe { Context::get_current().unwrap().context() }; 304 | let _guard = context.make_current().unwrap(); 305 | 306 | assert_eq!(get_current(), context); 307 | { 308 | let inner_context = Context::new(&runtime).unwrap(); 309 | let _guard = inner_context.make_current().unwrap(); 310 | assert_eq!(get_current(), inner_context); 311 | } 312 | assert_eq!(get_current(), context); 313 | } 314 | assert!(unsafe { Context::get_current() }.is_none()); 315 | } 316 | 317 | #[test] 318 | fn user_data() { 319 | test::run_with_context(|guard| { 320 | type Data = Vec; 321 | let context = guard.context(); 322 | 323 | let data: Data = vec![10, 20]; 324 | context.insert_user_data(data); 325 | 326 | let data = context.get_user_data::().unwrap(); 327 | assert_eq!(data.as_slice(), [10, 20]); 328 | 329 | assert!(context.remove_user_data::().is_some()); 330 | assert!(context.get_user_data::().is_none()); 331 | }); 332 | } 333 | 334 | #[test] 335 | fn promise_queue() { 336 | test::run_with_context(|guard| { 337 | let result = script::eval( 338 | guard, 339 | " 340 | var object = {}; 341 | Promise.resolve(5) 342 | .then(val => val + 5) 343 | .then(val => val / 5) 344 | .then(val => object.val = val); 345 | object;", 346 | ) 347 | .unwrap(); 348 | 349 | guard.execute_tasks(); 350 | 351 | let value = result 352 | .into_object() 353 | .unwrap() 354 | .get(guard, Property::new(guard, "val")) 355 | .to_integer(guard); 356 | assert_eq!(value, 2); 357 | }); 358 | } 359 | 360 | #[test] 361 | fn shared_objects() { 362 | let (runtime, context) = test::setup_env(); 363 | let context2 = Context::new(&runtime).unwrap(); 364 | 365 | let guard1 = context.make_current().unwrap(); 366 | let object = script::eval(&guard1, "({ foo: 1337 })").unwrap(); 367 | 368 | let guard2 = context2.make_current().unwrap(); 369 | assert_eq!(object.to_json(&guard2).unwrap(), r#"{"foo":1337}"#); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /chakracore/src/error.rs: -------------------------------------------------------------------------------- 1 | use chakracore_sys::JsErrorCode; 2 | use std::error::Error as StdError; 3 | use std::fmt; 4 | 5 | /// The result of a detour operation. 6 | pub type Result = ::std::result::Result; 7 | 8 | /// A representation of all possible errors. 9 | #[derive(Debug)] 10 | pub enum Error { 11 | /// A variant indicating that a runtime error has occured. 12 | ScriptException(String), 13 | /// An error caused by incorrect code syntax. 14 | ScriptCompilation(String), 15 | /// A JSRT call failed. 16 | JsrtCall(JsErrorCode), 17 | } 18 | 19 | impl StdError for Error {} 20 | 21 | impl fmt::Display for Error { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | match self { 24 | Error::ScriptException(message) => write!(f, "JavaScript exception: {}", message), 25 | Error::ScriptCompilation(message) => write!(f, "JavaScript compile error: {}", message), 26 | Error::JsrtCall(error) => write!(f, "JSRT call error: {:?}", error), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chakracore/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "unstable", feature(test))] 2 | //! A library for interfacing with ChakraCore using the JSRT API. 3 | //! 4 | //! This crate provides abstractions over nearly every JSRT API available, in a 5 | //! thread- and memory-safe implementation. 6 | //! 7 | //! ```rust 8 | //! extern crate chakracore as js; 9 | //! 10 | //! fn main() { 11 | //! let runtime = js::Runtime::new().unwrap(); 12 | //! let context = js::Context::new(&runtime).unwrap(); 13 | //! let guard = context.make_current().unwrap(); 14 | //! 15 | //! let result = js::script::eval(&guard, "(5 + 5)").unwrap(); 16 | //! assert_eq!(result.to_integer(&guard), 10); 17 | //! } 18 | //! ``` 19 | //! 20 | //! *NOTE: During pre-release (0.X.X) stability may vary.* 21 | 22 | pub use context::{Context, ContextGuard}; 23 | pub use error::{Error, Result}; 24 | pub use property::Property; 25 | pub use runtime::Runtime; 26 | 27 | #[macro_use] 28 | mod macros; 29 | mod context; 30 | mod error; 31 | mod property; 32 | pub mod runtime; 33 | pub mod script; 34 | mod util; 35 | pub mod value; 36 | 37 | #[cfg(test)] 38 | mod test { 39 | use super::*; 40 | 41 | pub fn setup_env() -> (Runtime, Context) { 42 | let runtime = Runtime::new().unwrap(); 43 | let context = Context::new(&runtime).unwrap(); 44 | (runtime, context) 45 | } 46 | 47 | pub fn run_with_context(callback: T) { 48 | let (_runtime, context) = setup_env(); 49 | context.exec_with(callback).unwrap(); 50 | } 51 | } 52 | 53 | #[cfg(all(feature = "unstable", test))] 54 | mod bench { 55 | extern crate test; 56 | use self::test::Bencher; 57 | use super::*; 58 | 59 | fn setup_env() -> (Runtime, Context) { 60 | let runtime = Runtime::new().unwrap(); 61 | let context = Context::new(&runtime).unwrap(); 62 | (runtime, context) 63 | } 64 | 65 | #[bench] 66 | fn property_bench(bench: &mut Bencher) { 67 | let (_runtime, context) = setup_env(); 68 | 69 | let guard = context.make_current().unwrap(); 70 | let object = value::Object::new(&guard); 71 | object.set( 72 | &guard, 73 | Property::new(&guard, "test"), 74 | value::Number::new(&guard, 10), 75 | ); 76 | 77 | bench.iter(|| { 78 | (0..10000).fold(0, |acc, _| { 79 | acc 80 | + object 81 | .get(&guard, Property::new(&guard, "test")) 82 | .to_integer(&guard) 83 | }); 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /chakracore/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Asserts the return value of a JSRT function call. 2 | macro_rules! jsassert { 3 | ($e: expr, $name: expr) => { 4 | let result = $e; 5 | 6 | // In many cases idiomatic code prevents any errors from happening 7 | // (except for memory resource issues). 8 | assert!( 9 | result == ::chakracore_sys::JsErrorCode::NoError, 10 | format!( 11 | "JSRT call to '{}' failed unexpectedly with: {:?}", 12 | $name, result 13 | ) 14 | ); 15 | }; 16 | 17 | ($e: expr) => { 18 | jsassert!($e, stringify!($e)); 19 | }; 20 | } 21 | 22 | /// Shared base reference implementation. 23 | macro_rules! reference_base { 24 | ($typ:ident) => { 25 | impl $typ { 26 | /// Creates an instance from a raw pointer. 27 | /// 28 | /// This is used for managing the lifetime of JSRT objects. They are 29 | /// tracked using reference counting; incrementing with `from_raw`, 30 | /// and decrementing with `drop`. 31 | /// 32 | /// This is required to support items stored on the heap, since the 33 | /// JSRT runtime only observes the stack. 34 | /// 35 | /// If used in conjunction with a `Property` or any `Value`, it is 36 | /// assumed a `Context` is active. 37 | pub unsafe fn from_raw(value: JsRef) -> $typ { 38 | jsassert!(JsAddRef(value, ::std::ptr::null_mut())); 39 | $typ(value) 40 | } 41 | 42 | /// Returns the underlying raw pointer. 43 | pub fn as_raw(&self) -> JsRef { 44 | self.0 45 | } 46 | } 47 | 48 | impl AsRef for $typ { 49 | fn as_ref(&self) -> &Self { 50 | self 51 | } 52 | } 53 | 54 | impl Clone for $typ { 55 | /// Duplicates a reference counted type. 56 | /// 57 | /// The underlying pointer will be copied, and its reference count 58 | /// will be incremented, returned wrapped as the type. 59 | fn clone(&self) -> $typ { 60 | unsafe { $typ::from_raw(self.as_raw()) } 61 | } 62 | } 63 | }; 64 | } 65 | 66 | /// Implements JSRT reference counting for a non-value type. 67 | macro_rules! reference { 68 | ($typ:ident) => { 69 | reference_base!($typ); 70 | 71 | impl Drop for $typ { 72 | /// Decrements the reference counter. 73 | fn drop(&mut self) { 74 | crate::util::release_reference(self.as_raw()); 75 | } 76 | } 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /chakracore/src/property.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, ContextGuard}; 2 | use chakracore_sys::*; 3 | use std::fmt; 4 | 5 | /// A property identifier used with objects. 6 | #[derive(PartialEq)] 7 | pub struct Property(JsPropertyIdRef); 8 | 9 | impl Property { 10 | /// Creates a property identifier from a string. 11 | /// 12 | /// If a property identifier with this name has already been created, it will 13 | /// be returned instead of creating a new one. 14 | pub fn new(_guard: &ContextGuard, name: &str) -> Self { 15 | let bytes = name.as_bytes(); 16 | let mut reference = JsPropertyIdRef::new(); 17 | unsafe { 18 | jsassert!(JsCreatePropertyId( 19 | bytes.as_ptr() as _, 20 | bytes.len(), 21 | &mut reference 22 | )); 23 | Self::from_raw(reference) 24 | } 25 | } 26 | 27 | /// Converts a JavaScript property to a native string. 28 | pub fn to_string(&self, _guard: &ContextGuard) -> String { 29 | crate::util::to_string_impl(self.as_raw(), JsCopyPropertyId) 30 | .expect("converting property to string") 31 | } 32 | } 33 | 34 | impl fmt::Debug for Property { 35 | /// Only use for debugging, it relies on an implicit active context and 36 | /// panics otherwise. 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | Context::exec_with_current(|guard| { 39 | let output = self.to_string(&guard); 40 | write!(f, "Property('{}')", output) 41 | }) 42 | .expect("property debug output without an active context") 43 | } 44 | } 45 | 46 | reference!(Property); 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use crate::{test, Property}; 51 | 52 | #[test] 53 | fn string_conversion() { 54 | test::run_with_context(|guard| { 55 | let property = Property::new(guard, "foo"); 56 | assert_eq!(property.to_string(guard), "foo"); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chakracore/src/runtime.rs: -------------------------------------------------------------------------------- 1 | //! Runtime and builder. 2 | use crate::{util::jstry, Result}; 3 | use chakracore_sys::*; 4 | use libc::c_void; 5 | use std::time::{Duration, Instant}; 6 | 7 | /// A callback triggered before objects are collected. 8 | pub type CollectCallback = dyn Fn() + Send; 9 | 10 | /// A builder for the runtime type. 11 | pub struct Builder { 12 | memory_limit: Option, 13 | collect_callback: Option>, 14 | attributes: JsRuntimeAttributes, 15 | } 16 | 17 | /// An isolated instance of a runtime. 18 | pub struct Runtime { 19 | #[allow(dead_code)] 20 | callback: Option>>, 21 | handle: JsRuntimeHandle, 22 | last_idle_tick: Option, 23 | last_idle: Option, 24 | } 25 | 26 | impl Runtime { 27 | /// Creates a new runtime. 28 | pub fn new() -> Result { 29 | Self::builder().build() 30 | } 31 | 32 | /// Returns a runtime builder. 33 | pub fn builder() -> Builder { 34 | Builder { 35 | memory_limit: None, 36 | collect_callback: None, 37 | attributes: JsRuntimeAttributeNone, 38 | } 39 | } 40 | 41 | /// Performs a full garbage collection. 42 | pub fn collect(&self) -> Result<()> { 43 | jstry(unsafe { JsCollectGarbage(self.as_raw()) }) 44 | } 45 | 46 | /// Runs any idle tasks that are in the queue. The returned duration is the 47 | /// least amount of time that should pass until this function is called 48 | /// again. This call will fail if the runtime was created without idle 49 | /// processing enabled. 50 | /// 51 | /// Returns whether any processing was done or not. 52 | pub fn run_idle_tasks(&mut self) -> Result { 53 | let should_idle = self.last_idle.map_or(true, |before| { 54 | // Assume that `last_idle_tick` is set, if `last_idle` is 55 | Instant::now().duration_since(before) >= self.last_idle_tick.unwrap() 56 | }); 57 | 58 | if should_idle { 59 | let mut ticks = 0; 60 | jstry(unsafe { JsIdle(&mut ticks) })?; 61 | 62 | self.last_idle_tick = Some(Duration::from_millis(ticks as u64)); 63 | self.last_idle = Some(Instant::now()); 64 | } 65 | 66 | Ok(should_idle) 67 | } 68 | 69 | /// Returns the runtime's memory usage 70 | pub fn get_memory_usage(&self) -> usize { 71 | let mut usage = 0; 72 | jsassert!(unsafe { JsGetRuntimeMemoryUsage(self.as_raw(), &mut usage) }); 73 | usage 74 | } 75 | 76 | /// Returns the underlying raw pointer behind this runtime. 77 | pub fn as_raw(&self) -> JsRuntimeHandle { 78 | self.handle 79 | } 80 | 81 | /// A collector callback, triggered before objects are released. 82 | unsafe extern "system" fn before_collect(data: *mut c_void) { 83 | let callback = data as *mut Box; 84 | (*callback)(); 85 | } 86 | } 87 | 88 | impl Drop for Runtime { 89 | fn drop(&mut self) { 90 | unsafe { 91 | jsassert!(JsDisposeRuntime(self.as_raw())); 92 | } 93 | } 94 | } 95 | 96 | macro_rules! attr { 97 | ($name:ident, $attribute:ident, $doc:expr) => { 98 | #[doc=$doc] 99 | pub fn $name(mut self) -> Self { 100 | self.attributes = self.attributes | $attribute; 101 | self 102 | } 103 | }; 104 | } 105 | 106 | impl Builder { 107 | attr!( 108 | disable_background_work, 109 | JsRuntimeAttributeDisableBackgroundWork, 110 | "Disable the runtime from doing any work on background threads." 111 | ); 112 | attr!( 113 | disable_eval, 114 | JsRuntimeAttributeDisableEval, 115 | "Disable `eval` and `function` by throwing an exception upon use." 116 | ); 117 | attr!( 118 | disable_jit, 119 | JsRuntimeAttributeDisableNativeCodeGeneration, 120 | "Disable just-in-time compilation." 121 | ); 122 | attr!( 123 | enable_experimental, 124 | JsRuntimeAttributeEnableExperimentalFeatures, 125 | "Allow experimental JavaScript features." 126 | ); 127 | attr!( 128 | enable_script_interrupt, 129 | JsRuntimeAttributeAllowScriptInterrupt, 130 | "Allow script interrupt." 131 | ); 132 | attr!( 133 | dispatch_exceptions, 134 | JsRuntimeAttributeDispatchSetExceptionsToDebugger, 135 | "Dispatch exceptions to any attached JavaScript debuggers." 136 | ); 137 | attr!( 138 | supports_idle_tasks, 139 | JsRuntimeAttributeEnableIdleProcessing, 140 | "Enable idle processing. `run_idle_tasks` must be called regularly." 141 | ); 142 | 143 | /// Set the runtime's memory limit. 144 | pub fn memory_limit(mut self, limit: usize) -> Self { 145 | self.memory_limit = Some(limit); 146 | self 147 | } 148 | 149 | /// Set a callback for when the runtime collects garbage. 150 | pub fn collect_callback(mut self, callback: Box) -> Self { 151 | self.collect_callback = Some(callback); 152 | self 153 | } 154 | 155 | /// Creates the runtime object with associated settings. 156 | pub fn build(self) -> Result { 157 | let mut handle = JsRuntimeHandle::new(); 158 | jstry(unsafe { JsCreateRuntime(self.attributes, None, &mut handle) })?; 159 | 160 | if let Some(limit) = self.memory_limit { 161 | jstry(unsafe { JsSetRuntimeMemoryLimit(handle, limit) })?; 162 | } 163 | 164 | let callback = self.collect_callback.map(|callback| unsafe { 165 | let mut wrapper = Box::new(callback); 166 | jsassert!(JsSetRuntimeBeforeCollectCallback( 167 | handle, 168 | &mut *wrapper as *mut _ as *mut _, 169 | Some(Runtime::before_collect) 170 | )); 171 | wrapper 172 | }); 173 | 174 | Ok(Runtime { 175 | last_idle: None, 176 | last_idle_tick: None, 177 | handle, 178 | callback, 179 | }) 180 | } 181 | } 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use crate::{script, test, Context, Runtime}; 186 | use std::{ 187 | sync::{Arc, Mutex}, 188 | thread, 189 | }; 190 | 191 | #[test] 192 | fn minimal() { 193 | test::run_with_context(|guard| { 194 | let result = script::eval(guard, "5 + 5").unwrap(); 195 | assert_eq!(result.to_integer(guard), 10); 196 | }); 197 | } 198 | 199 | #[test] 200 | fn collect_callback() { 201 | let called = Arc::new(Mutex::new(false)); 202 | { 203 | let called = called.clone(); 204 | let runtime = Runtime::builder() 205 | .collect_callback(Box::new(move || *called.lock().unwrap() = true)) 206 | .build() 207 | .unwrap(); 208 | runtime.collect().unwrap(); 209 | } 210 | assert!(*called.lock().unwrap()); 211 | } 212 | 213 | #[test] 214 | fn thread_send() { 215 | let runtime = Runtime::new().unwrap(); 216 | let context = Context::new(&runtime).unwrap(); 217 | let result = { 218 | let guard = context.make_current().unwrap(); 219 | script::eval(&guard, "[5, 'foo', {}]") 220 | .unwrap() 221 | .into_array() 222 | .unwrap() 223 | }; 224 | 225 | thread::spawn(move || { 226 | let guard = context.make_current().unwrap(); 227 | assert_eq!(result.len(&guard), 3); 228 | }) 229 | .join() 230 | .unwrap(); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /chakracore/src/script.rs: -------------------------------------------------------------------------------- 1 | //! Functionality for executing and parsing JavaScript. 2 | //! 3 | //! The simple `eval` function should cover most needs. It evalutes the supplied 4 | //! code directly and returns the script's value. 5 | //! 6 | //! ```rust 7 | //! # use chakracore as js; 8 | //! # let runtime = js::Runtime::new().unwrap(); 9 | //! # let context = js::Context::new(&runtime).unwrap(); 10 | //! # let guard = context.make_current().unwrap(); 11 | //! let result = js::script::eval(&guard, "10 + 10").unwrap(); 12 | //! assert_eq!(result.to_integer(&guard), 20); 13 | //! ``` 14 | //! 15 | //! Another option is to parse the source code and execute it at a later time 16 | //! with a function. This is done using the `parse` function: 17 | //! 18 | //! ```rust 19 | //! # use chakracore as js; 20 | //! # let runtime = js::Runtime::new().unwrap(); 21 | //! # let context = js::Context::new(&runtime).unwrap(); 22 | //! # let guard = context.make_current().unwrap(); 23 | //! let add = js::script::parse(&guard, "10 + 10").unwrap(); 24 | //! let result = add.call(&guard, &[]).unwrap(); 25 | //! assert_eq!(result.to_integer(&guard), 20); 26 | //! ``` 27 | use crate::{util::jstry, value, ContextGuard, Result}; 28 | use chakracore_sys::*; 29 | 30 | /// Evaluates code directly. 31 | pub fn eval(guard: &ContextGuard, code: &str) -> Result { 32 | eval_with_name(guard, "", code) 33 | } 34 | 35 | /// Evaluates code and associates it with a name. 36 | pub fn eval_with_name(guard: &ContextGuard, name: &str, code: &str) -> Result { 37 | process_code(guard, name, code, CodeAction::Execute) 38 | } 39 | 40 | /// Parses code and returns it as a function. 41 | pub fn parse(guard: &ContextGuard, code: &str) -> Result { 42 | parse_with_name(guard, "", code) 43 | } 44 | 45 | /// Parses code and associates it with a name, returns it as a function. 46 | pub fn parse_with_name(guard: &ContextGuard, name: &str, code: &str) -> Result { 47 | process_code(guard, name, code, CodeAction::Parse).map(|value| { 48 | value 49 | .into_function() 50 | .expect("converting parsing result to function") 51 | }) 52 | } 53 | 54 | /// Used for processing code. 55 | #[derive(Copy, Clone, Debug)] 56 | enum CodeAction { 57 | Execute, 58 | Parse, 59 | } 60 | 61 | // TODO: Should `JavascriptExternalArrayBuffer` be supported? Lifetime issues. 62 | /// Either parses or executes a script. 63 | fn process_code( 64 | guard: &ContextGuard, 65 | name: &str, 66 | code: &str, 67 | action: CodeAction, 68 | ) -> Result { 69 | let name = value::String::new(guard, name); 70 | let buffer = value::String::new(guard, code); 71 | 72 | let api = match action { 73 | CodeAction::Execute => JsRun, 74 | CodeAction::Parse => JsParse, 75 | }; 76 | 77 | unsafe { 78 | let mut result = JsValueRef::new(); 79 | jstry(api( 80 | buffer.as_raw(), 81 | generate_source_context(), 82 | name.as_raw(), 83 | JsParseScriptAttributeNone, 84 | &mut result, 85 | )) 86 | .map(|_| value::Value::from_raw(result)) 87 | } 88 | } 89 | 90 | /// Generates a new source context identifier. 91 | fn generate_source_context() -> JsSourceContext { 92 | // TODO: handle source context identifier 93 | JsSourceContext::max_value() 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use crate::{script, test, Error}; 99 | use matches::assert_matches; 100 | 101 | #[test] 102 | fn execute_exception() { 103 | test::run_with_context(|guard| { 104 | let error = script::eval(guard, "null[0] = 3;").unwrap_err(); 105 | let result = script::eval(guard, "5 + 5").unwrap(); 106 | 107 | assert_matches!(error, Error::ScriptException(_)); 108 | assert_eq!(result.to_integer(guard), 10); 109 | }); 110 | } 111 | 112 | #[test] 113 | fn compile_exception() { 114 | test::run_with_context(|guard| { 115 | let error = script::eval(guard, "err)").unwrap_err(); 116 | let result = script::eval(guard, "5 + 5").unwrap(); 117 | 118 | assert_eq!(result.to_integer(guard), 10); 119 | assert_matches!(error, Error::ScriptCompilation(_)); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn parse_script() { 125 | test::run_with_context(|guard| { 126 | let func = script::parse(guard, "new Number(10)").unwrap(); 127 | let result = func.call(guard, &[]).unwrap(); 128 | assert_eq!(result.to_integer(guard), 10); 129 | }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /chakracore/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{value, Context, ContextGuard, Error, Result}; 2 | use chakracore_sys::*; 3 | use std::ptr; 4 | 5 | /// Type for `JsCreateString` & `JsCreatePropertyId` 6 | pub type StringCall = unsafe extern "system" fn(JsRef, *mut i8, usize, *mut usize) -> JsErrorCode; 7 | 8 | /// This is dangerous because it may require an active context. 9 | pub fn to_string_impl(reference: JsRef, callback: StringCall) -> Result { 10 | let mut size = 0; 11 | unsafe { 12 | // Determine how large the string representation is 13 | jstry(callback(reference, ptr::null_mut(), 0, &mut size))?; 14 | 15 | // Allocate an appropriate buffer and retrieve the string 16 | let mut buffer: Vec = vec![0; size]; 17 | jstry(callback( 18 | reference, 19 | buffer.as_mut_ptr() as _, 20 | buffer.len(), 21 | ptr::null_mut(), 22 | ))?; 23 | 24 | // Assume the result is valid UTF-8 25 | Ok(String::from_utf8_unchecked(buffer)) 26 | } 27 | } 28 | 29 | /// Decrements a reference counter and asserts its value. 30 | pub fn release_reference(reference: JsRef) { 31 | let mut count = 0; 32 | jsassert!(unsafe { JsRelease(reference, &mut count) }); 33 | debug_assert!(count < libc::c_uint::max_value()); 34 | } 35 | 36 | /// Returns a script result as a `Function`. 37 | /// 38 | /// This is useful for functionality that the underlying JSRT API does not 39 | /// provide, such as JSON methods, or `RegExp` constructor. 40 | pub fn jsfunc(guard: &ContextGuard, function: &str) -> Option { 41 | crate::script::eval(guard, function) 42 | .ok() 43 | .and_then(|val| val.into_function()) 44 | } 45 | 46 | /// Converts a JSRT error code to a result. 47 | pub fn jstry(code: JsErrorCode) -> Result<()> { 48 | match code { 49 | JsErrorCode::NoError => Ok(()), 50 | JsErrorCode::ScriptException | JsErrorCode::ScriptCompile => { 51 | Context::exec_with_current(|guard| { 52 | // TODO: Use an exception with stack trace. 53 | let exception = get_and_clear_exception(guard); 54 | let message = exception.to_string(guard); 55 | 56 | Err(if code == JsErrorCode::ScriptException { 57 | Error::ScriptException(message) 58 | } else { 59 | Error::ScriptCompilation(message) 60 | }) 61 | }) 62 | .expect("active context in result handler") 63 | }, 64 | error @ _ => Err(Error::JsrtCall(error)), 65 | } 66 | } 67 | 68 | /// Retrieves and clears any exception thrown during compilation or execution. 69 | /// 70 | /// The runtime is set to a disabled state whenever an exception is thrown. 71 | fn get_and_clear_exception(_guard: &ContextGuard) -> value::Value { 72 | let mut exception = JsValueRef::new(); 73 | unsafe { 74 | jsassert!(JsGetAndClearException(&mut exception)); 75 | value::Value::from_raw(exception) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /chakracore/src/value/array.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{Object, Value}; 2 | use crate::{context::ContextGuard, Property}; 3 | use boolinator::Boolinator; 4 | use chakracore_sys::*; 5 | use libc::c_void; 6 | use std::{mem, ptr, slice}; 7 | 8 | /// A JavaScript array. 9 | pub struct Array(JsValueRef); 10 | 11 | /// An iterator for a JavaScript array. 12 | pub struct ArrayIter<'a> { 13 | guard: &'a ContextGuard<'a>, 14 | array: &'a Array, 15 | index: u32, 16 | size: u32, 17 | } 18 | 19 | /// A JavaScript array buffer. 20 | pub struct ArrayBuffer(JsValueRef); 21 | 22 | impl Array { 23 | /// Creates a new array with a specified length. 24 | pub fn new(_guard: &ContextGuard, length: u32) -> Self { 25 | let mut reference = JsValueRef::new(); 26 | unsafe { 27 | jsassert!(JsCreateArray(length, &mut reference)); 28 | Self::from_raw(reference) 29 | } 30 | } 31 | 32 | /// Returns the length of the array. 33 | pub fn len(&self, guard: &ContextGuard) -> usize { 34 | let length = Property::new(&guard, "length"); 35 | self.get(guard, &length).to_integer(&guard) as usize 36 | } 37 | 38 | /// Returns an iterator for the array. 39 | pub fn iter<'a>(&'a self, guard: &'a ContextGuard) -> ArrayIter<'a> { 40 | ArrayIter { 41 | guard, 42 | size: self.len(guard) as u32, 43 | array: self, 44 | index: 0, 45 | } 46 | } 47 | 48 | is_same!(Array, "Returns true if the value is an `Array`."); 49 | } 50 | 51 | impl ArrayBuffer { 52 | /// Creates a new array buffer with a specified size. 53 | pub fn new(_guard: &ContextGuard, size: u32) -> Self { 54 | let mut reference = JsValueRef::new(); 55 | unsafe { 56 | jsassert!(JsCreateArrayBuffer(size, &mut reference)); 57 | Self::from_raw(reference) 58 | } 59 | } 60 | 61 | /// Creates a new array buffer, owning the data. 62 | pub fn with_data(_guard: &ContextGuard, data: Vec) -> Self { 63 | let mut data = Box::new(data); 64 | let base = data.as_mut_ptr() as *mut _; 65 | let size = data.len() * mem::size_of::(); 66 | 67 | unsafe { 68 | let mut buffer = JsValueRef::new(); 69 | jsassert!(JsCreateExternalArrayBuffer( 70 | base, 71 | size as _, 72 | Some(Self::finalize::), 73 | Box::into_raw(data) as *mut _, 74 | &mut buffer 75 | )); 76 | Self::from_raw(buffer) 77 | } 78 | } 79 | 80 | /// Creates a new array buffer, wrapping external data. 81 | /// 82 | /// This is unsafe because the object does not take ownership of the 83 | /// resource. Therefore the data may become a dangling pointer. The caller is 84 | /// responsible for keeping the reference alive. 85 | pub unsafe fn from_slice(_guard: &ContextGuard, data: &mut [T]) -> Self { 86 | let base = data.as_mut_ptr() as *mut _; 87 | let size = (data.len() * mem::size_of::()) as _; 88 | 89 | let mut buffer = JsValueRef::new(); 90 | jsassert!(JsCreateExternalArrayBuffer( 91 | base, 92 | size, 93 | None, 94 | ptr::null_mut(), 95 | &mut buffer 96 | )); 97 | Self::from_raw(buffer) 98 | } 99 | 100 | /// Returns the underlying memory storage used by the array buffer. 101 | /// 102 | /// This may produce unexpected results if used in conjunction with the 103 | /// unsafe `from_slice`. 104 | pub fn as_slice(&self) -> &[u8] { 105 | let mut data = ptr::null_mut(); 106 | let mut size = 0; 107 | unsafe { 108 | jsassert!(JsGetArrayBufferStorage(self.as_raw(), &mut data, &mut size)); 109 | slice::from_raw_parts(data, size as usize) 110 | } 111 | } 112 | 113 | is_same!( 114 | ArrayBuffer, 115 | "Returns true if the value is an `ArrayBuffer`." 116 | ); 117 | 118 | /// A finalizer callback, triggered before an external buffer is removed. 119 | unsafe extern "system" fn finalize(data: *mut c_void) { 120 | Box::from_raw(data as *mut Vec); 121 | } 122 | } 123 | 124 | impl<'a> Iterator for ArrayIter<'a> { 125 | type Item = Value; 126 | 127 | /// Returns the next element in the array. 128 | fn next(&mut self) -> Option { 129 | (self.index < self.size).as_some_from(|| { 130 | self.index += 1; 131 | self.array.get_index(self.guard, self.index - 1) 132 | }) 133 | } 134 | } 135 | 136 | reference!(Array); 137 | inherit!(Array, Object); 138 | subtype!(Array, Value); 139 | reference!(ArrayBuffer); 140 | inherit!(ArrayBuffer, Object); 141 | subtype!(ArrayBuffer, Value); 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use crate::{test, value}; 146 | 147 | #[test] 148 | fn iterator() { 149 | test::run_with_context(|guard| { 150 | let length = 10; 151 | let array = value::Array::new(guard, length); 152 | 153 | for i in 0..length { 154 | array.set_index(guard, i, value::Number::new(guard, i as i32)); 155 | } 156 | 157 | assert_eq!(array.len(guard), 10); 158 | assert_eq!( 159 | array 160 | .iter(guard) 161 | .fold(0, |acc, value| acc + value.to_integer(guard)), 162 | 45 163 | ); 164 | }); 165 | } 166 | 167 | #[test] 168 | fn buffer_storage() { 169 | test::run_with_context(|guard| { 170 | let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; 171 | 172 | let array = value::ArrayBuffer::with_data(guard, data.clone()); 173 | assert_eq!(array.as_slice(), data.as_slice()); 174 | 175 | let array = unsafe { value::ArrayBuffer::from_slice(guard, &mut data) }; 176 | assert_eq!(array.as_slice(), data.as_slice()); 177 | }); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /chakracore/src/value/boolean.rs: -------------------------------------------------------------------------------- 1 | use crate::{value::Value, ContextGuard}; 2 | use chakracore_sys::*; 3 | 4 | /// A JavaScript boolean. 5 | pub struct Boolean(JsValueRef); 6 | 7 | impl Boolean { 8 | /// Creates a new boolean. 9 | pub fn new(_guard: &ContextGuard, boolean: bool) -> Self { 10 | let mut value = JsValueRef::new(); 11 | unsafe { 12 | jsassert!(JsBoolToBoolean(boolean, &mut value)); 13 | Self::from_raw(value) 14 | } 15 | } 16 | 17 | /// Converts a JavaScript boolean to a bool. 18 | pub fn value(&self) -> bool { 19 | let mut boolean = false; 20 | jsassert!(unsafe { JsBooleanToBool(self.as_raw(), &mut boolean) }); 21 | boolean 22 | } 23 | 24 | is_same!(Boolean, "Returns true if the value is a `Boolean`."); 25 | } 26 | 27 | reference!(Boolean); 28 | inherit!(Boolean, Value); 29 | -------------------------------------------------------------------------------- /chakracore/src/value/error.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{Object, Value}; 2 | use crate::{context::ContextGuard, Property}; 3 | use chakracore_sys::*; 4 | 5 | macro_rules! ctor { 6 | ($name:ident, $errtype:ident, $doc:expr) => { 7 | #[doc=$doc] 8 | pub fn $name(guard: &ContextGuard, message: &str) -> Self { 9 | create_error(guard, message, $errtype) 10 | } 11 | }; 12 | } 13 | 14 | /// A JavaScript error. 15 | pub struct Error(JsValueRef); 16 | 17 | impl Error { 18 | ctor!(new, JsCreateError, "Creates a new error."); 19 | ctor!(range_error, JsCreateRangeError, "Creates a new range error."); 20 | ctor!(reference_error, JsCreateReferenceError, "Creates a new reference error."); 21 | ctor!(syntax_error, JsCreateSyntaxError, "Creates a new syntax error."); 22 | ctor!(type_error, JsCreateTypeError, "Creates a new type error."); 23 | ctor!(uri_error, JsCreateURIError, "Creates a new URI error."); 24 | 25 | /// Returns the error's message. 26 | pub fn message(&self, guard: &ContextGuard) -> String { 27 | self 28 | .get(guard, Property::new(guard, "message")) 29 | .to_string(guard) 30 | } 31 | 32 | is_same!(Error, "Returns true if the value is an `Error`."); 33 | } 34 | 35 | /// Function definition for an error call. 36 | type ErrorCall = unsafe extern "system" fn(JsValueRef, *mut JsValueRef) -> JsErrorCode; 37 | 38 | /// Creates an error object from a specified API. 39 | fn create_error(guard: &ContextGuard, message: &str, api: ErrorCall) -> Error { 40 | let message = super::String::new(guard, message); 41 | let mut value = JsValueRef::new(); 42 | unsafe { 43 | jsassert!(api(message.as_raw(), &mut value)); 44 | Error::from_raw(value) 45 | } 46 | } 47 | 48 | reference!(Error); 49 | inherit!(Error, Object); 50 | subtype!(Error, Value); 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use crate::{test, value}; 55 | 56 | #[test] 57 | fn string_conversion() { 58 | test::run_with_context(|guard| { 59 | let error = value::Error::type_error(guard, "FooBar"); 60 | assert_eq!(error.to_string(guard), "TypeError: FooBar"); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /chakracore/src/value/external.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{Object, Value}; 2 | use crate::ContextGuard; 3 | use chakracore_sys::*; 4 | use libc::c_void; 5 | use std::ptr; 6 | 7 | /// A JavaScript external object. 8 | pub struct External(JsValueRef); 9 | 10 | // TODO: Make the entire implementation generic 11 | impl External { 12 | /// Creates a new object with external data. 13 | /// 14 | /// The object takes ownership of the resource. It is undetermined when, and 15 | /// even if, the destructor is called. It relies on the engine's finalize 16 | /// callback. 17 | /// 18 | /// As long as the object is referenced on the stack or in any script 19 | /// context, the `external` data will be kept alive (i.e it is not tied to 20 | /// the handle). 21 | pub fn new(_guard: &ContextGuard, external: Box) -> Self { 22 | let mut value = JsValueRef::new(); 23 | unsafe { 24 | jsassert!(JsCreateExternalObject( 25 | Box::into_raw(external) as *mut _, 26 | Some(Self::finalize::), 27 | &mut value 28 | )); 29 | Self::from_raw(value) 30 | } 31 | } 32 | 33 | /// Creates a new object with external data. 34 | /// 35 | /// This is unsafe because the object does not take ownership of the 36 | /// resource. Therefore the data may become a dangling pointer. The caller 37 | /// is responsible for keeping the reference alive. 38 | pub unsafe fn from_ptr(_guard: &ContextGuard, external: *mut T) -> Self { 39 | let mut value = JsValueRef::new(); 40 | jsassert!(JsCreateExternalObject(external as *mut _, None, &mut value)); 41 | Self::from_raw(value) 42 | } 43 | 44 | /// Returns the external object's data. 45 | pub unsafe fn value(&self) -> &mut T { 46 | let mut data = ptr::null_mut(); 47 | jsassert!(JsGetExternalData(self.as_raw(), &mut data)); 48 | (data as *mut T).as_mut().expect("retrieving external data") 49 | } 50 | 51 | /// Returns true if the value is an `External`. 52 | pub fn is_same>(value: V) -> bool { 53 | value.as_ref().get_type() == JsValueType::Object && Self::has_external_data(value.as_ref()) 54 | } 55 | 56 | /// Returns whether the value has external data or not. 57 | fn has_external_data(value: &Value) -> bool { 58 | let mut result = false; 59 | jsassert!(unsafe { JsHasExternalData(value.as_raw(), &mut result) }); 60 | result 61 | } 62 | 63 | /// A finalizer callback, triggered before an external is removed. 64 | unsafe extern "system" fn finalize(data: *mut c_void) { 65 | Box::from_raw(data as *mut T); 66 | } 67 | } 68 | 69 | reference!(External); 70 | inherit!(External, Object); 71 | subtype!(External, Value); 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use crate::{test, value}; 76 | 77 | #[test] 78 | fn destructor() { 79 | static mut CALLED: bool = false; 80 | { 81 | struct Foo(i32); 82 | impl Drop for Foo { 83 | fn drop(&mut self) { 84 | assert_eq!(self.0, 10); 85 | unsafe { CALLED = true }; 86 | } 87 | } 88 | 89 | test::run_with_context(|guard| { 90 | let _ = value::External::new(guard, Box::new(Foo(10))); 91 | }); 92 | } 93 | assert!(unsafe { CALLED }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /chakracore/src/value/function.rs: -------------------------------------------------------------------------------- 1 | //! A JavaScript function and associated types. 2 | use crate::value::{Object, Value}; 3 | use crate::{util::jstry, Context, ContextGuard, Result}; 4 | use chakracore_sys::*; 5 | use libc::{c_ushort, c_void}; 6 | use std::slice; 7 | 8 | /// The information passed to `FunctionCallback` closures. 9 | #[derive(Clone, Debug)] 10 | pub struct CallbackInfo { 11 | /// Whether it's a constructor call or not. 12 | pub is_construct_call: bool, 13 | /// Arguments supplied by the caller. 14 | pub arguments: Vec, 15 | /// The source of this function call. 16 | pub callee: Value, 17 | /// The function's `this` context. 18 | pub this: Value, 19 | } 20 | 21 | /// The result returned from a function callback. 22 | pub type CallbackResult = ::std::result::Result; 23 | 24 | /// Callback type for functions. 25 | pub type FunctionCallback = dyn Fn(&ContextGuard, CallbackInfo) -> CallbackResult + Send; 26 | 27 | /// A JavaScript function object. 28 | pub struct Function(JsValueRef); 29 | 30 | impl Function { 31 | /// Creates an anonymous function 32 | pub fn new(_guard: &ContextGuard, callback: Box) -> Self { 33 | Self::create(callback, |context, reference| unsafe { 34 | JsCreateFunction(Some(Self::callback), context, reference) 35 | }) 36 | } 37 | 38 | /// Creates a named function 39 | pub fn with_name(guard: &ContextGuard, name: &str, callback: Box) -> Self { 40 | Self::create(callback, |context, reference| unsafe { 41 | let name = super::String::new(guard, name); 42 | JsCreateNamedFunction(name.as_raw(), Some(Self::callback), context, reference) 43 | }) 44 | } 45 | 46 | /// Calls a function and returns the result. The context (i.e `this`) will 47 | /// be the global object associated with the `ContextGuard`. 48 | pub fn call(&self, guard: &ContextGuard, arguments: &[&Value]) -> Result { 49 | self.call_with_this(guard, &guard.global(), arguments) 50 | } 51 | 52 | /// Calls a function, with a context, and returns the result. 53 | pub fn call_with_this>( 54 | &self, 55 | _guard: &ContextGuard, 56 | this: V, 57 | arguments: &[&Value], 58 | ) -> Result { 59 | self.invoke(_guard, this, arguments, false) 60 | } 61 | 62 | /// Calls a function as a constructor and returns the result. 63 | pub fn construct>( 64 | &self, 65 | _guard: &ContextGuard, 66 | this: V, 67 | args: &[&Value], 68 | ) -> Result { 69 | self.invoke(_guard, this, args, true) 70 | } 71 | 72 | is_same!(Function, "Returns true if the value is a `Function`."); 73 | 74 | /// Invokes a function and returns the result. 75 | fn invoke>( 76 | &self, 77 | _guard: &ContextGuard, 78 | this: V, 79 | arguments: &[&Value], 80 | constructor: bool, 81 | ) -> Result { 82 | // Combine the context with the arguments 83 | let mut forward = Vec::with_capacity(arguments.len() + 1); 84 | forward.push(this.as_ref().as_raw()); 85 | forward.extend(arguments.iter().map(|value| value.as_raw())); 86 | 87 | let api = if constructor { 88 | JsConstructObject 89 | } else { 90 | JsCallFunction 91 | }; 92 | 93 | unsafe { 94 | let mut result = JsValueRef::new(); 95 | jstry(api( 96 | self.0, 97 | forward.as_mut_ptr(), 98 | forward.len() as c_ushort, 99 | &mut result, 100 | )) 101 | .map(|_| Value::from_raw(result)) 102 | } 103 | } 104 | 105 | /// Prevents boilerplate code in constructors. 106 | fn create(callback: Box, initialize: T) -> Self 107 | where 108 | T: FnOnce(*mut c_void, &mut JsValueRef) -> JsErrorCode, 109 | { 110 | // Because a boxed callback can be a fat pointer, it needs to be wrapped 111 | // in an additional Box to ensure it fits in a single pointer. 112 | let wrapper = Box::into_raw(Box::new(callback)); 113 | 114 | unsafe { 115 | let mut reference = JsValueRef::new(); 116 | jsassert!(initialize(wrapper as *mut _, &mut reference)); 117 | let function = Self::from_raw(reference); 118 | 119 | // Ensure the heap objects are freed 120 | function.set_collect_callback(Box::new(move |_| { 121 | Box::from_raw(wrapper); 122 | })); 123 | function 124 | } 125 | } 126 | 127 | /// A function callback, triggered on call. 128 | unsafe extern "system" fn callback( 129 | callee: JsValueRef, 130 | is_construct_call: bool, 131 | arguments: *mut JsValueRef, 132 | len: c_ushort, 133 | data: *mut c_void, 134 | ) -> JsRef { 135 | // This memory is cleaned up during object collection 136 | let callback = data as *mut Box; 137 | 138 | // There is always an active context in callbacks 139 | Context::exec_with_current(|guard| { 140 | // Construct the callback information object 141 | let arguments = slice::from_raw_parts_mut(arguments, len as usize); 142 | let info = CallbackInfo { 143 | is_construct_call, 144 | arguments: arguments[1..] 145 | .iter() 146 | .map(|value| Value::from_raw(*value)) 147 | .collect(), 148 | callee: Value::from_raw(callee), 149 | this: Value::from_raw(arguments[0]), 150 | }; 151 | 152 | // Call the user supplied callback 153 | match (*callback)(&guard, info) { 154 | Ok(value) => value.as_raw(), 155 | Err(error) => { 156 | jsassert!(JsSetException(error.as_raw())); 157 | error.as_raw() 158 | }, 159 | } 160 | }) 161 | .expect("executing function callback") 162 | } 163 | } 164 | 165 | reference!(Function); 166 | inherit!(Function, Object); 167 | subtype!(Function, Value); 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use crate::{script, test, value, Property}; 172 | 173 | #[test] 174 | fn multiply() { 175 | test::run_with_context(|guard| { 176 | let captured_variable = 5.0; 177 | let function = value::Function::new( 178 | guard, 179 | Box::new(move |guard, info| { 180 | // Ensure the defaults are sensible 181 | assert_eq!(info.is_construct_call, false); 182 | assert_eq!(info.arguments.len(), 2); 183 | assert_eq!(captured_variable, 5.0); 184 | 185 | let result = info.arguments[0].to_double(guard) 186 | + info.arguments[1].to_double(guard) 187 | + captured_variable; 188 | Ok(value::Number::from_double(guard, result).into()) 189 | }), 190 | ); 191 | 192 | let result = function 193 | .call( 194 | guard, 195 | &[ 196 | &value::Number::new(guard, 5).into(), 197 | &value::Number::from_double(guard, 10.5).into(), 198 | ], 199 | ) 200 | .unwrap(); 201 | 202 | assert_eq!(result.to_integer(guard), 20); 203 | assert_eq!(result.to_double(guard), 20.5); 204 | }); 205 | } 206 | 207 | #[test] 208 | fn exception() { 209 | test::run_with_context(|guard| { 210 | let function = value::Function::new( 211 | guard, 212 | Box::new(move |guard, _| Err(value::Error::new(guard, "Exception").into())), 213 | ); 214 | 215 | let global = guard.global(); 216 | let property = Property::new(guard, "test"); 217 | global.set(guard, property, function); 218 | 219 | let result = script::eval(guard, "try { test(); } catch (ex) { ex.message; }").unwrap(); 220 | 221 | assert_eq!(result.to_string(guard), "Exception"); 222 | }); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /chakracore/src/value/macros.rs: -------------------------------------------------------------------------------- 1 | /// Implements JSRT reference counting for a value type (overrides default 2 | /// `reference` macro definition). 3 | macro_rules! reference { 4 | ($typ:ident) => { 5 | reference_base!($typ); 6 | 7 | impl Drop for $typ { 8 | /// Decrements the reference counter if the object is recyclable. 9 | fn drop(&mut self) { 10 | use crate::{util, Context}; 11 | Context::exec_with_value(self, |_| { 12 | // This requires that the active context is the same as the 13 | // one it was created with (this is not mentioned whatsoever 14 | // in the ChakraCore documentation). 15 | util::release_reference(self.as_raw()); 16 | }) 17 | .expect("changing active context for release"); 18 | } 19 | } 20 | }; 21 | } 22 | 23 | /// Implements a relationship between two subtypes. 24 | macro_rules! subtype { 25 | ($child:ident, $parent:ident) => { 26 | impl From<$child> for $parent { 27 | fn from(child: $child) -> $parent { 28 | unsafe { ::std::mem::transmute(child) } 29 | } 30 | } 31 | 32 | impl AsRef<$parent> for $child { 33 | fn as_ref(&self) -> &$parent { 34 | unsafe { ::std::mem::transmute(self) } 35 | } 36 | } 37 | }; 38 | } 39 | 40 | /// Implements inheritance between two types. 41 | macro_rules! inherit { 42 | ($child:ident, $parent:ident) => { 43 | subtype!($child, $parent); 44 | 45 | impl ::std::ops::Deref for $child { 46 | type Target = $parent; 47 | 48 | fn deref(&self) -> &Self::Target { 49 | unsafe { ::std::mem::transmute(self) } 50 | } 51 | } 52 | }; 53 | } 54 | 55 | /// Implements JavaScript type equality method. 56 | macro_rules! is_same { 57 | ($target:ident, $target_doc:expr) => { 58 | #[doc=$target_doc] 59 | pub fn is_same>(value: V) -> bool { 60 | value.as_ref().get_type() == JsValueType::$target 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /chakracore/src/value/mod.rs: -------------------------------------------------------------------------------- 1 | //! Javascript values that user code can interact with. 2 | //! 3 | //! The [Value](struct.Value.html) object acts as the base type for all others 4 | //! using dereference. Therefore all types have access to the `Value` type's 5 | //! methods and traits. Implicit traits such as `Debug`, must be accessed by 6 | //! dereferencing. For example: `Function` inherits `Object`, which in turn 7 | //! inherits `Value`. To print a function using the debug trait, you need to 8 | //! dereference twice. 9 | //! 10 | //! ```norun 11 | //! println("Output: {:?}", **function_value); 12 | //! ``` 13 | //! 14 | //! Please note that the `Debug` trait should be carefully used. It relies 15 | //! implicitly on an active context, and that it's the same context the value 16 | //! was created with. Therefore it's mostly implemented to ease debugging. 17 | //! Prefer `to_string` for safety. 18 | //! 19 | //! All created values are tied to a specific context. Because of this a 20 | //! `ContextGuard` is required whenever creating new values, and they should 21 | //! not be passed between different contexts. 22 | use crate::ContextGuard; 23 | use chakracore_sys::*; 24 | 25 | // TODO: Add typed arrays and buffer view. 26 | pub use self::array::*; 27 | pub use self::boolean::Boolean; 28 | pub use self::error::Error; 29 | pub use self::external::External; 30 | pub use self::function::Function; 31 | pub use self::number::Number; 32 | pub use self::object::Object; 33 | pub use self::promise::Promise; 34 | pub use self::string::String; 35 | pub use self::value::Value; 36 | 37 | #[macro_use] 38 | mod macros; 39 | 40 | // Modules 41 | mod array; 42 | mod boolean; 43 | mod error; 44 | mod external; 45 | pub mod function; 46 | mod number; 47 | mod object; 48 | pub mod promise; 49 | mod string; 50 | mod value; 51 | 52 | /// Creates a `false` value. 53 | pub fn false_(_guard: &ContextGuard) -> Value { 54 | let mut value = JsValueRef::new(); 55 | unsafe { 56 | jsassert!(JsGetFalseValue(&mut value)); 57 | Value::from_raw(value) 58 | } 59 | } 60 | 61 | /// Creates a `true` value. 62 | pub fn true_(_guard: &ContextGuard) -> Value { 63 | let mut value = JsValueRef::new(); 64 | unsafe { 65 | jsassert!(JsGetTrueValue(&mut value)); 66 | Value::from_raw(value) 67 | } 68 | } 69 | 70 | /// Creates a `null` value. 71 | pub fn null(_guard: &ContextGuard) -> Value { 72 | let mut value = JsValueRef::new(); 73 | unsafe { 74 | jsassert!(JsGetNullValue(&mut value)); 75 | Value::from_raw(value) 76 | } 77 | } 78 | 79 | /// Creates an `undefined` value. 80 | pub fn undefined(_guard: &ContextGuard) -> Value { 81 | let mut value = JsValueRef::new(); 82 | unsafe { 83 | jsassert!(JsGetUndefinedValue(&mut value)); 84 | Value::from_raw(value) 85 | } 86 | } 87 | 88 | // These are hidden and exists merely for the `is_same` functionality. 89 | struct Null; 90 | struct Undefined; 91 | 92 | impl Null { 93 | is_same!(Null, "Returns true if the value is `null`"); 94 | } 95 | impl Undefined { 96 | is_same!(Undefined, "Returns true if the value is `undefined`"); 97 | } 98 | -------------------------------------------------------------------------------- /chakracore/src/value/number.rs: -------------------------------------------------------------------------------- 1 | use crate::{value::Value, ContextGuard}; 2 | use chakracore_sys::*; 3 | 4 | /// A JavaScript number. 5 | pub struct Number(JsValueRef); 6 | 7 | impl Number { 8 | /// Creates a new number. 9 | pub fn new(_guard: &ContextGuard, number: i32) -> Self { 10 | let mut value = JsValueRef::new(); 11 | unsafe { 12 | jsassert!(JsIntToNumber(number, &mut value)); 13 | Self::from_raw(value) 14 | } 15 | } 16 | 17 | /// Creates a new number from a double. 18 | pub fn from_double(_guard: &ContextGuard, number: f64) -> Self { 19 | let mut value = JsValueRef::new(); 20 | unsafe { 21 | jsassert!(JsDoubleToNumber(number, &mut value)); 22 | Self::from_raw(value) 23 | } 24 | } 25 | 26 | /// Converts a JavaScript number to a double. 27 | pub fn value_double(&self) -> f64 { 28 | let mut double = 0f64; 29 | unsafe { 30 | jsassert!(JsNumberToDouble(self.as_raw(), &mut double)); 31 | double 32 | } 33 | } 34 | 35 | /// Converts a JavaScript number to an integral. 36 | pub fn value(&self) -> i32 { 37 | let mut integer = 0; 38 | unsafe { 39 | jsassert!(JsNumberToInt(self.as_raw(), &mut integer)); 40 | integer 41 | } 42 | } 43 | 44 | is_same!(Number, "Returns true if the value is a `Number`."); 45 | } 46 | 47 | reference!(Number); 48 | inherit!(Number, Value); 49 | -------------------------------------------------------------------------------- /chakracore/src/value/object.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{Array, Function, Value}; 2 | use crate::{util::jstry, ContextGuard, Property, Result}; 3 | use chakracore_sys::*; 4 | use libc::c_void; 5 | 6 | /// Callback type for collector. 7 | type BeforeCollectCallback = dyn Fn(&Value); 8 | 9 | /// A JavaScript object. 10 | pub struct Object(JsValueRef); 11 | 12 | // TODO: Add `for .. in` iterator 13 | impl Object { 14 | /// Creates a new empty object. 15 | pub fn new(_guard: &ContextGuard) -> Self { 16 | let mut value = JsValueRef::new(); 17 | unsafe { 18 | jsassert!(JsCreateObject(&mut value)); 19 | Self::from_raw(value) 20 | } 21 | } 22 | 23 | /// Sets an object's property's value. 24 | pub fn set(&self, _guard: &ContextGuard, key: P, value: V) 25 | where 26 | P: AsRef, 27 | V: AsRef, 28 | { 29 | jsassert!(unsafe { 30 | JsSetProperty( 31 | self.as_raw(), 32 | key.as_ref().as_raw(), 33 | value.as_ref().as_raw(), 34 | false, 35 | ) 36 | }); 37 | } 38 | 39 | /// Sets an object's index value. 40 | pub fn set_index>(&self, guard: &ContextGuard, index: u32, value: V) { 41 | let index = super::Number::new(guard, index as i32); 42 | jsassert!(unsafe { 43 | JsSetIndexedProperty(self.as_raw(), index.as_raw(), value.as_ref().as_raw()) 44 | }); 45 | } 46 | 47 | /// Returns an object's property's value. 48 | pub fn get>(&self, _guard: &ContextGuard, key: P) -> Value { 49 | let mut result = JsValueRef::new(); 50 | unsafe { 51 | jsassert!(JsGetProperty( 52 | self.as_raw(), 53 | key.as_ref().as_raw(), 54 | &mut result 55 | )); 56 | Value::from_raw(result) 57 | } 58 | } 59 | 60 | /// Returns an object's index value. 61 | pub fn get_index(&self, guard: &ContextGuard, index: u32) -> Value { 62 | let index = super::Number::new(guard, index as i32); 63 | let mut result = JsValueRef::new(); 64 | unsafe { 65 | jsassert!(JsGetIndexedProperty( 66 | self.as_raw(), 67 | index.as_raw(), 68 | &mut result 69 | )); 70 | Value::from_raw(result) 71 | } 72 | } 73 | 74 | /// Deletes an object's property. 75 | pub fn delete>(&self, _guard: &ContextGuard, key: P) -> bool { 76 | let mut result = JsValueRef::new(); 77 | unsafe { 78 | jsassert!(JsDeleteProperty( 79 | self.as_raw(), 80 | key.as_ref().as_raw(), 81 | false, 82 | &mut result 83 | )); 84 | super::Boolean::from_raw(result).value() 85 | } 86 | } 87 | 88 | /// Deletes an object's index. 89 | pub fn delete_index(&self, guard: &ContextGuard, index: u32) { 90 | let index = super::Number::new(guard, index as i32); 91 | jsassert!(unsafe { JsDeleteIndexedProperty(self.as_raw(), index.as_raw()) }); 92 | } 93 | 94 | /// Determines whether an object has a property. 95 | pub fn has>(&self, _guard: &ContextGuard, key: P) -> bool { 96 | let mut result = false; 97 | jsassert!(unsafe { JsHasProperty(self.as_raw(), key.as_ref().as_raw(), &mut result) }); 98 | result 99 | } 100 | 101 | /// Determines whether an object has a value at the specified index. 102 | pub fn has_index(&self, guard: &ContextGuard, index: u32) -> bool { 103 | let mut result = false; 104 | let index = super::Number::new(guard, index as i32); 105 | jsassert!(unsafe { JsHasIndexedProperty(self.as_raw(), index.as_raw(), &mut result) }); 106 | result 107 | } 108 | 109 | /// Defines or modifies a property directly on an object. 110 | /// 111 | /// This is equivalent to `Object.defineProperty()`. 112 | pub fn define_property(&self, _guard: &ContextGuard, key: P, desc: O) -> bool 113 | where 114 | P: AsRef, 115 | O: AsRef, 116 | { 117 | let mut result = false; 118 | jsassert!(unsafe { 119 | JsDefineProperty( 120 | self.as_raw(), 121 | key.as_ref().as_raw(), 122 | desc.as_ref().as_raw(), 123 | &mut result, 124 | ) 125 | }); 126 | result 127 | } 128 | 129 | /// Sets the object's prototype. This will result in an error if it's called 130 | /// on the context's global object. 131 | pub fn set_prototype>(&self, _guard: &ContextGuard, prototype: V) -> Result<()> { 132 | unsafe { jstry(JsSetPrototype(self.as_raw(), prototype.as_ref().as_raw())) } 133 | } 134 | 135 | /// Returns the object's prototype. 136 | pub fn get_prototype(&self, _guard: &ContextGuard) -> Value { 137 | let mut prototype = JsValueRef::new(); 138 | unsafe { 139 | jsassert!(JsGetPrototype(self.as_raw(), &mut prototype)); 140 | Value::from_raw(prototype) 141 | } 142 | } 143 | 144 | /// Returns the object's property names 145 | pub fn get_own_property_names(&self, _guard: &ContextGuard) -> Array { 146 | let mut properties = JsValueRef::new(); 147 | unsafe { 148 | jsassert!(JsGetOwnPropertyNames(self.as_raw(), &mut properties)); 149 | Array::from_raw(properties) 150 | } 151 | } 152 | 153 | /// Returns whether the object is an instance of this `Function` or not. 154 | /// 155 | /// This must only be used on values that exists within the same context as 156 | /// the constructor, otherwise the result will always be `false`. 157 | pub fn instance_of>(&self, _guard: &ContextGuard, constructor: F) -> bool { 158 | let mut result = false; 159 | // TODO: #[cfg(debug_assertions)] validate same context 160 | unsafe { 161 | jsassert!(JsInstanceOf( 162 | self.as_raw(), 163 | constructor.as_ref().as_raw(), 164 | &mut result 165 | )); 166 | result 167 | } 168 | } 169 | 170 | /// Makes an object non-extensible. 171 | pub fn prevent_extension(&self) { 172 | jsassert!(unsafe { JsPreventExtension(self.as_raw()) }); 173 | } 174 | 175 | /// Returns whether the object is extensible or not. 176 | pub fn is_extensible(&self) -> bool { 177 | let mut result = false; 178 | jsassert!(unsafe { JsGetExtensionAllowed(self.as_raw(), &mut result) }); 179 | result 180 | } 181 | 182 | /// Sets a callback that is executed before the object is collected. 183 | /// 184 | /// This is highly unsafe to use. There is no bookkeeping whether any other 185 | /// caller replaces the current callback or not. It is also used internally 186 | /// by `Function` to cleanup user data (if it's replaced, memory will leak). 187 | pub unsafe fn set_collect_callback(&self, callback: Box) { 188 | let wrapper = Box::new(callback); 189 | let api = JsSetObjectBeforeCollectCallback; 190 | jsassert!(api( 191 | self.as_raw(), 192 | Box::into_raw(wrapper) as *mut _, 193 | Some(Self::collect) 194 | )); 195 | } 196 | 197 | /// Returns true if the value is an `Object`. 198 | pub fn is_same>(value: V) -> bool { 199 | match value.as_ref().get_type() { 200 | JsValueType::Object 201 | | JsValueType::Function 202 | | JsValueType::Error 203 | | JsValueType::Array 204 | | JsValueType::ArrayBuffer 205 | | JsValueType::TypedArray 206 | | JsValueType::DataView => true, 207 | _ => false, 208 | } 209 | } 210 | 211 | /// A collect callback, triggered before the object is destroyed. 212 | unsafe extern "system" fn collect(value: JsValueRef, data: *mut c_void) { 213 | let wrapper: Box> = Box::from_raw(data as *mut _); 214 | wrapper(&Value::from_raw(value)); 215 | } 216 | } 217 | 218 | reference!(Object); 219 | inherit!(Object, Value); 220 | 221 | #[cfg(test)] 222 | mod tests { 223 | use crate::{script, test, value, Context, Property, Runtime}; 224 | 225 | #[test] 226 | fn properties() { 227 | test::run_with_context(|guard| { 228 | let object = value::Object::new(guard); 229 | 230 | // Associate it with an object field 231 | let prop_foo = Property::new(guard, "foo"); 232 | let prop_bar = Property::new(guard, "bar"); 233 | 234 | object.set(guard, &prop_foo, value::Number::new(guard, 10)); 235 | object.set(guard, &prop_bar, value::null(guard)); 236 | 237 | // Ensure the fields have been created with the assigned values 238 | assert_eq!(object.get(guard, &prop_foo).to_integer(guard), 10); 239 | assert!(object.get(guard, &prop_bar).is_null()); 240 | 241 | // Retrieve all the objects' properties 242 | let properties = object 243 | .get_own_property_names(guard) 244 | .iter(guard) 245 | .map(|val| val.to_string(guard)) 246 | .collect::>(); 247 | assert_eq!(properties, ["foo", "bar"]); 248 | 249 | // Remove the object's property 250 | assert!(object.has(guard, &prop_foo)); 251 | object.delete(guard, &prop_foo); 252 | assert!(!object.has(guard, &prop_foo)); 253 | }); 254 | } 255 | 256 | #[test] 257 | fn instance_of() { 258 | test::run_with_context(|guard| { 259 | let constructor = value::Function::new( 260 | guard, 261 | Box::new(move |_, info| { 262 | assert!(info.is_construct_call); 263 | Ok(info.this) 264 | }), 265 | ); 266 | 267 | let global = guard.global(); 268 | let property = Property::new(guard, "FooBar"); 269 | global.set(guard, property, &constructor); 270 | 271 | let foo_bar = script::eval(guard, "new FooBar()") 272 | .unwrap() 273 | .into_object() 274 | .unwrap(); 275 | assert!(foo_bar.instance_of(guard, &constructor)); 276 | }); 277 | } 278 | 279 | #[test] 280 | fn instance_of_cross_contexts() { 281 | let runtime = Runtime::new().unwrap(); 282 | let c1 = Context::new(&runtime).unwrap(); 283 | let c2 = Context::new(&runtime).unwrap(); 284 | 285 | let g1 = c1.make_current().unwrap(); 286 | let p1 = script::eval(&g1, "new Promise(() => true)") 287 | .ok() 288 | .and_then(|v| v.into_object()) 289 | .unwrap(); 290 | 291 | let g2 = c2.make_current().unwrap(); 292 | let p2 = script::eval(&g1, "Promise") 293 | .ok() 294 | .and_then(|v| v.into_function()) 295 | .unwrap(); 296 | assert!(p1.instance_of(&g2, &p2)); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /chakracore/src/value/promise.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{Function, Object, Value}; 2 | use crate::{util::jsfunc, Context, ContextGuard, Result}; 3 | use chakracore_sys::*; 4 | 5 | /// A JavaScript promise executor. 6 | pub struct Executor { 7 | resolve: Function, 8 | reject: Function, 9 | } 10 | 11 | impl Executor { 12 | /// Consumes the `Executor` and fulfills the associated promise. 13 | pub fn resolve(self, guard: &ContextGuard, arguments: &[&Value]) -> Result<()> { 14 | self.resolve.call(guard, arguments).map(|_| ()) 15 | } 16 | 17 | /// Consumes the `Executor` and rejects the associated promise. 18 | pub fn reject(self, guard: &ContextGuard, arguments: &[&Value]) -> Result<()> { 19 | self.reject.call(guard, arguments).map(|_| ()) 20 | } 21 | } 22 | 23 | /// A JavaScript promise. 24 | /// 25 | /// To support promises within a context, see 26 | /// [Context](../context/struct.Context.html). 27 | pub struct Promise(JsValueRef); 28 | 29 | impl Promise { 30 | /// Creates a new promise with an associated executor. 31 | pub fn new(_guard: &ContextGuard) -> (Self, Executor) { 32 | let mut reference = JsValueRef::new(); 33 | let mut resolve = JsValueRef::new(); 34 | let mut reject = JsValueRef::new(); 35 | 36 | unsafe { 37 | jsassert!(JsCreatePromise(&mut reference, &mut resolve, &mut reject)); 38 | let executor = Executor { 39 | resolve: Function::from_raw(resolve), 40 | reject: Function::from_raw(reject), 41 | }; 42 | 43 | (Self::from_raw(reference), executor) 44 | } 45 | } 46 | 47 | /// Returns true if the value is a `Promise`. 48 | pub fn is_same>(value: V) -> bool { 49 | // See: https://github.com/Microsoft/ChakraCore/issues/135 50 | // There is no straight foward way to do this with the current API. 51 | value 52 | .as_ref() 53 | .clone() 54 | .into_object() 55 | .map_or(false, |object| { 56 | Context::exec_with_value(&object, |guard| { 57 | let promise = jsfunc(guard, "Promise").expect("retrieving Promise constructor"); 58 | object.instance_of(guard, &promise) 59 | }) 60 | .expect("changing active context for Promise comparison") 61 | .expect("missing associated context for Promise comparison") 62 | }) 63 | } 64 | } 65 | 66 | reference!(Promise); 67 | inherit!(Promise, Object); 68 | subtype!(Promise, Value); 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use crate::{script, test, value, Property}; 73 | 74 | #[test] 75 | fn resolve() { 76 | test::run_with_context(|guard| { 77 | let (promise, executor) = value::Promise::new(guard); 78 | executor 79 | .resolve(guard, &[&value::Number::new(guard, 10)]) 80 | .unwrap(); 81 | 82 | let property = Property::new(guard, "promise"); 83 | guard.global().set(guard, property, promise); 84 | 85 | let result = script::eval( 86 | guard, 87 | " 88 | var result = {}; 89 | promise.then(function(value) { result.val = value; }); 90 | result", 91 | ) 92 | .unwrap() 93 | .into_object() 94 | .unwrap(); 95 | guard.execute_tasks(); 96 | assert_eq!( 97 | result 98 | .get(guard, Property::new(guard, "val")) 99 | .to_integer(guard), 100 | 10 101 | ); 102 | }); 103 | } 104 | 105 | #[test] 106 | fn conversion() { 107 | test::run_with_context(|guard| { 108 | let (promise, _) = value::Promise::new(guard); 109 | let value: value::Value = promise.into(); 110 | assert!(value.into_promise().is_some()); 111 | 112 | let promise = script::eval(guard, "new Promise(() => {})") 113 | .unwrap() 114 | .into_promise(); 115 | assert!(promise.is_some()); 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /chakracore/src/value/string.rs: -------------------------------------------------------------------------------- 1 | use crate::value::Value; 2 | use crate::{context::ContextGuard, util}; 3 | use chakracore_sys::*; 4 | 5 | /// A JavaScript string. 6 | pub struct String(JsValueRef); 7 | 8 | impl String { 9 | /// Creates a string value from a native string. 10 | pub fn new(_guard: &ContextGuard, string: &str) -> Self { 11 | let mut value = JsValueRef::new(); 12 | unsafe { 13 | jsassert!(JsCreateString( 14 | string.as_ptr() as _, 15 | string.len(), 16 | &mut value 17 | )); 18 | Self::from_raw(value) 19 | } 20 | } 21 | 22 | /// Returns the length of the string. 23 | pub fn len(&self) -> usize { 24 | let mut length = 0; 25 | jsassert!(unsafe { JsGetStringLength(self.as_raw(), &mut length) }); 26 | length as usize 27 | } 28 | 29 | /// Converts a JavaScript string to a native string. 30 | pub fn value(&self) -> ::std::string::String { 31 | util::to_string_impl(self.as_raw(), JsCopyString).expect("converting string to native") 32 | } 33 | 34 | is_same!(String, "Returns true if the value is a `String`."); 35 | } 36 | 37 | reference!(String); 38 | inherit!(String, Value); 39 | -------------------------------------------------------------------------------- /chakracore/src/value/value.rs: -------------------------------------------------------------------------------- 1 | use crate::{util, value, Context, ContextGuard, Result}; 2 | use chakracore_sys::*; 3 | use std::{fmt, mem}; 4 | 5 | macro_rules! downcast { 6 | ($predicate:ident, $predicate_doc:expr, $target:ident) => { 7 | #[doc=$predicate_doc] 8 | pub fn $predicate(&self) -> bool { 9 | value::$target::is_same(self) 10 | } 11 | }; 12 | 13 | ($predicate:ident, $predicate_doc:expr, 14 | $conversion:ident, $conversion_doc:expr, $result:ident) => { 15 | downcast!($predicate, $predicate_doc, $result); 16 | 17 | #[doc=$conversion_doc] 18 | pub fn $conversion(self) -> Option { 19 | if self.$predicate() { 20 | Some(unsafe { mem::transmute(self) }) 21 | } else { 22 | None 23 | } 24 | } 25 | }; 26 | } 27 | 28 | macro_rules! nativecast { 29 | ($name:ident, $name_doc:expr, $result:ident, $into:ident, $represent:ident, $native:ident) => { 30 | #[doc=$name_doc] 31 | pub fn $name(&self, _guard: &ContextGuard) -> $result { 32 | match self.clone().$into() { 33 | None => self.$represent(_guard), 34 | Some(value) => value, 35 | }.$native() 36 | } 37 | } 38 | } 39 | 40 | macro_rules! representation { 41 | ($name:ident, $name_doc:expr, $result:ident, $function:ident) => { 42 | #[doc=$name_doc] 43 | pub fn $name(&self, _guard: &ContextGuard) -> super::$result { 44 | let mut value = JsValueRef::new(); 45 | unsafe { 46 | jsassert!($function(self.as_raw(), &mut value)); 47 | super::$result::from_raw(value) 48 | } 49 | } 50 | } 51 | } 52 | 53 | /// A JavaScript value, base class for all types. 54 | /// 55 | /// All values are tied to a specific context and should not be reused in 56 | /// between. 57 | /// 58 | /// The underlying object is represented as a `JsValueRef`, a reference to a 59 | /// ChakraCore value. 60 | /// 61 | /// This type implements the `Debug` trait, but it should be used carefully. It 62 | /// assumes there is an active context (the same context other the value was 63 | /// created with). 64 | /// 65 | /// Do not get intimidated by all the conversion methods. They are very easy to 66 | /// grok — there are three different types: 67 | /// 68 | /// > `into_*` 69 | /// >> These do not modify any data. They only check the type of the 70 | /// underlying value. If the value is the designated type (e.g `Object`), the 71 | /// underlying pointer is copied and returned wrapped as the specific type. 72 | /// 73 | /// > `*_representation` 74 | /// >> These create a new value, by casting to a specific type using JavaScript 75 | /// semantics. For example; calling `number_representation` on an `Object` 76 | /// results in a `Number(NaN)`. Casting a `Boolean(false)` using 77 | /// `string_representation` results in a `String('false')`. 78 | /// 79 | /// > `to_*` 80 | /// >> These are utility functions to easily retrieve a native representation of 81 | /// the internal value. The chain of actions performed is the following: 82 | /// `into_*() -> [*_representation()] -> value()`. A call to `*_representation` 83 | /// is only performed if required (i.e a string is not redundantly converted to 84 | /// a string). 85 | pub struct Value(JsValueRef); 86 | 87 | impl Value { 88 | // Transforms a value to another custom type 89 | downcast!( 90 | is_undefined, 91 | "Returns true if this value is `undefined`.", 92 | Undefined 93 | ); 94 | downcast!(is_null, "Returns true if this value is `null`.", Null); 95 | downcast!( 96 | is_number, 97 | "Returns true if this value is a `Number`.", 98 | into_number, 99 | "Represent the value as a `Number`. Does not affect the underlying value.", 100 | Number 101 | ); 102 | downcast!( 103 | is_string, 104 | "Returns true if this value is a `String`.", 105 | into_string, 106 | "Represent the value as a `String`. Does not affect the underlying value.", 107 | String 108 | ); 109 | downcast!( 110 | is_boolean, 111 | "Returns true if this value is a `Boolean`.", 112 | into_boolean, 113 | "Represent the value as a `Boolean`. Does not affect the underlying value.", 114 | Boolean 115 | ); 116 | downcast!( 117 | is_object, 118 | "Returns true if this value is an `Object`.", 119 | into_object, 120 | "Represent the value as an `Object`. Does not affect the underlying value.", 121 | Object 122 | ); 123 | downcast!( 124 | is_external, 125 | "Returns true if this value is an `External`.", 126 | into_external, 127 | "Represent the value as an `External`. Does not affect the underlying value.", 128 | External 129 | ); 130 | downcast!( 131 | is_function, 132 | "Returns true if this value is a `Function`.", 133 | into_function, 134 | "Represent the value as a `Function`. Does not affect the underlying value.", 135 | Function 136 | ); 137 | downcast!( 138 | is_array, 139 | "Returns true if this value is an `Array`.", 140 | into_array, 141 | "Represent the value as an `Array`. Does not affect the underlying value.", 142 | Array 143 | ); 144 | downcast!( 145 | is_array_buffer, 146 | "Returns true if this value is an `ArrayBuffer`.", 147 | into_array_buffer, 148 | "Represent the value as an `ArrayBuffer`. Does not affect the underlying value.", 149 | ArrayBuffer 150 | ); 151 | downcast!( 152 | is_promise, 153 | "Returns true if this value is a `Promise`.", 154 | into_promise, 155 | "Represent the value as a `Promise`. Does not affect the underlying value.", 156 | Promise 157 | ); 158 | 159 | // Converts a value to a native type 160 | nativecast!( 161 | to_string, 162 | "Converts the value to a native string, containing the value's string representation.", 163 | String, 164 | into_string, 165 | string_representation, 166 | value 167 | ); 168 | nativecast!( 169 | to_integer, 170 | "Converts the value to a native integer, containing the value's integer representation.", 171 | i32, 172 | into_number, 173 | number_representation, 174 | value 175 | ); 176 | nativecast!( 177 | to_double, 178 | "Converts the value to a native double, containing the value's floating point representation.", 179 | f64, 180 | into_number, 181 | number_representation, 182 | value_double 183 | ); 184 | nativecast!( 185 | to_bool, 186 | "Converts the value to a native boolean, containing the value's bool representation.", 187 | bool, 188 | into_boolean, 189 | boolean_representation, 190 | value 191 | ); 192 | 193 | /// Converts the value to a native string, containing the value's JSON 194 | /// representation. 195 | pub fn to_json(&self, guard: &ContextGuard) -> Result { 196 | // TODO: Use native functionality when implemented 197 | let stringify = 198 | util::jsfunc(guard, "JSON.stringify").expect("retrieving JSON.stringify function"); 199 | stringify.call(guard, &[self]).map(|v| v.to_string(guard)) 200 | } 201 | 202 | /// Parses JSON and returns it represented as a JavaScript value. 203 | pub fn from_json(guard: &ContextGuard, json: &str) -> Result { 204 | let parse = util::jsfunc(guard, "JSON.parse").expect("retrieving JSON.parse function"); 205 | let json = value::String::new(guard, json); 206 | parse.call(guard, &[&json]) 207 | } 208 | 209 | // Casts a value to the JavaScript expression of another type 210 | representation!( 211 | boolean_representation, 212 | "Creates a new boolean with this value represented as `Boolean`.", 213 | Boolean, 214 | JsConvertValueToBoolean 215 | ); 216 | representation!( 217 | number_representation, 218 | "Creates a new number with this value represented as `Number`.", 219 | Number, 220 | JsConvertValueToNumber 221 | ); 222 | representation!( 223 | object_representation, 224 | "Creates a new object with this value represented as `Object`.", 225 | Object, 226 | JsConvertValueToObject 227 | ); 228 | representation!( 229 | string_representation, 230 | "Creates a new string with this value represented as `String`.", 231 | String, 232 | JsConvertValueToString 233 | ); 234 | 235 | /// Returns the type of the value. This method should be used with 236 | /// consideration. It does not keep track of custom types, such as 237 | /// `External`. It only returns the runtime's definition of a type. 238 | pub fn get_type(&self) -> JsValueType { 239 | let mut value_type = JsValueType::Undefined; 240 | jsassert!(unsafe { JsGetValueType(self.as_raw(), &mut value_type) }); 241 | value_type 242 | } 243 | 244 | /// Compare two values for equality (`==`). 245 | pub fn equals>(&self, _guard: &ContextGuard, other: V) -> bool { 246 | let mut result = false; 247 | jsassert!(unsafe { JsEquals(self.as_raw(), other.as_ref().as_raw(), &mut result) }); 248 | result 249 | } 250 | 251 | /// Compare two values for strict equality (`===`). 252 | pub fn strict_equals>(&self, _guard: &ContextGuard, other: V) -> bool { 253 | let mut result = false; 254 | jsassert!(unsafe { JsStrictEquals(self.as_raw(), other.as_ref().as_raw(), &mut result) }); 255 | result 256 | } 257 | } 258 | 259 | impl PartialEq for Value { 260 | /// Use carefully (prefer `strict_equals`), this relies on an implicitly 261 | /// active context. 262 | fn eq(&self, other: &Value) -> bool { 263 | Context::exec_with_current(|guard| self.strict_equals(guard, other)) 264 | .expect("comparison to have an active context") 265 | } 266 | } 267 | 268 | impl fmt::Debug for Value { 269 | /// Only use for debugging, it relies on an implicitly active context. 270 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 271 | Context::exec_with_current(|guard| { 272 | let output = self.to_string(&guard); 273 | 274 | let value_type = self.get_type(); 275 | match value_type { 276 | JsValueType::String => write!(f, "Value({:?}: '{}')", value_type, output), 277 | _ => write!(f, "Value({:?}: {})", value_type, output), 278 | } 279 | }) 280 | .expect("value debug output without an active context") 281 | } 282 | } 283 | 284 | reference!(Value); 285 | 286 | #[cfg(test)] 287 | mod tests { 288 | use crate::{test, value, Property}; 289 | 290 | #[test] 291 | fn json_conversion() { 292 | test::run_with_context(|guard| { 293 | let object = value::Object::new(guard); 294 | let property = Property::new(guard, "foo"); 295 | object.set(guard, &property, value::Number::new(guard, 1337)); 296 | 297 | let json = object.to_json(guard).unwrap(); 298 | assert_eq!(json, r#"{"foo":1337}"#); 299 | 300 | let object = value::Value::from_json(guard, &json) 301 | .unwrap() 302 | .into_object() 303 | .unwrap(); 304 | assert_eq!(object.get(guard, &property).to_integer(guard), 1337); 305 | }); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | fn_single_line = false 3 | format_strings = true 4 | match_block_trailing_comma = true 5 | normalize_comments = true 6 | reorder_imports = true 7 | tab_spaces = 2 8 | use_field_init_shorthand = true 9 | use_try_shorthand = true 10 | wrap_comments = true --------------------------------------------------------------------------------