├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── ci ├── before_install.sh └── script.sh ├── rustfmt.toml ├── src ├── bin │ └── starling.rs ├── error.rs ├── future_ext.rs ├── gc_roots.rs ├── js_global.rs ├── js_native.rs ├── lib.rs ├── promise_future_glue.rs ├── promise_tracker.rs └── task.rs └── tests ├── js ├── child-outlives-parent-error.js ├── child-outlives-parent.js ├── empty-main.js ├── many-unhandled-rejected-promises.js ├── micro-tasks-delayed-error-handled.js ├── micro-tasks-flushed-on-main-error.js ├── micro-tasks-flushed-on-top-level-error.js ├── ordering.js ├── parent-does-not-handle-child-error.js ├── simple-exit-0.js ├── simple-exit-1.js ├── simple-micro-tasks-error.js ├── simple-micro-tasks.js ├── spawn-and-wait.js ├── spawn-and-wait │ ├── 0.js │ ├── 1.js │ ├── 10.js │ ├── 2.js │ ├── 3.js │ ├── 4.js │ ├── 5.js │ ├── 6.js │ ├── 7.js │ ├── 8.js │ └── 9.js ├── spawn-bad-file.js ├── spawn-task-that-errors.js ├── stay-alive-forever.js ├── task-resolves.js ├── task-resolves │ ├── function.js │ ├── number.js │ ├── object.js │ ├── string.js │ ├── symbol-for.js │ ├── symbol.js │ └── undefined.js ├── timeout-0.js ├── timeout-1.js ├── timeout-2.js ├── timeout-3.js └── timeout-4.js └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | dist: trusty 4 | 5 | os: 6 | - linux 7 | 8 | addons: 9 | apt: 10 | sources: 11 | # Provides newer gcc. 12 | - ubuntu-toolchain-r-test 13 | # Provides libclang 3.9. 14 | - llvm-toolchain-trusty-3.9 15 | packages: 16 | - autoconf2.13 17 | # bindgen requires libclang >= 3.9. 18 | - clang-3.9 19 | # SpiderMonkey needs gcc >= 4.9 and 5 is ICEing. 20 | - gcc-6 21 | - g++-6 22 | 23 | rust: 24 | - nightly 25 | 26 | cache: cargo 27 | 28 | env: 29 | matrix: 30 | - PROFILE="--release" FEATURES="" 31 | - PROFILE="" FEATURES="" 32 | - PROFILE="--release" FEATURES="--features debugmozjs" 33 | - PROFILE="" FEATURES="--features debugmozjs" 34 | 35 | before_install: 36 | - source ./ci/before_install.sh 37 | 38 | script: 39 | - ccache -z 40 | - PROFILE="$PROFILE" FEATURES="$FEATURES" travis_wait 60 ./ci/script.sh 41 | - ccache --show-stats 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Starling 2 | 3 | Hi! We'd love to have your contributions! If you want help or mentorship, reach 4 | out to us in a GitHub issue, or ping `fitzgen` in 5 | [#starling on irc.mozilla.org](irc://irc.mozilla.org#starling) and introduce yourself. 6 | 7 | 8 | 9 | 10 | 11 | - [Code of Conduct](#code-of-conduct) 12 | - [Building](#building) 13 | - [Testing](#testing) 14 | - [Pull Requests and Code Review](#pull-requests-and-code-review) 15 | - [Automatic Code Formatting](#automatic-code-formatting) 16 | 17 | 18 | 19 | ## Code of Conduct 20 | 21 | We abide by the [Rust Code of Conduct][coc] and ask that you do as well. 22 | 23 | [coc]: https://www.rust-lang.org/en-US/conduct.html 24 | 25 | ## Building 26 | 27 | To build Starling, you need `autoconf` 2.13 and `libclang` at least 3.9. You 28 | should set the `LIBCLANG_PATH` environment variable to the directory containing 29 | `libclang.so`. 30 | 31 | For example, in Ubuntu 16.04: 32 | 33 | ``` 34 | $ sudo apt-get install autoconf2.13 clang-4.0 35 | $ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-4.0 100 36 | $ sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-4.0 100 37 | $ LIBCLANG_PATH=/usr/lib/llvm-4.0/lib cargo build 38 | ``` 39 | 40 | The `LIBCLANG_PATH` environment variable is only needed by codegen when 41 | compiling `mozjs`. Once it is built, you can use the usual cargo commands such 42 | as `cargo +nightly test`. 43 | 44 | ## Testing 45 | 46 | To run all the tests: 47 | 48 | ``` 49 | $ cargo test 50 | ``` 51 | 52 | ## Pull Requests and Code Review 53 | 54 | Ensure that each commit stands alone, and passes tests. This enables better `git 55 | bisect`ing when needed. If your commits do not stand on their own, then rebase 56 | them on top of the latest master and squash them into a single commit. 57 | 58 | All pull requests undergo code review before merging. To request review, use 59 | GitHub's pull request review functionality. 60 | 61 | Unsure who to ask for review? Ask any of: 62 | 63 | * `@fitzgen` 64 | * `@tschneidereit` 65 | 66 | ## Automatic Code Formatting 67 | 68 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a 69 | consistent code style across the whole code base. 70 | 71 | You can install the latest version of `rustfmt` with this command: 72 | 73 | ``` 74 | $ rustup update nightly 75 | $ cargo +nightly install -f rustfmt-nightly 76 | ``` 77 | 78 | Ensure that `~/.cargo/bin` is on your path. 79 | 80 | Once that is taken care of, you can (re)format all code by running this command: 81 | 82 | ``` 83 | $ cargo +nightly fmt 84 | ``` 85 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.6.4" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.10.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "backtrace" 27 | version = "0.3.4" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "cpp_demangle 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "backtrace-sys" 42 | version = "0.1.16" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "bindgen" 51 | version = "0.32.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "cexpr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "clang-sys 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "which 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "0.7.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "1.0.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | [[package]] 78 | name = "byteorder" 79 | version = "1.2.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | 82 | [[package]] 83 | name = "bytes" 84 | version = "0.4.5" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | dependencies = [ 87 | "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "cc" 93 | version = "1.0.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | 96 | [[package]] 97 | name = "cexpr" 98 | version = "0.2.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 102 | ] 103 | 104 | [[package]] 105 | name = "cfg-if" 106 | version = "0.1.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "clang-sys" 111 | version = "0.21.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "clap" 121 | version = "2.29.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "cmake" 135 | version = "0.1.29" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "cpp_demangle" 143 | version = "0.2.7" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 147 | ] 148 | 149 | [[package]] 150 | name = "darling" 151 | version = "0.2.2" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "darling_macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "darling_core" 160 | version = "0.2.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | dependencies = [ 163 | "ident_case 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 167 | ] 168 | 169 | [[package]] 170 | name = "darling_macro" 171 | version = "0.2.2" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | dependencies = [ 174 | "darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 177 | ] 178 | 179 | [[package]] 180 | name = "dbghelp-sys" 181 | version = "0.2.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | dependencies = [ 184 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 185 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 186 | ] 187 | 188 | [[package]] 189 | name = "derive-error-chain" 190 | version = "0.11.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | dependencies = [ 193 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "derive_state_machine_future" 199 | version = "0.1.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | dependencies = [ 202 | "darling 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "petgraph 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 207 | ] 208 | 209 | [[package]] 210 | name = "env_logger" 211 | version = "0.4.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | dependencies = [ 214 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "error-chain" 220 | version = "0.11.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | dependencies = [ 223 | "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 224 | ] 225 | 226 | [[package]] 227 | name = "fixedbitset" 228 | version = "0.1.8" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | 231 | [[package]] 232 | name = "fuchsia-zircon" 233 | version = "0.2.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "fuchsia-zircon-sys" 241 | version = "0.2.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 245 | ] 246 | 247 | [[package]] 248 | name = "futures" 249 | version = "0.1.17" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | 252 | [[package]] 253 | name = "futures-cpupool" 254 | version = "0.1.7" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | dependencies = [ 257 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "glob" 263 | version = "0.2.11" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "heapsize" 268 | version = "0.4.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "heck" 276 | version = "0.3.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | dependencies = [ 279 | "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 280 | ] 281 | 282 | [[package]] 283 | name = "ident_case" 284 | version = "1.0.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | 287 | [[package]] 288 | name = "iovec" 289 | version = "0.1.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | dependencies = [ 292 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 294 | ] 295 | 296 | [[package]] 297 | name = "js" 298 | version = "0.1.4" 299 | source = "git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup#8667ad2fe4f68156dd1783ad0e23ceb6f56723b8" 300 | dependencies = [ 301 | "bindgen 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)", 302 | "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "heapsize 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 306 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 307 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 308 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 309 | "mozjs_sys 0.0.0 (git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup)", 310 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 311 | ] 312 | 313 | [[package]] 314 | name = "kernel32-sys" 315 | version = "0.2.2" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 319 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 320 | ] 321 | 322 | [[package]] 323 | name = "lazy_static" 324 | version = "0.2.11" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | 327 | [[package]] 328 | name = "lazy_static" 329 | version = "1.0.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | 332 | [[package]] 333 | name = "lazycell" 334 | version = "0.5.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | 337 | [[package]] 338 | name = "libc" 339 | version = "0.2.34" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | 342 | [[package]] 343 | name = "libloading" 344 | version = "0.4.3" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | dependencies = [ 347 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 348 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 350 | ] 351 | 352 | [[package]] 353 | name = "libz-sys" 354 | version = "1.0.18" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | dependencies = [ 357 | "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 361 | ] 362 | 363 | [[package]] 364 | name = "log" 365 | version = "0.3.8" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | 368 | [[package]] 369 | name = "memchr" 370 | version = "1.0.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | dependencies = [ 373 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 374 | ] 375 | 376 | [[package]] 377 | name = "memchr" 378 | version = "2.0.1" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | dependencies = [ 381 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 382 | ] 383 | 384 | [[package]] 385 | name = "mio" 386 | version = "0.6.11" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | dependencies = [ 389 | "fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 396 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 397 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 398 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 400 | ] 401 | 402 | [[package]] 403 | name = "miow" 404 | version = "0.2.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | dependencies = [ 407 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 408 | "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 409 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 411 | ] 412 | 413 | [[package]] 414 | name = "mozjs_sys" 415 | version = "0.0.0" 416 | source = "git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup#8667ad2fe4f68156dd1783ad0e23ceb6f56723b8" 417 | dependencies = [ 418 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 419 | "libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 420 | "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 421 | ] 422 | 423 | [[package]] 424 | name = "net2" 425 | version = "0.2.31" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | dependencies = [ 428 | "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 429 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 430 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 431 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 432 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 433 | ] 434 | 435 | [[package]] 436 | name = "nom" 437 | version = "3.2.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | dependencies = [ 440 | "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 441 | ] 442 | 443 | [[package]] 444 | name = "num-traits" 445 | version = "0.1.41" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | 448 | [[package]] 449 | name = "num_cpus" 450 | version = "1.7.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | dependencies = [ 453 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 454 | ] 455 | 456 | [[package]] 457 | name = "ordermap" 458 | version = "0.3.2" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | 461 | [[package]] 462 | name = "peeking_take_while" 463 | version = "0.1.2" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | 466 | [[package]] 467 | name = "petgraph" 468 | version = "0.4.10" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "ordermap 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 473 | ] 474 | 475 | [[package]] 476 | name = "pkg-config" 477 | version = "0.3.9" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | 480 | [[package]] 481 | name = "quote" 482 | version = "0.3.15" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | 485 | [[package]] 486 | name = "redox_syscall" 487 | version = "0.1.32" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | 490 | [[package]] 491 | name = "redox_termios" 492 | version = "0.1.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | dependencies = [ 495 | "redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 496 | ] 497 | 498 | [[package]] 499 | name = "regex" 500 | version = "0.2.3" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | dependencies = [ 503 | "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 504 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 508 | ] 509 | 510 | [[package]] 511 | name = "regex-syntax" 512 | version = "0.4.1" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | 515 | [[package]] 516 | name = "rent_to_own" 517 | version = "0.1.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | 520 | [[package]] 521 | name = "rustc-demangle" 522 | version = "0.1.5" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | 525 | [[package]] 526 | name = "scoped-tls" 527 | version = "0.1.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | 530 | [[package]] 531 | name = "slab" 532 | version = "0.3.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | 535 | [[package]] 536 | name = "slab" 537 | version = "0.4.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | 540 | [[package]] 541 | name = "starling" 542 | version = "0.1.0" 543 | dependencies = [ 544 | "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 545 | "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", 546 | "derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 547 | "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 548 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 549 | "futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 550 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 551 | "js 0.1.4 (git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup)", 552 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 553 | "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 554 | "state_machine_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 555 | "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 556 | "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 557 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 558 | ] 559 | 560 | [[package]] 561 | name = "state_machine_future" 562 | version = "0.1.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | dependencies = [ 565 | "derive_state_machine_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 566 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 567 | "rent_to_own 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 568 | ] 569 | 570 | [[package]] 571 | name = "strsim" 572 | version = "0.6.0" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | 575 | [[package]] 576 | name = "syn" 577 | version = "0.11.11" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | dependencies = [ 580 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 581 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 582 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 583 | ] 584 | 585 | [[package]] 586 | name = "synom" 587 | version = "0.11.3" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | dependencies = [ 590 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 591 | ] 592 | 593 | [[package]] 594 | name = "termion" 595 | version = "1.5.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | dependencies = [ 598 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 599 | "redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 600 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 601 | ] 602 | 603 | [[package]] 604 | name = "textwrap" 605 | version = "0.9.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | dependencies = [ 608 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 609 | ] 610 | 611 | [[package]] 612 | name = "thread_local" 613 | version = "0.3.5" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | dependencies = [ 616 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 617 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 618 | ] 619 | 620 | [[package]] 621 | name = "tokio-core" 622 | version = "0.1.11" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | dependencies = [ 625 | "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 626 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 627 | "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 628 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 629 | "mio 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", 630 | "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 632 | "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 633 | ] 634 | 635 | [[package]] 636 | name = "tokio-io" 637 | version = "0.1.4" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | dependencies = [ 640 | "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 641 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 642 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 643 | ] 644 | 645 | [[package]] 646 | name = "tokio-timer" 647 | version = "0.1.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | dependencies = [ 650 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 651 | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 652 | ] 653 | 654 | [[package]] 655 | name = "unicode-segmentation" 656 | version = "1.2.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | 659 | [[package]] 660 | name = "unicode-width" 661 | version = "0.1.4" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | 664 | [[package]] 665 | name = "unicode-xid" 666 | version = "0.0.4" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | 669 | [[package]] 670 | name = "unreachable" 671 | version = "1.0.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | dependencies = [ 674 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 675 | ] 676 | 677 | [[package]] 678 | name = "utf8-ranges" 679 | version = "1.0.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | 682 | [[package]] 683 | name = "vcpkg" 684 | version = "0.2.2" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | 687 | [[package]] 688 | name = "vec_map" 689 | version = "0.8.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | 692 | [[package]] 693 | name = "void" 694 | version = "1.0.2" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | 697 | [[package]] 698 | name = "which" 699 | version = "1.0.3" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | dependencies = [ 702 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 703 | ] 704 | 705 | [[package]] 706 | name = "winapi" 707 | version = "0.2.8" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | 710 | [[package]] 711 | name = "winapi-build" 712 | version = "0.1.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | 715 | [[package]] 716 | name = "ws2_32-sys" 717 | version = "0.2.1" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | dependencies = [ 720 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 721 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 722 | ] 723 | 724 | [metadata] 725 | "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" 726 | "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" 727 | "checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860" 728 | "checksum backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8709cc7ec06f6f0ae6c2c7e12f6ed41540781f72b488d83734978295ceae182e" 729 | "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" 730 | "checksum bindgen 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e57fd015c86d16b28d6409995045124a07665f36b38ca1992b1caf882fde6" 731 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 732 | "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" 733 | "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" 734 | "checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" 735 | "checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" 736 | "checksum cexpr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cdbb21df6ff3497a61df5059994297f746267020ba38ce237aad9c875f7b4313" 737 | "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" 738 | "checksum clang-sys 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00048189ee171715296dfe3b2fcfd439563c7bfec0d98d3976ce3402d62c8f07" 739 | "checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f" 740 | "checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb" 741 | "checksum cpp_demangle 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "23d0c112e412a39379dcb1ccbfbe1ea633bd337577ca2fc163ecdd098e636a93" 742 | "checksum darling 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1630fdbe3554154a50624487c79b0140a424e87dc08061db1a2211359792acab" 743 | "checksum darling_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d12d2eeb837786ace70b6bca9adfeaef4352cc68d6a42e8e3d0c4159bbca7ab2" 744 | "checksum darling_macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "01581bdeabb86f69970dbd9e6ee3c61963f9a7321169589e3dffa16033c0928c" 745 | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" 746 | "checksum derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92183014af72c63aea490e66526c712bf1066ac50f66c9f34824f02483ec1d98" 747 | "checksum derive_state_machine_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0387f3077166e37d454f8b55574698429bd841c2418c978abbdffb9ff86e87aa" 748 | "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" 749 | "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" 750 | "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478" 751 | "checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" 752 | "checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" 753 | "checksum futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "118b49cac82e04121117cbd3121ede3147e885627d82c4546b87c702debb90c1" 754 | "checksum futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e86f49cc0d92fe1b97a5980ec32d56208272cbb00f15044ea9e2799dde766fdf" 755 | "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" 756 | "checksum heapsize 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "54fab2624374e5137ae4df13bf32b0b269cb804df42d13a51221bbd431d1a237" 757 | "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" 758 | "checksum ident_case 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9826188e666f2ed92071d2dadef6edc430b11b158b5b2b3f4babbcc891eaaa" 759 | "checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7" 760 | "checksum js 0.1.4 (git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup)" = "" 761 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 762 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 763 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 764 | "checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b" 765 | "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" 766 | "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9" 767 | "checksum libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "87f737ad6cc6fd6eefe3d9dc5412f1573865bded441300904d2f42269e140f16" 768 | "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" 769 | "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" 770 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" 771 | "checksum mio 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0e8411968194c7b139e9105bc4ae7db0bae232af087147e72f0616ebf5fdb9cb" 772 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 773 | "checksum mozjs_sys 0.0.0 (git+https://github.com/fitzgen/mozjs?branch=smup-smup-smup)" = "" 774 | "checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" 775 | "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" 776 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" 777 | "checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d" 778 | "checksum ordermap 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "40cd62c688b4d8078c3f0eef70d7762f17c08bf52b225799ddcb4cf275dd1f19" 779 | "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 780 | "checksum petgraph 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "28d0872a49ce3ee71b345f4fa675afe394d9e0d077f8eeeb3d04081724065d67" 781 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 782 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 783 | "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0" 784 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 785 | "checksum regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ac6ab4e9218ade5b423358bbd2567d1617418403c7a512603630181813316322" 786 | "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" 787 | "checksum rent_to_own 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05a51ad2b1c5c710fa89e6b1631068dab84ed687bc6a5fe061ad65da3d0c25b2" 788 | "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" 789 | "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" 790 | "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 791 | "checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" 792 | "checksum state_machine_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "82540b6dceae526a6011db2dd2986470f93db498baf90939214e43a6e14ca071" 793 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 794 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 795 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 796 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 797 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 798 | "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" 799 | "checksum tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c87c27560184212c9dc45cd8f38623f37918248aad5b58fb65303b5d07a98c6e" 800 | "checksum tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "514aae203178929dbf03318ad7c683126672d4d96eccb77b29603d33c9e25743" 801 | "checksum tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" 802 | "checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946" 803 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 804 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 805 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 806 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 807 | "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" 808 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 809 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 810 | "checksum which 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4be6cfa54dab45266e98b5d7be2f8ce959ddd49abd141a05d52dce4b07f803bb" 811 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 812 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 813 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 814 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["The Starling Project Developers"] 3 | description = "The Starling JavaScript runtime." 4 | license = "Apache-2.0" 5 | name = "starling" 6 | version = "0.1.0" 7 | 8 | [[bin]] 9 | doc = false 10 | name = "starling" 11 | path = "src/bin/starling.rs" 12 | required-features = ["clap"] 13 | 14 | [build-dependencies] 15 | glob = "0.2.11" 16 | 17 | [dependencies] 18 | derive-error-chain = "0.11.0" 19 | error-chain = "0.11.0" 20 | futures = "0.1.15" 21 | futures-cpupool = "0.1.6" 22 | lazy_static = "0.2.8" 23 | num_cpus = "1.6.2" 24 | state_machine_future = "0.1.1" 25 | tokio-core = "0.1.9" 26 | tokio-timer = "0.1.2" 27 | void = "1.0.2" 28 | 29 | [dependencies.backtrace] 30 | features = ["cpp_demangle"] 31 | version = "0.3.2" 32 | 33 | [dependencies.clap] 34 | optional = true 35 | version = "2.26.0" 36 | 37 | [dependencies.js] 38 | branch = "smup-smup-smup" 39 | git = "https://github.com/fitzgen/mozjs" 40 | 41 | [features] 42 | debugmozjs = ["js/debugmozjs"] 43 | default = ["clap"] 44 | 45 | [patch.crates-io] 46 | # If you need to temporarily test Starling with a local fork of some upstream 47 | # crate, add that here. Use the form: 48 | # 49 | # = { path = "/path/to/local/checkout" } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] The Starling Project Developers 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `starling` 2 | 3 | [![Build Status](https://travis-ci.org/starlingjs/starling.png?branch=master)](https://travis-ci.org/starlingjs/starling) 4 | 5 | The Starling JavaScript runtime. 6 | 7 | **⚠️ VERY MUCH A WORK IN PROGRESS ⚠️** 8 | 9 | 10 | 11 | 12 | 13 | - [License](#license) 14 | - [Contributing](#contributing) 15 | 16 | 17 | 18 | ## License 19 | 20 | Licensed under the 21 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 22 | 23 | ## Contributing 24 | 25 | Want to help build Starling? Check out 26 | [CONTRIBUTING.md](https://github.com/starlingjs/starling/blob/master/CONTRIBUTING.md) 27 | for build instructions and hacking. 28 | 29 | Unless you explicitly state otherwise, any contribution intentionally submitted 30 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 31 | licensed as above, without any additional terms or conditions. 32 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate glob; 2 | 3 | fn main() { 4 | js_tests::generate(); 5 | } 6 | 7 | mod js_tests { 8 | use glob::glob; 9 | use std::env; 10 | use std::fs::File; 11 | use std::io::{BufRead, BufReader, Write}; 12 | use std::path::Path; 13 | 14 | pub fn generate() { 15 | match env::var("PROFILE") 16 | .expect("should have PROFILE env var") 17 | .as_ref() 18 | { 19 | "debug" => println!("cargo:rustc-env=STARLING_TEST_EXECUTABLE=./target/debug/starling"), 20 | "release" => { 21 | println!("cargo:rustc-env=STARLING_TEST_EXECUTABLE=./target/release/starling") 22 | } 23 | otherwise => panic!("Unknown $PROFILE: '{}'", otherwise), 24 | } 25 | 26 | let js_files = glob("./tests/js/**/*.js").expect("should create glob iterator OK"); 27 | 28 | let out_dir = env::var_os("OUT_DIR").expect("should have the OUT_DIR variable"); 29 | let generated_tests_path = Path::new(&out_dir).join("js_tests.rs"); 30 | 31 | let mut generated_tests = 32 | File::create(generated_tests_path).expect("should create generated tests file OK"); 33 | 34 | println!("cargo:rerun-if-changed=./tests/js/"); 35 | for path in js_files { 36 | let path = path.expect("should have permissions to read globbed files/dirs"); 37 | println!("cargo:rerun-if-changed={}", path.display()); 38 | 39 | let opts = TestOptions::read(&path); 40 | if opts.not_a_test { 41 | continue; 42 | } 43 | 44 | writeln!( 45 | &mut generated_tests, 46 | r###" 47 | #[test] 48 | fn {name}() {{ 49 | assert_starling_run_file( 50 | "{path}", 51 | {expect_success}, 52 | &{stdout_has:?}, 53 | &{stderr_has:?}, 54 | ); 55 | }} 56 | "###, 57 | name = path.display() 58 | .to_string() 59 | .chars() 60 | .map(|c| match c { 61 | 'a'...'z' | 'A'...'Z' | '0'...'9' => c, 62 | _ => '_', 63 | }) 64 | .collect::(), 65 | path = path.display(), 66 | expect_success = !opts.expect_fail, 67 | stdout_has = opts.stdout_has, 68 | stderr_has = opts.stderr_has, 69 | ).expect("should write to generated js tests file OK"); 70 | } 71 | } 72 | 73 | #[derive(Default)] 74 | struct TestOptions { 75 | //# starling-fail 76 | expect_fail: bool, 77 | 78 | //# starling-stdout-has: ... 79 | stdout_has: Vec, 80 | 81 | //# starling-stderr-has: ... 82 | stderr_has: Vec, 83 | 84 | //# starling-not-a-test 85 | not_a_test: bool, 86 | } 87 | 88 | impl TestOptions { 89 | fn read>(path: P) -> Self { 90 | let mut opts = Self::default(); 91 | 92 | let file = File::open(path.as_ref()).expect("should open JS file"); 93 | let file = BufReader::new(file); 94 | 95 | for line in file.lines() { 96 | let line = line.expect("should read a line from the JS file"); 97 | if line.starts_with("//# starling") { 98 | if line == "//# starling-fail" { 99 | opts.expect_fail = true; 100 | } else if line.starts_with("//# starling-stdout-has:") { 101 | let expected: String = line.chars() 102 | .skip("//# starling-stdout-has:".len()) 103 | .collect(); 104 | opts.stdout_has.push(expected.trim().into()); 105 | } else if line.starts_with("//# starling-stderr-has:") { 106 | let expected: String = line.chars() 107 | .skip("//# starling-stderr-has:".len()) 108 | .collect(); 109 | opts.stderr_has.push(expected.trim().into()); 110 | } else if line.starts_with("//# starling-not-a-test") { 111 | opts.not_a_test = true; 112 | break; 113 | } else { 114 | panic!("Unknown pragma: '{}' in {}", line, path.as_ref().display()); 115 | } 116 | } else { 117 | break; 118 | } 119 | } 120 | 121 | opts 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /ci/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # We always want backtraces for everything. 4 | export RUST_BACKTRACE=1 5 | 6 | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 7 | brew install autoconf@2.13 ccache llvm@3.9 yasm 8 | export LIBCLANG_PATH=$(find /usr/local/Cellar/llvm -type f -name libclang.dylib | head -n 1) 9 | export LIBCLANG_PATH=$(dirname $LIBCLANG_PATH) 10 | elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 11 | # export CC=clang-3.9 12 | # export CXX=clang++-3.9 13 | export CC=gcc-6 14 | export CXX=g++-6 15 | export LIBCLANG_PATH=/usr/lib/llvm-3.9/lib 16 | else 17 | echo "Error: unknown \$TRAVIS_OS_NAME: $TRAVIS_OS_NAME" 18 | exit 1 19 | fi 20 | 21 | ccache -c 22 | export CCACHE=$(which ccache) 23 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | # We seem to be OOMing with more build jobs. 6 | export CARGO_BUILD_JOBS=6 7 | 8 | cargo build $PROFILE $FEATURES 9 | cargo test $PROFILE $FEATURES 10 | 11 | if [[ "$PROFILE" == "--release" && "$FEATURES" == "" ]]; then 12 | cargo bench 13 | fi 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | closure_block_indent_threshold = 0 2 | -------------------------------------------------------------------------------- /src/bin/starling.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate starling; 3 | 4 | use clap::{App, Arg}; 5 | use std::process; 6 | 7 | /// Parse the given CLI arguments into a `starling::Options` configuration 8 | /// object. 9 | /// 10 | /// If argument parsing fails, then a usage string is printed, and the process 11 | /// is exited with 1. 12 | fn parse_cli_args() -> starling::Options { 13 | let matches = App::new(env!("CARGO_PKG_NAME")) 14 | .version(env!("CARGO_PKG_VERSION")) 15 | .author(env!("CARGO_PKG_AUTHORS")) 16 | .about(env!("CARGO_PKG_DESCRIPTION")) 17 | .arg( 18 | Arg::with_name("file") 19 | .required(true) 20 | .help("The JavaScript file to evaluate as the main task."), 21 | ) 22 | .get_matches(); 23 | 24 | let opts = starling::Options::new(matches.value_of("file").unwrap()); 25 | 26 | opts 27 | } 28 | 29 | fn main() { 30 | let opts = parse_cli_args(); 31 | if let Err(e) = opts.run() { 32 | eprintln!("error: {}", e); 33 | process::exit(1); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use futures::sync::mpsc; 2 | use gc_roots::GcRoot; 3 | use js::{jsapi, jsval}; 4 | use js::conversions::{ConversionResult, FromJSValConvertible}; 5 | use std::ffi; 6 | use std::fmt; 7 | use std::io; 8 | use std::ptr; 9 | 10 | /// The kind of error that occurred. 11 | #[derive(Debug, ErrorChain)] 12 | pub enum ErrorKind { 13 | /// Some other kind of miscellaneous error, described in the given string. 14 | Msg(String), 15 | 16 | /// An IO error. 17 | #[error_chain(foreign)] 18 | Io(io::Error), 19 | 20 | /// Tried to send a value on a channel when the receiving half was already 21 | /// dropped. 22 | #[error_chain(foreign)] 23 | SendError(mpsc::SendError<()>), 24 | 25 | /// Could not create a JavaScript runtime. 26 | #[error_chain(custom)] 27 | #[error_chain(description = r#"|| "Could not create a JavaScript Runtime""#)] 28 | #[error_chain(display = r#"|| write!(f, "Could not create a JavaScript Runtime")"#)] 29 | CouldNotCreateJavaScriptRuntime, 30 | 31 | /// Could not read a value from a channel. 32 | #[error_chain(custom)] 33 | #[error_chain(description = r#"|| "Could not read a value from a channel""#)] 34 | #[error_chain(display = r#"|| write!(f, "Could not read a value from a channel")"#)] 35 | CouldNotReadValueFromChannel, 36 | 37 | /// There was an exception in JavaScript code. 38 | #[error_chain(custom)] 39 | #[error_chain(description = r#"|_| "JavaScript exception""#)] 40 | #[error_chain(display = r#"|e| write!(f, "{}", e)"#)] 41 | JavaScriptException(JsException), 42 | 43 | /// There were one or more unhandled, rejected JavaScript promises. 44 | #[error_chain(custom)] 45 | #[error_chain(description = r#"|_| "Unhandled, rejected JavaScript promise""#)] 46 | #[error_chain(display = r#"|r| write!(f, "{}", r)"#)] 47 | JavaScriptUnhandledRejectedPromise(UnhandledRejectedPromises), 48 | 49 | /// The JavaScript `Promise` that was going to settle this future was 50 | /// reclaimed by the garbage collector without having been resolved or 51 | /// rejected. 52 | #[error_chain(custom)] 53 | #[error_chain(description = r#"|| "JavaScript Promise collected without settling""#)] 54 | #[error_chain(display = r#"|| write!(f, "JavaScript Promise collected without settling")"#)] 55 | JavaScriptPromiseCollectedWithoutSettling, 56 | 57 | /// There was an uncatchable JavaScript exception. This typically means that 58 | /// there was an OOM inside JSAPI code. 59 | #[error_chain(custom)] 60 | #[error_chain(description = r#"|| "There was an uncatchable JavaScript exception""#)] 61 | #[error_chain(display = r#"|| write!(f, "There was an uncatchable JavaScript exception")"#)] 62 | UncatchableJavaScriptException, 63 | } 64 | 65 | impl Clone for Error { 66 | fn clone(&self) -> Self { 67 | self.to_string().into() 68 | } 69 | } 70 | 71 | impl From for Error { 72 | fn from(rejected: UnhandledRejectedPromises) -> Error { 73 | ErrorKind::JavaScriptUnhandledRejectedPromise(rejected).into() 74 | } 75 | } 76 | 77 | impl Error { 78 | /// Given that some JSAPI call returned `false`, construct an `Error` from 79 | /// its pending exception, or if there is no pending exception (and 80 | /// therefore an uncatchable exception such as OOM was thrown) create an 81 | /// `Error` with kind `ErrorKind::UncatchableJavaScriptException`. 82 | /// 83 | /// # Safety 84 | /// 85 | /// The `cx` pointer must point to a valid `JSContext`. 86 | #[inline] 87 | pub unsafe fn from_cx(cx: *mut jsapi::JSContext) -> Error { 88 | Error::take_pending(cx).unwrap_or_else(|| ErrorKind::UncatchableJavaScriptException.into()) 89 | } 90 | } 91 | 92 | /// A trait for structured error types that can be constructed from a pending 93 | /// JSAPI exception. 94 | /// 95 | // TODO: Should we move this into mozjs? 96 | pub trait FromPendingJsapiException 97 | : fmt::Debug + FromJSValConvertible { 98 | /// Construct `Self` from the given JS value. 99 | /// 100 | /// If the `FromJSValConvertible` implementation for `Self` can fail, then 101 | /// override this default implementation so that it never fails. 102 | unsafe fn infallible_from_jsval( 103 | cx: *mut jsapi::JSContext, 104 | val: jsapi::JS::HandleValue, 105 | ) -> Self { 106 | match Self::from_jsval(cx, val, ()) { 107 | Ok(ConversionResult::Success(v)) => v, 108 | otherwise => panic!("infallible_from_jsval: {:?}", otherwise), 109 | } 110 | } 111 | 112 | /// Given a `cx` if it has a pending expection, take it and construct a 113 | /// `Self`. Otherwise, return `None`. 114 | unsafe fn take_pending(cx: *mut jsapi::JSContext) -> Option { 115 | if jsapi::JS_IsExceptionPending(cx) { 116 | rooted!(in(cx) let mut val = jsval::UndefinedValue()); 117 | assert!(jsapi::JS_GetPendingException(cx, val.handle_mut())); 118 | jsapi::JS_ClearPendingException(cx); 119 | Some(Self::infallible_from_jsval(cx, val.handle())) 120 | } else { 121 | None 122 | } 123 | } 124 | } 125 | 126 | type CResult = ::std::result::Result, ()>; 127 | 128 | impl FromJSValConvertible for Error { 129 | type Config = (); 130 | 131 | #[inline] 132 | unsafe fn from_jsval( 133 | cx: *mut jsapi::JSContext, 134 | val: jsapi::JS::HandleValue, 135 | _: (), 136 | ) -> CResult { 137 | Ok(ErrorKind::from_jsval(cx, val, ())?.map(|ek| ek.into())) 138 | } 139 | } 140 | 141 | impl FromPendingJsapiException for Error {} 142 | 143 | impl FromJSValConvertible for ErrorKind { 144 | type Config = (); 145 | 146 | #[inline] 147 | unsafe fn from_jsval( 148 | cx: *mut jsapi::JSContext, 149 | val: jsapi::JS::HandleValue, 150 | _: (), 151 | ) -> CResult { 152 | Ok(JsException::from_jsval(cx, val, ())?.map(ErrorKind::JavaScriptException)) 153 | } 154 | } 155 | 156 | impl FromPendingJsapiException for ErrorKind {} 157 | 158 | /// An exception that was thrown in JavaScript, or a promise was rejected with. 159 | #[derive(Debug, Clone)] 160 | pub enum JsException { 161 | /// The value thrown or rejected was not an `Error` object, so we 162 | /// stringified it into this value. 163 | Stringified(String), 164 | 165 | /// The value thrown or rejected was an `Error` object. 166 | Error { 167 | /// The error message. 168 | message: String, 169 | /// The JavaScript filename, if any. 170 | filename: Option, 171 | /// The line number the error originated on. 172 | line: u32, 173 | /// The column number the error originated on. 174 | column: u32, 175 | /// The JavaScript stack when the error was created, if any. 176 | stack: Option, 177 | }, 178 | } 179 | 180 | impl fmt::Display for JsException { 181 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 182 | match *self { 183 | JsException::Stringified(ref s) => write!(f, "{}", s), 184 | JsException::Error { 185 | ref message, 186 | ref filename, 187 | line, 188 | column, 189 | ref stack, 190 | } => { 191 | if let Some(ref filename) = *filename { 192 | write!(f, "{}:", filename)?; 193 | } 194 | 195 | write!(f, "{}:{}: {}", line, column, message)?; 196 | 197 | if let Some(ref stack) = *stack { 198 | write!(f, "\n\nStack:\n{}", stack)?; 199 | } 200 | 201 | Ok(()) 202 | } 203 | } 204 | } 205 | } 206 | 207 | impl FromJSValConvertible for JsException { 208 | type Config = (); 209 | 210 | unsafe fn from_jsval( 211 | cx: *mut jsapi::JSContext, 212 | val: jsapi::JS::HandleValue, 213 | _: (), 214 | ) -> CResult { 215 | // First try and convert the value into a JSErrorReport (aka some kind 216 | // of `Error` or `TypeError` etc.) If this fails, we'll just stringify 217 | // the value and use that as the error. 218 | rooted!(in(cx) let mut obj = ptr::null_mut()); 219 | let report = if val.is_object() { 220 | obj.set(val.to_object()); 221 | jsapi::JS_ErrorFromException(cx, obj.handle()) 222 | } else { 223 | ptr::null_mut() 224 | }; 225 | if report.is_null() { 226 | let stringified = match String::from_jsval(cx, val, ()) { 227 | Ok(ConversionResult::Success(s)) => s, 228 | Ok(ConversionResult::Failure(why)) => { 229 | format!("", why) 230 | } 231 | Err(()) => "".into(), 232 | }; 233 | debug_assert!(!jsapi::JS_IsExceptionPending(cx)); 234 | return Ok(ConversionResult::Success(JsException::Stringified( 235 | stringified, 236 | ))); 237 | } 238 | 239 | // Ok, we have an error report. Pull out all the metadata we can get 240 | // from it: filename, line, column, etc. 241 | 242 | let filename = (*report)._base.filename; 243 | let filename = if !filename.is_null() { 244 | Some(ffi::CStr::from_ptr(filename).to_string_lossy().to_string()) 245 | } else { 246 | None 247 | }; 248 | 249 | let line = (*report)._base.lineno; 250 | let column = (*report)._base.column; 251 | 252 | let message = (*report)._base.message_.data_; 253 | let message = ffi::CStr::from_ptr(message).to_string_lossy().to_string(); 254 | 255 | debug_assert!(!obj.is_null()); 256 | rooted!(in(cx) let stack = jsapi::ExceptionStackOrNull(obj.handle())); 257 | let stack = if stack.is_null() { 258 | None 259 | } else { 260 | rooted!(in(cx) let mut stack_string = ptr::null_mut()); 261 | assert!(jsapi::JS::BuildStackString( 262 | cx, 263 | stack.handle(), 264 | stack_string.handle_mut(), 265 | 0, 266 | jsapi::js::StackFormat::Default, 267 | )); 268 | rooted!(in(cx) let stack_string_val = jsval::StringValue( 269 | stack_string.get().as_ref().unwrap() 270 | )); 271 | match String::from_jsval(cx, stack_string_val.handle(), ()) { 272 | Ok(ConversionResult::Success(s)) => Some(s), 273 | _ => None, 274 | } 275 | }; 276 | 277 | debug_assert!(!jsapi::JS_IsExceptionPending(cx)); 278 | Ok(ConversionResult::Success(JsException::Error { 279 | message, 280 | filename, 281 | line, 282 | column, 283 | stack, 284 | })) 285 | } 286 | } 287 | 288 | impl FromPendingJsapiException for JsException {} 289 | 290 | /// A set of values that promises were rejected with and weren't handled. 291 | #[derive(Debug, Clone)] 292 | pub struct UnhandledRejectedPromises(Vec); 293 | 294 | impl UnhandledRejectedPromises { 295 | /// Construct an `UnhandledRejectedPromises` from the given list of 296 | /// unhandled rejected promises. 297 | pub(crate) unsafe fn from_promises( 298 | cx: *mut jsapi::JSContext, 299 | promises: I, 300 | ) -> UnhandledRejectedPromises 301 | where 302 | I: IntoIterator>, 303 | { 304 | let mut exceptions = vec![]; 305 | 306 | for p in promises { 307 | rooted!(in(cx) let p = p.raw()); 308 | debug_assert!(!p.is_null()); 309 | debug_assert!(jsapi::JS::IsPromiseObject(p.handle())); 310 | debug_assert_eq!( 311 | jsapi::JS::GetPromiseState(p.handle()), 312 | jsapi::JS::PromiseState::Rejected 313 | ); 314 | 315 | rooted!(in(cx) let mut val = jsapi::JS::GetPromiseResult(p.handle())); 316 | exceptions.push(JsException::infallible_from_jsval(cx, val.handle())); 317 | } 318 | 319 | debug_assert!(!exceptions.is_empty()); 320 | UnhandledRejectedPromises(exceptions) 321 | } 322 | } 323 | 324 | impl fmt::Display for UnhandledRejectedPromises { 325 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 326 | debug_assert!(!self.0.is_empty()); 327 | writeln!(f, "{} unhandled rejected promise(s):", self.0.len())?; 328 | 329 | for (i, rejected) in self.0.iter().enumerate() { 330 | let header = format!(" #{} ", i + 1); 331 | writeln!(f, "{:─^80}", header)?; 332 | writeln!(f, "{}", rejected)?; 333 | } 334 | Ok(()) 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/future_ext.rs: -------------------------------------------------------------------------------- 1 | use futures::{Async, Future, Poll}; 2 | use std::fmt::Debug; 3 | use std::marker::PhantomData; 4 | 5 | /// Wrap the given value into an `Ok(Async::Ready(...))`. 6 | #[inline] 7 | pub(crate) fn ready(t: T) -> Poll 8 | where 9 | U: From, 10 | { 11 | Ok(Async::Ready(t.into())) 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub(crate) struct OrDefault(F, PhantomData); 16 | 17 | impl Future for OrDefault 18 | where 19 | F: Future, 20 | F::Item: Default, 21 | { 22 | type Item = F::Item; 23 | type Error = E; 24 | 25 | fn poll(&mut self) -> Result, Self::Error> { 26 | match self.0.poll() { 27 | Ok(x) => Ok(x), 28 | Err(_) => Ok(Async::Ready(Self::Item::default())), 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Clone)] 34 | pub(crate) struct IgnoreResults(F, PhantomData); 35 | 36 | impl Future for IgnoreResults 37 | where 38 | F: Future, 39 | { 40 | type Item = (); 41 | type Error = E; 42 | 43 | fn poll(&mut self) -> Result, Self::Error> { 44 | match self.0.poll() { 45 | Ok(Async::NotReady) => Ok(Async::NotReady), 46 | Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), 47 | } 48 | } 49 | } 50 | 51 | #[derive(Debug, Clone)] 52 | pub(crate) struct Unwrap(F, PhantomData); 53 | 54 | impl Future for Unwrap 55 | where 56 | F: Future, 57 | F::Error: Debug, 58 | { 59 | type Item = F::Item; 60 | type Error = E; 61 | 62 | fn poll(&mut self) -> Result, Self::Error> { 63 | Ok(self.0.poll().unwrap()) 64 | } 65 | } 66 | 67 | #[derive(Debug, Clone)] 68 | pub(crate) struct Expect<'a, F, E>(F, &'a str, PhantomData); 69 | 70 | impl<'a, F, E> Future for Expect<'a, F, E> 71 | where 72 | F: Future, 73 | F::Error: Debug, 74 | { 75 | type Item = F::Item; 76 | type Error = E; 77 | 78 | fn poll(&mut self) -> Result, Self::Error> { 79 | Ok(self.0.poll().expect(self.1)) 80 | } 81 | } 82 | 83 | /// Extension methods for `Future`. 84 | pub(crate) trait FutureExt: Future { 85 | /// Get a mutable reference to this `Future` to use as a `Future` without 86 | /// taking ownership. 87 | fn by_ref(&mut self) -> &mut Self { 88 | self 89 | } 90 | 91 | /// If the future results in an error, instead return the default 92 | /// `Self::Item` value. 93 | fn or_default(self) -> OrDefault 94 | where 95 | Self: Sized, 96 | Self::Item: Default, 97 | { 98 | OrDefault(self, PhantomData) 99 | } 100 | 101 | /// Ignore the results (both `Ok` and `Err`) of this future. Just drive it 102 | /// to completion. 103 | fn ignore_results(self) -> IgnoreResults 104 | where 105 | Self: Sized, 106 | { 107 | IgnoreResults(self, PhantomData) 108 | } 109 | 110 | /// Panic if this future results in an error. Like `Result::unwrap`. 111 | fn unwrap(self) -> Unwrap 112 | where 113 | Self: Sized, 114 | Self::Error: Debug, 115 | { 116 | Unwrap(self, PhantomData) 117 | } 118 | 119 | /// Panic with the given message if this future results in an error. Like 120 | /// `Result::expect`. 121 | fn expect<'a, E>(self, msg: &'a str) -> Expect<'a, Self, E> 122 | where 123 | Self: Sized, 124 | Self::Error: Debug, 125 | { 126 | Expect(self, msg, PhantomData) 127 | } 128 | } 129 | 130 | impl FutureExt for T {} 131 | -------------------------------------------------------------------------------- /src/gc_roots.rs: -------------------------------------------------------------------------------- 1 | //! Keeping GC-things alive and preventing them from being collected. 2 | 3 | use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; 4 | use js::heap::{Heap, Trace}; 5 | use js::jsapi; 6 | use js::rust::GCMethods; 7 | use std::cell::{Ref, RefCell}; 8 | use std::rc::{Rc, Weak}; 9 | 10 | thread_local! { 11 | static TASK_GC_ROOTS: RefCell> = RefCell::new(None); 12 | } 13 | 14 | /// The set of persistent GC roots for a task. 15 | /// 16 | /// When we create `GcRoot`s, we register them here. This set is unconditionally 17 | /// marked during GC root marking for a JS task, so anything registered within 18 | /// will be kept alive and won't be reclaimed by the collector. 19 | /// 20 | /// The actual GC thing being rooted lives within the `GcRootInner`, which is 21 | /// reference counted. Whenever we trace this root set, we clear out any entries 22 | /// whose reference count has dropped to zero. 23 | #[derive(Default)] 24 | pub(crate) struct GcRootSet { 25 | objects: RefCell>>>, 26 | values: RefCell>>>, 27 | } 28 | 29 | unsafe impl Trace for GcRootSet { 30 | unsafe fn trace(&self, tracer: *mut jsapi::JSTracer) { 31 | let mut objects = self.objects.borrow_mut(); 32 | objects.retain(|entry| { 33 | if let Some(inner) = entry.upgrade() { 34 | let inner = inner.ptr.borrow(); 35 | let inner = inner 36 | .as_ref() 37 | .expect("should not have severed while GcRootSet is initialized"); 38 | inner.trace(tracer); 39 | true 40 | } else { 41 | // Don't keep this root -- no one is using it anymore. 42 | false 43 | } 44 | }); 45 | 46 | let mut values = self.values.borrow_mut(); 47 | values.retain(|entry| { 48 | if let Some(inner) = entry.upgrade() { 49 | let inner = inner.ptr.borrow(); 50 | let inner = inner 51 | .as_ref() 52 | .expect("should not have severed while GcRootSet is initialized"); 53 | inner.trace(tracer); 54 | true 55 | } else { 56 | false 57 | } 58 | }); 59 | } 60 | } 61 | 62 | impl GcRootSet { 63 | /// Gain immutable access to the current task's set of `Future2Promise` GC 64 | /// roots. 65 | /// 66 | /// This is _not_ reentrant! Do not call into JS or do anything that might 67 | /// GC within this function. 68 | #[inline] 69 | pub fn with_ref(mut f: F) -> T 70 | where 71 | F: FnMut(&Self) -> T, 72 | { 73 | TASK_GC_ROOTS.with(|roots| { 74 | let roots = roots.borrow(); 75 | let roots = roots.as_ref().expect( 76 | "Should only call `GcRootSet::with_ref` after \ 77 | `GcRootSet::initialize`", 78 | ); 79 | f(&*roots) 80 | }) 81 | } 82 | 83 | /// Gain mutable access to the current task's set of `Future2Promise` GC 84 | /// roots. 85 | /// 86 | /// This is _not_ reentrant! Do not call into JS or do anything that might 87 | /// GC within this function. 88 | #[inline] 89 | pub fn with_mut(mut f: F) -> T 90 | where 91 | F: FnMut(&mut Self) -> T, 92 | { 93 | TASK_GC_ROOTS.with(|roots| { 94 | let mut roots = roots.borrow_mut(); 95 | let roots = roots.as_mut().expect( 96 | "Should only call `GcRootSet::with_mut` after \ 97 | `GcRootSet::initialize`", 98 | ); 99 | f(&mut *roots) 100 | }) 101 | } 102 | 103 | /// Initialize the current JS task's GC roots set. 104 | pub fn initialize() { 105 | TASK_GC_ROOTS.with(|r| { 106 | let mut r = r.borrow_mut(); 107 | 108 | assert!( 109 | r.is_none(), 110 | "should never call `GcRootSet::initialize` more than \ 111 | once per task" 112 | ); 113 | 114 | *r = Some(Self::default()); 115 | }); 116 | } 117 | 118 | /// Destroy the current JS task's GC roots set. 119 | pub fn uninitialize() { 120 | TASK_GC_ROOTS.with(|r| { 121 | let mut r = r.borrow_mut(); 122 | 123 | { 124 | let roots = r.as_ref().expect( 125 | "should only call `GcRootSet::uninitialize` on initialized `GcRootSet`s", 126 | ); 127 | 128 | // You might be expecting an assertion that all of the 129 | // registered roots have a reference count of zero here. You 130 | // won't find it. 131 | // 132 | // We allow for registered roots that still have greater than 133 | // zero reference counts during shutdown. Their existence is 134 | // safe, but dereferencing them is most definitely not! We're 135 | // pushing dangerously close to UAF here, and the invariant is 136 | // somewhat subtle. 137 | // 138 | // So what do we gain by relaxing "no greater than zero 139 | // reference counts on roots at shutdown" into "no dereferencing 140 | // roots after shutdown"? It allows us to embed `GcRoot` 141 | // directly into futures that we spawn into the `tokio` event 142 | // loop. Spawning a future gives up ownership of it, and we 143 | // can't ensure that its lifetime is contained within the JS 144 | // task's lifetime. This makes the stronger invariant impossible 145 | // to maintain. Instead, we add the ability "sever" outstanding 146 | // GC roots by setting the `GcRootInner::ptr` to `None` and 147 | // dropping the wrapped `js::heap::Heap`. Note that we have to 148 | // make sure all `js::heap::Heap`s *do* maintain the stronger 149 | // invariant, because their write barriers are fired upon 150 | // destruction, which would lead to UAF if we didn't maintain 151 | // the stronger invariant for them. 152 | 153 | let objects = roots.objects.borrow(); 154 | for entry in &*objects { 155 | if let Some(entry) = entry.upgrade() { 156 | entry.sever(); 157 | } 158 | } 159 | 160 | let values = roots.values.borrow(); 161 | for entry in &*values { 162 | if let Some(entry) = entry.upgrade() { 163 | entry.sever(); 164 | } 165 | } 166 | } 167 | 168 | *r = None; 169 | }); 170 | } 171 | } 172 | 173 | /// A trait implemented by types that can be rooted by Starling. 174 | /// 175 | /// This should *not* be implemented by anything outside of the Starling crate! 176 | /// It is only exposed because it is a trait bound on other `pub` types. 177 | pub unsafe trait GcRootable: GCMethods + Copy { 178 | #[doc(hidden)] 179 | unsafe fn root(&GcRoot); 180 | } 181 | 182 | unsafe impl GcRootable for *mut jsapi::JSObject { 183 | #[inline] 184 | unsafe fn root(root: &GcRoot<*mut jsapi::JSObject>) { 185 | GcRootSet::with_mut(|roots| { 186 | let mut objects = roots.objects.borrow_mut(); 187 | objects.push(Rc::downgrade(&root.inner)); 188 | }); 189 | } 190 | } 191 | 192 | unsafe impl GcRootable for jsapi::JS::Value { 193 | #[inline] 194 | unsafe fn root(root: &GcRoot) { 195 | GcRootSet::with_mut(|roots| { 196 | let mut values = roots.values.borrow_mut(); 197 | values.push(Rc::downgrade(&root.inner)); 198 | }); 199 | } 200 | } 201 | 202 | #[derive(Debug)] 203 | struct GcRootInner { 204 | ptr: RefCell>>, 205 | } 206 | 207 | impl Default for GcRootInner 208 | where 209 | T: GcRootable, 210 | Heap: Default, 211 | { 212 | fn default() -> Self { 213 | GcRootInner { 214 | ptr: RefCell::new(Some(Heap::default())), 215 | } 216 | } 217 | } 218 | 219 | impl GcRootInner 220 | where 221 | T: GcRootable, 222 | { 223 | // See `GcRootSet::uninitialize` for details. 224 | fn sever(&self) { 225 | let mut ptr = self.ptr.borrow_mut(); 226 | *ptr = None; 227 | } 228 | } 229 | 230 | /// A persistent GC root. 231 | /// 232 | /// These should never be stored within some object that is managed by the GC, 233 | /// only for outside-the-gc-heap --> inside-the-gc-heap edges. 234 | /// 235 | /// Unlike the `js` crate's `rooted!(in(cx) ...)` and SpiderMonkey's 236 | /// `JS::Rooted` type, these do not need to maintain a strict LIFO ordering on 237 | /// their construction and destruction, and may persist in the heap. 238 | #[derive(Debug, Clone)] 239 | pub struct GcRoot { 240 | inner: Rc>, 241 | } 242 | 243 | impl Default for GcRoot 244 | where 245 | T: GcRootable, 246 | Heap: Default, 247 | { 248 | fn default() -> Self { 249 | GcRoot { 250 | inner: Rc::new(GcRootInner::default()), 251 | } 252 | } 253 | } 254 | 255 | impl GcRoot 256 | where 257 | T: GcRootable, 258 | Heap: Default, 259 | { 260 | /// Root the given GC thing, so that it is not reclaimed by the collector. 261 | #[inline] 262 | pub fn new(value: T) -> GcRoot { 263 | let root = GcRoot::default(); 264 | unsafe { 265 | GcRootable::root(&root); 266 | } 267 | 268 | root.borrow().set(value); 269 | root 270 | } 271 | 272 | /// Borrow the underlying GC thing. 273 | /// 274 | /// ## Panics 275 | /// 276 | /// Panics if the JS task has already shutdown. 277 | #[inline] 278 | pub fn borrow(&self) -> Ref> { 279 | Ref::map(self.inner.ptr.borrow(), |r| { 280 | r.as_ref().expect("should not be severed") 281 | }) 282 | } 283 | 284 | /// Get a raw pointer the underlying GC thing. 285 | /// 286 | /// This is mostly useful for translating from a `GcRoot` into a 287 | /// `JS::Rooted`: 288 | /// 289 | /// ```ignore 290 | /// // Given we have some `GcRoot`, 291 | /// let some_gc_root: GcRoot<*mut jsapi::JSObject> = ...; 292 | /// 293 | /// // We can convert it into a `JS::Rooted` so we can create `JS::Handle`s 294 | /// // and `JS::MutableHandle`s. 295 | /// rooted!(in(cx) let rooted = unsafe { some_gc_root.raw() }); 296 | /// ``` 297 | /// 298 | /// ## Panics 299 | /// 300 | /// Panics if the JS task has already shutdown. 301 | /// 302 | /// ## Unsafety 303 | /// 304 | /// SpiderMonkey has a moving GC, so there is no guarantee that the returned 305 | /// pointer will continue to point at the right thing. 306 | /// 307 | /// If this `GcRoot` is dropped, there is no guarantee that the pointer will 308 | /// still be live, and that it won't be reclaimed by the GC. 309 | pub unsafe fn raw(&self) -> T { 310 | self.borrow().get() 311 | } 312 | } 313 | 314 | impl FromJSValConvertible for GcRoot 315 | where 316 | T: FromJSValConvertible + GcRootable, 317 | Heap: Default, 318 | { 319 | type Config = T::Config; 320 | 321 | #[inline] 322 | unsafe fn from_jsval( 323 | cx: *mut jsapi::JSContext, 324 | val: jsapi::JS::HandleValue, 325 | config: Self::Config, 326 | ) -> Result, ()> { 327 | match T::from_jsval(cx, val, config) { 328 | Ok(cr) => Ok(cr.map(GcRoot::new)), 329 | Err(()) => Err(()), 330 | } 331 | } 332 | } 333 | 334 | impl ToJSValConvertible for GcRoot 335 | where 336 | T: ToJSValConvertible + GcRootable, 337 | Heap: Default, 338 | { 339 | #[inline] 340 | unsafe fn to_jsval(&self, cx: *mut jsapi::JSContext, rval: jsapi::JS::MutableHandleValue) { 341 | self.raw().to_jsval(cx, rval); 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/js_global.rs: -------------------------------------------------------------------------------- 1 | //! Definitions related to a task's JavaScript global object. 2 | 3 | use futures::Future; 4 | use js; 5 | use js::jsapi; 6 | use js::rust::Runtime as JsRuntime; 7 | use promise_future_glue::future_to_promise; 8 | use std::ffi; 9 | use std::os::raw; 10 | use std::path; 11 | use std::ptr; 12 | use std::time::Duration; 13 | use task; 14 | use tokio_timer::Timer; 15 | 16 | js_native_no_panic! { 17 | /// Print the given arguments to stdout for debugging. 18 | pub fn print( 19 | cx: *mut jsapi::JSContext, 20 | argc: raw::c_uint, 21 | vp: *mut jsapi::JS::Value 22 | ) -> bool { 23 | let args = unsafe { 24 | jsapi::JS::CallArgs::from_vp(vp, argc) 25 | }; 26 | 27 | for i in 0..argc { 28 | rooted!(in(cx) let s = unsafe { 29 | js::rust::ToString(cx, args.index(i)) 30 | }); 31 | if s.get().is_null() { 32 | return false; 33 | } 34 | 35 | let char_ptr = unsafe { 36 | jsapi::JS_EncodeStringToUTF8(cx, s.handle()) 37 | }; 38 | if char_ptr.is_null() { 39 | return false; 40 | } 41 | 42 | { 43 | let cstr = unsafe { 44 | ffi::CStr::from_ptr(char_ptr) 45 | }; 46 | let string = cstr.to_string_lossy(); 47 | print!("{}", string); 48 | } 49 | 50 | unsafe { 51 | jsapi::JS_free(cx, char_ptr as *mut _); 52 | } 53 | } 54 | 55 | println!(); 56 | true 57 | } 58 | } 59 | 60 | js_native! { 61 | fn timeout( 62 | millis: js::conversions::EnforceRange 63 | ) -> *mut js::jsapi::JSObject { 64 | let timer = Timer::default(); 65 | let duration = Duration::from_millis(millis.0); 66 | let future = timer.sleep(duration).map_err(|e| e.to_string()); 67 | let promise = future_to_promise(future); 68 | unsafe { 69 | promise.raw() 70 | } 71 | } 72 | } 73 | 74 | js_native! { 75 | fn spawn( 76 | js_file: String 77 | ) -> *mut js::jsapi::JSObject { 78 | let mut file = task::this_js_file(); 79 | file.pop(); 80 | file.push(path::Path::new(&js_file)); 81 | 82 | let starling = task::starling_handle(); 83 | match task::Task::spawn_child(starling, file) { 84 | Ok(child) => { 85 | unsafe { 86 | child.raw() 87 | } 88 | } 89 | Err(_) => { 90 | unsafe { 91 | js::glue::ReportError( 92 | JsRuntime::get(), 93 | b"failed to spawn new task\0" as *const u8 as *const _ 94 | ); 95 | } 96 | ptr::null_mut() 97 | } 98 | } 99 | } 100 | } 101 | 102 | lazy_static! { 103 | pub static ref GLOBAL_FUNCTIONS: [jsapi::JSFunctionSpec; 4] = [ 104 | jsapi::JSFunctionSpec::js_fn( 105 | b"print\0".as_ptr() as *const _, 106 | Some(print), 107 | 0, 108 | 0 109 | ), 110 | jsapi::JSFunctionSpec::js_fn( 111 | b"timeout\0".as_ptr() as *const _, 112 | Some(timeout::js_native), 113 | 1, 114 | 0 115 | ), 116 | jsapi::JSFunctionSpec::js_fn( 117 | b"spawn\0".as_ptr() as *const _, 118 | Some(spawn::js_native), 119 | 1, 120 | 0 121 | ), 122 | jsapi::JSFunctionSpec::NULL 123 | ]; 124 | } 125 | -------------------------------------------------------------------------------- /src/js_native.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for exposing native functions to JavaScript. 2 | 3 | /// Wrap a `JSNative` function with a catch unwind to ensure we don't panic and 4 | /// unwind across language boundaries, which is undefined behavior. 5 | #[macro_export] 6 | macro_rules! js_native_no_panic { 7 | ( 8 | $( #[$attr:meta] )* 9 | pub fn $name:ident ( $( $arg_name:ident : $arg_ty:ty $(,)* )* ) -> bool { 10 | $( $body:tt )* 11 | } 12 | ) => { 13 | $( #[$attr] )* 14 | pub extern "C" fn $name( $( $arg_name : $arg_ty , )* ) -> bool { 15 | match ::std::panic::catch_unwind(move || { 16 | $( $body )* 17 | }) { 18 | Ok(b) => b, 19 | Err(_) => { 20 | eprintln!( 21 | concat!( 22 | "error: JSNative '", 23 | stringify!($name), 24 | "' panicked: must not panic across FFI boundaries!" 25 | ) 26 | ); 27 | false 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | macro_rules! len_idents { 35 | () => { 36 | 0 37 | }; 38 | ( $x:ident $( $xs:ident )* ) => { 39 | 1 + len_idents!( $( $xs )* ) 40 | } 41 | } 42 | 43 | /// Define a native Rust function that can be exposed to JS as a `JSNative`. 44 | /// 45 | /// All parameter types must implement the 46 | /// `js::conversions::FromJSValConvertible` trait. 47 | /// 48 | /// The return type must implement `js::conversions::ToJSValConvertible`. 49 | /// 50 | /// The resulting `JSNative` function will be `$name::js_native`, and will 51 | /// automatically perform conversion of parameter and return types. 52 | #[macro_export] 53 | macro_rules! js_native { 54 | ( 55 | $( #[$attr:meta] )* 56 | fn $name:ident ( $( $arg_name:ident : $arg_ty:ty $(,)* )* ) -> $ret_ty:ty { 57 | $( $body:tt )* 58 | } 59 | ) => { 60 | fn $name ( $( $arg_name : $arg_ty , )* ) -> $ret_ty { 61 | $( $body )* 62 | } 63 | 64 | mod $name { 65 | use js; 66 | use std::os::raw; 67 | 68 | js_native_no_panic! { 69 | pub fn js_native( 70 | cx: *mut js::jsapi::JSContext, 71 | argc: raw::c_uint, 72 | vp: *mut js::jsapi::JS::Value 73 | ) -> bool { 74 | 75 | // Check that the required number of arguments are passed in. 76 | 77 | let num_required_args = len_idents!( $( $arg_name )* ); 78 | if argc < num_required_args { 79 | unsafe { 80 | js::glue::ReportError( 81 | cx, 82 | concat!( 83 | "Not enough arguments to `", 84 | stringify!($name), 85 | "`" 86 | ).as_ptr() as *const _ 87 | ); 88 | } 89 | return false; 90 | } 91 | 92 | // Convert each argument into the expected Rust type. 93 | 94 | let args = unsafe { 95 | js::jsapi::JS::CallArgs::from_vp(vp, argc) 96 | }; 97 | 98 | let mut i = 0; 99 | $( 100 | let $arg_name = args.index({ 101 | assert!(i < argc); 102 | let j = i; 103 | i += 1; 104 | j 105 | }); 106 | let $arg_name = match unsafe { 107 | <$arg_ty as js::conversions::FromJSValConvertible>::from_jsval( 108 | cx, 109 | $arg_name, 110 | () 111 | ) 112 | } { 113 | Err(()) => { 114 | debug_assert!(unsafe { 115 | js::jsapi::JS_IsExceptionPending(cx) 116 | }); 117 | return false; 118 | } 119 | Ok(js::conversions::ConversionResult::Failure(e)) => { 120 | unsafe { 121 | js::glue::ReportError(cx, e.as_ptr() as _); 122 | } 123 | return false; 124 | } 125 | Ok(js::conversions::ConversionResult::Success(x)) => x, 126 | }; 127 | )* 128 | let _ = i; 129 | 130 | // Call the function and then convert the return type into a 131 | // JS value. 132 | 133 | let ret = super::$name( $( $arg_name , )* ); 134 | unsafe { 135 | <$ret_ty as js::conversions::ToJSValConvertible>::to_jsval( 136 | &ret, 137 | cx, 138 | args.rval() 139 | ); 140 | } 141 | 142 | true 143 | } 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The Starling JavaScript runtime. 2 | //! 3 | 4 | // `error_chain!` can recurse deeply 5 | #![recursion_limit = "1024"] 6 | #![deny(missing_docs)] 7 | #![deny(missing_debug_implementations)] 8 | // Annoying warning emitted from the `error_chain!` macro. 9 | #![allow(unused_doc_comment)] 10 | 11 | #[macro_use] 12 | extern crate derive_error_chain; 13 | #[macro_use] 14 | extern crate futures; 15 | extern crate futures_cpupool; 16 | #[macro_use] 17 | extern crate js; 18 | #[macro_use] 19 | extern crate lazy_static; 20 | extern crate num_cpus; 21 | #[macro_use] 22 | extern crate state_machine_future; 23 | extern crate tokio_core; 24 | extern crate tokio_timer; 25 | extern crate void; 26 | 27 | #[macro_use] 28 | pub mod js_native; 29 | 30 | mod error; 31 | mod future_ext; 32 | pub mod gc_roots; 33 | pub(crate) mod js_global; 34 | pub mod promise_future_glue; 35 | pub(crate) mod promise_tracker; 36 | pub(crate) mod task; 37 | 38 | pub use error::*; 39 | use futures::{Sink, Stream}; 40 | use futures::sync::mpsc; 41 | use futures_cpupool::CpuPool; 42 | use std::cmp; 43 | use std::collections::HashMap; 44 | use std::fmt; 45 | use std::mem; 46 | use std::path; 47 | use std::sync::Arc; 48 | use std::thread; 49 | 50 | /// Configuration options for building a Starling event loop. 51 | /// 52 | /// ``` 53 | /// extern crate starling; 54 | /// 55 | /// # fn foo() -> starling::Result<()> { 56 | /// // Construct a new `Options` builder, providing the file containing 57 | /// // the main JavaScript task. 58 | /// starling::Options::new("path/to/main.js") 59 | /// // Finish configuring the `Options` builder and run the event 60 | /// // loop! 61 | /// .run()?; 62 | /// # Ok(()) 63 | /// # } 64 | /// ``` 65 | #[derive(Clone, Debug)] 66 | pub struct Options { 67 | main: path::PathBuf, 68 | sync_io_pool_threads: usize, 69 | cpu_pool_threads: usize, 70 | channel_buffer_size: usize, 71 | } 72 | 73 | const DEFAULT_SYNC_IO_POOL_THREADS: usize = 8; 74 | const DEFAULT_CHANNEL_BUFFER_SIZE: usize = 4096; 75 | 76 | impl Options { 77 | /// Construct a new `Options` object for configuring the Starling event 78 | /// loop. 79 | /// 80 | /// The given `main` JavaScript file will be evaluated as the main task. 81 | pub fn new

(main: P) -> Options 82 | where 83 | P: Into, 84 | { 85 | Options { 86 | main: main.into(), 87 | sync_io_pool_threads: DEFAULT_SYNC_IO_POOL_THREADS, 88 | cpu_pool_threads: num_cpus::get(), 89 | channel_buffer_size: DEFAULT_CHANNEL_BUFFER_SIZE, 90 | } 91 | } 92 | 93 | /// Configure the number of threads to reserve for the synchronous IO pool. 94 | /// 95 | /// The synchronous IO pool is a collection of threads for adapting 96 | /// synchronous IO libraries into the (otherwise completely asynchronous) 97 | /// Starling system. 98 | /// 99 | /// ### Panics 100 | /// 101 | /// Panics if `threads` is 0. 102 | pub fn sync_io_pool_threads(mut self, threads: usize) -> Self { 103 | assert!(threads > 0); 104 | self.sync_io_pool_threads = threads; 105 | self 106 | } 107 | 108 | /// Configure the number of threads to reserve for the CPU pool. 109 | /// 110 | /// The CPU pool is a collection of worker threads for CPU-bound native Rust 111 | /// tasks. 112 | /// 113 | /// Defaults to the number of logical CPUs on the machine. 114 | /// 115 | /// ### Panics 116 | /// 117 | /// Panics if `threads` is 0. 118 | pub fn cpu_pool_threads(mut self, threads: usize) -> Self { 119 | assert!(threads > 0); 120 | self.cpu_pool_threads = threads; 121 | self 122 | } 123 | 124 | /// Configure the size of mpsc buffers in the system. 125 | /// 126 | /// ### Panics 127 | /// 128 | /// Panics if `size` is 0. 129 | pub fn channel_buffer_size(mut self, size: usize) -> Self { 130 | assert!(size > 0); 131 | self.channel_buffer_size = size; 132 | self 133 | } 134 | 135 | /// Finish this `Options` builder and run the Starling event loop with its 136 | /// specified configuration. 137 | pub fn run(self) -> Result<()> { 138 | Starling::new(self)?.run() 139 | } 140 | } 141 | 142 | impl Options { 143 | // Get the number of `T`s that should be buffered in an mpsc channel for the 144 | // current configuration. 145 | fn buffer_capacity_for(&self) -> usize { 146 | let size_of_t = cmp::max(1, mem::size_of::()); 147 | let capacity = self.channel_buffer_size / size_of_t; 148 | cmp::max(1, capacity) 149 | } 150 | } 151 | 152 | /// The Starling supervisory thread. 153 | /// 154 | /// The supervisory thread doesn't do much other than supervise other threads: the IO 155 | /// event loop thread, various utility thread pools, and JavaScript task 156 | /// threads. Its primary responsibility is ensuring clean system shutdown and 157 | /// joining thread handles. 158 | pub(crate) struct Starling { 159 | handle: StarlingHandle, 160 | receiver: mpsc::Receiver, 161 | 162 | // Currently there is a 1:1 mapping between JS tasks and native 163 | // threads. That is expected to change in the future, hence the 164 | // distinction between `self.tasks` and `self.threads`. 165 | tasks: HashMap, 166 | threads: HashMap>, 167 | } 168 | 169 | impl fmt::Debug for Starling { 170 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 171 | write!(f, "Starling {{ .. }}") 172 | } 173 | } 174 | 175 | impl Starling { 176 | /// Construct a Starling system from the given options. 177 | pub fn new(opts: Options) -> Result { 178 | let tasks = HashMap::new(); 179 | let threads = HashMap::new(); 180 | 181 | let (sender, receiver) = mpsc::channel(opts.buffer_capacity_for::()); 182 | 183 | let Options { 184 | sync_io_pool_threads, 185 | cpu_pool_threads, 186 | .. 187 | } = opts; 188 | 189 | let handle = StarlingHandle { 190 | options: Arc::new(opts), 191 | sync_io_pool: CpuPool::new(sync_io_pool_threads), 192 | cpu_pool: CpuPool::new(cpu_pool_threads), 193 | sender, 194 | }; 195 | 196 | Ok(Starling { 197 | handle, 198 | receiver, 199 | tasks, 200 | threads, 201 | }) 202 | } 203 | 204 | /// Run the main Starling event loop with the specified options. 205 | pub fn run(mut self) -> Result<()> { 206 | let (main, thread) = 207 | task::Task::spawn_main(self.handle.clone(), self.handle.options().main.clone())?; 208 | 209 | self.tasks.insert(main.id(), main.clone()); 210 | self.threads.insert(thread.thread().id(), thread); 211 | 212 | for msg in self.receiver.wait() { 213 | let msg = msg.map_err(|_| Error::from_kind(ErrorKind::CouldNotReadValueFromChannel))?; 214 | 215 | match msg { 216 | StarlingMessage::TaskFinished(id) => { 217 | assert!(self.tasks.remove(&id).is_some()); 218 | 219 | let thread_id = id.into(); 220 | let join_handle = self.threads 221 | .remove(&thread_id) 222 | .expect("should have a thread join handle for the finished task"); 223 | join_handle 224 | .join() 225 | .expect("should join finished task's thread OK"); 226 | 227 | if id == main.id() { 228 | // TODO: notification of shutdown and joining other threads and things. 229 | return Ok(()); 230 | } 231 | } 232 | 233 | StarlingMessage::TaskErrored(id, error) => { 234 | assert!(self.tasks.remove(&id).is_some()); 235 | 236 | let thread_id = id.into(); 237 | let join_handle = self.threads 238 | .remove(&thread_id) 239 | .expect("should have a thread join handle for the errored task"); 240 | join_handle 241 | .join() 242 | .expect("should join errored task's thread OK"); 243 | 244 | if id == main.id() { 245 | // TODO: notification of shutdown and joining other threads and things. 246 | return Err(error); 247 | } 248 | } 249 | 250 | StarlingMessage::NewTask(task, join_handle) => { 251 | self.tasks.insert(task.id(), task); 252 | self.threads.insert(join_handle.thread().id(), join_handle); 253 | } 254 | } 255 | } 256 | 257 | Ok(()) 258 | } 259 | } 260 | 261 | /// Messages that threads can send to the Starling supervisory thread. 262 | /// 263 | /// This needs to be `pub` because it is used in a trait implementation; don't 264 | /// actually use it! 265 | #[derive(Debug)] 266 | #[doc(hidden)] 267 | pub enum StarlingMessage { 268 | /// The task on the given thread completed successfully. 269 | TaskFinished(task::TaskId), 270 | 271 | /// The task on the given thread failed with the given error. 272 | TaskErrored(task::TaskId, Error), 273 | 274 | /// A new child task was created. 275 | NewTask(task::TaskHandle, thread::JoinHandle<()>), 276 | } 277 | 278 | /// A handle to the Starling system. 279 | /// 280 | /// A `StarlingHandle` is a capability to schedule IO on the event loop, spawn 281 | /// work in one of the utility thread pools, and communicate with the Starling 282 | /// supervisory thread. Handles can be cloned and sent across threads, 283 | /// propagating these capabilities. 284 | #[derive(Clone)] 285 | pub(crate) struct StarlingHandle { 286 | options: Arc, 287 | sync_io_pool: CpuPool, 288 | cpu_pool: CpuPool, 289 | sender: mpsc::Sender, 290 | } 291 | 292 | impl fmt::Debug for StarlingHandle { 293 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 294 | write!(f, "StarlingHandle {{ .. }}") 295 | } 296 | } 297 | 298 | impl StarlingHandle { 299 | /// Get the `Options` that this Starling system was configured with. 300 | pub fn options(&self) -> &Arc { 301 | &self.options 302 | } 303 | 304 | /// Get a handle to the thread pool for adapting synchronous IO (perhaps 305 | /// from a library that wasn't written to be async) into the system. 306 | pub fn sync_io_pool(&self) -> &CpuPool { 307 | &self.sync_io_pool 308 | } 309 | 310 | /// Get a handle to the thread pool for performing CPU-bound native Rust 311 | /// tasks. 312 | pub fn cpu_pool(&self) -> &CpuPool { 313 | &self.cpu_pool 314 | } 315 | 316 | /// Send a message to the Starling supervisory thread. 317 | pub fn send(&self, msg: StarlingMessage) -> futures::sink::Send> { 318 | self.sender.clone().send(msg) 319 | } 320 | } 321 | 322 | #[cfg(test)] 323 | mod tests { 324 | use super::*; 325 | use task::{TaskHandle, TaskMessage}; 326 | 327 | fn assert_clone() {} 328 | fn assert_send() {} 329 | 330 | #[test] 331 | fn error_is_send() { 332 | assert_send::(); 333 | } 334 | 335 | #[test] 336 | fn options_is_send_clone() { 337 | assert_clone::(); 338 | assert_send::(); 339 | } 340 | 341 | #[test] 342 | fn starling_handle_is_send_clone() { 343 | assert_clone::(); 344 | assert_send::(); 345 | } 346 | 347 | #[test] 348 | fn task_handle_is_send_clone() { 349 | assert_clone::(); 350 | assert_send::(); 351 | } 352 | 353 | #[test] 354 | fn starling_message_is_send() { 355 | assert_send::(); 356 | } 357 | 358 | #[test] 359 | fn task_message_is_send() { 360 | assert_send::(); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/promise_future_glue.rs: -------------------------------------------------------------------------------- 1 | //! Gluing Rust's `Future` and JavaScript's `Promise` together. 2 | //! 3 | //! JavaScript's `Promise` and Rust's `Future` are two abstractions with the 4 | //! same goal: asynchronous programming with eventual values. Both `Promise`s 5 | //! and `Future`s represent a value that may or may not have been computed 6 | //! yet. However, the way that you interact with each of them is very different. 7 | //! 8 | //! JavaScript's `Promise` follows a completion-based model. You register 9 | //! callbacks on a promise, and the runtime invokes each of the registered 10 | //! callbacks (there may be many!) when the promise is resolved with a 11 | //! value. You build larger asynchronous computations by chaining promises; 12 | //! registering a callback with a promise returns a new promise of that 13 | //! callback's result. Dependencies between promises are managed by the runtime. 14 | //! 15 | //! Rust's `Future` follows a readiness-based model. You define a `poll` method 16 | //! that either returns the future's value if it is ready, or a sentinel that 17 | //! says the future's value is not ready yet. You build larger asynchronous 18 | //! computations by defining (or using existing) combinators that wrap some 19 | //! inner future (or futures). These combinators delegate polling to the inner 20 | //! future, and once the inner future's value is ready, perform their 21 | //! computation on the value, and return the transformed result. Dependencies 22 | //! between futures are managed by the futures themselves, while scheduling 23 | //! polling is the runtime's responsibility. 24 | //! 25 | //! To translate between futures and promises we take two different approaches 26 | //! for each direction, by necessity. 27 | //! 28 | //! To treat a Rust `Future` as a JavaScript `Promise`, we define a `Future` 29 | //! combinator called `Future2Promise`. It takes a fresh, un-resolved `Promise` 30 | //! object, and an inner future upon construction. It's `poll` method delegates 31 | //! to the inner future's `poll`, and once the inner future's value is ready, it 32 | //! resolves the promise with the ready value, and the JavaScript runtime 33 | //! ensures that the promise's registered callbacks are invoked appropriately. 34 | //! 35 | //! To treat a JavaScript `Promise` as a Rust `Future`, we register a callback 36 | //! to the promise that sends the resolution value over a one-shot 37 | //! channel. One-shot channels are split into their two halves: sender and 38 | //! receiver. The sender half moves into the callback, but the receiver half is 39 | //! a future, and it represents the future resolution value of the original 40 | //! promise. 41 | //! 42 | //! The final concern to address is that the runtime scheduling the polling of 43 | //! Rust `Future`s (`tokio`) still knows which futures to poll despite it only 44 | //! seeing half the picture now. I say "half the picture" because dependencies 45 | //! that would otherwise live fully within the futures ecosystem are now hidden 46 | //! in promises inside the JavaScript runtime. 47 | //! 48 | //! First, we must understand how `tokio` schedules polling. It is not busy 49 | //! spinning and calling `poll` continuously in a loop. `tokio` maintains a set 50 | //! of "root" futures. These are the futures passed to `Core::run` and 51 | //! `Handle::spawn` directly. When `tokio` polls a "root" future, that `poll` 52 | //! call will transitively reach down and call `poll` on "leaf" futures that 53 | //! wrap file descriptors and sockets and such things. It is these "leaf" 54 | //! futures' responsibilty to use OS APIs to trigger wake ups when new data is 55 | //! available on a socket or what have you, and then it is `tokio`'s 56 | //! responsibilty to map that wake up back to which "root" future it should poll 57 | //! again. If the "leaf" futures do not properly register to be woken up again, 58 | //! `tokio` will never poll that "root" future again, effectively dead locking 59 | //! it. 60 | //! 61 | //! So we must ensure that our `Promise`-backed futures will always be polled 62 | //! again by making sure that they have proper "leaf" futures. Luckily, the 63 | //! receiver half of a one-shot channel is such a "leaf" future that properly 64 | //! registers future wake ups. If instead, for example, we tried directly 65 | //! checking the promise's state in `poll` with JSAPI methods, we *wouldn't* 66 | //! register any wake ups, `tokio` would never `poll` the future again, and the 67 | //! future would dead lock. 68 | 69 | use super::{Error, ErrorKind}; 70 | use futures::{self, Async, Future, Poll, Select}; 71 | use futures::sync::oneshot; 72 | use future_ext::{ready, FutureExt}; 73 | use gc_roots::GcRoot; 74 | use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; 75 | use js::glue::ReportError; 76 | use js::jsapi; 77 | use js::jsval; 78 | use js::rust::Runtime as JsRuntime; 79 | use state_machine_future::RentToOwn; 80 | use std::marker::PhantomData; 81 | use std::os::raw; 82 | use std::ptr; 83 | use task; 84 | use void::Void; 85 | 86 | type GenericVoid = (Void, PhantomData); 87 | 88 | /// A future that resolves a promise with its inner future's value when ready. 89 | #[derive(StateMachineFuture)] 90 | #[allow(dead_code)] 91 | enum Future2Promise 92 | where 93 | F: Future, 94 | ::Item: ToJSValConvertible, 95 | ::Error: ToJSValConvertible, 96 | { 97 | /// Initially, we are waiting on the inner future to be ready or error. 98 | #[state_machine_future(start, transitions(Finished, NotifyingOfError))] 99 | WaitingOnInner { 100 | future: F, 101 | promise: GcRoot<*mut jsapi::JSObject>, 102 | }, 103 | 104 | /// If we encountered an error that needs to propagate, we must send it to 105 | /// the task. 106 | #[state_machine_future(transitions(Finished))] 107 | NotifyingOfError { 108 | notify: futures::sink::Send>, 109 | phantom: PhantomData, 110 | }, 111 | 112 | /// All done. 113 | #[state_machine_future(ready)] 114 | Finished(PhantomData), 115 | 116 | /// We explicitly handle all errors, so make `Future::Error` impossible to 117 | /// construct. 118 | #[state_machine_future(error)] 119 | Impossible(GenericVoid), 120 | } 121 | 122 | impl PollFuture2Promise for Future2Promise 123 | where 124 | F: Future, 125 | ::Item: ToJSValConvertible, 126 | ::Error: ToJSValConvertible, 127 | { 128 | fn poll_waiting_on_inner<'a>( 129 | waiting: &'a mut RentToOwn<'a, WaitingOnInner>, 130 | ) -> Poll, GenericVoid> { 131 | let error = match waiting.future.poll() { 132 | Ok(Async::NotReady) => { 133 | return Ok(Async::NotReady); 134 | } 135 | Ok(Async::Ready(t)) => { 136 | let cx = JsRuntime::get(); 137 | 138 | unsafe { 139 | rooted!(in(cx) let mut val = jsval::UndefinedValue()); 140 | t.to_jsval(cx, val.handle_mut()); 141 | 142 | rooted!(in(cx) let promise = waiting.promise.raw()); 143 | assert!(jsapi::JS::ResolvePromise( 144 | cx, 145 | promise.handle(), 146 | val.handle() 147 | )); 148 | 149 | if let Err(e) = task::drain_micro_task_queue() { 150 | e 151 | } else { 152 | return ready(Finished(PhantomData)); 153 | } 154 | } 155 | } 156 | Err(error) => { 157 | let cx = JsRuntime::get(); 158 | 159 | unsafe { 160 | rooted!(in(cx) let mut val = jsval::UndefinedValue()); 161 | error.to_jsval(cx, val.handle_mut()); 162 | 163 | rooted!(in(cx) let promise = waiting.promise.raw()); 164 | assert!(jsapi::JS::RejectPromise(cx, promise.handle(), val.handle())); 165 | 166 | if let Err(e) = task::drain_micro_task_queue() { 167 | e 168 | } else { 169 | return ready(Finished(PhantomData)); 170 | } 171 | } 172 | } 173 | }; 174 | 175 | let msg = task::TaskMessage::UnhandledRejectedPromise { error }; 176 | ready(NotifyingOfError { 177 | notify: task::this_task().send(msg), 178 | phantom: PhantomData, 179 | }) 180 | } 181 | 182 | fn poll_notifying_of_error<'a>( 183 | notification: &'a mut RentToOwn<'a, NotifyingOfError>, 184 | ) -> Poll, GenericVoid> { 185 | match notification.notify.poll() { 186 | Ok(Async::NotReady) => { 187 | return Ok(Async::NotReady); 188 | } 189 | // The only way we can get an error here is if we lost a 190 | // race between notifying the task of an error and the task 191 | // finishing. 192 | Err(_) | Ok(Async::Ready(_)) => ready(Finished(PhantomData)), 193 | } 194 | } 195 | } 196 | 197 | /// Convert a Rust `Future` into a JavaScript `Promise`. 198 | /// 199 | /// The `Future` is spawned onto the current thread's `tokio` event loop. 200 | pub fn future_to_promise(future: F) -> GcRoot<*mut jsapi::JSObject> 201 | where 202 | F: 'static + Future, 203 | T: 'static + ToJSValConvertible, 204 | E: 'static + ToJSValConvertible, 205 | { 206 | let cx = JsRuntime::get(); 207 | rooted!(in(cx) let executor = ptr::null_mut()); 208 | rooted!(in(cx) let proto = ptr::null_mut()); 209 | rooted!(in(cx) let promise = unsafe { 210 | jsapi::JS::NewPromiseObject( 211 | cx, 212 | executor.handle(), 213 | proto.handle() 214 | ) 215 | }); 216 | assert!(!promise.get().is_null()); 217 | let promise = GcRoot::new(promise.get()); 218 | let future = Future2Promise::start(future, promise.clone()).ignore_results(); 219 | task::event_loop().spawn(future); 220 | promise 221 | } 222 | 223 | const CLOSURE_SLOT: usize = 0; 224 | 225 | // JSNative that forwards the call `f`. 226 | unsafe extern "C" fn trampoline( 227 | cx: *mut jsapi::JSContext, 228 | argc: raw::c_uint, 229 | vp: *mut jsapi::JS::Value, 230 | ) -> bool 231 | where 232 | F: 'static + FnOnce(*mut jsapi::JSContext, &jsapi::JS::CallArgs) -> bool, 233 | { 234 | let args = jsapi::JS::CallArgs::from_vp(vp, argc); 235 | rooted!(in(cx) let callee = args.callee()); 236 | 237 | let private = jsapi::js::GetFunctionNativeReserved(callee.get(), CLOSURE_SLOT); 238 | let f = (*private).to_private() as *mut F; 239 | if f.is_null() { 240 | ReportError(cx, b"May only be called once\0".as_ptr() as *const _); 241 | return false; 242 | } 243 | 244 | let private = jsval::PrivateValue(ptr::null()); 245 | jsapi::js::SetFunctionNativeReserved(callee.get(), CLOSURE_SLOT, &private); 246 | 247 | let f = Box::from_raw(f); 248 | f(cx, &args) 249 | } 250 | 251 | /// This is unsafe because the resulting function object will _not_ trace `f`'s 252 | /// closed over values. Don't close over GC things! 253 | unsafe fn make_js_fn(f: F) -> GcRoot<*mut jsapi::JSObject> 254 | where 255 | F: 'static + FnOnce(*mut jsapi::JSContext, &jsapi::JS::CallArgs) -> bool, 256 | { 257 | let cx = JsRuntime::get(); 258 | 259 | rooted!(in(cx) let func = jsapi::js::NewFunctionWithReserved( 260 | cx, 261 | Some(trampoline::), 262 | 0, // nargs 263 | 0, // flags 264 | ptr::null_mut() // name 265 | )); 266 | assert!(!func.get().is_null()); 267 | 268 | let private = Box::new(f); 269 | let private = jsval::PrivateValue(Box::into_raw(private) as *const _); 270 | jsapi::js::SetFunctionNativeReserved(func.get() as *mut _, CLOSURE_SLOT, &private); 271 | 272 | GcRoot::new(func.get() as *mut jsapi::JSObject) 273 | } 274 | 275 | type ResultReceiver = oneshot::Receiver>>; 276 | 277 | /// A future of either a JavaScript promise's resolution `T` or rejection `E`. 278 | pub struct Promise2Future 279 | where 280 | T: 'static + FromJSValConvertible, 281 | E: 'static + FromJSValConvertible, 282 | { 283 | inner: Select, ResultReceiver>, 284 | } 285 | 286 | impl ::std::fmt::Debug for Promise2Future 287 | where 288 | T: 'static + FromJSValConvertible, 289 | E: 'static + FromJSValConvertible, 290 | { 291 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 292 | write!(f, "Promise2Future {{ .. }}") 293 | } 294 | } 295 | 296 | impl Future for Promise2Future 297 | where 298 | T: 'static + FromJSValConvertible, 299 | E: 'static + FromJSValConvertible, 300 | { 301 | type Item = Result; 302 | type Error = super::Error; 303 | 304 | fn poll(&mut self) -> Result, Self::Error> { 305 | match self.inner.poll() { 306 | Err((oneshot::Canceled, _)) => { 307 | Err(ErrorKind::JavaScriptPromiseCollectedWithoutSettling.into()) 308 | } 309 | Ok(Async::NotReady) => Ok(Async::NotReady), 310 | // One of the handlers was called, but then we encountered an error 311 | // converting the value from JS into Rust or something like that. 312 | Ok(Async::Ready((Err(e), _))) => Err(e), 313 | Ok(Async::Ready((Ok(result), _))) => Ok(Async::Ready(result)), 314 | } 315 | } 316 | } 317 | 318 | /// Convert the given JavaScript `Promise` object into a future. 319 | /// 320 | /// The resulting future is of either an `Ok(T)` if the promise gets resolved, 321 | /// or an `Err(E)` if the promise is rejected. 322 | /// 323 | /// Failure to convert the resolution or rejection JavaScript value into a `T` 324 | /// or `E` will cause the resulting future's `poll` to return an error. 325 | /// 326 | /// If the promise object is reclaimed by the garbage collector without being 327 | /// resolved or rejected, then the resulting future's `poll` will return an 328 | /// error of kind `ErrorKind::JavaScriptPromiseCollectedWithoutSettling`. 329 | pub fn promise_to_future(promise: &GcRoot<*mut jsapi::JSObject>) -> Promise2Future 330 | where 331 | T: 'static + FromJSValConvertible, 332 | E: 'static + FromJSValConvertible, 333 | { 334 | unsafe { 335 | let cx = JsRuntime::get(); 336 | 337 | let (resolve_sender, resolve_receiver) = oneshot::channel(); 338 | let on_resolve = make_js_fn(move |cx, args| { 339 | match T::from_jsval(cx, args.get(0), ()) { 340 | Err(()) => { 341 | let err = Error::from_cx(cx); 342 | let _ = resolve_sender.send(Err(err)); 343 | } 344 | Ok(ConversionResult::Failure(s)) => { 345 | let err = Err(ErrorKind::Msg(s.to_string()).into()); 346 | let _ = resolve_sender.send(err); 347 | } 348 | Ok(ConversionResult::Success(t)) => { 349 | let _ = resolve_sender.send(Ok(Ok(t))); 350 | } 351 | } 352 | true 353 | }); 354 | 355 | let (reject_sender, reject_receiver) = oneshot::channel(); 356 | let on_reject = make_js_fn(move |cx, args| { 357 | match E::from_jsval(cx, args.get(0), ()) { 358 | Err(()) => { 359 | let err = Error::from_cx(cx); 360 | let _ = reject_sender.send(Err(err)); 361 | } 362 | Ok(ConversionResult::Failure(s)) => { 363 | let err = Err(ErrorKind::Msg(s.to_string()).into()); 364 | let _ = reject_sender.send(err); 365 | } 366 | Ok(ConversionResult::Success(t)) => { 367 | let _ = reject_sender.send(Ok(Err(t))); 368 | } 369 | } 370 | true 371 | }); 372 | 373 | rooted!(in(cx) let promise = promise.raw()); 374 | rooted!(in(cx) let on_resolve = on_resolve.raw()); 375 | rooted!(in(cx) let on_reject = on_reject.raw()); 376 | assert!(jsapi::JS::AddPromiseReactions( 377 | cx, 378 | promise.handle(), 379 | on_resolve.handle(), 380 | on_reject.handle() 381 | )); 382 | 383 | Promise2Future { 384 | inner: resolve_receiver.select(reject_receiver), 385 | } 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /src/promise_tracker.rs: -------------------------------------------------------------------------------- 1 | //! Infrastructure for tracking unhandled rejected promises. 2 | 3 | use super::UnhandledRejectedPromises; 4 | use gc_roots::GcRoot; 5 | use js::jsapi; 6 | use js::rust::Runtime as JsRuntime; 7 | use std::cell::RefCell; 8 | use std::os; 9 | 10 | /// Keeps track of unhandled, rejected promises. 11 | #[derive(Debug, Default)] 12 | pub struct RejectedPromisesTracker { 13 | unhandled_rejected_promises: Vec>, 14 | } 15 | 16 | impl RejectedPromisesTracker { 17 | /// Register a promises tracker with the given runtime. 18 | pub fn register(runtime: &JsRuntime, tracker: &RefCell) { 19 | unsafe { 20 | let tracker: *const RefCell = tracker as _; 21 | let tracker = tracker as *mut os::raw::c_void; 22 | jsapi::JS::SetPromiseRejectionTrackerCallback( 23 | runtime.cx(), 24 | Some(Self::promise_rejection_callback), 25 | tracker, 26 | ); 27 | } 28 | } 29 | 30 | /// Clear the set of unhandled, rejected promises. 31 | pub fn clear(&mut self) { 32 | self.unhandled_rejected_promises.clear(); 33 | } 34 | 35 | /// Take this tracker's set of unhandled, rejected promises, leaving an 36 | /// empty set in its place. 37 | pub fn take(&mut self) -> Option { 38 | if self.unhandled_rejected_promises.is_empty() { 39 | return None; 40 | } 41 | 42 | let cx = JsRuntime::get(); 43 | let rejected = unsafe { 44 | let unhandled = self.unhandled_rejected_promises.drain(..); 45 | UnhandledRejectedPromises::from_promises(cx, unhandled) 46 | }; 47 | debug_assert!(self.unhandled_rejected_promises.is_empty()); 48 | Some(rejected) 49 | } 50 | 51 | /// The given rejected `promise` has been handled, so remove it from the 52 | /// unhandled set. 53 | fn handled(&mut self, promise: jsapi::JS::HandleObject) { 54 | let idx = self.unhandled_rejected_promises 55 | .iter() 56 | .position(|p| unsafe { p.raw() == promise.get() }); 57 | 58 | if let Some(idx) = idx { 59 | self.unhandled_rejected_promises.swap_remove(idx); 60 | } 61 | } 62 | 63 | /// The given rejected `promise` has not been handled, so add it to the 64 | /// rejected set if necessary. 65 | fn unhandled(&mut self, promise: jsapi::JS::HandleObject) { 66 | let idx = self.unhandled_rejected_promises 67 | .iter() 68 | .position(|p| unsafe { p.raw() == promise.get() }); 69 | 70 | if let None = idx { 71 | self.unhandled_rejected_promises 72 | .push(GcRoot::new(promise.get())); 73 | } 74 | } 75 | 76 | /// Raw JSAPI callback for observing rejected promise handling. 77 | unsafe extern "C" fn promise_rejection_callback( 78 | _cx: *mut jsapi::JSContext, 79 | promise: jsapi::JS::HandleObject, 80 | state: jsapi::PromiseRejectionHandlingState, 81 | data: *mut os::raw::c_void, 82 | ) { 83 | let tracker: *const RefCell = data as _; 84 | let tracker: &RefCell = tracker.as_ref().unwrap(); 85 | let mut tracker = tracker.borrow_mut(); 86 | 87 | if state == jsapi::PromiseRejectionHandlingState::Handled { 88 | tracker.handled(promise); 89 | } else { 90 | assert_eq!(state, jsapi::PromiseRejectionHandlingState::Unhandled); 91 | tracker.unhandled(promise); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/task.rs: -------------------------------------------------------------------------------- 1 | //! Tasks are lightweight, isolated, pre-emptively scheduled JavaScript 2 | //! execution threads. 3 | //! 4 | //! One of the most foundational properties of Starling is that no individual 5 | //! misbehaving JavaScript task can bring down the whole system. 6 | //! 7 | //! Tasks are **pre-emptively scheduled**. If one task goes into an infinite 8 | //! loop, or generally becomes CPU bound for a sustained period of time, other 9 | //! tasks in the system are not starved of resources, and system latency does 10 | //! not fall off a cliff. 11 | //! 12 | //! Tasks are **isolated** from each other and share no memory. They communicate 13 | //! by **message passing** over readable and writable streams. This means that 14 | //! if one task's local state is corrupted, it doesn't immediately infect all 15 | //! other tasks' states that are communicating with it. The corrupted task can 16 | //! be killed by its supervisor and then respawned with the last known good 17 | //! state or with a clean slate. 18 | //! 19 | //! Tasks are **lightweight**. In order to facilitate let-it-fail error handling 20 | //! coupled with supervision hierarchies, idle tasks have little memory 21 | //! overhead, and near no time overhead. Note that this is *aspirational* at the 22 | //! moment: there is [ongoing work in SpiderMonkey][ongoing] to fully unlock 23 | //! this. 24 | //! 25 | //! [ongoing]: https://bugzilla.mozilla.org/show_bug.cgi?id=1323066 26 | 27 | use super::{Error, ErrorKind, FromPendingJsapiException, Result, StarlingHandle, StarlingMessage}; 28 | use future_ext::{ready, FutureExt}; 29 | use futures::{self, Async, Future, Poll, Sink, Stream}; 30 | use futures::sync::mpsc; 31 | use futures::sync::oneshot; 32 | use futures_cpupool::CpuFuture; 33 | use gc_roots::{GcRoot, GcRootSet}; 34 | use js; 35 | use js::sc; 36 | use js::heap::Trace; 37 | use js::jsapi; 38 | use js::jsval; 39 | use js::rust::Runtime as JsRuntime; 40 | use js_global::GLOBAL_FUNCTIONS; 41 | use promise_future_glue::{future_to_promise, promise_to_future, Promise2Future}; 42 | use promise_tracker::RejectedPromisesTracker; 43 | use state_machine_future::RentToOwn; 44 | use std::cell::RefCell; 45 | use std::collections::HashMap; 46 | use std::fmt; 47 | use std::os; 48 | use std::path; 49 | use std::ptr; 50 | use std::thread; 51 | use tokio_core; 52 | use tokio_core::reactor::Core as TokioCore; 53 | use void::Void; 54 | 55 | /// A unique identifier for some `Task`. 56 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 57 | pub struct TaskId(thread::ThreadId); 58 | 59 | impl From for thread::ThreadId { 60 | fn from(task_id: TaskId) -> Self { 61 | task_id.0 62 | } 63 | } 64 | 65 | thread_local! { 66 | static THIS_JS_FILE: RefCell> = RefCell::new(None); 67 | static STARLING: RefCell> = RefCell::new(None); 68 | static THIS_TASK: RefCell> = RefCell::new(None); 69 | static REJECTED_PROMISES: RefCell = RefCell::new(Default::default()); 70 | 71 | static EVENT_LOOP: RefCell> = RefCell::new(None); 72 | 73 | static CHILDREN: RefCell>>) 76 | >> = { 77 | RefCell::new(HashMap::new()) 78 | }; 79 | } 80 | 81 | /// Get a handle to this thread's task's starling supervisor. 82 | pub(crate) fn starling_handle() -> StarlingHandle { 83 | STARLING.with(|starling| { 84 | starling 85 | .borrow() 86 | .clone() 87 | .expect("called `task::starling_handle` before creating thread's task") 88 | }) 89 | } 90 | 91 | /// Get a handle to this thread's task. 92 | pub(crate) fn this_task() -> TaskHandle { 93 | THIS_TASK.with(|this_task| { 94 | this_task 95 | .borrow() 96 | .clone() 97 | .expect("called `task::this_task` before creating thread's task") 98 | }) 99 | } 100 | 101 | /// Get the JS file that this thread's task was spawned to evaluate. 102 | pub(crate) fn this_js_file() -> path::PathBuf { 103 | THIS_JS_FILE.with(|this_js_file| { 104 | this_js_file 105 | .borrow() 106 | .clone() 107 | .expect("called `task::this_js_file` before creating thread's task") 108 | }) 109 | } 110 | 111 | /// Get a handle to this thread's `tokio` event loop. 112 | pub fn event_loop() -> tokio_core::reactor::Handle { 113 | EVENT_LOOP.with(|el| { 114 | el.borrow() 115 | .clone() 116 | .expect("called `task::event_loop` before initializing thread's event loop") 117 | }) 118 | } 119 | 120 | /// Check for unhandled, rejected promises and return an error if any exist. 121 | pub(crate) fn check_for_unhandled_rejected_promises() -> Result<()> { 122 | if let Some(unhandled) = REJECTED_PROMISES.with(|tracker| tracker.borrow_mut().take()) { 123 | Err(unhandled.into()) 124 | } else { 125 | Ok(()) 126 | } 127 | } 128 | 129 | /// Drain the micro-task queue and then check for unhandled rejected 130 | /// promises. It is the caller's responsibility to ensure that any error 131 | /// propagates along the task hierarchy correctly. 132 | pub(crate) fn drain_micro_task_queue() -> Result<()> { 133 | let cx = JsRuntime::get(); 134 | unsafe { 135 | jsapi::js::RunJobs(cx); 136 | } 137 | check_for_unhandled_rejected_promises() 138 | } 139 | 140 | type StructuredCloneBuffer = sc::StructuredCloneBuffer<'static, sc::SameProcessDifferentThread>; 141 | 142 | static STRUCTURED_CLONE_CALLBACKS: jsapi::JSStructuredCloneCallbacks = 143 | jsapi::JSStructuredCloneCallbacks { 144 | read: None, 145 | write: None, 146 | reportError: None, 147 | readTransfer: None, 148 | writeTransfer: None, 149 | freeTransfer: None, 150 | }; 151 | 152 | /// A `Task` is a JavaScript execution thread. 153 | /// 154 | /// A `Task` is not `Send` nor `Sync`; it must be communicated with via its 155 | /// `TaskHandle` which is both `Send` and `Sync`. 156 | /// 157 | /// See the module level documentation for details. 158 | /// 159 | /// This needs to be `pub` because it is used in a trait implementation; don't 160 | /// actually use it! 161 | #[doc(hidden)] 162 | pub struct Task { 163 | handle: TaskHandle, 164 | receiver: mpsc::Receiver, 165 | global: js::heap::Heap<*mut jsapi::JSObject>, 166 | runtime: Option, 167 | starling: StarlingHandle, 168 | js_file: path::PathBuf, 169 | parent: Option, 170 | } 171 | 172 | impl Drop for Task { 173 | fn drop(&mut self) { 174 | unsafe { 175 | jsapi::JS_LeaveCompartment(self.runtime().cx(), ptr::null_mut()); 176 | } 177 | 178 | self.global.set(ptr::null_mut()); 179 | REJECTED_PROMISES.with(|rejected_promises| { 180 | rejected_promises.borrow_mut().clear(); 181 | }); 182 | GcRootSet::uninitialize(); 183 | 184 | unsafe { 185 | jsapi::JS_RemoveExtraGCRootsTracer( 186 | self.runtime().cx(), 187 | Some(Self::trace_task_gc_roots), 188 | self as *const _ as *mut _, 189 | ); 190 | } 191 | 192 | let _ = self.runtime.take().unwrap(); 193 | } 194 | } 195 | 196 | /// ### Constructors 197 | /// 198 | /// There are two kinds of tasks: the main task and child tasks. There is only 199 | /// one main task, and there may be many child tasks. Every child task has a 200 | /// parent whose lifetime strictly encapsulates the child's lifetime. The main 201 | /// task's lifetime encapsulates the whole system's lifetime. 202 | /// 203 | /// There are two different constructors for the two different kinds of tasks. 204 | impl Task { 205 | /// Spawn the main task in a new native thread. 206 | /// 207 | /// The lifetime of the Starling system is tied to this task. If it exits 208 | /// successfully, the process will exit 0. If it fails, then the process 209 | /// will exit non-zero. 210 | pub(crate) fn spawn_main( 211 | starling: StarlingHandle, 212 | js_file: path::PathBuf, 213 | ) -> Result<(TaskHandle, thread::JoinHandle<()>)> { 214 | let (send_handle, recv_handle) = oneshot::channel(); 215 | let starling2 = starling.clone(); 216 | 217 | let join_handle = thread::spawn(move || { 218 | let result = TokioCore::new() 219 | .map_err(|e| e.into()) 220 | .and_then(|event_loop| { 221 | EVENT_LOOP.with(|el| { 222 | let mut el = el.borrow_mut(); 223 | assert!(el.is_none()); 224 | *el = Some(event_loop.handle()); 225 | }); 226 | 227 | Self::create_main(starling2, js_file).map(|task| (event_loop, task)) 228 | }); 229 | 230 | let (mut event_loop, task) = match result { 231 | Ok(t) => t, 232 | Err(e) => { 233 | send_handle 234 | .send(Err(e)) 235 | .expect("Receiver half of the oneshot should not be dropped"); 236 | return; 237 | } 238 | }; 239 | 240 | send_handle 241 | .send(Ok(task.handle())) 242 | .expect("Receiver half of the oneshot should not be dropped"); 243 | 244 | if event_loop.run(TaskStateMachine::start(task)).is_err() { 245 | // Because our error type, `Void`, is uninhabited -- indeed, 246 | // that is its sole reason for existence -- the only way we 247 | // could get here is if someone transmuted a `Void` out of thin 248 | // air. That is, hopefully obviously, not a good idea. 249 | unreachable!(); 250 | } 251 | }); 252 | 253 | let task_handle = recv_handle 254 | .wait() 255 | .expect("Sender half of the oneshot should never cancel")?; 256 | 257 | Ok((task_handle, join_handle)) 258 | } 259 | 260 | /// Create a new child task. 261 | /// 262 | /// The child task's lifetime is constrained within its parent task's 263 | /// lifetime. When the parent task terminates, the child is terminated as 264 | /// well. 265 | /// 266 | /// May only be called from a parent task. 267 | /// 268 | /// Returns a Promise object that is resolved or rejected when the child 269 | /// task finishes cleanly or with an error respectively. 270 | pub(crate) fn spawn_child( 271 | starling: StarlingHandle, 272 | js_file: path::PathBuf, 273 | ) -> Result> { 274 | let parent_task = this_task(); 275 | let (send_handle, recv_handle) = oneshot::channel(); 276 | let starling2 = starling.clone(); 277 | 278 | let join_handle = thread::spawn(move || { 279 | let result = TokioCore::new() 280 | .map_err(|e| e.into()) 281 | .and_then(|event_loop| { 282 | EVENT_LOOP.with(|el| { 283 | let mut el = el.borrow_mut(); 284 | assert!(el.is_none()); 285 | *el = Some(event_loop.handle()); 286 | }); 287 | 288 | Self::create_child(starling2.clone(), parent_task, js_file) 289 | .map(|task| (event_loop, task)) 290 | }); 291 | 292 | let (mut event_loop, task) = match result { 293 | Ok(t) => t, 294 | Err(e) => { 295 | send_handle 296 | .send(Err(e)) 297 | .expect("Receiver half of the oneshot should not be dropped"); 298 | return; 299 | } 300 | }; 301 | 302 | send_handle 303 | .send(Ok(task.handle())) 304 | .expect("Receiver half of the oneshot should not be dropped"); 305 | 306 | if event_loop.run(TaskStateMachine::start(task)).is_err() { 307 | // Same deal as in `spawn_main`. 308 | unreachable!(); 309 | } 310 | }); 311 | 312 | let task_handle = recv_handle 313 | .wait() 314 | .expect("Sender half of the oneshot should never cancel")?; 315 | 316 | starling 317 | .send(StarlingMessage::NewTask(task_handle.clone(), join_handle)) 318 | .wait() 319 | .expect("should never outlive supervisor"); 320 | 321 | let (sender, receiver) = oneshot::channel(); 322 | 323 | CHILDREN.with(|children| { 324 | let mut children = children.borrow_mut(); 325 | children.insert(task_handle.id(), (task_handle, sender)); 326 | }); 327 | 328 | Ok(future_to_promise( 329 | receiver 330 | .map_err(|e| e.to_string()) 331 | .flatten() 332 | .map_err(|e| e.to_string()), 333 | )) 334 | } 335 | 336 | fn create_main(starling: StarlingHandle, js_file: path::PathBuf) -> Result> { 337 | Self::create(starling, None, js_file) 338 | } 339 | 340 | fn create_child( 341 | starling: StarlingHandle, 342 | parent: TaskHandle, 343 | js_file: path::PathBuf, 344 | ) -> Result> { 345 | Self::create(starling, Some(parent), js_file) 346 | } 347 | 348 | fn create( 349 | starling: StarlingHandle, 350 | parent: Option, 351 | js_file: path::PathBuf, 352 | ) -> Result> { 353 | let runtime = Some(JsRuntime::new(true) 354 | .map_err(|_| Error::from_kind(ErrorKind::CouldNotCreateJavaScriptRuntime))?); 355 | 356 | let capacity = starling.options().buffer_capacity_for::(); 357 | let (sender, receiver) = mpsc::channel(capacity); 358 | 359 | let id = TaskId(thread::current().id()); 360 | let handle = TaskHandle { id, sender }; 361 | 362 | THIS_JS_FILE.with(|this_js_file| { 363 | let mut this_js_file = this_js_file.borrow_mut(); 364 | assert!(this_js_file.is_none()); 365 | *this_js_file = Some(js_file.clone()); 366 | }); 367 | 368 | THIS_TASK.with(|this_task| { 369 | let mut this_task = this_task.borrow_mut(); 370 | assert!(this_task.is_none()); 371 | *this_task = Some(handle.clone()); 372 | }); 373 | 374 | STARLING.with(|starling_handle| { 375 | let mut starling_handle = starling_handle.borrow_mut(); 376 | assert!(starling_handle.is_none()); 377 | *starling_handle = Some(starling.clone()); 378 | }); 379 | 380 | let task = Box::new(Task { 381 | handle, 382 | receiver, 383 | global: js::heap::Heap::default(), 384 | runtime, 385 | starling, 386 | js_file, 387 | parent, 388 | }); 389 | 390 | GcRootSet::initialize(); 391 | REJECTED_PROMISES.with(|rejected_promises| { 392 | RejectedPromisesTracker::register(task.runtime(), &rejected_promises); 393 | }); 394 | task.create_global(); 395 | 396 | Ok(task) 397 | } 398 | } 399 | 400 | impl Task { 401 | /// Get a handle to this task. 402 | pub(crate) fn handle(&self) -> TaskHandle { 403 | self.handle.clone() 404 | } 405 | 406 | fn id(&self) -> TaskId { 407 | self.handle.id 408 | } 409 | 410 | fn runtime(&self) -> &JsRuntime { 411 | self.runtime 412 | .as_ref() 413 | .expect("Task should always have a JS runtime except at the very end of its Drop") 414 | } 415 | 416 | fn create_global(&self) { 417 | assert_eq!(self.global.get(), ptr::null_mut()); 418 | 419 | unsafe { 420 | let cx = self.runtime().cx(); 421 | 422 | rooted!(in(cx) let global = jsapi::JS_NewGlobalObject( 423 | cx, 424 | &js::rust::SIMPLE_GLOBAL_CLASS, 425 | ptr::null_mut(), 426 | jsapi::JS::OnNewGlobalHookOption::FireOnNewGlobalHook, 427 | &jsapi::JS::CompartmentOptions::default() 428 | )); 429 | assert!(!global.get().is_null()); 430 | self.global.set(global.get()); 431 | jsapi::JS_EnterCompartment(cx, self.global.get()); 432 | 433 | js::rust::define_methods(cx, global.handle(), &GLOBAL_FUNCTIONS[..]) 434 | .expect("should define global functions OK"); 435 | 436 | assert!(jsapi::JS_AddExtraGCRootsTracer( 437 | cx, 438 | Some(Self::trace_task_gc_roots), 439 | self as *const Task as *mut os::raw::c_void 440 | )); 441 | } 442 | } 443 | 444 | /// Notify the SpiderMonkey GC of our additional GC roots. 445 | unsafe extern "C" fn trace_task_gc_roots( 446 | tracer: *mut jsapi::JSTracer, 447 | task: *mut os::raw::c_void, 448 | ) { 449 | let task = task as *const os::raw::c_void; 450 | let task = task as *const Task; 451 | let task = task.as_ref().unwrap(); 452 | task.trace(tracer); 453 | } 454 | 455 | fn evaluate_top_level(&mut self, src: String) -> Result> { 456 | let cx = self.runtime().cx(); 457 | rooted!(in(cx) let global = self.global.get()); 458 | 459 | let filename = self.js_file.display().to_string(); 460 | 461 | // Evaluate the JS source. 462 | 463 | rooted!(in(cx) let mut rval = jsval::UndefinedValue()); 464 | let eval_result = 465 | self.runtime() 466 | .evaluate_script(global.handle(), &src, &filename, 1, rval.handle_mut()); 467 | if let Err(()) = eval_result { 468 | unsafe { 469 | let error = Error::from_cx(cx); 470 | jsapi::js::RunJobs(cx); 471 | return Err(error); 472 | } 473 | } 474 | 475 | drain_micro_task_queue()?; 476 | self.evaluate_main() 477 | } 478 | 479 | fn evaluate_main(&mut self) -> Result> { 480 | let cx = self.runtime().cx(); 481 | rooted!(in(cx) let global = self.global.get()); 482 | 483 | let mut has_main = false; 484 | unsafe { 485 | assert!(jsapi::JS_HasProperty( 486 | cx, 487 | global.handle(), 488 | b"main\0".as_ptr() as _, 489 | &mut has_main 490 | )); 491 | } 492 | if !has_main { 493 | return Ok(None); 494 | } 495 | 496 | rooted!(in(cx) let mut rval = jsval::UndefinedValue()); 497 | let args = jsapi::JS::HandleValueArray::new(); 498 | unsafe { 499 | let ok = jsapi::JS_CallFunctionName( 500 | cx, 501 | global.handle(), 502 | b"main\0".as_ptr() as _, 503 | &args, 504 | rval.handle_mut(), 505 | ); 506 | 507 | if !ok { 508 | let error = Error::from_cx(cx); 509 | 510 | // TODO: It isn't obvious that we should drain the micro-task 511 | // queue here. But it also isn't obvious that we shouldn't. 512 | // Let's investigate this sometime in the future. 513 | jsapi::js::RunJobs(cx); 514 | 515 | return Err(error); 516 | } 517 | } 518 | 519 | rooted!(in(cx) let mut obj = ptr::null_mut()); 520 | 521 | let mut is_promise = false; 522 | if rval.get().is_object() { 523 | obj.set(rval.get().to_object()); 524 | is_promise = unsafe { jsapi::JS::IsPromiseObject(obj.handle()) }; 525 | } 526 | if is_promise { 527 | let obj = GcRoot::new(obj.get()); 528 | let future = promise_to_future(&obj); 529 | drain_micro_task_queue()?; 530 | Ok(Some(future)) 531 | } else { 532 | drain_micro_task_queue()?; 533 | Ok(None) 534 | } 535 | } 536 | 537 | fn shutdown_children(&self) -> Box> { 538 | let mut shutdowns_sent: Box> = 539 | Box::new(futures::future::ok(())); 540 | 541 | CHILDREN.with(|children| { 542 | let mut children = children.borrow_mut(); 543 | for (_, (child, _)) in children.drain() { 544 | shutdowns_sent = Box::new( 545 | shutdowns_sent 546 | .join( 547 | child 548 | .send(TaskMessage::Shutdown) 549 | .map_err(|_| "could not send shutdown notice to child".into()), 550 | ) 551 | .map(|_| ()), 552 | ); 553 | } 554 | 555 | shutdowns_sent 556 | }) 557 | } 558 | 559 | #[inline] 560 | fn propagate(self: Box, error: Error) -> Poll 561 | where 562 | T: From, 563 | { 564 | let shutdowns_sent = self.shutdown_children(); 565 | ready(ShutdownChildrenErrored { 566 | task: self, 567 | error, 568 | shutdowns_sent, 569 | }) 570 | } 571 | 572 | #[inline] 573 | fn finished(self: Box, result: GcRoot) -> Poll 574 | where 575 | T: From, 576 | { 577 | let shutdowns_sent = self.shutdown_children(); 578 | ready(ShutdownChildrenFinished { 579 | task: self, 580 | result, 581 | shutdowns_sent, 582 | }) 583 | } 584 | 585 | fn handle_child_finished( 586 | &mut self, 587 | id: TaskId, 588 | mut result: StructuredCloneBuffer, 589 | ) -> Result<()> { 590 | let cx = self.runtime().cx(); 591 | rooted!(in(cx) let mut val = jsval::UndefinedValue()); 592 | if let Err(()) = result.read(val.handle_mut()) { 593 | return Err(unsafe { Error::from_cx(cx) }); 594 | } 595 | let val = GcRoot::new(val.get()); 596 | 597 | let mut error = None; 598 | CHILDREN.with(|children| { 599 | let mut children = children.borrow_mut(); 600 | if let Some((_, sender)) = children.remove(&id) { 601 | // The receiver half could be dropped if the promise is GC'd, so 602 | // ignore any `send` errors. 603 | let _ = sender.send(Ok(val)); 604 | } else { 605 | let msg = format!( 606 | "task received message for child that isn't \ 607 | actually a child: {:?}", 608 | id 609 | ); 610 | error = Some(msg.into()); 611 | } 612 | }); 613 | 614 | if let Some(e) = error { 615 | return Err(e); 616 | } 617 | 618 | // Make sure to run JS outside of the `CHILDREN.with` closure, since JS 619 | // could spawn a new task, causing us to re-enter `CHILDREN` and panic. 620 | drain_micro_task_queue() 621 | } 622 | 623 | fn handle_child_errored(&mut self, id: TaskId, err: Error) -> Result<()> { 624 | let mut error = None; 625 | 626 | CHILDREN.with(|children| { 627 | let mut children = children.borrow_mut(); 628 | if let Some((_, sender)) = children.remove(&id) { 629 | // Again, the receiver half could have been GC'd, causing the 630 | // oneshot `send` to fail. If that is the case, then we 631 | // propagate the error to the parent. 632 | if sender.send(Err(err.clone())).is_err() { 633 | error = Some(err); 634 | } 635 | } else { 636 | let msg = format!( 637 | "task received message for child that isn't \ 638 | actually a child: {:?}", 639 | id 640 | ); 641 | error = Some(msg.into()); 642 | } 643 | }); 644 | 645 | if let Some(e) = error { 646 | return Err(e); 647 | } 648 | 649 | // As in `handle_child_finished`, we take care that we don't run JS 650 | // inside the `CHILDREN` block. 651 | drain_micro_task_queue() 652 | } 653 | 654 | fn notify_starling_finished(self: Box) -> NotifyStarlingFinished { 655 | let notification = self.starling.send(StarlingMessage::TaskFinished(self.id())); 656 | NotifyStarlingFinished { 657 | task: self, 658 | notification, 659 | } 660 | } 661 | 662 | fn notify_starling_errored(self: Box, error: Error) -> NotifyStarlingErrored { 663 | let notification = self.starling 664 | .send(StarlingMessage::TaskErrored(self.id(), error)); 665 | NotifyStarlingErrored { 666 | task: self, 667 | notification, 668 | } 669 | } 670 | } 671 | 672 | unsafe impl Trace for Task { 673 | unsafe fn trace(&self, tracer: *mut jsapi::JSTracer) { 674 | self.global.trace(tracer); 675 | 676 | GcRootSet::with_ref(|roots| { 677 | roots.trace(tracer); 678 | }); 679 | } 680 | } 681 | 682 | impl fmt::Debug for Task { 683 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 684 | write!(f, "Task {{ .. }}") 685 | } 686 | } 687 | 688 | type Promise2FutureJsValue = Promise2Future, GcRoot>; 689 | 690 | #[derive(StateMachineFuture)] 691 | #[allow(dead_code)] 692 | enum TaskStateMachine { 693 | /// The initial state where the task is created but has not evaluated any JS 694 | /// yet. 695 | #[state_machine_future(start, transitions(ReadingJsModule))] 696 | Created { task: Box }, 697 | 698 | /// We are waiting on reading the JS source text from disk. 699 | #[state_machine_future(transitions(WaitingOnPromise, ShutdownChildrenFinished, 700 | ShutdownChildrenErrored))] 701 | ReadingJsModule { 702 | task: Box, 703 | reading: CpuFuture, 704 | }, 705 | 706 | /// The most common state: waiting on the task's `main`'s result promise to 707 | /// complete, and handling incoming messages while we do so. 708 | #[state_machine_future(transitions(WaitingOnPromise, ShutdownChildrenFinished, 709 | ShutdownChildrenErrored))] 710 | WaitingOnPromise { 711 | task: Box, 712 | promise: Promise2FutureJsValue, 713 | }, 714 | 715 | /// The task has finished OK, and now we need to shutdown its children. 716 | #[state_machine_future(transitions(NotifyParentFinished, NotifyStarlingFinished, 717 | ShutdownChildrenErrored))] 718 | ShutdownChildrenFinished { 719 | task: Box, 720 | result: GcRoot, 721 | shutdowns_sent: Box>, 722 | }, 723 | 724 | /// The task has finished OK, and now we need to notify the parent that it 725 | /// is shutting down. 726 | #[state_machine_future(transitions(NotifyStarlingFinished))] 727 | NotifyParentFinished { 728 | task: Box, 729 | notification: futures::sink::Send>, 730 | }, 731 | 732 | /// The task has finished OK, and now we need to notify the Starling 733 | /// supervisor that it is shutting down. 734 | #[state_machine_future(transitions(Finished))] 735 | NotifyStarlingFinished { 736 | task: Box, 737 | notification: futures::sink::Send>, 738 | }, 739 | 740 | /// The task errored out, and now we need to clean up its children. 741 | #[state_machine_future(transitions(NotifyParentErrored, NotifyStarlingErrored))] 742 | ShutdownChildrenErrored { 743 | task: Box, 744 | error: Error, 745 | shutdowns_sent: Box>, 746 | }, 747 | 748 | /// The task errored out, and now we need to notify its parent. 749 | #[state_machine_future(transitions(NotifyStarlingErrored))] 750 | NotifyParentErrored { 751 | task: Box, 752 | error: Error, 753 | notification: futures::sink::Send>, 754 | }, 755 | 756 | /// The task errored out, and now we need to notify the Starling supervisor. 757 | #[state_machine_future(transitions(Finished))] 758 | NotifyStarlingErrored { 759 | task: Box, 760 | notification: futures::sink::Send>, 761 | }, 762 | 763 | /// The task completed (either OK or with an error) and we're done notifying 764 | /// the appropriate parties. 765 | #[state_machine_future(ready)] 766 | Finished(()), 767 | 768 | /// We handle and propagate all errors, and to enforce this at the type 769 | /// system level, our `Future::Error` type is the uninhabited `Void`. 770 | #[state_machine_future(error)] 771 | Impossible(Void), 772 | } 773 | 774 | /// Principles for error handling, recovery, and propagation when driving 775 | /// tasks to completion: 776 | /// 777 | /// * Whenever possible, catch errors and then inform 778 | /// 1. this task's children, 779 | /// 2. the parent task (if any), and 780 | /// 3. the Starling system supervisor thread 781 | /// in that order to propagate the errors. This ordering is enforced by the 782 | /// state machine transitions and generated typestates. All errors that we 783 | /// can't reasonably catch-and-propagate should be `unwrap`ed. 784 | /// 785 | /// * Tasks should never outlive the Starling system supervisor thread, 786 | /// so don't attempt to catch errors sending messages to it. Just panic 787 | /// if that fails, since something very wrong has happened. 788 | /// 789 | /// * We can't rely on this task's children or parent strictly observing the 790 | /// tree lifetime discipline that we expose to JavaScript. There can be races 791 | /// where both shut down at the same time, perhaps because they both had 792 | /// unhandled exceptions at the same time. Therefore, we allow all inter-task 793 | /// messages to fail silently, locally ignoring errors, and relying on the 794 | /// Starling system supervisor thread to perform ultimate clean up. 795 | impl PollTaskStateMachine for TaskStateMachine { 796 | fn poll_created<'a>(created: &'a mut RentToOwn<'a, Created>) -> Poll { 797 | let js_file_path = created.task.js_file.clone(); 798 | 799 | let reading = created.task.starling.sync_io_pool().spawn_fn(|| { 800 | use std::fs; 801 | use std::io::Read; 802 | 803 | let mut file = fs::File::open(js_file_path)?; 804 | let mut contents = String::new(); 805 | file.read_to_string(&mut contents)?; 806 | Ok(contents) 807 | }); 808 | 809 | let task = created.take().task; 810 | ready(ReadingJsModule { task, reading }) 811 | } 812 | 813 | fn poll_reading_js_module<'a>( 814 | reading: &'a mut RentToOwn<'a, ReadingJsModule>, 815 | ) -> Poll { 816 | let src = match reading.reading.poll() { 817 | Err(e) => return reading.take().task.propagate(e), 818 | Ok(Async::NotReady) => return Ok(Async::NotReady), 819 | Ok(Async::Ready(src)) => src, 820 | }; 821 | 822 | match reading.task.evaluate_top_level(src) { 823 | Err(e) => reading.take().task.propagate(e), 824 | Ok(None) => reading 825 | .take() 826 | .task 827 | .finished(GcRoot::new(jsval::UndefinedValue())), 828 | Ok(Some(promise)) => ready(WaitingOnPromise { 829 | task: reading.take().task, 830 | promise, 831 | }), 832 | } 833 | } 834 | 835 | fn poll_waiting_on_promise<'a>( 836 | waiting: &'a mut RentToOwn<'a, WaitingOnPromise>, 837 | ) -> Poll { 838 | let next_msg = waiting 839 | .task 840 | .receiver 841 | .by_ref() 842 | .take(1) 843 | .into_future() 844 | .map(|(item, _)| item) 845 | .map_err(|_| ErrorKind::CouldNotReadValueFromChannel.into()) 846 | .poll(); 847 | match next_msg { 848 | Err(e) => return waiting.take().task.propagate(e), 849 | Ok(Async::Ready(None)) => { 850 | let error = ErrorKind::CouldNotReadValueFromChannel.into(); 851 | return waiting.take().task.propagate(error); 852 | } 853 | Ok(Async::Ready(Some(TaskMessage::UnhandledRejectedPromise { error }))) => { 854 | return waiting.take().task.propagate(error); 855 | } 856 | Ok(Async::Ready(Some(TaskMessage::Shutdown))) => { 857 | waiting.task.parent = None; 858 | return waiting 859 | .take() 860 | .task 861 | .finished(GcRoot::new(jsval::UndefinedValue())); 862 | } 863 | Ok(Async::Ready(Some(TaskMessage::ChildTaskFinished { child, result }))) => { 864 | return match waiting.task.handle_child_finished(child, result) { 865 | Err(e) => waiting.take().task.propagate(e), 866 | Ok(()) => ready(waiting.take()), 867 | }; 868 | } 869 | Ok(Async::Ready(Some(TaskMessage::ChildTaskErrored { child, error }))) => { 870 | return match waiting.task.handle_child_errored(child, error) { 871 | Err(e) => waiting.take().task.propagate(e), 872 | Ok(()) => ready(waiting.take()), 873 | }; 874 | } 875 | Ok(Async::NotReady) => {} 876 | } 877 | 878 | match waiting.promise.poll() { 879 | Err(e) => waiting.take().task.propagate(e), 880 | Ok(Async::NotReady) => Ok(Async::NotReady), 881 | Ok(Async::Ready(Err(val))) => { 882 | let cx = waiting.task.runtime().cx(); 883 | unsafe { 884 | rooted!(in(cx) let val = val.raw()); 885 | let err = Error::infallible_from_jsval(cx, val.handle()); 886 | waiting.take().task.propagate(err) 887 | } 888 | } 889 | Ok(Async::Ready(Ok(val))) => waiting.take().task.finished(val), 890 | } 891 | } 892 | 893 | fn poll_shutdown_children_finished<'a>( 894 | shutdown: &'a mut RentToOwn<'a, ShutdownChildrenFinished>, 895 | ) -> Poll { 896 | try_ready!( 897 | shutdown 898 | .shutdowns_sent 899 | .by_ref() 900 | .ignore_results::() 901 | .poll() 902 | ); 903 | 904 | if let Some(parent) = shutdown.task.parent.clone() { 905 | let child = shutdown.task.id(); 906 | let shutdown = shutdown.take(); 907 | 908 | let result = { 909 | let cx = shutdown.task.runtime().cx(); 910 | rooted!(in(cx) let result = unsafe { shutdown.result.raw() }); 911 | let mut buf = StructuredCloneBuffer::new(&STRUCTURED_CLONE_CALLBACKS); 912 | if let Err(()) = buf.write(result.handle()) { 913 | let e = unsafe { Error::from_cx(cx) }; 914 | return shutdown.task.propagate(e); 915 | } 916 | buf 917 | }; 918 | 919 | ready(NotifyParentFinished { 920 | task: shutdown.task, 921 | notification: parent.send(TaskMessage::ChildTaskFinished { child, result }), 922 | }) 923 | } else { 924 | ready(shutdown.take().task.notify_starling_finished()) 925 | } 926 | } 927 | 928 | fn poll_notify_parent_finished<'a>( 929 | notify: &'a mut RentToOwn<'a, NotifyParentFinished>, 930 | ) -> Poll { 931 | try_ready!(notify.notification.by_ref().ignore_results::().poll()); 932 | ready(notify.take().task.notify_starling_finished()) 933 | } 934 | 935 | fn poll_notify_starling_finished<'a>( 936 | notify: &'a mut RentToOwn<'a, NotifyStarlingFinished>, 937 | ) -> Poll { 938 | try_ready!(notify.notification.by_ref().ignore_results::().poll()); 939 | ready(Finished(())) 940 | } 941 | 942 | fn poll_shutdown_children_errored<'a>( 943 | shutdown: &'a mut RentToOwn<'a, ShutdownChildrenErrored>, 944 | ) -> Poll { 945 | try_ready!( 946 | shutdown 947 | .shutdowns_sent 948 | .by_ref() 949 | .ignore_results::() 950 | .poll() 951 | ); 952 | 953 | if let Some(parent) = shutdown.task.parent.clone() { 954 | let child = shutdown.task.id(); 955 | let shutdown = shutdown.take(); 956 | let error = shutdown.error.clone(); 957 | ready(NotifyParentErrored { 958 | task: shutdown.task, 959 | error: shutdown.error, 960 | notification: parent.send(TaskMessage::ChildTaskErrored { child, error }), 961 | }) 962 | } else { 963 | let shutdown = shutdown.take(); 964 | ready(shutdown.task.notify_starling_errored(shutdown.error)) 965 | } 966 | } 967 | 968 | fn poll_notify_parent_errored<'a>( 969 | notify: &'a mut RentToOwn<'a, NotifyParentErrored>, 970 | ) -> Poll { 971 | try_ready!(notify.notification.by_ref().ignore_results::().poll()); 972 | let notify = notify.take(); 973 | ready(notify.task.notify_starling_errored(notify.error)) 974 | } 975 | 976 | fn poll_notify_starling_errored<'a>( 977 | notify: &'a mut RentToOwn<'a, NotifyStarlingErrored>, 978 | ) -> Poll { 979 | try_ready!(notify.notification.by_ref().ignore_results::().poll()); 980 | ready(Finished(())) 981 | } 982 | } 983 | 984 | /// A non-owning handle to a task. 985 | /// 986 | /// A `TaskHandle` grants the ability to communicate with its referenced task 987 | /// and send it `TaskMessage`s. 988 | /// 989 | /// A `TaskHandle` does not keep its referenced task running. For example, if 990 | /// the task's `main` function returns, or its parent terminates, or it stops 991 | /// running for any other reason, then any further messages sent to the task 992 | /// from the handle will return `Err`s. 993 | /// 994 | /// This needs to be `pub` because it is used in a trait implementation; don't 995 | /// actually use it! 996 | #[derive(Clone)] 997 | #[doc(hidden)] 998 | pub struct TaskHandle { 999 | id: TaskId, 1000 | sender: mpsc::Sender, 1001 | } 1002 | 1003 | impl TaskHandle { 1004 | /// Get this task's ID. 1005 | pub(crate) fn id(&self) -> TaskId { 1006 | self.id 1007 | } 1008 | 1009 | /// Send a message to the task. 1010 | pub(crate) fn send(&self, msg: TaskMessage) -> futures::sink::Send> { 1011 | self.sender.clone().send(msg) 1012 | } 1013 | } 1014 | 1015 | impl fmt::Debug for TaskHandle { 1016 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1017 | write!(f, "TaskHandle {{ {:?} }}", self.id) 1018 | } 1019 | } 1020 | 1021 | /// Messages that can be sent to a task. 1022 | /// 1023 | /// This needs to be `pub` because it is used in a trait implementation; don't 1024 | /// actually use it! 1025 | #[derive(Debug)] 1026 | #[doc(hidden)] 1027 | pub enum TaskMessage { 1028 | /// A shutdown request sent from a parent task to its child. 1029 | Shutdown, 1030 | 1031 | /// A notification that a child task finished OK. Sent from a child task to 1032 | /// its parent. 1033 | ChildTaskFinished { 1034 | /// The ID of the child task that finished OK. 1035 | child: TaskId, 1036 | /// A structured clone buffer containing the serialized result of the 1037 | /// child's `main` function. 1038 | result: StructuredCloneBuffer, 1039 | }, 1040 | 1041 | /// A notification that a child task failed. Sent from the failed child task 1042 | /// to its parent. 1043 | ChildTaskErrored { 1044 | /// The ID of the child task that failed. 1045 | child: TaskId, 1046 | /// The error that the child task failed with. 1047 | error: Error, 1048 | }, 1049 | 1050 | /// A notification of an unhandled rejected promise. Sent from a future in 1051 | /// this task's thread to this task. 1052 | UnhandledRejectedPromise { 1053 | /// The rejection error value that was not handled. 1054 | error: Error, 1055 | }, 1056 | } 1057 | -------------------------------------------------------------------------------- /tests/js/child-outlives-parent-error.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stdout-has: parent: throwing error in `main` 3 | //# starling-stdout-has: child: uh uh uh uh, staying alive, staying alive 4 | //# starling-stderr-has: error: tests/js/child-outlives-parent-error.js:15:9: 5 | 6 | async function main() { 7 | // Spawn it, but don't await it so that we can ensure that the child task gets 8 | // reclaimed once we exit. 9 | spawn("./stay-alive-forever.js"); 10 | 11 | // This should be enough time for the child to spawn and print to stdout. 12 | await timeout(500); 13 | 14 | print("parent: throwing error in `main`"); 15 | throw new Error; 16 | } 17 | -------------------------------------------------------------------------------- /tests/js/child-outlives-parent.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: done 2 | 3 | async function main() { 4 | // Spawn a child task but do not await on it. It should get killed after this 5 | // parent task exits, and then reclaimed by the Starling system supervisor 6 | // thread. 7 | spawn("./stay-alive-forever.js") 8 | await timeout(0); 9 | print("done"); 10 | } 11 | -------------------------------------------------------------------------------- /tests/js/empty-main.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | // Nothing... 3 | } 4 | -------------------------------------------------------------------------------- /tests/js/many-unhandled-rejected-promises.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stderr-has: error: 6 unhandled rejected promise(s) 3 | //# starling-stderr-has: uno 4 | //# starling-stderr-has: dos 5 | //# starling-stderr-has: tres 6 | //# starling-stderr-has: the froggle is the wrong bodoozer 7 | //# starling-stderr-has: woops 8 | //# starling-stderr-has: how many promises do I rip on the daily? 9 | 10 | async function main() { 11 | Promise.reject("uno"); 12 | Promise.reject("dos"); 13 | Promise.reject("tres"); 14 | Promise.reject(new Error("the froggle is the wrong bodoozer")); 15 | (function gogogo () { 16 | Promise.reject(new Error("woops")); 17 | 18 | (function further() { 19 | Promise.reject(new Error("how many promises do I rip on the daily?")); 20 | }()); 21 | }()) 22 | 23 | await timeout(10000000); 24 | return true; 25 | } 26 | -------------------------------------------------------------------------------- /tests/js/micro-tasks-delayed-error-handled.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: 1 2 | //# starling-stdout-has: 2 3 | //# starling-stdout-has: 3 4 | //# starling-stdout-has: 4 5 | //# starling-stdout-has: 5 6 | //# starling-stdout-has: 6 7 | //# starling-stdout-has: 7 8 | //# starling-stdout-has: 9 9 | 10 | print("1"); 11 | 12 | async function delayedError() { 13 | await Promise.resolve(); 14 | throw new Error(); 15 | } 16 | 17 | delayedError().catch(_ => {}); 18 | delayedError().catch(_ => {}); 19 | delayedError().catch(_ => {}); 20 | delayedError().catch(_ => {}); 21 | 22 | (async function () { 23 | print("2"); 24 | await Promise.resolve(); 25 | print("3"); 26 | await Promise.resolve(); 27 | print("4"); 28 | await Promise.resolve(); 29 | print("5"); 30 | await Promise.resolve(); 31 | print("6"); 32 | await Promise.resolve(); 33 | print("7"); 34 | throw new Error(); 35 | print("8"); 36 | }()).catch(_ => {}); 37 | 38 | print("9"); 39 | -------------------------------------------------------------------------------- /tests/js/micro-tasks-flushed-on-main-error.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stdout-has: micro 3 | 4 | const assertEvent = (function () { 5 | let currentEvent = 0; 6 | return expected => { 7 | print("assertEvent(", expected, ") @ ", currentEvent); 8 | if (currentEvent != expected) { 9 | throw new Error("expected event " + expected + " but saw event " + currentEvent); 10 | } 11 | currentEvent++; 12 | }; 13 | }()); 14 | 15 | async function main() { 16 | Promise.resolve().then(_ => { 17 | assertEvent(1); 18 | print("micro") 19 | }); 20 | 21 | assertEvent(0); 22 | throw new Error(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/js/micro-tasks-flushed-on-top-level-error.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stdout-has: micro 3 | 4 | const assertEvent = (function () { 5 | let currentEvent = 0; 6 | return expected => { 7 | print("assertEvent(", expected, ") @ ", currentEvent); 8 | if (currentEvent != expected) { 9 | throw new Error("expected event " + expected + " but saw event " + currentEvent); 10 | } 11 | currentEvent++; 12 | }; 13 | }()); 14 | 15 | Promise.resolve().then(_ => { 16 | assertEvent(1); 17 | print("micro") 18 | }); 19 | 20 | assertEvent(0); 21 | throw new Error(); 22 | -------------------------------------------------------------------------------- /tests/js/ordering.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: done 2 | 3 | // The top level is evaluated, then the micro-task queue is drained, then `main` 4 | // is evaluated and the micro-task queue is drained again. Once `main` 5 | // completes, we drain the micro-task queue once more and then the task 6 | // completes. 7 | 8 | const assertEvent = (function () { 9 | let currentEvent = 0; 10 | return expected => { 11 | print("assertEvent(", expected, ") @ ", currentEvent); 12 | if (currentEvent != expected) { 13 | throw new Error("expected event " + expected + " but saw event " + currentEvent); 14 | } 15 | currentEvent++; 16 | }; 17 | }()); 18 | 19 | assertEvent(0); 20 | 21 | (async function () { 22 | assertEvent(1); 23 | await Promise.resolve(); 24 | assertEvent(3); 25 | }()); 26 | 27 | assertEvent(2); 28 | 29 | async function main() { 30 | assertEvent(4); 31 | 32 | // Micro-task queue always happens before any new ticks of the event loop. 33 | await Promise.all([ 34 | Promise.resolve().then(_ => { 35 | assertEvent(5); 36 | }), 37 | timeout(0).then(_ => { 38 | assertEvent(6); 39 | }), 40 | ]); 41 | 42 | assertEvent(7); 43 | 44 | // No await: we're testing that the micro-task queue is flushed one last time 45 | // after `main` exits. 46 | Promise.resolve().then(_ => { 47 | assertEvent(8); 48 | print("done"); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /tests/js/parent-does-not-handle-child-error.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stderr-has: error: 1 unhandled rejected promise(s) 3 | //# starling-stderr-has: this should cause an exit 1 4 | 5 | async function main() { 6 | // Spawn a child task that will error. Since we don't handle the child error, 7 | // it will propagate as an unhandled, rejected promise and kill this task as 8 | // well. 9 | print("parent: spawning simple-exit-1.js"); 10 | spawn("./simple-exit-1.js"); 11 | 12 | // Wait for the error to propagate. 13 | while (true) { 14 | await timeout(9999); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/js/simple-exit-0.js: -------------------------------------------------------------------------------- 1 | 40 + 2; 2 | -------------------------------------------------------------------------------- /tests/js/simple-exit-1.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stderr-has: this should cause an exit 1 3 | 4 | throw new Error("this should cause an exit 1"); 5 | -------------------------------------------------------------------------------- /tests/js/simple-micro-tasks-error.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stdout-has: 1 3 | //# starling-stdout-has: 2 4 | //# starling-stdout-has: 3 5 | //# starling-stdout-has: 4 6 | //# starling-stdout-has: 5 7 | //# starling-stdout-has: 6 8 | //# starling-stdout-has: 7 9 | //# starling-stderr-has: error: 5 unhandled rejected promise(s) 10 | 11 | print("1"); 12 | 13 | async function delayedError() { 14 | await Promise.resolve(); 15 | throw new Error(); 16 | } 17 | 18 | delayedError(); 19 | delayedError(); 20 | delayedError(); 21 | delayedError(); 22 | 23 | (async function () { 24 | print("2"); 25 | await Promise.resolve(); 26 | print("3"); 27 | await Promise.resolve(); 28 | print("4"); 29 | await Promise.resolve(); 30 | print("5"); 31 | await Promise.resolve(); 32 | print("6"); 33 | await Promise.resolve(); 34 | print("7"); 35 | throw new Error(); 36 | print("8"); 37 | }()); 38 | print("9"); 39 | -------------------------------------------------------------------------------- /tests/js/simple-micro-tasks.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: 1 2 | //# starling-stdout-has: 2 3 | //# starling-stdout-has: 3 4 | //# starling-stdout-has: 4 5 | //# starling-stdout-has: 5 6 | //# starling-stdout-has: 6 7 | //# starling-stdout-has: 7 8 | //# starling-stdout-has: 8 9 | //# starling-stdout-has: 9 10 | 11 | print("1"); 12 | 13 | (async function () { 14 | print("2"); 15 | await Promise.resolve(); 16 | print("3"); 17 | await Promise.resolve(); 18 | print("4"); 19 | await Promise.resolve(); 20 | print("5"); 21 | await Promise.resolve(); 22 | print("6"); 23 | await Promise.resolve(); 24 | print("7"); 25 | await Promise.resolve(); 26 | print("8"); 27 | }()); 28 | 29 | print("9"); 30 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: 10.js: spawned 2 | //# starling-stdout-has: 9.js: spawned 3 | //# starling-stdout-has: 8.js: spawned 4 | //# starling-stdout-has: 7.js: spawned 5 | //# starling-stdout-has: 6.js: spawned 6 | //# starling-stdout-has: 5.js: spawned 7 | //# starling-stdout-has: 4.js: spawned 8 | //# starling-stdout-has: 3.js: spawned 9 | //# starling-stdout-has: 2.js: spawned 10 | //# starling-stdout-has: 1.js: spawned 11 | //# starling-stdout-has: 0.js: spawned and done 12 | //# starling-stdout-has: 1.js: done 13 | //# starling-stdout-has: 2.js: done 14 | //# starling-stdout-has: 3.js: done 15 | //# starling-stdout-has: 4.js: done 16 | //# starling-stdout-has: 5.js: done 17 | //# starling-stdout-has: 6.js: done 18 | //# starling-stdout-has: 7.js: done 19 | //# starling-stdout-has: 8.js: done 20 | //# starling-stdout-has: 9.js: done 21 | //# starling-stdout-has: 10.js: done 22 | 23 | async function main() { 24 | const then = Date.now(); 25 | await spawn("./spawn-and-wait/10.js"); 26 | const now = Date.now(); 27 | print("Time: ", now - then, " ms"); 28 | } 29 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/0.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | function main() { 4 | print("0.js: spawned and done"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/1.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("1.js: spawned"); 5 | await spawn("./0.js"); 6 | print("1.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/10.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("10.js: spawned"); 5 | await spawn("./9.js"); 6 | print("10.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/2.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("2.js: spawned"); 5 | await spawn("./1.js"); 6 | print("2.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/3.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("3.js: spawned"); 5 | await spawn("./2.js"); 6 | print("3.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/4.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("4.js: spawned"); 5 | await spawn("./3.js"); 6 | print("4.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/5.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("5.js: spawned"); 5 | await spawn("./4.js"); 6 | print("5.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/6.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("6.js: spawned"); 5 | await spawn("./5.js"); 6 | print("6.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/7.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("7.js: spawned"); 5 | await spawn("./6.js"); 6 | print("7.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/8.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("8.js: spawned"); 5 | await spawn("./7.js"); 6 | print("8.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-and-wait/9.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | print("9.js: spawned"); 5 | await spawn("./8.js"); 6 | print("9.js: done"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/spawn-bad-file.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | let awaited = false; 3 | let errored = false; 4 | 5 | try { 6 | await spawn("xxx-some-file-that-really-does-not-exist.js"); 7 | awaited = true; 8 | } catch (_) { 9 | errored = true; 10 | } 11 | 12 | if (awaited) { 13 | print("should not have awaited"); 14 | throw new Error; 15 | } 16 | 17 | if (!errored) { 18 | print("should have errored"); 19 | throw new Error; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/js/spawn-task-that-errors.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | let errored = false; 3 | try { 4 | await spawn("./simple-exit-1.js"); 5 | } catch (_) { 6 | errored = true; 7 | } 8 | 9 | if (!errored) { 10 | print("did not throw an error, when we expected one"); 11 | throw new Error; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/js/stay-alive-forever.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | 3 | async function main() { 4 | // Loop forever, but yield to the event loop so that we can get killed 5 | // properly if the parent finishes. 6 | while (true) { 7 | print("child: uh uh uh uh, staying alive, staying alive"); 8 | await timeout(99999); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/js/task-resolves.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | // Things that should structure clone OK. 3 | 4 | let undef = await spawn("./task-resolves/undefined.js"); 5 | if (undef !== undefined) { 6 | throw new Error("task resolves to undefined"); 7 | } 8 | 9 | let n = await spawn("./task-resolves/number.js"); 10 | if (n !== 42) { 11 | throw new Error("task resolves to number"); 12 | } 13 | 14 | let s = await spawn("./task-resolves/string.js"); 15 | if (s !== "hello") { 16 | throw new Error("task resolves to string"); 17 | } 18 | 19 | let o = await spawn("./task-resolves/object.js"); 20 | let keys = Object.keys(o); 21 | if (keys.length !== 1) { 22 | throw new Error("task resolves to object: keys.length"); 23 | } 24 | if (keys[0] !== "pinball") { 25 | throw new Error("task resolves to object: keys[0]"); 26 | } 27 | if (o.pinball !== "wizard") { 28 | throw new Error("task resolves to object: o.pinball"); 29 | } 30 | 31 | // Things that should fail to structure clone, raising an error. 32 | 33 | let error; 34 | 35 | error = null; 36 | try { 37 | await spawn("./task-resolves/function.js"); 38 | } catch(e) { 39 | error = e; 40 | } 41 | if (error === null) { 42 | throw new Error("task resolves to function did not throw an error"); 43 | } 44 | 45 | error = null; 46 | try { 47 | await spawn("./task-resolves/symbol.js"); 48 | } catch(e) { 49 | error = e; 50 | } 51 | if (error === null) { 52 | throw new Error("task resolves to symbol did not throw an error"); 53 | } 54 | 55 | error = null; 56 | try { 57 | await spawn("./task-resolves/symbol-for.js"); 58 | } catch (e) { 59 | error = e; 60 | } 61 | if (error === null) { 62 | throw new Error("task resolve to Symbol.for(..) symbol did not throw an error"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/js/task-resolves/function.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return x => x * x; 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/task-resolves/number.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return 42; 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/task-resolves/object.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return { 4 | pinball: "wizard" 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /tests/js/task-resolves/string.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return "hello"; 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/task-resolves/symbol-for.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return Symbol.for("blah"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/task-resolves/symbol.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return new Symbol("I do not structured clone"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/task-resolves/undefined.js: -------------------------------------------------------------------------------- 1 | //# starling-not-a-test 2 | async function main() { 3 | return undefined; 4 | } 5 | -------------------------------------------------------------------------------- /tests/js/timeout-0.js: -------------------------------------------------------------------------------- 1 | // The `timeout` function defined via `js_native!` should exist. 2 | 3 | if (typeof timeout != "function") { 4 | throw new Error("typeof timeout != function"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/js/timeout-1.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stderr-has: error: tests/js/timeout-1.js:5:1: Not enough arguments to `timeout` 3 | 4 | // Not enough arguments 5 | timeout(); 6 | -------------------------------------------------------------------------------- /tests/js/timeout-2.js: -------------------------------------------------------------------------------- 1 | //# starling-fail 2 | //# starling-stderr-has: error: tests/js/timeout-2.js:5:1: value out of range in an EnforceRange argument 3 | 4 | // Wrong type of argument. 5 | timeout(_ => {}); 6 | -------------------------------------------------------------------------------- /tests/js/timeout-3.js: -------------------------------------------------------------------------------- 1 | //# starling-stdout-has: after await timeout 2 | 3 | async function main() { 4 | print("before await timeout"); 5 | await timeout(0); 6 | print("after await timeout"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/js/timeout-4.js: -------------------------------------------------------------------------------- 1 | // The task stops after `main` exits and then we flush the micro-task queue. Any 2 | // promise that hasn't settled is abandoned. Therefore, the error after the 3 | // timeout will not trigger our unhandled-rejected-promises task killing because 4 | // it will never execute and throw. 5 | 6 | async function main() { 7 | // No await. We want to test that the event loop stops getting pumped after 8 | // the `main` function exits. 9 | timeout(1).then(_ => { 10 | throw new Error(); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | extern crate starling; 2 | 3 | use std::path::Path; 4 | use std::process::Command; 5 | 6 | fn assert_starling_run_file

( 7 | path: P, 8 | expect_success: bool, 9 | stdout_has: &[&'static str], 10 | stderr_has: &[&'static str], 11 | ) where 12 | P: AsRef, 13 | { 14 | let path = path.as_ref().display().to_string(); 15 | 16 | let output = Command::new(env!("STARLING_TEST_EXECUTABLE")) 17 | .arg(&path) 18 | .output() 19 | .expect("Should spawn `starling` OK"); 20 | 21 | let was_success = output.status.success(); 22 | 23 | if expect_success { 24 | assert!(was_success, "should exit OK: {}", path); 25 | } else { 26 | assert!(!was_success, "should fail: {}", path); 27 | } 28 | 29 | let stdout = String::from_utf8_lossy(&output.stdout); 30 | for s in stdout_has { 31 | assert!( 32 | stdout.contains(s), 33 | "should have '{}' in stdout for {}", 34 | s, 35 | path 36 | ); 37 | } 38 | 39 | let stderr = String::from_utf8_lossy(&output.stderr); 40 | for s in stderr_has { 41 | assert!( 42 | stderr.contains(s), 43 | "should have '{}' in stderr for {}", 44 | s, 45 | path 46 | ); 47 | } 48 | } 49 | 50 | include!(concat!(env!("OUT_DIR"), "/js_tests.rs")); 51 | --------------------------------------------------------------------------------