>(),
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