├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── README.md.skt.md ├── doc └── paper │ ├── .gitignore │ ├── ACM-Reference-Format.bst │ ├── acmart.cls │ ├── api.tex │ ├── appendix.lagda │ ├── compartments.tex │ ├── gc.tex │ ├── intro.tex │ ├── macros.sty │ ├── outro.tex │ ├── paper.bib │ ├── paper.pdf │ └── paper.tex ├── examples ├── dbllist │ ├── dbllist.rs │ └── main.rs └── minidom │ ├── fake_codegen.rs │ ├── main.rs │ └── minidom.rs ├── josephine-derive ├── Cargo.toml └── lib.rs └── src ├── compartment.rs ├── context.rs ├── ffi.rs ├── lib.rs ├── managed.rs ├── root.rs ├── runtime.rs ├── string.rs └── trace.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | /target/ 6 | /josephine-derive/target/ 7 | 8 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 9 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 10 | Cargo.lock 11 | 12 | # These are backup files generated by rustfmt 13 | **/*.rs.bk 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: 9000 3 | dist: trusty 4 | rust: 5 | - nightly 6 | 7 | cache: cargo 8 | 9 | before_install: 10 | # Make sure there's only one josephine crate in the deps directory, 11 | # which is needed for the rustdoc script. 12 | - rm -rf target/debug/deps/*josephine* 13 | 14 | addons: 15 | apt: 16 | sources: 17 | # Provides newer gcc. 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - autoconf2.13 21 | - gcc-5 22 | - g++-5 23 | 24 | env: 25 | global: 26 | - CC="gcc-5" 27 | - CXX="g++-5" 28 | # Where to fetch the smup version of mozjs from 29 | - SMUP_GIT="https://github.com/fitzgen/mozjs" 30 | - SMUP_BRANCH="smup-smup-smup" 31 | matrix: 32 | - CARGO_FLAGS="" 33 | - CARGO_FLAGS="--release" 34 | - CARGO_FLAGS="--features debugmozjs" 35 | - CARGO_FLAGS="--features debugmozjs --release" 36 | - CARGO_FLAGS="--features smup" SMUP=true 37 | - CARGO_FLAGS="--features smup --release" SMUP=true 38 | 39 | script: 40 | # If we're doing a smup, patch in the smup repo 41 | - if [ $SMUP ]; then echo -e "\n[patch.crates-io]\nmozjs = { git = \"$SMUP_GIT\", branch = \"$SMUP_BRANCH\" }" >> Cargo.toml; fi 42 | - cat Cargo.toml 43 | - cargo build $CARGO_FLAGS -vv 44 | - cargo test --doc $CARGO_FLAGS 45 | # The minidom example currently doesn't work with the smup 46 | - if [ ! $SMUP ]; then cargo run $CARGO_FLAGS --example minidom; fi 47 | - cargo run $CARGO_FLAGS --example dbllist 48 | - if [ -z "$CARGO_FLAGS" ]; then rustdoc -L target/debug/deps/ --test README.md; fi 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "josephine" 3 | version = "0.2.0" 4 | authors = ["ajeffrey@mozilla.com"] 5 | license = "MPL-2.0" 6 | description = "Josephine: using JavaScript to safely manage the lifetimes of Rust data" 7 | repository = "https://github.com/asajeffrey/josephine/" 8 | 9 | [lib] 10 | 11 | [features] 12 | debugmozjs = ["mozjs/debugmozjs"] 13 | smup = [] 14 | 15 | [dependencies] 16 | josephine_derive = "0.1.0" 17 | libc = "0.2.30" 18 | log = "0.3" 19 | mozjs = "<0.3" # Requires the smup feature for mozjs 0.2.* 20 | 21 | [dev-dependencies] 22 | env_logger = "0.4" 23 | 24 | [[example]] 25 | name = "minidom" 26 | path = "examples/minidom/main.rs" 27 | 28 | [[example]] 29 | name = "dbllist" 30 | path = "examples/dbllist/main.rs" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Josephine: using JavaScript to safely manage the lifetimes of Rust data 2 | 3 | [![Build Status](https://travis-ci.org/asajeffrey/josephine.svg?branch=master)](https://travis-ci.org/asajeffrey/josephine) 4 | 5 | ( 6 | [Doc](https://docs.rs/josephine/) | 7 | [CI](https://travis-ci.org/asajeffrey/josephine) 8 | ) 9 | 10 | The josephine crate allows Rust data to be attached to JavaScript objects: 11 | the lifetime of the Rust data is then the same as the JS object it is attached to. 12 | Since JS is garbage collected, it is safe to copy and discard references to 13 | JS managed data, and allows examples like doubly-linked lists which would 14 | otherwise require reference counting. Reference counting requires dynamic checks, 15 | for example getting mutable access to reference-counted data panics if the reference 16 | count is more than 1. 17 | 18 | The goals are: 19 | 20 | 1. Use JS to manage the lifetime of Rust data. 21 | 2. Allow references to JS managed data to be freely copied and discarded, relying on 22 | the garbage collector for safety. 23 | 3. Maintain Rust memory safety (for example no mutable aliasing), 24 | without requiring additional static analysis such as a lint. 25 | 4. Allow mutable and immutable access to Rust data via JS managed references, so 26 | we do not need to rely on interior mutability. 27 | 5. Provide a rooting API to ensure that JS managed data is not garbage collected 28 | while it is being used. 29 | 30 | To support safe access to JS managed data, the API uses a *JS context*, which 31 | is used as an access token to allow JS managed data to be accessed, allocated 32 | or deallocated. Mutable access to JS managed data requires mutable access to the 33 | JS context, which is how the API achieves memory safety even though JS managed 34 | references can be copied and discarded freely. 35 | 36 | JS managed memory is split into *compartments*, which are 37 | separately garbage collected, so the garbage collector can avoid 38 | scanning the entire heap. The API statically tracks compartments, to 39 | ensure that there are no direct references between compartments. 40 | 41 | The API is implemented as bindings to the SpiderMonkey JS engine, 42 | from which it borrows the garbage collector and the notions of compartment 43 | and JS context. The API allows calling into JavaScript 44 | from Rust, and calling back into Rust from JavaScript. These bindings are unsafe, 45 | and are intended for use by a trusted bindings generator. 46 | 47 | ## Using 48 | 49 | The josephine crate is based on the `rust-mozjs` crate for the SpiderMonkey engine, 50 | which uses nightly features, so `josephine` also requires nightly. 51 | 52 | In your `Cargo.toml`: 53 | ```toml 54 | [dependencies] 55 | josephine = "0.1" 56 | ``` 57 | Build with `cargo +nightly build`. 58 | 59 | ## Examples 60 | 61 | A minimal example, which uses JS to manage the lifetime of a string: 62 | 63 | ```rust 64 | # extern crate josephine; 65 | use ::josephine::JSContext; 66 | 67 | // Giving JavaScript some data to manage. 68 | pub fn main() { 69 | // Create a new JavaScript context. 70 | // All interaction with JavaScript takes place in a context. 71 | let ref mut cx = JSContext::new().expect("Failed to initialize JS"); 72 | 73 | // Create a new compartment in that context. 74 | // All memory managed by JavaScript is divided into compartments, 75 | // which do not directly refer to each other. 76 | // Each compartment has a global object, which we can 77 | // give some Rust data to manage. 78 | let native = String::from("hello"); 79 | let ref cx = cx.create_compartment().global_manage(native); 80 | 81 | // Check that the global contains the expected "hello" message. 82 | let global = cx.global(); 83 | assert_eq!(global.borrow(cx), "hello"); 84 | } 85 | ``` 86 | 87 | Larger examples are: 88 | 89 | * [Doubly linked lists](examples/dbllist): an implementation of a simple cyclic data structure. 90 | Run with `cargo +nightly run --example dbllist`. 91 | * [A fragment of the DOM](examples/minidom): A tiny subset of the HTML Document Object Model. 92 | Run with `cargo +nightly run --example minidom`. 93 | -------------------------------------------------------------------------------- /README.md.skt.md: -------------------------------------------------------------------------------- 1 | ```rust,skt-josephine 2 | extern crate josephine; 3 | {} 4 | ``` 5 | -------------------------------------------------------------------------------- /doc/paper/.gitignore: -------------------------------------------------------------------------------- 1 | *.aux 2 | *.bbl 3 | *.blg 4 | *.dvi 5 | *.fdb_latexmk 6 | *.fls 7 | *.log 8 | *.out 9 | *.xcp 10 | *.agdai 11 | -------------------------------------------------------------------------------- /doc/paper/api.tex: -------------------------------------------------------------------------------- 1 | \section{The Josephine API} 2 | 3 | There are two important concepts in Josephine's API: \emph{JS-managed} data, 4 | and the JS \emph{context}. For readers familiar with the region-based 5 | variant~\cite{l3-with-regions} of $L^3$~\cite{l3}, JS-managed data 6 | corresponds to $L^3$ references, and JS contexts to $L^3$ capabilities. 7 | 8 | \subsection{JS-managed data} 9 | 10 | JS-managed data has the type $\JSManaged{\alpha, C, T}$, which represents 11 | a reference to data whose lifetime is managed by JS, which: 12 | \begin{itemize} 13 | 14 | \item is guaranteed to live at least as long as $\alpha$, 15 | \item is allocated in JS compartment $C$, and 16 | \item points to native data of type $T$. 17 | 18 | \end{itemize} 19 | This type is copyable, so not subject to the affine type discipline, 20 | even though it can be used to gain mutable access to the native 21 | data. We shall see later that this is safe for the same reason as 22 | $L^3$: we are using the JS context as a capability, and it is not 23 | copyable. 24 | 25 | In examples, we make use of Rust's \emph{lifetime elision}~\cite[\S3.4]{rustinomicon}, 26 | and just write $\JSManaged{C,T}$ where the lifetime $\alpha$ can be 27 | inferred. 28 | 29 | In the simplest case, $T$ is a base type like $\STRING$, but in more complex 30 | cases, $T$ might itself contain JS-managed data, for example a type of 31 | cells in a doubly-linked list can be defined: 32 | \begin{verbatim} 33 | type Cell<'a, C> = JSManaged<'a, C, NativeCell<'a, C>>; 34 | \end{verbatim} 35 | where: 36 | \begin{verbatim} 37 | struct NativeCell<'a, C> { 38 | data: String, 39 | prev: Option>, 40 | next: Option>, 41 | } 42 | \end{verbatim} 43 | This pattern is a common idiom, in that there are two types: 44 | \begin{itemize} 45 | \item $\NativeCell{\alpha,C}$ containing the native representation 46 | of a cell, including the prev and next 47 | references, and 48 | \item $\Cell{\alpha,C}$ containing a reference to a native cell, 49 | whose lifetime is managed by JS. 50 | \end{itemize} 51 | These types are both parameterized by a lower bound $\alpha$ on the lifetime 52 | of the cell, and the compartment $C$ that the cell lives in. 53 | 54 | Doubly-linked lists are an interesting example of programming in Rust, 55 | and indeed there is an introductory text \emph{Learning Rust With 56 | Entirely Too Many Linked Lists}~\cite{too-many-lists}, in which safe 57 | implementations of doubly-linked lists require interior mutability 58 | (and hence dynamic checks) and reference counting. 59 | 60 | \subsection{The JS context} 61 | 62 | By itself, JS-managed references are not much use: there has to be an 63 | API for creating and dereferencing them: this is the role of the 64 | JS \emph{context}, which acts as a capability for manipulating 65 | JS-managed data. The JS context is part of the SpiderMonkey API, 66 | where it is used to store state that is global to the runtime system. 67 | 68 | There is only one JS context per thread (and JS contexts cannot be shared 69 | or sent between threads) so unique access to the JS context implies unique 70 | access to all JS-managed data. We can use this to give safe mutable access 71 | to JS-managed data, since the JS context is a unique capability. 72 | 73 | The JS context has a state, notably keeping track of the current 74 | compartment, but also permissions such as ``allowed to create new 75 | references'' or ``allowed to dereference''. This state is tracked 76 | using phantom types, so the JS context 77 | has type $\JSContext{S}$, where $S$ is the current state. 78 | 79 | For example, a program to allocate a new JS-managed reference is: 80 | \begin{verbatim} 81 | let x: JSManaged = cx.manage(String::from("hello")); 82 | \end{verbatim} 83 | and a program to access a JS-managed reference is: 84 | \begin{verbatim} 85 | let msg: &String = x.borrow(cx); 86 | \end{verbatim} 87 | These programs make use of the JS context \verb|cx|. In order for the 88 | first example to typecheck: 89 | \begin{itemize} 90 | 91 | \item \verb|cx| must have type $\REFMUT{}\JSContext{S}$, where 92 | \item $S$ (the state of the context) must have permission to allocate 93 | references in $C$, and 94 | \item $C$ must be a compartment. 95 | 96 | \end{itemize} 97 | The second example is similar, except: 98 | \begin{itemize} 99 | 100 | \item we do not need mutable access to the context, and 101 | \item $S$ must have permission to access compartment $C$. 102 | 103 | \end{itemize} 104 | Fortunately, Rust has a \emph{trait} system (similar to Haskell's 105 | class system), which allows us to express these constraints. In the 106 | same way that $C$ and $S$ are phantom types, these are \emph{marker} 107 | traits with no computational value. The typing for 108 | the first example is: 109 | \begin{verbatim} 110 | (cx: &mut JSContext) where 111 | S: CanAlloc + InCompartment, 112 | C: Compartment, 113 | \end{verbatim} 114 | and for the second: 115 | \begin{verbatim} 116 | (cx: &JSContext) where 117 | S: CanAccess, 118 | C: Compartment, 119 | \end{verbatim} 120 | A program to mutably access a JS-managed reference is: 121 | \begin{verbatim} 122 | let msg: &mut String = x.borrow_mut(cx); 123 | \end{verbatim} 124 | at which point the fact that the JS context is an affine capability 125 | becomes important. The typing required for this is: 126 | \begin{verbatim} 127 | (cx: &mut JSContext) where 128 | S: CanAccess, 129 | C: Compartment, 130 | \end{verbatim} 131 | That is \emph{unique access to JS-managed data requires unique access to the JS context}, 132 | and so we cannot simultaneously have mutable access to two different JS-managed 133 | references. This is the same safety condition that region-based $L^3$ uses. 134 | 135 | For example, we can use this (together with Rust's built-in replace function 136 | which swaps the contents of a mutable reference) to replace the contents of a cell 137 | with a new value: 138 | \begin{verbatim} 139 | fn replace(self, cx: &'a mut JSContext, new_data: String) -> String where 140 | S: CanAccess, 141 | C: Compartment, 142 | { 143 | let ref mut old_data = self.0.borrow_mut(cx).data; 144 | replace(old_data, new_data) 145 | } 146 | \end{verbatim} 147 | 148 | \subsection{Typing access} 149 | \label{sec:typing-access} 150 | 151 | A first-cut type rule for accessing data in a typing context 152 | in which $S: \CanAccess$ and $C: \Compartment$ is: 153 | \begin{quote} 154 | if~$cx: \REF{} \JSContext{S}$ and $p: \JSManaged{C,T}$ 155 | then $p.\borrow(cx): \REF{} T$ 156 | \end{quote} 157 | (and similarly for mutable access) 158 | which is fine, but does not mention the lifetimes. Adding these in gives 159 | the type rule: 160 | \begin{quote} 161 | if~$cx: \REF{\alpha} \JSContext{S}$ and $p: \JSManaged{\alpha,C,T}$ 162 | then $p.\borrow(cx): \REF\alpha T$ 163 | \end{quote} 164 | which is correct, but assumes that the lifetime that the JS context 165 | has been borrowed for is exactly the same as the lifetime of the 166 | reference. Separating these gives (when $\alpha \subseteq \beta$): 167 | \begin{quote} 168 | if~$cx: \REF{\alpha} \JSContext{S}$ and $p: \JSManaged{\beta,C,T}$ 169 | then $p.\borrow(cx): \REF\alpha T$ 170 | \end{quote} 171 | This rule is still incorrect, but for a slightly subtle reason. 172 | It \emph{is} correct when $T$ is a base type, but fails in the case of 173 | a type which includes nested JS-managed references. If that were the 174 | rule, then we could write programs such as 175 | \begin{verbatim} 176 | let cell: Cell<'a, C> = ...; 177 | let next: Cell<'a, C> = cell.borrow(cx).next?; // cell is keeping next alive 178 | cell.borrow_mut(cx).next = None; // nothing is keeping next alive 179 | cx.gc(); // something that triggers GC 180 | next.borrow(cx); // this is a use-after-free 181 | \end{verbatim} 182 | The problem in this example is that after setting cell's next pointer to \verb|None|, 183 | there is nothing in JS keeping \verb|next| alive, so it is reachable 184 | from Rust but not from JS. After a GC, the JS runtime can deallocate 185 | \verb|next|, so accessing it is a use-after-free error. 186 | 187 | In a language with built-in support for GC, there would be a hidden 188 | GC root introduced by putting \verb|next| on the stack, but Rust does 189 | not have support for such hidden rooting. 190 | 191 | The problem in general is that when accessing $p:\JSManaged{\beta,C,T}$, 192 | using a JS context borrowed 193 | for lifetime $\alpha\subseteq\beta$, there may be nested JS-managed 194 | data, also with lifetime $\beta$. These are being kept alive by $p$, 195 | which is fine as long as $p$ is not mutated, but mutating $p$ 196 | might cause them to become unreachable in JS, and thus candidates 197 | for garbage collection. 198 | 199 | The fix used by Josephine is to replace any nested uses of 200 | $\beta$ in $T$ by $\alpha$, that is the type rule is 201 | (when $\alpha \subseteq \beta$): 202 | \begin{quote} 203 | if~$cx: \REF{\alpha} \JSContext{S}$ and $p: \JSManaged{\beta,C,T}$ 204 | then $p.\borrow(cx): \REF\alpha T[\alpha/\beta]$ 205 | \end{quote} 206 | The conjecture that Josephine makes is that this is safe, because 207 | GC cannot happen during the lifetime $\alpha$. In order to ensure 208 | this, we maintain an invariant: 209 | \begin{quote}\em 210 | Any operation that can trigger garbage collection 211 | requires mutable access to the JS context. 212 | \end{quote} 213 | This is why \verb|cx.manage(data)| requires 214 | \verb|cx| to have type $\REFMUT{}\JSContext{S}$, \emph{not} because 215 | we are mutating the JS context itself, but because allocating 216 | a new reference might trigger GC. 217 | 218 | In Rust, the substitution $T[\alpha/\beta]$ is expressed by 219 | $T$ implementing a trait $\JSLifetime{\alpha}$: 220 | \begin{verbatim} 221 | pub unsafe trait JSLifetime<'a> { 222 | type Aged; 223 | unsafe fn change_lifetime(self) -> Self::Aged { ... } 224 | } 225 | \end{verbatim} 226 | This is using an \emph{associated type} $T\cc\Aged$ 227 | to represent $T[\alpha/\beta]$. 228 | In particular, $\JSManaged{\beta,C,T}$ implements 229 | $\JSLifetime{\alpha}$ as long as $T$ does, 230 | and $\JSManaged{\beta,C,T}[\alpha/\beta]$ is $\JSManaged{\alpha,C,T[\alpha/\beta]}$. 231 | 232 | The implementation of $\JSLifetime{\alpha}$ has a lot of boilerplate, 233 | but fortunately that boilerplate is amenable to Rust's metaprogramming 234 | system, so user-defined types can just mark their type as 235 | \verb|#[derive(JSLifetime)]|. 236 | -------------------------------------------------------------------------------- /doc/paper/compartments.tex: -------------------------------------------------------------------------------- 1 | \section{Compartments} 2 | 3 | SpiderMonkey uses \emph{compartments} to organize memory, 4 | so that garbage collection does not have to sweep the 5 | entire memory, just one compartment\footnote{% 6 | For purposes of this paper, we are ignoring the distinction between zones and 7 | compartments. 8 | } 9 | To achieve this, SpiderMonkey maintains the invariant: 10 | \begin{quote}\em 11 | There are no direct references between compartments. 12 | \end{quote} 13 | This invariant is expected to be maintained by 14 | any native data: tracing a JS-managed object 15 | should never result in tracing an object from a different 16 | compartment. 17 | 18 | In Josephine, the compartment that 19 | native data has been placed in is part of its 20 | type. Data of type $\JSManaged{C,T}$ is attached 21 | to a JS object in compartment $C$. 22 | 23 | \subsection{Maintaining the invariant} 24 | 25 | It would be possible for user-defined types to break 26 | the compartment invariant, for example: 27 | \begin{verbatim} 28 | type BadCell<'a, C, D> = JSManaged<'a, C, NativeBadCell<'a, C, D>>; 29 | \end{verbatim} 30 | where: 31 | \begin{verbatim} 32 | struct NativeBadCell<'a, C, D> { 33 | data: String, 34 | prev: Option>, 35 | next: Option>, 36 | } 37 | \end{verbatim} 38 | This type violates the compartment invariant, because 39 | a cell of type \verb|BadCell<'a, C, D>| is in compartment 40 | \verb|C| but its next pointer is in compartment \verb|D|. 41 | 42 | To maintain the compartment invariant, we introduce 43 | a trait similar to \verb|JSLifetime|, but for compartments: 44 | \begin{verbatim} 45 | pub unsafe trait JSCompartmental { 46 | type ChangeCompartment; 47 | } 48 | \end{verbatim} 49 | In the same way that $\JSLifetime{\alpha}$ is used to implement 50 | lifetime substitution $T[\alpha/\beta]$, the trait $\JSCompartmental{C,D}$ 51 | is used to implement compartment substitution $T[D/C]$. A type $T$ implementing 52 | $\JSCompartmental{C,D}$ is asked to ensure that: 53 | \begin{itemize} 54 | 55 | \item $T$ is in compartment $C$, 56 | \item $T$ only contains references to other types implementing $\JSCompartmental{C,D}$, and 57 | \item $T\cc\ChangeCompartment$ is $T[D/C]$. 58 | 59 | \end{itemize} 60 | If the implementation of this type is incorrect, there may be safety issues, 61 | which is why the trait is marked as \verb|unsafe|. Fortunately, deriving an 62 | implementation of this trait is straightforward meta-programming. 63 | Josephine provides a \verb|#[derive(JSCompartmental)| 64 | which is guaranteed to maintain 65 | the compartment invariant. 66 | 67 | \subsection{Creating compartments} 68 | 69 | In SpiderMonkey, a new compartment is created each time a 70 | \emph{global object}~\cite[\S18]{ecmascript} is created. 71 | 72 | In Josephine, there are two functions: one to create a new compartment, 73 | and another to attach native data to the global. The global object can 74 | be accessed with \verb|cx.global()|. For example: 75 | \begin{verbatim} 76 | let cx = cx.create_compartment(); 77 | let name = String::from("Alice"); 78 | let cx = cx.global_manage(name); 79 | \end{verbatim} 80 | In some cases, the global (in freshly created compartment $C$) 81 | contains some JS-managed data (also in compartment $C$), which is why 82 | the initialization is split into two steps. First, create compartment 83 | compartment $C$, then initialize the native data, which may make 84 | use of $C$. For example: 85 | \begin{verbatim} 86 | let cx = cx.create_compartment(); 87 | let ref mut root = cx.new_root(); 88 | let name = cx.manage(String::from("Alice")).in_root(root); 89 | let cx = cx.global_manage(NativeMyGlobal { name: name }); 90 | \end{verbatim} 91 | where: 92 | \begin{verbatim} 93 | struct NativeMyGlobal<'a, C> { name: JSManaged<'a, C, String> } 94 | type MyGlobal<'a, C> = JSManaged<'a, C, NativeMyGlobal<'a, C>>; 95 | \end{verbatim} 96 | The type rule for creating a compartment is: 97 | \begin{quote} 98 | if $cx:\REFMUT{\alpha}{\JSContext{S}}$ 99 | and $S: \CanAlloc + \CanAccess$ \\ 100 | then $cx.\createCompartment() : \JSContext{S'}$ \\\mbox{}\quad 101 | where $S': \alpha + \CanAlloc + \InCompartment{C} + \IsInitializing{\alpha,C,T}$ \\\mbox{}\qquad 102 | for fresh $C: \Compartment$. 103 | \end{quote} 104 | Note: 105 | \begin{itemize} 106 | 107 | \item this is the first type rule which has changed the state of 108 | the JS context from $S$ to $S'$, 109 | 110 | \item although $S$ can access data, $S'$ cannot: this is necessary for 111 | safety, since the global does not yet have any data attached to it, 112 | so accessing it would be undefined behaviour, 113 | 114 | \item $S'$ only has lifetime $\alpha$, so we do not have two JS contexts 115 | live simultaneously, 116 | 117 | \item $S'$ has entered the compartment $C$, and has the permission to create 118 | new objects in $C$, 119 | 120 | \item $S'$ has the permission $\IsInitializing{C,T}$, which allows the global 121 | to be initialized with native data of type $T$. 122 | 123 | \end{itemize} 124 | The type rule for initializing a compartment is: 125 | \begin{quote} 126 | if $cx: \JSContext{S}$ 127 | and $x: T$ 128 | and $S: \IsInitializing{\alpha,C,T}$ \\ 129 | then $cx.\globalManage(x) : \JSContext{S'}$ \\\mbox{}\quad 130 | where $S': \alpha + \CanAccess + \CanAlloc + \InCompartment{C}$ 131 | \end{quote} 132 | 133 | \subsection{Entering a compartment} 134 | \label{sec:compartment-entering} 135 | 136 | Given a JS-managed reference \verb|x|, we can enter its compartment with \verb|cx.enter_known_compartment(x)|. 137 | This returns a JS context whose current compartment is that of \verb|x|. 138 | For example, given a JS-managed reference \verb|x|, 139 | we can create a new JS-managed reference in the same compartment with: 140 | \begin{verbatim} 141 | let ref mut cx = cx.enter_known_compartment(x); 142 | let y = cx.manage(String::from("hello")); 143 | \end{verbatim} 144 | This has type rule: 145 | \begin{quote} 146 | if $cx: \REFMUT{\alpha}{\JSContext{S}}$ 147 | and $x: \JSManaged{C,T}$ \\ 148 | and $S: \CanAccess + \CanAlloc$ 149 | and $C: \Compartment$ \\ 150 | then $cx.\enterKnownCompartment(x) : \JSContext{S'}$ \\\mbox{}\quad 151 | where $S': \alpha + \CanAccess + \CanAlloc + \InCompartment{C}$ 152 | \end{quote} 153 | 154 | \subsection{Wildcard compartments} 155 | \label{sec:compartment-wildcard} 156 | 157 | Working with named compartments is fine when there is a fixed number of 158 | them, but not when the number of compartments is unbounded. For 159 | example, the type \verb|Vec>| contains a vector of managed 160 | objects, all in the same compartment, but sometimes you need a vector 161 | of objects in different compartments. This is the same problem that 162 | existential polymorphism~\cite{expoly}, and in particular 163 | Java wildcards~\cite[\S8.1.2]{jls} is designed to solve, and we adopt 164 | a similar approach. 165 | 166 | The wildcard is called \verb|SOMEWHERE|, which we will 167 | often abbreviate as $\SOMEWHERE$. 168 | $\JSManaged{\SOMEWHERE, T}$ refers to JS-managed data whose 169 | compartment is unknown. For example \verb|Vec>| 170 | contains a vector of managed objects, which may all be in different 171 | compartments. 172 | 173 | To create a wildcard, we use $x.\forgetCompartment()$, with type 174 | rule: 175 | \begin{quote} 176 | if $x: \JSManaged{C,T}$ 177 | then $x.\forgetCompartment(): \JSManaged{\SOMEWHERE,T[\SOMEWHERE/C]}$ 178 | \end{quote} 179 | Entering a wildcard compartment is the same as for a known compartment, 180 | but renames the compartment to a fresh name: 181 | \begin{quote} 182 | if $cx: \REFMUT{\alpha}{\JSContext{S}}$ 183 | and $x: \JSManaged{\SOMEWHERE,T}$ \\ 184 | and $S: \CanAccess + \CanAlloc$ \\ 185 | then $cx.\enterUnknownCompartment(x) : \JSContext{S'}$ \\\mbox{}\quad 186 | where $S': \alpha + \CanAccess + \CanAlloc + \InCompartment{D}$ \\\mbox{}\qquad 187 | for fresh $D: \Compartment$. 188 | \end{quote} 189 | We also have a function $cx.\entered()$ of type $\JSManaged{D,T[D/\SOMEWHERE]}$, 190 | which gives access to $x$ in its newly named compartment. 191 | Note that access to data in a wildcard compartment is not allowed 192 | (in the type system this is enforced since we do not have $\SOMEWHERE:\Compartment$), 193 | for example: 194 | \begin{verbatim} 195 | fn example(cx: &mut JSContext, x: JSManaged) where 196 | S: CanAccess, 197 | { 198 | // We can't access x without first entering its compartment. 199 | // Commenting out the next two lines gives an error 200 | // the trait `Compartment` is not implemented for `SOMEWHERE`. 201 | let ref mut cx = cx.enter_unknown_compartment(x); 202 | let x = cx.entered(); 203 | println!("Hello, {}.", x.borrow(cx)); 204 | } 205 | \end{verbatim} 206 | -------------------------------------------------------------------------------- /doc/paper/gc.tex: -------------------------------------------------------------------------------- 1 | \section{Interfacing to the garbage collector} 2 | 3 | Interfacing to the SpiderMonkey GC has two parts: 4 | \begin{itemize} 5 | 6 | \item \emph{tracing}: from a JS-managed reference, find 7 | the JS-managed references immediately reachable from it, and 8 | 9 | \item \emph{rooting}: find the JS-managed references which 10 | are reachable from the stack. 11 | 12 | \end{itemize} 13 | From these two functions, it is possible to find all of the 14 | JS-managed references which are reachable from Rust. Together 15 | with SpiderMonkey's regular GC, this allows the runtime to 16 | find all of the reachable JS objects, and then to reclaim the 17 | unreachable ones. 18 | 19 | These interfaces are important for program correctness, since 20 | under-approximation can result in use-after-free, 21 | and over-approximation can result in space leaks. 22 | 23 | In this section, we discuss how Josephine supports these interfaces. 24 | 25 | \subsection{Tracing} 26 | \label{sec:tracing} 27 | 28 | Interfacing to the SpiderMonkey tracer via Josephine is achieved 29 | in the same way as Servo~\cite{servo}, 30 | by implementing a trait: 31 | \begin{verbatim} 32 | pub unsafe trait JSTraceable { 33 | unsafe fn trace(&self, trc: *mut JSTracer); 34 | } 35 | \end{verbatim} 36 | Josephine provides an implementation: 37 | \begin{verbatim} 38 | unsafe impl<'a, C, T> JSTraceable for JSManaged<'a, C, T> where 39 | T: JSTraceable { ... } 40 | \end{verbatim} 41 | User-defined types can then implement the interface by 42 | recursively visiting fields, for example: 43 | \begin{verbatim} 44 | unsafe impl<'a, C, T> JSTraceable for NativeCell<'a, C> { 45 | unsafe fn trace(&self, trc: *mut JSTracer) { 46 | self.prev.trace(trc); 47 | self.next.trace(trc); 48 | } 49 | } 50 | \end{verbatim} 51 | This is a lot of unsafe boilerplate, but fortunately can 52 | also be mechanized using meta-programming by marking a type 53 | as \verb|#[derive(JSTraceable)]|. 54 | 55 | One subtlety is that during tracing data of type $T$, the JS runtime 56 | has a reference of type $\REF{}{T}$ given by the \verb|self| parameter 57 | to \verb|trace|. For this to be safe, we have to ensure that there is 58 | no mutable reference to that data. This is maintained by the 59 | previously mentioned invariant: 60 | \begin{quote}\em 61 | Any operation that can trigger garbage collection 62 | requires mutable access to the JS context. 63 | \end{quote} 64 | Tracing is triggered by garbage collection, and so had unique access 65 | to the JS context, so there cannot be any other live mutable access 66 | to any JS-managed data. 67 | 68 | \subsection{Rooting} 69 | \label{sec:rooting} 70 | 71 | In languages with native support for GC, rooting is 72 | supported by the compiler, which can provide metadata for 73 | each stack frame allowing it to be traced. In languages like 74 | Rust that do not have a native GC, this metadata is not 75 | present, and instead rooting has to be performed explicitly. 76 | 77 | This explicit rooting is needed whenever an object is 78 | needed to outlive the borrow of the JS context that produced 79 | it. For example, a function to insert a new cell after 80 | an existing one is: 81 | \begin{verbatim} 82 | pub fn insert(cell: Cell, data: String, cx: &mut JSContext) where 83 | S: CanAccess + CanAlloc + InCompartment, 84 | C: Compartment, 85 | { 86 | let old_next = cell.borrow(cx).next; 87 | let new_next = cx.manage(NativeCell { 88 | data: data, 89 | prev: Some(cell), 90 | next: old_next, 91 | }); 92 | cell.borrow_mut(cx).next = Some(new_next); 93 | if let Some(old_next) = old_next { 94 | old_next.borrow_mut(cx).prev = Some(new_next); 95 | } 96 | } 97 | \end{verbatim} 98 | This is the ``code you would first think of'' for inserting an 99 | element into a doubly-linked list, but is in fact not safe because 100 | the local variables \verb|old_next| and \verb|new_next| have not been 101 | rooted. If GC were triggered just after \verb|new_next| was created, 102 | then it could be reclaimed, causing a later use-after-free. 103 | 104 | Fortunately, Josephine will catch these safety problems, and report 105 | errors such as: 106 | \begin{verbatim} 107 | error[E0502]: cannot borrow `*cx` as mutable because 108 | it is also borrowed as immutable 109 | | 110 | | let old_next = self.borrow(cx).next; 111 | | -- immutable borrow occurs here 112 | | let new_next = cx.manage(NativeCell { 113 | | ^^ mutable borrow occurs here 114 | ... 115 | | } 116 | | - immutable borrow ends here 117 | \end{verbatim} 118 | The fix is to explicitly root the local variables. In Josephine this is: 119 | \begin{verbatim} 120 | let ref mut root1 = cx.new_root(); 121 | let ref mut root2 = cx.new_root(); 122 | let old_next = (... as before ...).in_root(root1); 123 | let new_next = (... as before ...).in_root(root2); 124 | \end{verbatim} 125 | The declaration of a \verb|root| allocates space on the stack 126 | for a new root, and \verb|managed.in_root(root)| roots \verb|managed|. 127 | Note that it is just the reference that is copied to the stack, 128 | the JS-managed data itself doesn't move. 129 | Roots have type $\JSRoot{\beta,T}$ where $\beta$ is the lifetime 130 | of the root, and $T$ is the type being rooted. 131 | 132 | Once the local variables are rooted, the code typecheck, 133 | because rooting changes the lifetime of the JS-managed 134 | data, for example (when $\alpha \subseteq \beta$): 135 | \begin{quote} 136 | if~$p: \JSManaged{\alpha,C,T}$ \\ 137 | and~$r: \REFMUT{\beta}{\JSRoot{\beta,\JSManaged{\beta,C,T[\beta/\alpha]}}}$ \\ 138 | then~$p.\inRoot(r): \JSManaged{\beta,C,T[\beta/\alpha]}$. 139 | \end{quote} 140 | Before rooting, the JS-managed data had lifetime $\alpha$, 141 | which is usually the lifetime of the borrow of the JS context 142 | that created or accessed it. 143 | After rooting, the JS-managed data has lifetime $\beta$, 144 | which is the lifetime of the root itself. Since roots are 145 | considered reachable by GC, the contents of a root 146 | are guaranteed not to be GC'd during its lifetime, 147 | so this rule is sound. 148 | 149 | Note that this use of substitution $T[\beta/\alpha]$ 150 | is being used to extend the lifetime of the 151 | JS-managed data, since $\alpha\subseteq\beta$. This 152 | is in comparison to the use of substitution $T[\alpha/\beta]$ 153 | in \S\ref{sec:typing-access}, which was used to contract the 154 | lifetime. 155 | 156 | -------------------------------------------------------------------------------- /doc/paper/intro.tex: -------------------------------------------------------------------------------- 1 | \section{Introduction} 2 | 3 | This paper is about the interface between languages 4 | which use a garbage collector and those which use fancy 5 | types for safe manual memory management. 6 | 7 | Garbage collection is the most common memory management technique for 8 | functional programming languages, dating back to LISP~\cite{LISP}. 9 | Having a garbage collector guarantees memory safety, but at the 10 | cost of requiring a runtime system. 11 | 12 | Imperative languages often require the programmer to perform 13 | manual memory management, such as the \verb|malloc| and \verb|free| 14 | functions provided by C~\cite{K+R}. The safety of a program 15 | (in particular the absence of \emph{use-after-free} errors) 16 | is considered the programmer's problem. 17 | More recently, languages such as Cyclone~\cite{cyclone} 18 | and Rust~\cite{rust} have used fancy type systems 19 | such as substructural types~\cite{girard,Go4,walker} 20 | and region analysis~\cite{regions} to guarantee memory 21 | safety without garbage collection. 22 | 23 | This paper discusses the Josephine API~\cite{josephine} for using the 24 | garbage collector provided by the SpiderMonkey~\cite{spidermonkey} 25 | JavaScript runtime to safely manage the lifetime of Rust~\cite{rust} 26 | data. It uses techniques from $L^3$~\cite{l3} and its application 27 | to regions~\cite{l3-with-regions}, but the application to languages 28 | with a tracing garbage collector, and to languages with explicit 29 | lifetimes is new. 30 | 31 | \subsection{Rust} 32 | 33 | Rust is a systems programming language which uses fancy types to 34 | ensure memory safety even in the presence of mutable update, and 35 | manual memory management. Rust has an affine type system, which 36 | allows data to be discarded but does not allow data to be arbitrarily 37 | copied. For example, the Rust program: 38 | \begin{verbatim} 39 | let hello = String::from("hello"); 40 | let moved = hello; 41 | println!("Oh look {} is hello", moved); 42 | \end{verbatim} 43 | is fine, but the program: 44 | \begin{verbatim} 45 | let hello = String::from("hello"); 46 | let copied = hello; 47 | println!("Oh look {} is {}", hello, copied); 48 | \end{verbatim} 49 | is not, since \verb|hello| and \verb|copied| are simultaneously live. Trying to compile 50 | this program produces: 51 | \begin{verbatim} 52 | use of moved value: `hello` 53 | --> src/main.rs:4:32 54 | | 55 | 3 | let copied = hello; 56 | | ------ value moved here 57 | 4 | println!("Oh look {} is {}", hello, copied); 58 | | ^^^^^ value used here after move 59 | \end{verbatim} 60 | The use of affine types allows aliasing to be tracked. For example, a 61 | classic problem with aliasing is appending a string to itself. In 62 | Rust, an example of appending a string is: 63 | \begin{verbatim} 64 | let mut hello = String::from("hello"); 65 | let ref world = String::from("world"); 66 | hello.push_str(world); 67 | println!("Oh look hello is {}", hello); 68 | \end{verbatim} 69 | The important operation is \verb|hello.push_str(world)|, which mutates the 70 | string \verb|hello| (hence the \verb|mut| annotation on the declaration of \verb|hello|). 71 | The appended string \verb|world| is passed by reference, 72 | (hence the \verb|ref| annotation on the declaration of \verb|world|). 73 | 74 | A problem with mutably appending strings is ensuring that the string 75 | is not appended to itself, for example the documentation for 76 | C \verb|strcat| states ``Source and destination may not 77 | overlap,'' but C does not check aliasing and relies on the programmer 78 | to ensure correctness. In contrast, attempting to append a string 79 | to itself in Rust: 80 | \begin{verbatim} 81 | let ref mut hello = String::from("hello"); 82 | hello.push_str(hello); 83 | \end{verbatim} 84 | produces an error: 85 | \begin{verbatim} 86 | cannot borrow `*hello` as immutable because it is also borrowed as mutable 87 | --> src/main.rs:3:18 88 | | 89 | 3 | hello.push_str(hello); 90 | | ----- ^^^^^- mutable borrow ends here 91 | | | | 92 | | | immutable borrow occurs here 93 | | mutable borrow occurs here 94 | \end{verbatim} 95 | In Rust, the crucial invariant maintained by affine types is: 96 | \begin{quote}\em 97 | Any memory that can be reached simultaneously by two different paths 98 | is immutable. 99 | \end{quote} 100 | For example in \verb|hello.push(hello)| there are two occurrences of \verb|hello| that 101 | are live simultaneously, the first of which is mutating the string, so this is outlawed. 102 | 103 | In order to track statically which variables are live simultaneously, Rust uses a lifetime 104 | system similar to that used by region-based memory~\cite{regions}. Each allocation of 105 | memory has a lifetime $\alpha$, and lifetimes are ordered $\alpha\subseteq\beta$. 106 | Each code block introduces a lifetime, and for data which does not escape from its scope, 107 | the nesting of blocks determines the ordering of lifetimes. 108 | 109 | For example in the program: 110 | \begin{verbatim} 111 | let ref x = String::from("hi"); 112 | { 113 | let ref y = x; 114 | println!("y is {}", y); 115 | } 116 | println!("x is {}", x); 117 | \end{verbatim} 118 | the variable \verb|x| has a lifetime $\alpha$ given by the outer block, 119 | and the variable \verb|y| has a lifetime $\beta\subseteq\alpha$ given by the inner block. 120 | 121 | These lifetimes are mentioned in the types of references: the type $\REF\alpha T$ 122 | is a reference giving immutable access to data of type $T$, which will live at least as long as 123 | $\alpha$. Similarly, the type $\REFMUT\alpha T$ gives mutable access to the data: the crucial 124 | difference is that $\REF\alpha T$ is a copyable type, but $\REFMUT\alpha T$ is not. 125 | For example 126 | the type of \verb|x| is $\REF\alpha\STRING$ and the type of \verb|y| is 127 | $\REF\beta(\REF\alpha\STRING)$, which is well-formed because $\beta\subseteq\alpha$. 128 | 129 | Lifetimes are used to prevent another class of memory safety issues: use-after-free. 130 | For example, consider the program: 131 | \begin{verbatim} 132 | let hi = String::from("hi"); 133 | let ref mut handle = &hi; 134 | { 135 | let lo = String::from("lo"); 136 | *handle = &lo; 137 | } // lo is deallocated here 138 | println!("handle is {}", **handle); 139 | \end{verbatim} 140 | If this program were to execute, its behaviour would be undefined, 141 | since \verb|**handle| is used after \verb|lo| 142 | (which \verb|handle| points to) is deallocated. Fortunately, this program 143 | does not pass Rust's borrow-checker: 144 | \begin{verbatim} 145 | `lo` does not live long enough 146 | --> src/main.rs:6:11 147 | | 148 | 6 | *handle = &lo; 149 | | ^^ borrowed value does not live long enough 150 | 7 | } // lo is deallocated here 151 | | - `lo` dropped here while still borrowed 152 | 8 | println!("handle is {}", **handle); 153 | 9 | } 154 | | - borrowed value needs to live until here 155 | \end{verbatim} 156 | This use-after-free error can be detected because (naming the outer lifetime as 157 | $\alpha$ and the inner lifetime as $\beta\subseteq\alpha$) \verb|handle| has type 158 | $\REFMUT\alpha\REF\alpha\STRING$, but \verb|&lo| only has type $\REF\beta\STRING$, no 159 | $\REF\alpha\STRING$ as required by the assignment. 160 | 161 | Lifetimes avoid use-after-free by maintaining two invariants: 162 | \begin{quote}\em 163 | Any dereference happens during the lifetime of the reference, \\ 164 | and deallocation happens after the lifetime of all references. 165 | \end{quote} 166 | There is more to the Rust type system than described here 167 | (higher-order functions, traits, variance, concurrency, \dots) but the important features 168 | are \emph{affine types} and \emph{lifetimes} for ensuring memory safety, 169 | even in the presence of manual memory management. 170 | 171 | \subsection{SpiderMonkey} 172 | 173 | SpiderMonkey~\cite{spidermonkey} is Mozilla's JavaScript runtime, used in the Firefox browser, 174 | and the Servo~\cite{servo} next-generation web engine. This is a full-featured 175 | JS implementation, but the focus of this paper is its automatic memory management. 176 | 177 | Inside a web engine, there are often native implementations of HTML features, 178 | which are exposed to JavaScript using DOM interfaces. For example, an HTML image 179 | is exposed to JavaScript as a DOM object representing an \verb|| element, 180 | but behind the scenes there is native code responsible for loading and rendering 181 | images. 182 | 183 | When a JavaScript object is garbage collected, a destructor is called to 184 | deallocate any attached native memory. In the case that the native code 185 | is implemented in Rust, this leads to a situation where Rust relies on affine 186 | types and lifetimes for memory safety, but JavaScript respects neither of these. 187 | As a result, the raw SpiderMonkey interface to Rust is very unsafe, 188 | for example there are nearly 400 instances of unsafe code in the Servo 189 | DOM implementation: 190 | \begin{verbatim} 191 | $ grep "unsafe_code" components/script/dom/*.rs | wc 192 | 393 734 25514 193 | \end{verbatim} 194 | Since JavaScript does not respect Rust's affine types, 195 | Servo's DOM implementation makes use of Rust~\cite[\S3.11]{rust} 196 | \emph{interior mutability} which replaces the compile-time type 197 | checks with run-time dynamic checks. This carries run-time overhead, 198 | and the possibility of checks failing, and Servo panicking. 199 | 200 | Moreover, SpiderMonkey has its own invariants, and if an embedding 201 | application does not respect these invariants, then runtime errors can 202 | occur. One of these invariants is the division of JavaScript memory 203 | into \emph{compartments}~\cite{compartments}, which can be garbage collected 204 | separately. The runtime has a notion of ``current compartment'', 205 | and the embedding application is asked to maintain two invariants: 206 | \begin{itemize} 207 | \item whenever an object is used, the object is in the current compartment, and 208 | \item there are no references between objects which cross compartments. 209 | \end{itemize} 210 | In order for native code to interact well with the SpiderMonkey garbage collector, 211 | it has to provide two functions: 212 | \begin{itemize} 213 | \item a \emph{trace} function, that given an object, iterates over all of the 214 | JavaScript objects which are reachable from it, and 215 | \item a \emph{roots} function, which iterates over all of the JavaScript 216 | objects that are live on the call stack. 217 | \end{itemize} 218 | From these two functions, the garbage collector can find all of the reachable 219 | JavaScript objects, including those reachable from JavaScript directly, and 220 | those reached via native code. The Josephine interface to tracing 221 | is discussed in \S\ref{sec:tracing}, and the interface to rooting 222 | is discussed in \S\ref{sec:rooting}. 223 | 224 | Automatically generating the trace function is reasonably straightforward 225 | metaprogramming, but rooting safely turns out to be quite tricky. 226 | Servo provides an approximate analysis for safe rooting using an ad-hoc 227 | static analysis (the \emph{rooting lint}), but this is problematic because 228 | a) the lint is syntax-driven, so does not understand about Rust features 229 | such as generics, and b) even if it could be made sound it is disabled 230 | more than 200 times: 231 | \begin{verbatim} 232 | $ grep "unrooted_must_root" components/script/dom/*.rs | wc 233 | 213 456 15961 234 | \end{verbatim} 235 | 236 | \subsection{Josephine} 237 | 238 | Josephine~\cite{josephine} is intended to act as a safe bridge between 239 | SpiderMonkey and Rust. Its goals are: 240 | \begin{itemize} 241 | 242 | \item to use JavaScript to manage the lifetime of Rust data, 243 | and to allow JavaScript to garbage collect unreachable data, 244 | 245 | \item to allow references to JavaScript-managed data to be freely copied and discarded, 246 | relying on SpiderMonkey's garbage collector for safety, 247 | 248 | \item to maintain Rust memory safety via affine types and lifetimes, 249 | without requiring additional static analysis such as the rooting lint, 250 | 251 | \item to allow mutable and immutable access to Rust data via JavaScript-managed references, 252 | so avoiding interior mutability, and 253 | 254 | \item to provide a rooting API to ensure that JavaScript-managed data is not garbage collected 255 | while it is being used. 256 | 257 | \end{itemize} 258 | Josephine is intended to be safe, in that any programs built using Josephine's API 259 | do not introduce undefined behaviour or runtime errors. 260 | Josephine achieves this by providing controlled access to 261 | SpiderMonkey's \emph{JavaScript context}, and maintaining invariants about it: 262 | \begin{itemize} 263 | 264 | \item immutable access to JavaScript-managed data requires immutable access 265 | to the JavaScript context, 266 | 267 | \item mutable access to JavaScript-managed data requires mutable access 268 | to the JavaScript context, and 269 | 270 | \item any action that can trigger garbage collection (for example allocating 271 | new objects) requires mutable access to the JavaScript context. 272 | 273 | \end{itemize} 274 | As a result, since access to the JavaScript context does respect 275 | Rust's affine types, mutation or garbage collection cannot occur 276 | simultaneously with accessing JavaScript-managed data. 277 | 278 | In other words, Josephine treats the JavaScript context as an affine 279 | access token, or capability, which controls access to the JavaScript-managed 280 | data. The data accesses respect affine types, even though the JavaScript objects 281 | themselves do not. 282 | 283 | This use of an access token to safely access data in a substructural 284 | type system is \emph{not} new, it is the heart of Ahmed, Fluet and 285 | Morrisett's $L^3$ Linear Language with Locations~\cite{l3} and its 286 | application to linear regions~\cite{regions}. 287 | Moreover, type systems for mixed linear/non-linear programming have 288 | been known for more than 20 years~\cite{mixed}. 289 | 290 | Other integrations of GC with linear types include Linear Lisp~\cite{linear-lisp}, 291 | Alms~\cite{alms}, 292 | Linear Haskell~\cite{linear-haskell}, and 293 | linear OCaml~\cite{linear-ocaml}, but these do not integrate with Rust's 294 | lifetime model. 295 | 296 | Garbage collection for Rust has previously been investigated, 297 | e.g.~in Servo~\cite{servo-gc} or the rust-gc library~\cite{rust-gc}, 298 | but these approaches take a different approach: in Servo, the 299 | API by itself is unsafe and depends on interior mutability and 300 | a separate rooting lint for safety. The rust-gc library uses 301 | reference counting and interior mutability. Neither of them interact 302 | with lifetimes in the way Josephine does. 303 | 304 | The aspects of Josephine that are novel are: 305 | \begin{itemize} 306 | 307 | \item the languages being mixed are Rust and 308 | JavaScript, which are both industrial-strength, 309 | 310 | \item the treatment of garbage collection requires 311 | different typing rules than regions in $L^3$, and 312 | 313 | \item the types for JS-managed references respect the Rust 314 | lifetime system. 315 | 316 | \end{itemize} 317 | 318 | \subsection*{Acknowledgments} 319 | 320 | This work benefited greatly from conversations with 321 | Amal Ahmed, 322 | Nick Benton, 323 | Josh Bowman-Matthews, 324 | Manish Goregaokar, 325 | Bobby Holly, and 326 | Anthony Ramine. 327 | -------------------------------------------------------------------------------- /doc/paper/macros.sty: -------------------------------------------------------------------------------- 1 | \newcommand{\REF}[1]{\\,} 2 | \newcommand{\REFMUT}[1]{\\,\mathsf{mut}\,} 3 | \newcommand{\STRING}{\mathsf{String}} 4 | \newcommand{\Option}[1]{\mathsf{Option}\langle#1\rangle} 5 | \newcommand{\SOMEWHERE}{\textbf{?}} 6 | \newcommand{\CanAlloc}{\mathsf{CanAlloc}} 7 | \newcommand{\CanAccess}{\mathsf{CanAccess}} 8 | \newcommand{\Compartment}{\mathsf{Compartment}} 9 | \newcommand{\InCompartment}[1]{\mathsf{InCompartment}\langle #1\rangle} 10 | \newcommand{\IsInitializing}[1]{\mathsf{IsInitializing}\langle #1\rangle} 11 | \newcommand{\JSContext}[1]{\mathsf{JSContext}\langle #1\rangle} 12 | \newcommand{\JSManaged}[1]{\mathsf{JSManaged}\langle #1\rangle} 13 | \newcommand{\JSLifetime}[1]{\mathsf{JSLifetime}\langle #1\rangle} 14 | \newcommand{\JSCompartmental}[1]{\mathsf{JSCompartmental}\langle #1\rangle} 15 | \newcommand{\JSRoot}[1]{\mathsf{JSRoot}\langle #1\rangle} 16 | \newcommand{\Aged}{\mathsf{Aged}} 17 | \newcommand{\ChangeCompartment}{\mathsf{ChangeCompartment}} 18 | \newcommand{\Cell}[1]{\mathsf{Cell}\langle #1\rangle} 19 | \newcommand{\NativeCell}[1]{\mathsf{NativeCell}\langle #1\rangle} 20 | \newcommand{\borrow}{\mathsf{borrow}} 21 | \newcommand{\inRoot}{\mathsf{in\_root}} 22 | \newcommand{\createCompartment}{\mathsf{create\_compartment}} 23 | \newcommand{\globalManage}{\mathsf{global\_manage}} 24 | \newcommand{\enterKnownCompartment}{\mathsf{enter\_known\_compartment}} 25 | \newcommand{\enterUnknownCompartment}{\mathsf{enter\_unknown\_compartment}} 26 | \newcommand{\forgetCompartment}{\mathsf{forget\_compartment}} 27 | \newcommand{\inCompartment}{\mathsf{in\_compartment}} 28 | \newcommand{\entered}{\mathsf{entered}} 29 | 30 | \newcommand{\cc}{{::}} 31 | 32 | \def\kw#1{% 33 | \expandafter\ifx\csname#1\endcsname\relax 34 | \mathsf{#1}% 35 | \else 36 | \csname#1\expandafter\endcsname 37 | \fi 38 | } 39 | 40 | \def\setSlashActive{\catcode`\/=\active} 41 | 42 | {\setSlashActive 43 | \gdef\activeSlash#1/{\kw{#1}} 44 | \gdef\activateSlash{\setSlashActive\let/=\activeSlash} 45 | } 46 | 47 | \newenvironment{code}{\[\activateSlash\begin{array}{l}}{\end{array}\]} 48 | 49 | \newcommand{\Nat}{\mathbb{N}} 50 | \newcommand{\fun}{\mathbin\rightarrow} 51 | \newcommand{\init}{\rho_0} 52 | \newcommand{\p}{{}'} 53 | \newcommand{\pp}{{}''} 54 | \newcommand{\ppp}{{}'''} 55 | \newcommand{\LP}{L'} 56 | \newcommand{\rhoP}{\rho'} 57 | \newcommand{\sigmaP}{\sigma'} 58 | \newcommand{\GammaP}{\Gamma'} 59 | \newcommand{\DeltaP}{\Delta'} 60 | \newcommand{\rhoPP}{\rho''} 61 | \newcommand{\rhoPPP}{\rho'''} 62 | \newcommand{\bigStep}{\mathrel\Downarrow} 63 | \newcommand{\bigSubst}{\mathrel\Downarrow} 64 | \newcommand{\words}{} 65 | \newcommand{\var}{} 66 | \newcommand{\pat}{} 67 | \newcommand{\num}{} 68 | \newcommand{\deref}{} 69 | \newcommand{\ptr}{} 70 | \newcommand{\enum}{\mathop{\mathstrut\Sigma}} 71 | \newcommand{\tagged}{} 72 | \newcommand{\singleton}{} 73 | \newcommand{\cons}{} 74 | \newcommand{\append}{} 75 | \newcommand{\st}{\mathbin.} 76 | \newcommand{\comma}{,} 77 | \newcommand{\equals}{=} 78 | \newcommand{\with}{{::}} 79 | \newcommand{\where}{\mathrel{\mathrm{where}}} 80 | \newcommand{\wherenot}{\mathrel{\mathrm{unless}}} 81 | \newcommand{\andalso}{\mathrel{\mathrm{and}}} 82 | \newcommand{\member}{\in} 83 | \newcommand{\vdashv}{\vdash} 84 | \newcommand{\iflet}{\mathop{\mathsf{if\,let}}} 85 | \newcommand{\thn}{\,\{} 86 | \newcommand{\els}{\}\,\mathsf{else}\,\{} 87 | \newcommand{\telfi}{\}} 88 | \newcommand{\matches}{\mathrel{\mathrm{matches}}} 89 | \newcommand{\subst}{} 90 | \newcommand{\semi}{;} 91 | \newcommand{\override}{\oplus} 92 | \newcommand{\lift}{} 93 | \newcommand{\slice}{} 94 | \newcommand{\sem}{\llbracket} 95 | \newcommand{\mes}{\rrbracket} 96 | \newcommand{\val}{} 97 | \newcommand{\loc}{} 98 | \newcommand{\varref}{\mathop{\mathsf{ref}}} 99 | \newcommand{\mut}{\mathop{\mathsf{mut}}} 100 | \newcommand{\addr}{\&} 101 | \newcommand{\unit}{{()}} 102 | \newcommand{\step}{\longrightarrow} 103 | \newcommand{\period}{{.}} 104 | \newcommand{\lett}{\mathop{\mathsf{let}}} 105 | \newcommand{\local}{\mathop{\mathsf{local}}} 106 | \newcommand{\sizet}{\mathop{\mathsf{size}}} 107 | \newcommand{\sizep}{\mathop{\mathsf{size}}} 108 | \newcommand{\reft}{\&_} 109 | \newcommand{\after}{{}_} 110 | \newcommand{\of}{} 111 | \newcommand{\never}{{\bullet}} 112 | \newcommand{\always}{{\circ}} 113 | \newcommand{\Boxt}{\Box} 114 | \newcommand{\set}{{*}} 115 | 116 | \def\Lift(#1){{#1}_\bot} 117 | \def\List(#1){{#1}^*} 118 | -------------------------------------------------------------------------------- /doc/paper/outro.tex: -------------------------------------------------------------------------------- 1 | \section{Conclusions} 2 | 3 | The contributions of this work are: 4 | \begin{itemize} 5 | 6 | \item an implementation of the ideas in $L^3$~\cite{l3} to mixed 7 | linear/non-linear programming~\cite{mixed}, where the 8 | languages being mixed are Rust and JavaScript, 9 | 10 | \item a treatment of garbage collection (rather than region-based 11 | memory management) for such a system, and 12 | 13 | \item a treatment of how operations such as accessing, mutating, and 14 | rooting can change the lifetimes of objects. 15 | 16 | \end{itemize} 17 | The main item left for future work is formalizing the approach 18 | described here: memory safety is conjectured, but not proved 19 | formally. 20 | 21 | There are some aspects of the API which need more investigation: 22 | \begin{itemize} 23 | 24 | \item Other JavaScript engines take a different approach to 25 | rooting, notably V8 \emph{handle scopes}~\cite{v8-embedding}, 26 | which have different trade-offs. In terms of this paper, the 27 | roots are attached to the JS context, rather than stored 28 | on the stack. It would be interesting to compare these approaches. 29 | 30 | \item Josephine uses phantom types to track which compartment 31 | memory is allocated in, but does not support features such 32 | as \emph{cross-compartment wrappers}~\cite{compartments}, 33 | which allow references between compartments. 34 | 35 | \item In this paper, we have just used the SpiderMonkey runtime 36 | engine for its garbage collector, but it is a full-featured 37 | JavaScript engine, and it would be good to provide safe 38 | access to executing JS code. This would be simpler to achieve 39 | if there were a JS type system to generate bindings from, 40 | such as TypeScript~\cite{typescript}. 41 | 42 | \end{itemize} 43 | The distribution includes some simple examples such as doubly-linked lists 44 | and a stripped-down DOM, but more examples are needed to see if the API 45 | is usable for practical code. 46 | -------------------------------------------------------------------------------- /doc/paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{LISP, 2 | author = {McCarthy, J.}, 3 | title = {Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part {I}}, 4 | journal = {Commun. {ACM}}, 5 | volume = {3}, 6 | number = {4}, 7 | year = {1960}, 8 | pages = {184--195}, 9 | } 10 | 11 | @book{K+R, 12 | author = {Kernighan, B. W. and Ritchie, D. M.}, 13 | title = {The C Programming Language}, 14 | year = {1988}, 15 | edition = {2nd}, 16 | } 17 | 18 | @inproceedings{cyclone, 19 | author = {Jim, T. and Morrisett, J. G. and Grossman, D. and Hicks, M. W. and Cheney, J. and Wang, Y.}, 20 | title = {Cyclone: A Safe Dialect of {C}}, 21 | booktitle = {Proc. {USENIX}}, 22 | year = {2002}, 23 | pages = {275--288}, 24 | } 25 | 26 | @book{rust, 27 | author = {The Rust Project Developers}, 28 | title = {The Rust Programming Language}, 29 | note = {\url{https://doc.rust-lang.org/book}}, 30 | year = 2011, 31 | } 32 | 33 | @inproceedings{Go4, 34 | author = {Benton, P. N. and Bierman, G. M. and de Paiva, V. and Hyland, M.}, 35 | title = {A Term Calculus for Intuitionistic Linear Logic}, 36 | booktitle = {Proc. Int. Conf. Typed Lambda Calculi and Applications}, 37 | year = {1993}, 38 | pages = {75--90}, 39 | } 40 | 41 | @article{girard, 42 | author = {Girard, J.-Y.}, 43 | title = {Linear logic}, 44 | journal = {Theoretical Computer Science}, 45 | volume = 50, 46 | number = 1, 47 | pages = {1--102}, 48 | year = 1987, 49 | } 50 | 51 | @inproceedings{walker, 52 | author = {Walker, D.}, 53 | year = 2002, 54 | title = {Substructural Type Systems}, 55 | editor = {Pierce, B. C.}, 56 | booktitle = {Advanced Topics in Types and Programming Languages}, 57 | publisher = {MIT Press}, 58 | pages = {3-43}, 59 | } 60 | 61 | @article{regions, 62 | author = {Tofte, M. and Talpin, J.-P.}, 63 | title = {Region-Based Memory Management}, 64 | journal = {Inf. Comput.}, 65 | volume = {132}, 66 | number = {2}, 67 | year = {1997}, 68 | pages = {109--176}, 69 | } 70 | 71 | @misc{josephine, 72 | key = {Jeffrey, A.S.A.}, 73 | year = 2017, 74 | title = {Josephine}, 75 | note = {\url{https://docs.rs/josephine/}}, 76 | } 77 | 78 | @article{l3, 79 | title = {L3: A Linear Language with Locations}, 80 | author = {A. Ahmed and M. Fluet and G. Morrisett}, 81 | journal = {Fundamenta Informaticae}, 82 | volume = 77, 83 | number = 4, 84 | pages = {397--449}, 85 | year = 2007, 86 | } 87 | 88 | @inproceedings{l3-with-regions, 89 | title = {Linear Regions Are All You Need}, 90 | author = {M. Fluet and G. Morrisett and A. Ahmed}, 91 | booktitle = {Proc. European Symp. Programming}, 92 | pages = {7-21}, 93 | year = {2006}, 94 | } 95 | 96 | @misc{spidermonkey, 97 | key = {SpiderMonkey}, 98 | title = {SpiderMonkey}, 99 | note = {\url{https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey}}, 100 | } 101 | 102 | @book{rustinomicon, 103 | title = {The Rustinomicon}, 104 | author = {The Rust Project Developers}, 105 | note = {\url{https://doc.rust-lang.org/beta/nomicon/}}, 106 | year = 2011, 107 | } 108 | 109 | @inproceedings{mixed, 110 | title = {A Mixed Linear and Non-linear Logic: Proofs, Terms and Models}, 111 | author = {N. Benton}, 112 | booktitle = {Proc. Computer Science Logic}, 113 | pages = {121-135}, 114 | year = 1995, 115 | } 116 | 117 | @misc{servo, 118 | title = {Servo, the Parallel Browser Engine Project}, 119 | key = {The Servo Project Developers}, 120 | note = {\url{https://servo.org/}}, 121 | } 122 | 123 | @book{too-many-lists, 124 | author = {A. Beingessner and others}, 125 | title = {Learning {Rust} With Entirely Too Many Linked Lists}, 126 | note = {\url{http://cglab.ca/~abeinges/blah/too-many-lists/book/}}, 127 | } 128 | 129 | @misc{rust-gc, 130 | author = {M. Goregaokar and M. Layzell}, 131 | title = {Designing a {GC} in {Rust}}, 132 | note = {\url{https://manishearth.github.io/blog/2015/09/01/designing-a-gc-in-rust/}}, 133 | year = 2015, 134 | } 135 | 136 | @misc{servo-gc, 137 | author = {J. Matthews and K. McAllister}, 138 | title = {JavaScript: {Servo}'s only garbage collector}, 139 | note = {\url{https://research.mozilla.org/2014/08/26/javascript-servos-only-garbage-collector/}}, 140 | year = 2014, 141 | } 142 | 143 | @article{linear-lisp, 144 | author = {Baker, H. G.}, 145 | title = {Lively Linear {Lisp}: ``Look Ma, No Garbage!''}, 146 | journal = {{SIGPLAN} Not.}, 147 | volume = {27}, 148 | number = {8}, 149 | year = {1992}, 150 | pages = {89--98}, 151 | } 152 | 153 | @inproceedings{alms, 154 | author = {J. A. Tov and R. Pucella}, 155 | title = {Practical Affine Types}, 156 | booktitle = {Proc. ACM Symp. Principles of Programming Languages}, 157 | year = 2011, 158 | pages = {447-458}, 159 | } 160 | 161 | @article{linear-haskell, 162 | author = {Bernardy, J.-P. and Boespflug, M. and Newton, R. R. and Peyton Jones, S. and Spiwack, A.}, 163 | title = {Linear Haskell: Practical Linearity in a Higher-order Polymorphic Language}, 164 | journal = {Proc. ACM Program. Lang.}, 165 | volume = {2}, 166 | number = {POPL}, 167 | year = {2017}, 168 | pages = {5:1--5:29}, 169 | } 170 | 171 | @misc{linear-ocaml, 172 | author = {G. Munch-Maccagnoni}, 173 | title = {Resource Polymorphism}, 174 | year = 2018, 175 | note = {arXiv:1803.02796 [cs.PL]}, 176 | } 177 | 178 | @misc{v8-embedding, 179 | title = {V8 Embedder's Guide}, 180 | key = {V8}, 181 | note = {\url{https://github.com/v8/v8/wiki/Embedder\%27s-Guide}}, 182 | } 183 | 184 | @misc{compartments, 185 | author = {Andreas Gal}, 186 | title = {Compartments}, 187 | year = 2010, 188 | note = {\url{https://andreasgal.com/2010/10/13/compartments/}}, 189 | } 190 | 191 | @misc{typescript, 192 | title = {TypeScript}, 193 | key = {TypeScript}, 194 | note = {\url{https://www.typescriptlang.org/}}, 195 | } 196 | 197 | @article{expoly, 198 | author = {Cardelli, L. and Wegner, P.}, 199 | title = {On Understanding Types, Data Abstraction, and Polymorphism}, 200 | journal = {ACM Comput. Surv.}, 201 | volume = {17}, 202 | number = {4}, 203 | year = {1985}, 204 | pages = {471--523}, 205 | } 206 | 207 | @misc{ecmascript, 208 | title = {{ECMAScript} 2017 Language Specification (8th Ed.)}, 209 | key = {ECMAScript}, 210 | year = {2017}, 211 | note = {\url{https://www.ecma-international.org/ecma-262/}}, 212 | } 213 | 214 | @misc{jls, 215 | title = {The {Java} Language Specification ({SE} 9 Ed.)}, 216 | author = {J. Gosling and B. Joy and G. Steele and G. Bracha and A. Buckley and D. Smith}, 217 | year = {2017}, 218 | note = {\url{https://docs.oracle.com/javase/specs/jls/se9/html/}}, 219 | } 220 | -------------------------------------------------------------------------------- /doc/paper/paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asajeffrey/josephine/1f0563bd525ac9515c03919614b4eb16fa5d0887/doc/paper/paper.pdf -------------------------------------------------------------------------------- /doc/paper/paper.tex: -------------------------------------------------------------------------------- 1 | \documentclass[acmsmall]{acmart} 2 | \settopmatter{printfolios=false,printacmref=false} 3 | 4 | \usepackage{macros} 5 | 6 | %% \acmJournal{PACMPL} 7 | %% \acmVolume{1} 8 | %% \acmNumber{CONF} % CONF = POPL or ICFP or OOPSLA 9 | %% \acmArticle{1} 10 | %% \acmYear{2018} 11 | %% \acmMonth{1} 12 | %% \acmDOI{} % \acmDOI{10.1145/nnnnnnn.nnnnnnn} 13 | %% \startPage{1} 14 | 15 | \setcopyright{none} 16 | \pagestyle{plain} 17 | 18 | \begin{document} 19 | 20 | \title{Josephine} 21 | \subtitle{Using JavaScript to safely manage the lifetimes of Rust data} 22 | 23 | \author{Alan Jeffrey} 24 | \orcid{0000-0001-6342-0318} 25 | \affiliation{ 26 | \position{Research Engineer} 27 | \institution{Mozilla Research} 28 | } 29 | \email{ajeffrey@mozilla.com} 30 | 31 | \begin{abstract} 32 | This paper is about the interface between languages 33 | which use a garbage collector and those which use fancy 34 | types for safe manual memory management. 35 | Garbage collection is the traditional memory management 36 | scheme for functional languages, whereas type systems 37 | are now used for memory safety in imperative languages. 38 | We use existing techniques for linear capabilities 39 | to provide safe access to copyable references, 40 | but the application to languages 41 | with a tracing garbage collector, 42 | and to data with explicit lifetimes is new. 43 | This work is related to mixed linear/non-linear 44 | programming, but the languages being mixed are Rust and JavaScript. 45 | \end{abstract} 46 | 47 | \begin{CCSXML} 48 | 49 | 50 | 10011007.10011006.10011008.10011009.10011012 51 | Software and its engineering~Functional languages 52 | 500 53 | 54 | 55 | 10011007.10011006.10011008.10011009.10011010 56 | Software and its engineering~Imperative languages 57 | 300 58 | 59 | 60 | \end{CCSXML} 61 | 62 | \ccsdesc[500]{Software and its engineering~Functional languages} 63 | \ccsdesc[300]{Software and its engineering~Imperative languages} 64 | 65 | \keywords{JavaScript, Rust, interoperability, memory management, affine types} 66 | 67 | \renewcommand\footnotetextcopyrightpermission[1]{} 68 | \maketitle 69 | \thispagestyle{empty} 70 | 71 | \input{intro} 72 | \input{api} 73 | \input{gc} 74 | \input{compartments} 75 | \input{outro} 76 | 77 | \bibliographystyle{plain} 78 | \bibliography{paper.bib} 79 | 80 | \end{document} 81 | -------------------------------------------------------------------------------- /examples/dbllist/dbllist.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | //! A simple doubly linked list class. 6 | 7 | use josephine::CanAccess; 8 | use josephine::CanAlloc; 9 | use josephine::Compartment; 10 | use josephine::InCompartment; 11 | use josephine::Initialized; 12 | use josephine::IsInitialized; 13 | use josephine::IsInitializing; 14 | use josephine::JSContext; 15 | use josephine::JSManaged; 16 | use josephine::JSLifetime; 17 | 18 | #[derive(Copy, Clone, JSTraceable, JSLifetime, JSCompartmental)] 19 | pub struct DoublyLinkedList<'a, C>(JSManaged<'a, C, NativeDoublyLinkedList<'a, C>>); 20 | 21 | #[derive(JSTraceable, JSLifetime, JSInitializable, JSCompartmental)] 22 | pub struct NativeDoublyLinkedList<'a, C> { 23 | first: Option>, 24 | last: Option>, 25 | } 26 | 27 | #[derive(Copy, Clone, JSTraceable, JSLifetime, JSCompartmental)] 28 | pub struct Cell<'a, C>(JSManaged<'a, C, NativeCell<'a, C>>); 29 | 30 | #[derive(JSTraceable, JSLifetime, JSInitializable, JSCompartmental)] 31 | pub struct NativeCell<'a, C> { 32 | data: String, 33 | prev: Option>, 34 | next: Option>, 35 | } 36 | 37 | impl<'a, C:'a> DoublyLinkedList<'a, C> { 38 | pub fn init(cx: JSContext) -> JSContext>> where 39 | S: IsInitializing<'a, C, NativeDoublyLinkedList<'a, C>>, 40 | { 41 | cx.global_manage(NativeDoublyLinkedList { first: None, last: None }) 42 | } 43 | 44 | pub fn global(cx: &JSContext) -> DoublyLinkedList<'a, C> where 45 | S: IsInitialized<'a, C, NativeDoublyLinkedList<'a, C>>, 46 | { 47 | DoublyLinkedList(cx.global()) 48 | } 49 | } 50 | 51 | impl<'a, C:'a> DoublyLinkedList<'a, C> { 52 | pub fn first(self, cx: &'a JSContext) -> Option> where 53 | S: CanAccess, 54 | C: Compartment, 55 | { 56 | self.0.borrow(cx).first 57 | } 58 | 59 | pub fn last(self, cx: &'a JSContext) -> Option> where 60 | S: CanAccess, 61 | C: Compartment, 62 | { 63 | self.0.borrow(cx).last 64 | } 65 | 66 | pub fn push(self, cx: &mut JSContext, data: String) where 67 | S: CanAccess + CanAlloc + InCompartment, 68 | C: Compartment, 69 | { 70 | let ref mut root1 = cx.new_root(); 71 | let ref mut root2 = cx.new_root(); 72 | let old_last = self.last(cx).in_root(root1); 73 | let new_last = Cell::new(cx, data, old_last, None).in_root(root2); 74 | self.0.borrow_mut(cx).last = Some(new_last); 75 | if let Some(old_last) = old_last { 76 | old_last.0.borrow_mut(cx).next = Some(new_last); 77 | } else { 78 | self.0.borrow_mut(cx).first = Some(new_last); 79 | } 80 | } 81 | } 82 | 83 | impl<'a, C:'a> Cell<'a, C> { 84 | fn new(cx: &'a mut JSContext, data: String, prev: Option>, next: Option>) -> Cell<'a, C> where 85 | S: CanAlloc + InCompartment, 86 | C: Compartment, 87 | { 88 | Cell(cx.manage(NativeCell { 89 | data: data, 90 | prev: prev, 91 | next: next, 92 | })) 93 | } 94 | 95 | pub fn data(self, cx: &'a JSContext) -> &'a str where 96 | S: CanAccess, 97 | C: Compartment, 98 | { 99 | &*self.0.borrow(cx).data 100 | } 101 | 102 | pub fn prev(self, cx: &'a JSContext) -> Option> where 103 | S: CanAccess, 104 | C: Compartment, 105 | { 106 | self.0.borrow(cx).prev 107 | } 108 | 109 | pub fn next(self, cx: &'a JSContext) -> Option> where 110 | S: CanAccess, 111 | C: Compartment, 112 | { 113 | self.0.borrow(cx).next 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /examples/dbllist/main.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #[macro_use] extern crate josephine; 6 | 7 | mod dbllist; 8 | 9 | use dbllist::DoublyLinkedList; 10 | 11 | use josephine::JSContext; 12 | 13 | fn main() { 14 | // Create a new JS context 15 | let ref mut cx = JSContext::new().expect("Creating a JSContext failed"); 16 | 17 | // Create a new doubly-linked list in its own compartment 18 | let ref mut cx = DoublyLinkedList::init(cx.create_compartment()); 19 | 20 | // Get the list from the newly created compartment 21 | let list = DoublyLinkedList::global(cx); 22 | 23 | // Push some data onto the list 24 | list.push(cx, String::from("hello")); 25 | list.push(cx, String::from("world")); 26 | 27 | // Check that the list has the data we expect 28 | assert_eq!(list.first(cx).unwrap().data(cx), "hello"); 29 | assert_eq!(list.first(cx).unwrap().next(cx).unwrap().data(cx), "world"); 30 | assert_eq!(list.last(cx).unwrap().data(cx), "world"); 31 | assert_eq!(list.last(cx).unwrap().prev(cx).unwrap().data(cx), "hello"); 32 | } 33 | -------------------------------------------------------------------------------- /examples/minidom/fake_codegen.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use js::jsapi; 6 | use js::jsapi::CallArgs; 7 | use js::jsapi::JSClass; 8 | use js::jsapi::JSClassOps; 9 | use js::jsapi::JSFunctionSpec; 10 | use js::jsapi::JSNativeWrapper; 11 | use js::jsapi::JSPropertySpec; 12 | use js::jsapi::JS_GlobalObjectTraceHook; 13 | use js::jsapi::JSPROP_ENUMERATE; 14 | use js::jsapi::JSPROP_SHARED; 15 | 16 | use js::jsval::JSVal; 17 | use js::jsval::UndefinedValue; 18 | 19 | use js::panic::wrap_panic; 20 | 21 | use libc::c_char; 22 | use libc::c_uint; 23 | 24 | use josephine::CanAccess; 25 | use josephine::CanAlloc; 26 | use josephine::Compartment; 27 | use josephine::JSContext; 28 | use josephine::JSString; 29 | use josephine::ffi::JSEvaluateErr; 30 | use josephine::ffi::JSInitializer; 31 | use josephine::ffi::finalize_jsobject_with_native_data; 32 | use josephine::ffi::jsclass_global_flags_with_slots; 33 | use josephine::ffi::jsclass_has_reserved_slots; 34 | use josephine::ffi::jscontext_called_from_js; 35 | use josephine::ffi::jsmanaged_called_from_js; 36 | use josephine::ffi::jsstring_called_from_js; 37 | use josephine::ffi::null_function; 38 | use josephine::ffi::null_property; 39 | use josephine::ffi::null_wrapper; 40 | use josephine::ffi::trace_jsobject_with_native_data; 41 | 42 | use minidom::Console; 43 | use minidom::Document; 44 | use minidom::Element; 45 | use minidom::Window; 46 | 47 | use std::panic; 48 | use std::ptr; 49 | 50 | // In a real example, this code would be produced by a codegen tool 51 | // from webidl. For the moment, we do it by hand. 52 | 53 | // Window 54 | 55 | #[allow(non_snake_case)] 56 | pub trait WindowMethods<'a, C> { 57 | fn Console(self, cx: &'a JSContext) -> Console<'a, C> where S: CanAccess + CanAlloc, C: Compartment; 58 | fn Document(self, cx: &'a JSContext) -> Document<'a, C> where S: CanAccess + CanAlloc, C: Compartment; 59 | fn Window(self, cx: &'a JSContext) -> Window<'a, C> where S: CanAccess + CanAlloc, C: Compartment; 60 | } 61 | 62 | static WINDOW_CLASS: JSClass = JSClass { 63 | name: b"Window\0" as *const u8 as *const c_char, 64 | flags: jsclass_global_flags_with_slots(2), 65 | cOps: &JSClassOps { 66 | addProperty: None, 67 | call: None, 68 | construct: None, 69 | delProperty: None, 70 | enumerate: None, 71 | finalize: Some(finalize_jsobject_with_native_data), 72 | getProperty: None, 73 | hasInstance: None, 74 | mayResolve: None, 75 | resolve: None, 76 | setProperty: None, 77 | trace: Some(JS_GlobalObjectTraceHook), 78 | }, 79 | reserved: [0 as *mut _; 3], 80 | }; 81 | 82 | const WINDOW_PROPERTIES: &[JSPropertySpec] = &[ 83 | JSPropertySpec { 84 | name: b"window\0" as *const u8 as *const c_char, 85 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 86 | getter: JSNativeWrapper { 87 | op: Some(window_window_getter_op), 88 | info: ptr::null(), 89 | }, 90 | setter: null_wrapper(), 91 | }, 92 | JSPropertySpec { 93 | name: b"console\0" as *const u8 as *const c_char, 94 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 95 | getter: JSNativeWrapper { 96 | op: Some(window_console_getter_op), 97 | info: ptr::null(), 98 | }, 99 | setter: null_wrapper(), 100 | }, 101 | JSPropertySpec { 102 | name: b"document\0" as *const u8 as *const c_char, 103 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 104 | getter: JSNativeWrapper { 105 | op: Some(window_document_getter_op), 106 | info: ptr::null(), 107 | }, 108 | setter: null_wrapper(), 109 | }, 110 | null_property(), 111 | ]; 112 | 113 | #[allow(unsafe_code)] 114 | unsafe extern "C" fn window_window_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 115 | wrap_panic(panic::AssertUnwindSafe(|| { 116 | let args = CallArgs::from_vp(vp, argc); 117 | match window_window_getter(cx, args) { 118 | Ok(result) => { args.rval().set(result); true }, 119 | Err(_err) => { false } // TODO: set the exception 120 | } 121 | }), false) 122 | } 123 | 124 | #[allow(unsafe_code)] 125 | unsafe fn window_window_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 126 | debug!("Getting window."); 127 | let this = Window(jsmanaged_called_from_js(args.thisv())?); 128 | let ref mut cx = jscontext_called_from_js(cx); 129 | let result = this.Window(cx); 130 | Ok(result.0.to_jsval()) 131 | } 132 | 133 | #[allow(unsafe_code)] 134 | unsafe extern "C" fn window_console_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 135 | wrap_panic(panic::AssertUnwindSafe(|| { 136 | let args = CallArgs::from_vp(vp, argc); 137 | match window_console_getter(cx, args) { 138 | Ok(result) => { args.rval().set(result); true }, 139 | Err(_err) => { false } // TODO: set the exception 140 | } 141 | }), false) 142 | } 143 | 144 | #[allow(unsafe_code)] 145 | unsafe fn window_console_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 146 | debug!("Getting console."); 147 | let this = Window(jsmanaged_called_from_js(args.thisv())?); 148 | let ref mut cx = jscontext_called_from_js(cx); 149 | let result = this.Console(cx); 150 | Ok(result.0.to_jsval()) 151 | } 152 | 153 | #[allow(unsafe_code)] 154 | unsafe extern "C" fn window_document_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 155 | wrap_panic(panic::AssertUnwindSafe(|| { 156 | let args = CallArgs::from_vp(vp, argc); 157 | match window_document_getter(cx, args) { 158 | Ok(result) => { args.rval().set(result); true }, 159 | Err(_err) => { false } // TODO: set the exception 160 | } 161 | }), false) 162 | } 163 | 164 | #[allow(unsafe_code)] 165 | unsafe fn window_document_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 166 | debug!("Getting document."); 167 | let this = Window(jsmanaged_called_from_js(args.thisv())?); 168 | let ref mut cx = jscontext_called_from_js(cx); 169 | let result = this.Document(cx); 170 | Ok(result.0.to_jsval()) 171 | } 172 | 173 | pub struct WindowInitializer; 174 | 175 | impl JSInitializer for WindowInitializer { 176 | #[allow(unsafe_code)] 177 | unsafe fn global_classp() -> *const JSClass { 178 | &WINDOW_CLASS 179 | } 180 | 181 | #[allow(unsafe_code)] 182 | unsafe fn properties() -> *const JSPropertySpec { 183 | &WINDOW_PROPERTIES[0] 184 | } 185 | } 186 | 187 | // Console 188 | 189 | #[allow(non_snake_case)] 190 | pub trait ConsoleMethods<'a, C> { 191 | fn Log(self, cx: &'a mut JSContext, arg: JSString<'a, C>) where S: CanAccess + CanAlloc, C: Compartment; 192 | } 193 | 194 | static CONSOLE_CLASS: JSClass = JSClass { 195 | name: b"Console\0" as *const u8 as *const c_char, 196 | flags: jsclass_has_reserved_slots(2), 197 | cOps: &JSClassOps { 198 | addProperty: None, 199 | call: None, 200 | construct: None, 201 | delProperty: None, 202 | enumerate: None, 203 | finalize: Some(finalize_jsobject_with_native_data), 204 | getProperty: None, 205 | hasInstance: None, 206 | mayResolve: None, 207 | resolve: None, 208 | setProperty: None, 209 | trace: Some(trace_jsobject_with_native_data), 210 | }, 211 | reserved: [0 as *mut _; 3], 212 | }; 213 | 214 | const CONSOLE_FUNCTIONS: &[JSFunctionSpec] = &[ 215 | JSFunctionSpec { 216 | name: b"log\0" as *const u8 as *const c_char, 217 | selfHostedName: ptr::null(), 218 | flags: JSPROP_ENUMERATE as u16, 219 | nargs: 1, 220 | call: JSNativeWrapper { 221 | op: Some(console_log_op), 222 | info: ptr::null(), 223 | }, 224 | }, 225 | null_function(), 226 | ]; 227 | 228 | pub struct ConsoleInitializer; 229 | 230 | impl JSInitializer for ConsoleInitializer { 231 | #[allow(unsafe_code)] 232 | unsafe fn classp() -> *const JSClass { 233 | &CONSOLE_CLASS 234 | } 235 | 236 | #[allow(unsafe_code)] 237 | unsafe fn functions() -> *const JSFunctionSpec { 238 | &CONSOLE_FUNCTIONS[0] 239 | } 240 | } 241 | 242 | #[allow(unsafe_code)] 243 | unsafe extern "C" fn console_log_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 244 | wrap_panic(panic::AssertUnwindSafe(|| { 245 | let args = CallArgs::from_vp(vp, argc); 246 | match console_log(cx, args) { 247 | Ok(result) => { args.rval().set(result); true }, 248 | Err(_err) => { false } // TODO: set the exception 249 | } 250 | }), false) 251 | } 252 | 253 | #[allow(unsafe_code)] 254 | unsafe fn console_log(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 255 | debug!("Logging."); 256 | let this = Console(jsmanaged_called_from_js(args.thisv())?); 257 | let arg = jsstring_called_from_js(cx, args.get(0))?; 258 | let ref mut cx = jscontext_called_from_js(cx); 259 | this.Log(cx, arg); 260 | Ok(UndefinedValue()) 261 | } 262 | 263 | // Document 264 | 265 | #[allow(non_snake_case)] 266 | pub trait DocumentMethods<'a, C> { 267 | fn Body(self, cx: &'a mut JSContext) -> Element<'a, C> where S: CanAccess + CanAlloc, C: Compartment; 268 | } 269 | 270 | static DOCUMENT_CLASS: JSClass = JSClass { 271 | name: b"Document\0" as *const u8 as *const c_char, 272 | flags: jsclass_has_reserved_slots(2), 273 | cOps: &JSClassOps { 274 | addProperty: None, 275 | call: None, 276 | construct: None, 277 | delProperty: None, 278 | enumerate: None, 279 | finalize: Some(finalize_jsobject_with_native_data), 280 | getProperty: None, 281 | hasInstance: None, 282 | mayResolve: None, 283 | resolve: None, 284 | setProperty: None, 285 | trace: Some(trace_jsobject_with_native_data), 286 | }, 287 | reserved: [0 as *mut _; 3], 288 | }; 289 | 290 | const DOCUMENT_PROPERTIES: &[JSPropertySpec] = &[ 291 | JSPropertySpec { 292 | name: b"body\0" as *const u8 as *const c_char, 293 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 294 | getter: JSNativeWrapper { 295 | op: Some(document_body_getter_op), 296 | info: ptr::null(), 297 | }, 298 | setter: null_wrapper(), 299 | }, 300 | null_property(), 301 | ]; 302 | 303 | pub struct DocumentInitializer; 304 | 305 | impl JSInitializer for DocumentInitializer { 306 | #[allow(unsafe_code)] 307 | unsafe fn classp() -> *const JSClass { 308 | &DOCUMENT_CLASS 309 | } 310 | 311 | #[allow(unsafe_code)] 312 | unsafe fn properties() -> *const JSPropertySpec { 313 | &DOCUMENT_PROPERTIES[0] 314 | } 315 | } 316 | 317 | #[allow(unsafe_code)] 318 | unsafe extern "C" fn document_body_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 319 | wrap_panic(panic::AssertUnwindSafe(|| { 320 | let args = CallArgs::from_vp(vp, argc); 321 | match document_body_getter(cx, args) { 322 | Ok(result) => { args.rval().set(result); true }, 323 | Err(_err) => { false } // TODO: set the exception 324 | } 325 | }), false) 326 | } 327 | 328 | #[allow(unsafe_code)] 329 | unsafe fn document_body_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 330 | debug!("Getting body."); 331 | let this = Document(jsmanaged_called_from_js(args.thisv())?); 332 | let ref mut cx = jscontext_called_from_js(cx); 333 | let result = this.Body(cx); 334 | Ok(result.0.to_jsval()) 335 | } 336 | 337 | // Element 338 | 339 | #[allow(non_snake_case)] 340 | pub trait ElementMethods<'a, C> { 341 | fn Parent(self, cx: &'a mut JSContext) -> Option> where S: CanAccess + CanAlloc, C: Compartment; 342 | fn TagName(self, cx: &'a mut JSContext) -> JSString<'a, C> where S: CanAccess + CanAlloc, C: Compartment; 343 | fn Append(self, cx: &'a mut JSContext, child: Element<'a, D>) where S: CanAccess + CanAlloc, C: Compartment, D: Compartment; 344 | } 345 | 346 | static ELEMENT_CLASS: JSClass = JSClass { 347 | name: b"Element\0" as *const u8 as *const c_char, 348 | flags: jsclass_has_reserved_slots(2), 349 | cOps: &JSClassOps { 350 | addProperty: None, 351 | call: None, 352 | construct: None, 353 | delProperty: None, 354 | enumerate: None, 355 | finalize: Some(finalize_jsobject_with_native_data), 356 | getProperty: None, 357 | hasInstance: None, 358 | mayResolve: None, 359 | resolve: None, 360 | setProperty: None, 361 | trace: Some(trace_jsobject_with_native_data), 362 | }, 363 | reserved: [0 as *mut _; 3], 364 | }; 365 | 366 | const ELEMENT_PROPERTIES: &[JSPropertySpec] = &[ 367 | JSPropertySpec { 368 | name: b"parent\0" as *const u8 as *const c_char, 369 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 370 | getter: JSNativeWrapper { 371 | op: Some(element_parent_getter_op), 372 | info: ptr::null(), 373 | }, 374 | setter: null_wrapper(), 375 | }, 376 | JSPropertySpec { 377 | name: b"tagName\0" as *const u8 as *const c_char, 378 | flags: (JSPROP_ENUMERATE | JSPROP_SHARED) as u8, 379 | getter: JSNativeWrapper { 380 | op: Some(element_tagName_getter_op), 381 | info: ptr::null(), 382 | }, 383 | setter: null_wrapper(), 384 | }, 385 | null_property(), 386 | ]; 387 | 388 | pub struct ElementInitializer; 389 | 390 | impl JSInitializer for ElementInitializer { 391 | #[allow(unsafe_code)] 392 | unsafe fn classp() -> *const JSClass { 393 | &ELEMENT_CLASS 394 | } 395 | 396 | #[allow(unsafe_code)] 397 | unsafe fn properties() -> *const JSPropertySpec { 398 | &ELEMENT_PROPERTIES[0] 399 | } 400 | } 401 | 402 | #[allow(unsafe_code)] 403 | unsafe extern "C" fn element_parent_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 404 | wrap_panic(panic::AssertUnwindSafe(|| { 405 | let args = CallArgs::from_vp(vp, argc); 406 | match element_parent_getter(cx, args) { 407 | Ok(result) => { args.rval().set(result); true }, 408 | Err(_err) => { false } // TODO: set the exception 409 | } 410 | }), false) 411 | } 412 | 413 | #[allow(unsafe_code)] 414 | unsafe fn element_parent_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 415 | debug!("Getting parent."); 416 | let this = Element(jsmanaged_called_from_js(args.thisv())?); 417 | let ref mut cx = jscontext_called_from_js(cx); 418 | let result = this.Parent(cx); 419 | Ok(result.map(|result| result.0.to_jsval()).unwrap_or(UndefinedValue())) 420 | } 421 | 422 | #[allow(unsafe_code,non_snake_case)] 423 | unsafe extern "C" fn element_tagName_getter_op(cx: *mut jsapi::JSContext, argc: c_uint, vp: *mut JSVal) -> bool { 424 | wrap_panic(panic::AssertUnwindSafe(|| { 425 | let args = CallArgs::from_vp(vp, argc); 426 | match element_tagName_getter(cx, args) { 427 | Ok(result) => { args.rval().set(result); true }, 428 | Err(_err) => { false } // TODO: set the exception 429 | } 430 | }), false) 431 | } 432 | 433 | #[allow(unsafe_code,non_snake_case)] 434 | unsafe fn element_tagName_getter(cx: *mut jsapi::JSContext, args: CallArgs) -> Result { 435 | debug!("Getting tagName."); 436 | let this = Element(jsmanaged_called_from_js(args.thisv())?); 437 | let ref mut cx = jscontext_called_from_js(cx); 438 | let result = this.TagName(cx); 439 | Ok(result.to_jsval()) 440 | } 441 | 442 | -------------------------------------------------------------------------------- /examples/minidom/main.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #![feature(const_fn)] 6 | #![feature(const_ptr_null)] 7 | #![allow(dead_code)] 8 | #![deny(unsafe_code)] 9 | 10 | extern crate env_logger; 11 | extern crate mozjs as js; 12 | extern crate libc; 13 | #[macro_use] extern crate josephine; 14 | #[macro_use] extern crate log; 15 | 16 | mod fake_codegen; 17 | mod minidom; 18 | 19 | use josephine::JSContext; 20 | use josephine::JSLifetime; 21 | use minidom::Window; 22 | 23 | fn main() { 24 | let _ = env_logger::init(); 25 | 26 | debug!("Creating JSContext."); 27 | let ref mut cx = JSContext::new().expect("Failed to build JSContext"); 28 | 29 | debug!("Creating global."); 30 | let ref mut root = cx.new_root(); 31 | let window = Window::new(cx).in_root(root); 32 | 33 | debug!("Entering compartment."); 34 | let ref mut cx = cx.enter_unknown_compartment(window.0); 35 | 36 | debug!("Printing hello"); 37 | cx.evaluate("console.log('Hello World. 😃')").unwrap(); 38 | 39 | debug!("Printing body"); 40 | cx.evaluate("console.log(document.body.tagName)").unwrap(); 41 | 42 | debug!("Done."); 43 | } 44 | -------------------------------------------------------------------------------- /examples/minidom/minidom.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use josephine::CanAccess; 6 | use josephine::CanAlloc; 7 | use josephine::Compartment; 8 | use josephine::InCompartment; 9 | use josephine::JSContext; 10 | use josephine::JSInitializable; 11 | use josephine::JSManaged; 12 | use josephine::JSLifetime; 13 | use josephine::JSString; 14 | use josephine::SOMEWHERE; 15 | 16 | use fake_codegen::ConsoleInitializer; 17 | use fake_codegen::ConsoleMethods; 18 | use fake_codegen::DocumentInitializer; 19 | use fake_codegen::DocumentMethods; 20 | use fake_codegen::ElementInitializer; 21 | use fake_codegen::ElementMethods; 22 | use fake_codegen::WindowInitializer; 23 | use fake_codegen::WindowMethods; 24 | 25 | // ------------------------------------------------------------------- 26 | 27 | // TODO: the contents are pub so that codegen can get at it, this should be fixed! 28 | // https://github.com/asajeffrey/josephine/issues/27 29 | #[derive(Copy, Clone, Debug, Eq, PartialEq, JSTraceable, JSLifetime, JSCompartmental)] 30 | pub struct Window<'a, C> (pub JSManaged<'a, C, NativeWindow<'a, C>>); 31 | 32 | #[derive(JSTraceable, JSLifetime, JSCompartmental)] 33 | pub struct NativeWindow<'a, C> { 34 | console: Console<'a, C>, 35 | document: Document<'a, C>, 36 | } 37 | 38 | impl<'a, C> JSInitializable for NativeWindow<'a, C> { 39 | type Init = WindowInitializer; 40 | } 41 | 42 | impl<'a> Window<'a, SOMEWHERE> { 43 | pub fn new(cx: &'a mut JSContext) -> Window<'a, SOMEWHERE> where 44 | S: CanAccess + CanAlloc, 45 | { 46 | let mut cx = cx.create_compartment(); 47 | let ref mut console_root = cx.new_root(); 48 | let ref mut document_root = cx.new_root(); 49 | let console = Console::new(&mut cx).in_root(console_root); 50 | let document = Document::new(&mut cx).in_root(document_root); 51 | let cx = cx.global_manage(NativeWindow { 52 | console: console, 53 | document: document, 54 | }); 55 | Window(cx.global().forget_compartment()) 56 | } 57 | } 58 | 59 | impl<'a, C> WindowMethods<'a, C> for Window<'a, C> where C: 'a { 60 | fn Console(self, cx: &'a JSContext) -> Console<'a, C> where 61 | S: CanAccess, 62 | C: Compartment, 63 | { 64 | self.0.borrow(cx).console 65 | } 66 | 67 | fn Document(self, cx: &'a JSContext) -> Document<'a, C> where 68 | S: CanAccess, 69 | C: Compartment, 70 | { 71 | self.0.borrow(cx).document 72 | } 73 | 74 | fn Window(self, _cx: &'a JSContext) -> Window<'a, C> { 75 | self 76 | } 77 | } 78 | 79 | // ------------------------------------------------------------------- 80 | 81 | #[derive(Copy, Clone, JSTraceable, JSLifetime, JSCompartmental)] 82 | pub struct Console<'a, C> (pub JSManaged<'a, C, NativeConsole>); 83 | 84 | #[derive(JSTraceable, JSLifetime, JSCompartmental)] 85 | pub struct NativeConsole(()); 86 | 87 | impl JSInitializable for NativeConsole { 88 | type Init = ConsoleInitializer; 89 | } 90 | 91 | impl<'a, C> Console<'a, C> { 92 | fn new(cx: &'a mut JSContext) -> Console<'a, C> where 93 | S: CanAlloc + InCompartment, 94 | C: Compartment, 95 | { 96 | Console(cx.manage(NativeConsole(()))) 97 | } 98 | } 99 | 100 | impl<'a, C> ConsoleMethods<'a, C> for Console<'a, C> { 101 | fn Log(self, _cx: &mut JSContext, arg: JSString<'a, C>) { 102 | debug!("Logging"); 103 | println!("{}", arg); 104 | } 105 | } 106 | 107 | // ------------------------------------------------------------------- 108 | 109 | #[derive(Copy, Clone, Debug, Eq, PartialEq, JSTraceable, JSLifetime, JSCompartmental)] 110 | pub struct Document<'a, C> (pub JSManaged<'a, C, NativeDocument<'a, C>>); 111 | 112 | #[derive(JSTraceable, JSLifetime, JSCompartmental)] 113 | pub struct NativeDocument<'a, C> { 114 | body: Element<'a, C>, 115 | } 116 | 117 | impl<'a, C> JSInitializable for NativeDocument<'a, C> { 118 | type Init = DocumentInitializer; 119 | } 120 | 121 | impl<'a, C:'a> Document<'a, C> { 122 | pub fn new(cx: &'a mut JSContext) -> Document<'a, C> where 123 | S: CanAlloc + InCompartment, 124 | C: Compartment, 125 | { 126 | let ref mut root1 = cx.new_root(); 127 | let ref mut root2 = cx.new_root(); 128 | let name = JSString::from_str(cx, "body").in_root(root1); 129 | let body = Element::new(cx, name).in_root(root2); 130 | Document(cx.manage(NativeDocument { 131 | body: body, 132 | })) 133 | } 134 | } 135 | 136 | impl<'a, C> DocumentMethods<'a, C> for Document<'a, C> where C: 'a { 137 | fn Body(self, cx: &'a mut JSContext) -> Element<'a, C> where 138 | S: CanAccess, 139 | C: Compartment, 140 | { 141 | self.0.borrow(cx).body 142 | } 143 | } 144 | 145 | // ------------------------------------------------------------------- 146 | 147 | #[derive(Copy, Clone, Debug, Eq, PartialEq, JSTraceable, JSLifetime, JSCompartmental)] 148 | pub struct Element<'a, C> (pub JSManaged<'a, C, NativeElement<'a, C>>); 149 | 150 | #[derive(JSTraceable, JSLifetime, JSCompartmental)] 151 | pub struct NativeElement<'a, C> { 152 | name: JSString<'a, C>, 153 | parent: Option>, 154 | children: Vec>, 155 | } 156 | 157 | impl<'a, C> JSInitializable for NativeElement<'a, C> { 158 | type Init = ElementInitializer; 159 | } 160 | 161 | impl<'a, C:'a> Element<'a, C> { 162 | pub fn new(cx: &'a mut JSContext, name: JSString) -> Element<'a, C> where 163 | S: CanAlloc + InCompartment, 164 | C: Compartment, 165 | { 166 | Element(cx.manage(NativeElement { 167 | name: name, 168 | parent: None, 169 | children: Vec::new(), 170 | })) 171 | } 172 | 173 | fn shallow_clone(self, cx: &'a mut JSContext) -> Element<'a, D> where 174 | S: CanAccess + CanAlloc + InCompartment, 175 | C: Compartment, 176 | D: Compartment, 177 | { 178 | let ref mut root1 = cx.new_root(); 179 | let ref mut root2 = cx.new_root(); 180 | let name = self.0.borrow(cx).name.in_root(root1).clone_in(cx).in_root(root2); 181 | Element::new(cx, name) 182 | } 183 | 184 | fn clone_children_from(self, cx: &mut JSContext, element: Element) where 185 | S: CanAccess + CanAlloc + InCompartment, 186 | C: Compartment, 187 | D: Compartment, 188 | { 189 | let mut i = 0; 190 | loop { 191 | let ref mut root = cx.new_root(); 192 | let child = match element.0.borrow(cx).children.get(i).cloned().in_root(root) { 193 | None => return, 194 | Some(child) => child, 195 | }; 196 | self.append_clone(cx, child); 197 | i = i+1; 198 | } 199 | } 200 | 201 | fn append_clone(self, cx: &mut JSContext, child: Element) where 202 | S: CanAccess + CanAlloc + InCompartment, 203 | C: Compartment, 204 | D: Compartment, 205 | { 206 | let ref mut root = cx.new_root(); 207 | let clone = child.shallow_clone(cx).in_root(root); 208 | clone.clone_children_from(cx, child); 209 | self.append_child(cx, clone); 210 | } 211 | 212 | fn append_child(self, cx: &'a mut JSContext, child: Element<'a, C>) where 213 | S: CanAccess + CanAlloc + InCompartment, 214 | C: Compartment, 215 | { 216 | self.0.borrow_mut(cx).children.push(child); 217 | child.0.borrow_mut(cx).parent = Some(self); 218 | } 219 | 220 | fn in_compartment(self, cx: &JSContext) -> Option> where 221 | S: InCompartment, 222 | { 223 | self.0.in_compartment(cx).map(Element) 224 | } 225 | } 226 | 227 | impl<'a, C> ElementMethods<'a, C> for Element<'a, C> where C: 'a { 228 | fn Append(self, cx: &'a mut JSContext, child: Element<'a, D>) where 229 | S: CanAccess + CanAlloc, 230 | C: Compartment, 231 | D: Compartment, 232 | { 233 | let ref mut cx = cx.enter_known_compartment(self.0); 234 | if let Some(child) = child.in_compartment(cx) { 235 | self.append_child(cx, child); 236 | } else { 237 | self.append_clone(cx, child); 238 | } 239 | } 240 | 241 | fn Parent(self, cx: &'a mut JSContext) -> Option> where 242 | S: CanAccess, 243 | C: Compartment, 244 | { 245 | self.0.borrow(cx).parent 246 | } 247 | 248 | fn TagName(self, cx: &'a mut JSContext) -> JSString<'a, C> where 249 | S: CanAccess, 250 | C: Compartment, 251 | { 252 | self.0.borrow(cx).name 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /josephine-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "josephine_derive" 3 | version = "0.1.0" 4 | authors = ["ajeffrey@mozilla.com"] 5 | license = "MPL-2.0" 6 | description = "The derive macros for josephine" 7 | repository = "https://github.com/asajeffrey/josephine/" 8 | 9 | [dependencies] 10 | syn = "0.11.11" 11 | synstructure = "0.5" 12 | quote = "0.3.15" 13 | 14 | [lib] 15 | path = "lib.rs" 16 | proc-macro = true 17 | -------------------------------------------------------------------------------- /josephine-derive/lib.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | extern crate proc_macro; 6 | extern crate syn; 7 | extern crate synstructure; 8 | 9 | #[macro_use] 10 | extern crate quote; 11 | 12 | use proc_macro::TokenStream; 13 | use std::iter; 14 | 15 | // ------------------------------------------------------------------------------------------------------- 16 | 17 | #[proc_macro_derive(JSLifetime)] 18 | pub fn derive_js_rootable(input: TokenStream) -> TokenStream { 19 | let s = input.to_string(); 20 | let ast = syn::parse_derive_input(&s).unwrap(); 21 | let gen = impl_js_rootable(&ast); 22 | gen.parse().unwrap() 23 | } 24 | 25 | fn impl_js_rootable(ast: &syn::DeriveInput) -> quote::Tokens { 26 | let name = &ast.ident; 27 | let (_, ty_generics, _) = ast.generics.split_for_impl(); 28 | 29 | let impl_generics = ast.generics.ty_params.iter().map(|ty| quote! { #ty }); 30 | let impl_generics = quote! { #(#impl_generics),* }; 31 | 32 | // append the lifetime constraints to the generic type parameters 33 | let lifetime_constraints = ast.generics.ty_params.iter().map(|ty| { 34 | let ident = &ty.ident; 35 | quote! { #ident: 'b } 36 | }); 37 | 38 | let where_clause_predicates = ast.generics.where_clause.predicates.iter().map(|pred| quote! { #pred }); 39 | let where_clause_items = lifetime_constraints.chain(where_clause_predicates).collect::>(); 40 | let where_clause = if where_clause_items.is_empty() { 41 | quote! { } 42 | } else { 43 | quote! { where #(#where_clause_items),* } 44 | }; 45 | 46 | // For types without any liftime parameters, we provide a trivial 47 | // implementation of `JSLifetime`. 48 | if ast.generics.lifetimes.is_empty() { 49 | return quote! { 50 | #[allow(unsafe_code)] 51 | unsafe impl<'a, #impl_generics> ::josephine::JSLifetime<'a> for #name #ty_generics #where_clause { 52 | type Aged = #name #ty_generics; 53 | } 54 | } 55 | } 56 | 57 | // we assume there's only one lifetime param, not named 'b 58 | assert!(ast.generics.lifetimes.len() == 1, "deriving JSLifetime requires a single lifetime"); 59 | 60 | let impl_lifetime = &ast.generics.lifetimes[0].lifetime.ident; 61 | assert!(impl_lifetime != "'b", "deriving JSLifetime requires the lifetime to not be named 'b"); 62 | 63 | // the `Aged` associated type params are the ty_params without their bounds 64 | let aged_ty_params = ast.generics.ty_params.iter().map(|ty| { 65 | let ident = &ty.ident; 66 | quote! { #ident } 67 | }); 68 | let aged_ty_params = quote! { #(#aged_ty_params),* }; 69 | 70 | quote! { 71 | #[allow(unsafe_code)] 72 | unsafe impl<#impl_lifetime, 'b, #impl_generics> ::josephine::JSLifetime<'b> for #name #ty_generics #where_clause { 73 | type Aged = #name<'b, #aged_ty_params>; 74 | } 75 | } 76 | } 77 | 78 | // ------------------------------------------------------------------------------------------------------- 79 | 80 | #[proc_macro_derive(JSCompartmental)] 81 | pub fn derive_js_transplantable(input: TokenStream) -> TokenStream { 82 | let s = input.to_string(); 83 | let ast = syn::parse_derive_input(&s).unwrap(); 84 | let gen = impl_js_transplantable(&ast); 85 | gen.parse().unwrap() 86 | } 87 | 88 | fn impl_js_transplantable(ast: &syn::DeriveInput) -> quote::Tokens { 89 | let name = &ast.ident; 90 | let (_, ty_generics, where_clause) = ast.generics.split_for_impl(); 91 | let ref lifetimes_and_d: Vec<_> = ast.generics.lifetimes.iter().map(|lifetime| lifetime.lifetime.ident.clone()) 92 | .chain(iter::once(syn::Ident::from("D"))) 93 | .collect::>(); 94 | 95 | // For types without any generic parameters, we provide a trivial 96 | // implementation of `JSCompartmental`. 97 | if ast.generics.ty_params.is_empty() { 98 | let style = synstructure::BindStyle::Ref.into(); 99 | let match_body = synstructure::each_field(&ast, &style, |binding| { 100 | let ty = &binding.field.ty; 101 | quote! { 102 | if !<#ty as ::josephine::JSCompartmental>::is_in_compartment(#binding, cx) { return false; } 103 | } 104 | }); 105 | 106 | return quote! { 107 | #[allow(unsafe_code)] 108 | unsafe impl<#(#lifetimes_and_d),*, C> ::josephine::JSCompartmental for #name #ty_generics #where_clause { 109 | type ChangeCompartment = #name #ty_generics; 110 | fn is_in_compartment(&self, cx: &::josephine::JSContext) -> bool where 111 | S: InCompartment 112 | { 113 | match *self { 114 | #match_body 115 | } 116 | true 117 | } 118 | } 119 | } 120 | } 121 | 122 | // we assume there's only one type param, not named D 123 | assert!(ast.generics.ty_params.len() == 1, "deriving JSCompartmental requires a single type parameter"); 124 | 125 | let impl_ty_param = &ast.generics.ty_params[0].ident; 126 | assert!(impl_ty_param != "D", "deriving JSCompartmental requires the type parameter to not be named D"); 127 | 128 | let style = synstructure::BindStyle::Ref.into(); 129 | let match_body = synstructure::each_field(&ast, &style, |binding| { 130 | let ty = &binding.field.ty; 131 | quote! { 132 | if !<#ty as ::josephine::JSCompartmental<#impl_ty_param, D>>::is_in_compartment(#binding, cx) { return false; } 133 | } 134 | }); 135 | 136 | quote! { 137 | #[allow(unsafe_code)] 138 | unsafe impl<#(#lifetimes_and_d),*, #impl_ty_param> ::josephine::JSCompartmental<#impl_ty_param, D> for #name #ty_generics #where_clause { 139 | type ChangeCompartment = #name<#(#lifetimes_and_d),*>; 140 | fn is_in_compartment(&self, cx: &::josephine::JSContext) -> bool where 141 | S: InCompartment 142 | { 143 | match *self { 144 | #match_body 145 | } 146 | true 147 | } 148 | } 149 | } 150 | } 151 | 152 | // ------------------------------------------------------------------------------------------------------- 153 | 154 | #[proc_macro_derive(JSInitializable)] 155 | pub fn derive_js_initializable(input: TokenStream) -> TokenStream { 156 | let s = input.to_string(); 157 | let ast = syn::parse_derive_input(&s).unwrap(); 158 | let gen = impl_js_initializable(&ast); 159 | gen.parse().unwrap() 160 | } 161 | 162 | fn impl_js_initializable(ast: &syn::DeriveInput) -> quote::Tokens { 163 | let name = &ast.ident; 164 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 165 | 166 | quote! { 167 | impl #impl_generics ::josephine::JSInitializable for #name #ty_generics #where_clause {} 168 | } 169 | } 170 | 171 | // ------------------------------------------------------------------------------------------------------- 172 | 173 | #[proc_macro_derive(JSTraceable)] 174 | pub fn expand_token_stream(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 175 | expand_string(&input.to_string()).parse().unwrap() 176 | } 177 | 178 | fn expand_string(input: &str) -> String { 179 | let mut type_ = syn::parse_macro_input(input).unwrap(); 180 | 181 | let style = synstructure::BindStyle::Ref.into(); 182 | let match_body = synstructure::each_field(&mut type_, &style, |binding| { 183 | Some(quote! { #binding.trace(tracer); }) 184 | }); 185 | 186 | let name = &type_.ident; 187 | let (impl_generics, ty_generics, where_clause) = type_.generics.split_for_impl(); 188 | let mut where_clause = where_clause.clone(); 189 | for param in type_.generics.ty_params.iter().skip(1) { 190 | where_clause.predicates.push(syn::WherePredicate::BoundPredicate(syn::WhereBoundPredicate { 191 | bound_lifetimes: Vec::new(), 192 | bounded_ty: syn::Ty::Path(None, param.ident.clone().into()), 193 | bounds: vec![syn::TyParamBound::Trait( 194 | syn::PolyTraitRef { 195 | bound_lifetimes: Vec::new(), 196 | trait_ref: syn::parse_path("::josephine::JSTraceable").unwrap(), 197 | }, 198 | syn::TraitBoundModifier::None 199 | )], 200 | })) 201 | } 202 | 203 | let tokens = quote! { 204 | #[allow(unsafe_code)] 205 | unsafe impl #impl_generics ::josephine::JSTraceable for #name #ty_generics #where_clause { 206 | #[inline] 207 | #[allow(unused_variables, unused_imports)] 208 | unsafe fn trace(&self, tracer: *mut ::josephine::trace::JSTracer) { 209 | use ::josephine::JSTraceable; 210 | match *self { 211 | #match_body 212 | } 213 | } 214 | } 215 | }; 216 | 217 | tokens.to_string() 218 | } 219 | -------------------------------------------------------------------------------- /src/compartment.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::InCompartment; 6 | use super::JSContext; 7 | use super::JSManaged; 8 | use super::JSString; 9 | 10 | use std::fmt::Debug; 11 | use std::hash::Hash; 12 | use std::marker::PhantomData; 13 | 14 | /// A marker trait for JS compartments. 15 | /// We mark it as `Copy` so that anything that uses `[#derive{Copy)]` will be copyable. 16 | /// Ditto `Eq` and `Hash`. 17 | pub trait Compartment: Copy + Debug + Eq + Hash {} 18 | 19 | /// A wildcard compartment name. 20 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 21 | pub struct SOMEWHERE(()); 22 | 23 | /// A compartment name that remembers the lifetime it was bound for. 24 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 25 | pub struct BOUND<'a>(PhantomData<&'a mut &'a ()>); 26 | impl<'a> Compartment for BOUND<'a> {} 27 | 28 | /// Data which can be transplanted from compartment C into compartment D. 29 | pub unsafe trait JSCompartmental { 30 | type ChangeCompartment; 31 | fn is_in_compartment(&self, _cx: &JSContext) -> bool where 32 | S: InCompartment; 33 | } 34 | 35 | unsafe impl JSCompartmental for String { 36 | type ChangeCompartment = String; 37 | fn is_in_compartment(&self, _cx: &JSContext) -> bool where 38 | S: InCompartment 39 | { 40 | true 41 | } 42 | } 43 | 44 | unsafe impl JSCompartmental for usize { 45 | type ChangeCompartment = usize; 46 | fn is_in_compartment(&self, _cx: &JSContext) -> bool where 47 | S: InCompartment 48 | { 49 | true 50 | } 51 | } 52 | 53 | unsafe impl JSCompartmental for () { 54 | type ChangeCompartment = (); 55 | fn is_in_compartment(&self, _cx: &JSContext) -> bool where 56 | S: InCompartment 57 | { 58 | true 59 | } 60 | } 61 | 62 | unsafe impl JSCompartmental for Option where 63 | T: JSCompartmental 64 | { 65 | type ChangeCompartment = Option; 66 | fn is_in_compartment(&self, cx: &JSContext) -> bool where 67 | S: InCompartment 68 | { 69 | self.iter().all(|this| this.is_in_compartment(cx)) 70 | } 71 | } 72 | 73 | unsafe impl JSCompartmental for Vec where 74 | T: JSCompartmental 75 | { 76 | type ChangeCompartment = Vec; 77 | fn is_in_compartment(&self, cx: &JSContext) -> bool where 78 | S: InCompartment 79 | { 80 | self.iter().all(|this| this.is_in_compartment(cx)) 81 | } 82 | } 83 | 84 | unsafe impl<'a, C, D, T> JSCompartmental for JSManaged<'a, C, T> where 85 | T: JSCompartmental 86 | { 87 | type ChangeCompartment = JSManaged<'a, D, T::ChangeCompartment>; 88 | fn is_in_compartment(&self, cx: &JSContext) -> bool where 89 | S: InCompartment 90 | { 91 | self.in_compartment(cx).is_some() 92 | } 93 | } 94 | 95 | 96 | unsafe impl<'a, C, D> JSCompartmental for JSString<'a, C> { 97 | type ChangeCompartment = JSString<'a, D>; 98 | fn is_in_compartment(&self, _cx: &JSContext) -> bool where 99 | S: InCompartment 100 | { 101 | // Rather annoyingly the Rust jsapi bindings don't export 102 | // GetStringZone which we'd need in order to implement this properly 103 | false 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::Compartment; 6 | use super::JSInitializable; 7 | use super::JSManaged; 8 | use super::JSRoot; 9 | use super::JSLifetime; 10 | use super::JSTraceable; 11 | use super::JSCompartmental; 12 | use super::compartment::BOUND; 13 | use super::ffi::JSEvaluateErr; 14 | use super::ffi::JSInitializer; 15 | use super::managed::JSManageable; 16 | use super::root::trace_thread_local_roots; 17 | 18 | use js::glue::NewCompileOptions; 19 | 20 | use js::jsapi; 21 | use js::jsapi::JS::Evaluate2; 22 | use js::jsapi::JS::Handle; 23 | use js::heap::Heap; 24 | use js::jsapi::JSAutoCompartment; 25 | use js::jsapi::JSObject; 26 | use js::jsapi::JS_AddExtraGCRootsTracer; 27 | use js::jsapi::JS_ClearPendingException; 28 | use js::jsapi::JS_DefineFunctions; 29 | use js::jsapi::JS_DefineProperties; 30 | use js::jsapi::JS_GC; 31 | use js::jsapi::JS_GetPendingException; 32 | use js::jsapi::JS_GetReservedSlot; 33 | use js::jsapi::JS_GetRuntime; 34 | use js::jsapi::JS_IsExceptionPending; 35 | use js::jsapi::JS_NewGlobalObject; 36 | use js::jsapi::JS_SetReservedSlot; 37 | use js::jsapi::JS::MutableHandle; 38 | use js::jsapi::JS::Value; 39 | 40 | use js::jsval::PrivateValue; 41 | use js::jsval::UndefinedValue; 42 | 43 | use js::rust::Runtime; 44 | 45 | use libc; 46 | 47 | use std::any::TypeId; 48 | use std::cell::RefCell; 49 | use std::collections::HashMap; 50 | use std::marker::PhantomData; 51 | use std::mem; 52 | use std::ptr; 53 | use std::str; 54 | 55 | /// The type for JS contexts whose current state is `S`. 56 | pub struct JSContext { 57 | jsapi_context: *mut jsapi::JSContext, 58 | global_js_object: *mut Heap<*mut JSObject>, 59 | global_raw: *mut (), 60 | auto_compartment: Option, 61 | runtime: Option, 62 | marker: PhantomData, 63 | } 64 | 65 | /// A context state in an initialized compartment `C` with lifetime `'a` and global type `T`. 66 | pub struct Initialized<'a, C, T> (PhantomData<(&'a(), C, T)>); 67 | 68 | /// A context state in the middTle of initializing a compartment `C` with lifetime `'a` and global type `T`. 69 | pub struct Initializing<'a, C, T> (PhantomData<(&'a(), C, T)>); 70 | 71 | /// A context state that has entered compartment `C` via an object with lifetime `'a` and global type `T`. 72 | /// The previous context state was `S`. 73 | pub struct Entered<'a, C, T, S> (PhantomData<(&'a(), C, T, S)>); 74 | 75 | /// A context state for JS contexts owned by Rust. 76 | pub struct Owned (()); 77 | 78 | /// A context state for callbacks from JS, 79 | pub struct FromJS (()); 80 | 81 | /// A context state in snapshotted compartment in underlying state `S`, 82 | /// which guarantees that no GC will happen during the lifetime `'a`. 83 | pub struct Snapshotted<'a, S> (PhantomData<(&'a(), S)>); 84 | 85 | /// A marker trait for JS contexts in compartment `C` 86 | pub trait InCompartment {} 87 | impl<'a, C, T> InCompartment for Initializing<'a, C, T> {} 88 | impl<'a, C, T> InCompartment for Initialized<'a, C, T> {} 89 | impl<'a, C, T, S> InCompartment for Entered<'a, C, T, S> {} 90 | impl<'a, C, S> InCompartment for Snapshotted<'a, S> where S: InCompartment {} 91 | 92 | /// A marker trait for JS contexts that can access native state 93 | pub trait CanAccess {} 94 | impl<'a, C, T> CanAccess for Initialized<'a, C, T> {} 95 | impl CanAccess for FromJS {} 96 | impl CanAccess for Owned {} 97 | impl<'a, C, T, S> CanAccess for Entered<'a, C, T, S> where S: CanAccess {} 98 | impl<'a, S> CanAccess for Snapshotted<'a, S> where S: CanAccess {} 99 | 100 | /// A marker trait for JS contexts that are snapshots 101 | pub trait IsSnapshot<'a> {} 102 | impl<'a, S> IsSnapshot<'a> for Snapshotted<'a, S> {} 103 | impl<'a, 'b, C, T, S> IsSnapshot<'a> for Entered<'b, C, T, S> where S: IsSnapshot<'a> {} 104 | 105 | /// A marker trait for JS contexts that can (de)allocate objects 106 | pub trait CanAlloc {} 107 | impl<'a, C, T> CanAlloc for Initialized<'a, C, T> {} 108 | impl<'a, C, T> CanAlloc for Initializing<'a, C, T> {} 109 | impl CanAlloc for FromJS {} 110 | impl CanAlloc for Owned {} 111 | impl<'a, C, T, S> CanAlloc for Entered<'a, C, T, S> where S: CanAlloc {} 112 | 113 | /// A marker trait for JS contexts that are in the middle of initializing 114 | pub trait IsInitializing<'a, C, T> {} 115 | impl<'a, C, T> IsInitializing<'a, C, T> for Initializing<'a, C, T> {} 116 | 117 | /// A marker trait for JS contexts that have initialized a global 118 | pub trait IsInitialized<'a, C, T> {} 119 | impl<'a, C, T> IsInitialized<'a, C, T> for Initialized<'a, C, T> {} 120 | 121 | /// A marker trait for JS contexts that have been entered 122 | pub trait IsEntered<'a, C, T> {} 123 | impl<'a, C, T, S> IsEntered<'a, C, T> for Entered<'a, C, T, S> {} 124 | 125 | #[cfg(feature = "smup")] 126 | thread_local!{ static RUNTIME: RefCell> = RefCell::new(Runtime::new(true)) } 127 | #[cfg(not(feature = "smup"))] 128 | thread_local!{ static RUNTIME: RefCell> = RefCell::new(Runtime::new()) } 129 | 130 | impl JSContext { 131 | /// Create a new JSContext. 132 | pub fn new() -> Result,()> { 133 | let runtime = RUNTIME.with(|runtime| runtime.replace(Err(())))?; 134 | let jsapi_context = runtime.cx(); 135 | #[cfg(feature = "smup")] 136 | unsafe { JS_AddExtraGCRootsTracer(jsapi_context, Some(trace_thread_local_roots), ptr::null_mut()); } 137 | #[cfg(not(feature = "smup"))] 138 | unsafe { JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_thread_local_roots), ptr::null_mut()); } 139 | Ok(JSContext { 140 | jsapi_context: jsapi_context, 141 | global_js_object: ptr::null_mut(), 142 | global_raw: ptr::null_mut(), 143 | auto_compartment: None, 144 | runtime: Some(runtime), 145 | marker: PhantomData, 146 | }) 147 | } 148 | } 149 | 150 | impl JSContext { 151 | /// Get a snapshot of the JS state. 152 | /// The snapshot only allows access to the methods that are guaranteed not to call GC, 153 | /// so we don't need to root JS-managed pointers during the lifetime of a snapshot. 154 | pub fn snapshot<'a>(&'a mut self) -> JSContext> { 155 | debug!("Creating snapshot."); 156 | JSContext { 157 | jsapi_context: self.jsapi_context, 158 | global_js_object: self.global_js_object, 159 | global_raw: self.global_raw, 160 | auto_compartment: None, 161 | runtime: None, 162 | marker: PhantomData, 163 | } 164 | } 165 | 166 | /// Enter a known compartment. 167 | pub fn enter_known_compartment<'a, 'b, C, T>(&'a mut self, managed: JSManaged<'b, C, T>) -> JSContext> where 168 | T: JSLifetime<'a>, 169 | C: Compartment, 170 | { 171 | debug!("Entering compartment."); 172 | #[allow(unused_unsafe)] 173 | let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, managed.to_jsobject()) }; 174 | JSContext { 175 | jsapi_context: self.jsapi_context, 176 | global_js_object: managed.to_heap_object(), 177 | global_raw: managed.to_raw_native() as *mut (), 178 | auto_compartment: Some(ac), 179 | runtime: None, 180 | marker: PhantomData, 181 | } 182 | } 183 | 184 | /// Enter a compartment. 185 | pub fn enter_unknown_compartment<'a, 'b, C, T>(&'a mut self, managed: JSManaged<'b, C, T>) -> JSContext, T::Aged, S>> where 186 | T: JSLifetime<'a>, 187 | { 188 | debug!("Entering compartment."); 189 | #[allow(unused_unsafe)] 190 | let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, managed.to_jsobject()) }; 191 | JSContext { 192 | jsapi_context: self.jsapi_context, 193 | global_js_object: managed.to_heap_object(), 194 | global_raw: managed.to_raw_native() as *mut (), 195 | auto_compartment: Some(ac), 196 | runtime: None, 197 | marker: PhantomData, 198 | } 199 | } 200 | 201 | /// Give ownership of data to JS. 202 | /// This allocates JS heap, which may trigger GC. 203 | pub fn manage<'a, C, T>(&'a mut self, value: T) -> JSManaged<'a, C, T::Aged> where 204 | S: CanAlloc + InCompartment, 205 | C: Compartment, 206 | T: JSTraceable + JSInitializable + JSLifetime<'a>, 207 | { 208 | JSManaged::new(self, value) 209 | } 210 | 211 | /// Give ownership of data to JS. 212 | /// This allocates JS heap, which may trigger GC. 213 | pub fn snapshot_manage<'a, C, T>(&'a mut self, value: T) -> (JSContext>, JSManaged<'a, C, T::Aged>) where 214 | S: CanAlloc + InCompartment, 215 | C: Compartment, 216 | T: JSTraceable + JSInitializable + JSLifetime<'a>, 217 | { 218 | let jsapi_context = self.jsapi_context; 219 | let global_js_object = self.global_js_object; 220 | let global_raw = self.global_raw; 221 | let managed = self.manage(value); 222 | 223 | debug!("Creating snapshot while managing."); 224 | let snapshot = JSContext { 225 | jsapi_context: jsapi_context, 226 | global_js_object: global_js_object, 227 | global_raw: global_raw, 228 | auto_compartment: None, 229 | runtime: None, 230 | marker: PhantomData, 231 | }; 232 | (snapshot, managed) 233 | } 234 | 235 | /// Create a compartment 236 | pub fn create_compartment<'a, T>(&'a mut self) -> JSContext, T>> where 237 | S: CanAlloc + CanAccess, 238 | T: JSInitializable + JSTraceable, 239 | { 240 | debug!("Creating compartment."); 241 | let value: Option = None; 242 | let boxed_value: Box = Box::new(value); 243 | let fat_value: [*const libc::c_void; 2] = unsafe { mem::transmute(boxed_value) }; 244 | 245 | let classp = unsafe { T::Init::global_classp() }; 246 | let principals = unsafe { T::Init::global_principals() }; 247 | let hook_options = unsafe { T::Init::global_hook_option() }; 248 | let options = unsafe { T::Init::global_options() }; 249 | let properties = unsafe { T::Init::properties() }; 250 | let functions = unsafe { T::Init::functions() }; 251 | 252 | let boxed_jsobject = Box::new(Heap::default()); 253 | debug!("Boxed global {:p}", boxed_jsobject); 254 | let unboxed_jsobject = unsafe { JS_NewGlobalObject(self.jsapi_context, classp, principals, hook_options, &options) }; 255 | debug!("Unboxed global {:p}", unboxed_jsobject); 256 | assert!(!unboxed_jsobject.is_null()); 257 | boxed_jsobject.set(unboxed_jsobject); 258 | 259 | // TODO: can we be sure that this won't trigger GC? Or do we need to root the boxed object? 260 | debug!("Entering compartment."); 261 | #[allow(unused_unsafe)] 262 | let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, boxed_jsobject.get()) }; 263 | 264 | // Keep a hash map of all the class prototypes 265 | let prototypes: Box>>> = Box::new(HashMap::new()); 266 | 267 | // Save a pointer to the native value in a private slot 268 | #[cfg(feature = "smup")] 269 | unsafe { 270 | JS_SetReservedSlot(boxed_jsobject.get(), 0, &PrivateValue(fat_value[0])); 271 | JS_SetReservedSlot(boxed_jsobject.get(), 1, &PrivateValue(fat_value[1])); 272 | // TODO: Fix this space leak! 273 | JS_SetReservedSlot(boxed_jsobject.get(), 2, &PrivateValue(Box::into_raw(prototypes) as *const _)); 274 | } 275 | #[cfg(not(feature = "smup"))] 276 | unsafe { 277 | JS_SetReservedSlot(boxed_jsobject.get(), 0, PrivateValue(fat_value[0])); 278 | JS_SetReservedSlot(boxed_jsobject.get(), 1, PrivateValue(fat_value[1])); 279 | // TODO: Fix this space leak! 280 | JS_SetReservedSlot(boxed_jsobject.get(), 2, PrivateValue(Box::into_raw(prototypes) as *const _)); 281 | } 282 | 283 | // Define the properties and functions of the global 284 | if !properties.is_null() { unsafe { JS_DefineProperties(self.jsapi_context, boxed_jsobject.handle(), properties) }; } 285 | if !functions.is_null() { unsafe { JS_DefineFunctions(self.jsapi_context, boxed_jsobject.handle(), functions) }; } 286 | 287 | // TODO: can we be sure that this won't trigger GC? Or do we need to root the boxed object? 288 | debug!("Initializing compartment."); 289 | unsafe { T::Init::js_init_global(self.jsapi_context, boxed_jsobject.handle()) }; 290 | 291 | debug!("Created compartment."); 292 | JSContext { 293 | jsapi_context: self.jsapi_context, 294 | global_js_object: Box::into_raw(boxed_jsobject), 295 | global_raw: fat_value[0] as *mut (), 296 | auto_compartment: Some(ac), 297 | runtime: None, 298 | marker: PhantomData, 299 | } 300 | } 301 | 302 | /// Finish initializing a JS Context 303 | pub fn global_manage<'a, C, T>(mut self, value: T) -> JSContext> where 304 | S: IsInitializing<'a, C, T>, 305 | T: JSTraceable + JSLifetime<'a> + JSCompartmental, 306 | { 307 | debug!("Managing native global."); 308 | let raw = self.global_raw as *mut Option; 309 | let uninitialized = unsafe { mem::replace(&mut *raw, Some(value)) }; 310 | mem::forget(uninitialized); 311 | 312 | debug!("Initialized compartment."); 313 | JSContext { 314 | jsapi_context: self.jsapi_context, 315 | global_js_object: self.global_js_object, 316 | global_raw: self.global_raw, 317 | auto_compartment: self.auto_compartment.take(), 318 | runtime: self.runtime.take(), 319 | marker: PhantomData, 320 | } 321 | } 322 | 323 | /// Get the object we entered the current compartment via 324 | pub fn entered<'a, C, T>(&self) -> JSManaged<'a, C, T> where 325 | S: IsEntered<'a, C, T> 326 | { 327 | unsafe { JSManaged::from_raw(self.global_js_object, self.global_raw) } 328 | } 329 | 330 | /// Get the global of an initialized context. 331 | pub fn global<'a, C, T>(&self) -> JSManaged<'a, C, T> where 332 | S: IsInitialized<'a, C, T> 333 | { 334 | unsafe { JSManaged::from_raw(self.global_js_object, self.global_raw) } 335 | } 336 | 337 | /// Create a new root. 338 | pub fn new_root<'a, 'b, T>(&'b mut self) -> JSRoot<'a, T> { 339 | JSRoot::new(self) 340 | } 341 | 342 | pub fn cx(&self) -> *mut jsapi::JSContext { 343 | self.jsapi_context 344 | } 345 | 346 | pub fn rt(&self) -> *mut jsapi::JSRuntime { 347 | unsafe { JS_GetRuntime(self.jsapi_context) } 348 | } 349 | 350 | pub fn gc(&mut self) where 351 | S: CanAlloc, 352 | { 353 | #[cfg(feature = "smup")] 354 | unsafe { JS_GC(self.cx()); } 355 | #[cfg(not(feature = "smup"))] 356 | unsafe { JS_GC(self.rt()); } 357 | } 358 | 359 | pub fn prototype_of(&mut self) -> Handle<*mut JSObject> where 360 | T: JSInitializable, 361 | { 362 | let global = unsafe { &*self.global_js_object }.handle(); 363 | let prototypes = unsafe { &mut *(JS_GetReservedSlot(global.get(), 2).to_private() as *mut HashMap>>) }; 364 | prototypes.entry(TypeId::of::()).or_insert_with(|| { 365 | let boxed = Box::new(Heap::default()); 366 | boxed.set(unsafe { T::Init::js_init_class(self.jsapi_context, global) }); 367 | boxed 368 | }).handle() 369 | } 370 | 371 | pub fn evaluate(&mut self, code: &str) -> Result where 372 | S: InCompartment, 373 | { 374 | let cx = self.jsapi_context; 375 | 376 | let options = unsafe { NewCompileOptions(cx, &0, 0) }; 377 | 378 | let code_utf16: Vec = code.encode_utf16().collect(); 379 | let code_ptr = &code_utf16[0] as *const u16; 380 | let code_len = code_utf16.len() as usize; 381 | 382 | let mut result = UndefinedValue(); 383 | let result_mut = unsafe { MutableHandle::from_marked_location(&mut result) }; 384 | 385 | unsafe { Evaluate2(cx, options, code_ptr, code_len, result_mut) }; 386 | self.take_pending_exception()?; 387 | 388 | Ok(result) 389 | } 390 | 391 | pub fn take_pending_exception(&mut self) -> Result<(), JSEvaluateErr> { 392 | let cx = self.jsapi_context; 393 | if !unsafe { JS_IsExceptionPending(cx) } { return Ok(()); } 394 | 395 | let ref mut exn_ref = UndefinedValue(); 396 | let exn_mut = unsafe { MutableHandle::from_marked_location(exn_ref) }; 397 | let _exn_handle = exn_mut.handle(); 398 | 399 | unsafe { JS_GetPendingException(cx, exn_mut) }; 400 | // TODO: include the exception in the error report 401 | unsafe { JS_ClearPendingException(cx) }; 402 | 403 | Err(JSEvaluateErr::JSException) 404 | } 405 | } 406 | 407 | pub unsafe fn jscontext_called_from_js(cx: *mut jsapi::JSContext) -> JSContext { 408 | JSContext { 409 | jsapi_context: cx, 410 | global_js_object: ptr::null_mut(), 411 | global_raw: ptr::null_mut(), 412 | auto_compartment: None, 413 | runtime: None, 414 | marker: PhantomData, 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::Compartment; 6 | use super::managed::JSManageable; 7 | 8 | pub use super::context::jscontext_called_from_js; 9 | pub use super::managed::jsmanaged_called_from_js; 10 | pub use super::string::jsstring_called_from_js; 11 | 12 | use js::jsapi; 13 | use js::jsapi::JS::CompartmentOptions; 14 | use js::jsapi::JS::Handle; 15 | use js::jsapi::JS::HandleObject; 16 | use js::jsapi::JSCLASS_RESERVED_SLOTS_SHIFT; 17 | use js::jsapi::JSClass; 18 | use js::jsapi::JSClassOps; 19 | use js::jsapi::JSFreeOp; 20 | use js::jsapi::JSFunctionSpec; 21 | use js::jsapi::JSNative; 22 | use js::jsapi::JSNativeWrapper; 23 | use js::jsapi::JSObject; 24 | use js::jsapi::JSPrincipals; 25 | use js::jsapi::JSPropertySpec; 26 | use js::jsapi::JSTracer; 27 | use js::jsapi::JSTraceOp; 28 | use js::jsapi::JSVersion; 29 | use js::jsapi::JS_GetObjectPrototype; 30 | use js::jsapi::JS_GetReservedSlot; 31 | use js::jsapi::JS_GlobalObjectTraceHook; 32 | use js::jsapi::JS_InitClass; 33 | use js::jsapi::JS_InitStandardClasses; 34 | use js::jsapi::JS_IsNative; 35 | use js::jsapi::JS::OnNewGlobalHookOption; 36 | 37 | use js::jsapi::JSCLASS_GLOBAL_SLOT_COUNT; 38 | use js::jsapi::JSCLASS_IS_GLOBAL; 39 | use js::jsapi::JSCLASS_RESERVED_SLOTS_MASK; 40 | 41 | use libc::c_char; 42 | use libc::c_uint; 43 | 44 | use std::mem; 45 | use std::ptr; 46 | 47 | /// The errors which might be returned from cx.evaluate("code") 48 | // TODO: store more information about the reason for the error 49 | #[derive(Clone, Debug)] 50 | pub enum JSEvaluateErr { 51 | JSException, 52 | NotAnObject, 53 | NotAString, 54 | NotJSManaged, 55 | WrongClass, 56 | } 57 | 58 | /// An unsafe compartment name, which we only give access to via unsafe code. 59 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 60 | pub struct UNSAFE(()); 61 | impl Compartment for UNSAFE {} 62 | 63 | /// A trait for Rust data which can be reflected 64 | 65 | pub trait JSInitializable { 66 | type Init: 'static + JSInitializer = DefaultInitializer; 67 | } 68 | 69 | /// Basic types 70 | impl JSInitializable for String {} 71 | impl JSInitializable for usize {} 72 | // etc. 73 | 74 | /// Initialize JS data 75 | 76 | pub trait JSInitializer { 77 | unsafe fn parent_prototype(cx: *mut jsapi::JSContext, global: HandleObject) -> *mut JSObject { 78 | JS_GetObjectPrototype(cx, global) 79 | } 80 | 81 | unsafe fn classp() -> *const JSClass { 82 | &DEFAULT_CLASS 83 | } 84 | 85 | unsafe fn global_classp() -> *const JSClass { 86 | &DEFAULT_GLOBAL_CLASS 87 | } 88 | 89 | unsafe fn global_principals() -> *mut JSPrincipals { 90 | ptr::null_mut() 91 | } 92 | 93 | unsafe fn global_trace_hook() -> JSTraceOp { 94 | Some(trace_jsobject_with_native_data) 95 | } 96 | 97 | unsafe fn global_hook_option() -> OnNewGlobalHookOption { 98 | OnNewGlobalHookOption::FireOnNewGlobalHook 99 | } 100 | 101 | unsafe fn global_options() -> CompartmentOptions { 102 | let mut options = CompartmentOptions::default(); 103 | options.behaviors_.version_ = JSVersion::JSVERSION_ECMA_5; 104 | options.creationOptions_.sharedMemoryAndAtomics_ = true; 105 | options.creationOptions_.traceGlobal_ = Self::global_trace_hook(); 106 | options 107 | } 108 | 109 | unsafe fn constructor() -> (JSNative, c_uint) { 110 | (None, 0) 111 | } 112 | 113 | unsafe fn properties() -> *const JSPropertySpec { 114 | ptr::null() 115 | } 116 | 117 | unsafe fn functions() -> *const JSFunctionSpec { 118 | ptr::null() 119 | } 120 | 121 | unsafe fn static_properties() -> *const JSPropertySpec { 122 | ptr::null() 123 | } 124 | 125 | unsafe fn static_functions() -> *const JSFunctionSpec { 126 | ptr::null() 127 | } 128 | 129 | unsafe fn js_init_class(cx: *mut jsapi::JSContext, global: HandleObject) -> *mut JSObject { 130 | let ref parent_proto = Self::parent_prototype(cx, global); 131 | let parent_proto_handle = Handle::from_marked_location(parent_proto); 132 | let classp = Self::classp(); 133 | let (constructor, nargs) = Self::constructor(); 134 | let ps = Self::properties(); 135 | let fs = Self::functions(); 136 | let static_ps = Self::static_properties(); 137 | let static_fs = Self::static_functions(); 138 | JS_InitClass(cx, global, parent_proto_handle, classp, constructor, nargs, ps, fs, static_ps, static_fs) 139 | } 140 | 141 | unsafe fn js_init_object(_cx: *mut jsapi::JSContext, _obj: HandleObject) { 142 | } 143 | 144 | unsafe fn js_init_global(cx: *mut jsapi::JSContext, global: HandleObject) { 145 | JS_InitStandardClasses(cx, global); 146 | } 147 | } 148 | 149 | /// A default class. 150 | 151 | pub struct DefaultInitializer; 152 | 153 | impl JSInitializer for DefaultInitializer {} 154 | 155 | static DEFAULT_CLASS: JSClass = JSClass { 156 | name: b"[Object]\0" as *const u8 as *const c_char, 157 | flags: jsclass_has_reserved_slots(2), 158 | cOps: &JSClassOps { 159 | addProperty: None, 160 | call: None, 161 | construct: None, 162 | delProperty: None, 163 | enumerate: None, 164 | #[cfg(feature = "smup")] 165 | newEnumerate: None, 166 | finalize: Some(finalize_jsobject_with_native_data), 167 | #[cfg(not(feature = "smup"))] 168 | getProperty: None, 169 | hasInstance: None, 170 | mayResolve: None, 171 | resolve: None, 172 | #[cfg(not(feature = "smup"))] 173 | setProperty: None, 174 | trace: Some(trace_jsobject_with_native_data), 175 | }, 176 | reserved: [0 as *mut _; 3], 177 | }; 178 | 179 | static DEFAULT_GLOBAL_CLASS: JSClass = JSClass { 180 | name: b"[Global]\0" as *const u8 as *const c_char, 181 | flags: jsclass_global_flags_with_slots(2), 182 | cOps: &JSClassOps { 183 | addProperty: None, 184 | call: None, 185 | construct: None, 186 | delProperty: None, 187 | enumerate: None, 188 | #[cfg(feature = "smup")] 189 | newEnumerate: None, 190 | finalize: Some(finalize_jsobject_with_native_data), 191 | #[cfg(not(feature = "smup"))] 192 | getProperty: None, 193 | hasInstance: None, 194 | mayResolve: None, 195 | resolve: None, 196 | #[cfg(not(feature = "smup"))] 197 | setProperty: None, 198 | trace: Some(JS_GlobalObjectTraceHook), 199 | }, 200 | reserved: [0 as *mut _; 3], 201 | }; 202 | 203 | pub const fn null_wrapper() -> JSNativeWrapper { 204 | JSNativeWrapper { 205 | op: None, 206 | info: ptr::null(), 207 | } 208 | } 209 | 210 | #[cfg(feature = "smup")] 211 | pub const fn null_property() -> JSPropertySpec { 212 | JSPropertySpec::NULL 213 | } 214 | #[cfg(not(feature = "smup"))] 215 | pub const fn null_property() -> JSPropertySpec { 216 | JSPropertySpec { 217 | name: ptr::null(), 218 | flags: 0, 219 | getter: null_wrapper(), 220 | setter: null_wrapper(), 221 | } 222 | } 223 | 224 | #[cfg(feature = "smup")] 225 | pub const fn null_function() -> JSFunctionSpec { 226 | JSFunctionSpec::NULL 227 | } 228 | #[cfg(not(feature = "smup"))] 229 | pub const fn null_function() -> JSFunctionSpec { 230 | JSFunctionSpec { 231 | name: ptr::null(), 232 | flags: 0, 233 | call: null_wrapper(), 234 | nargs: 0, 235 | selfHostedName: ptr::null(), 236 | } 237 | } 238 | 239 | // Repating stuff from https://dxr.mozilla.org/mozilla-central/source/js/public/Class.h 240 | // (it uses #defines which are not available in Rust) 241 | 242 | pub const fn jsclass_has_reserved_slots(n: c_uint) -> c_uint { 243 | (n & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT 244 | } 245 | 246 | pub const fn jsclass_global_flags_with_slots(n: c_uint) -> c_uint { 247 | JSCLASS_IS_GLOBAL | jsclass_has_reserved_slots(JSCLASS_GLOBAL_SLOT_COUNT + n) 248 | } 249 | 250 | pub unsafe extern "C" fn trace_jsobject_with_native_data(trc: *mut JSTracer, obj: *mut JSObject) { 251 | if !JS_IsNative(obj) { 252 | debug!("Not a native object (should be a prototype)."); 253 | } 254 | 255 | debug!("Tracing {:p}.", obj); 256 | let slot0 = JS_GetReservedSlot(obj, 0); 257 | let slot1 = JS_GetReservedSlot(obj, 1); 258 | if slot0.is_undefined() || slot1.is_undefined() { 259 | return debug!("Tracing uninitialized object."); 260 | } 261 | let traceable: &JSManageable = mem::transmute([ slot0.to_private(), slot1.to_private() ]); 262 | debug!("Tracing native {:p}.", traceable); 263 | traceable.trace(trc); 264 | } 265 | 266 | pub unsafe extern "C" fn finalize_jsobject_with_native_data(_op: *mut JSFreeOp, obj: *mut JSObject) { 267 | if !JS_IsNative(obj) { 268 | debug!("Not a native object (should be a prototype)."); 269 | // TODO: remove the object from the prototype hash table? 270 | } 271 | 272 | debug!("Finalizing {:p}.", obj); 273 | let slot0 = JS_GetReservedSlot(obj, 0); 274 | let slot1 = JS_GetReservedSlot(obj, 1); 275 | if slot0.is_undefined() || slot1.is_undefined() { 276 | return debug!("Finalizing uninitialized object."); 277 | } 278 | let traceable: *mut JSManageable = mem::transmute([ slot0.to_private(), slot1.to_private() ]); 279 | debug!("Finalizing native {:p}.", traceable); 280 | Box::from_raw(traceable); 281 | } 282 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | //! A library which uses JavaScript to safely manage the lifetimes of Rust data. 6 | //! 7 | //! ( 8 | //! [Repo](https://github.com/asajeffrey/josephine) | 9 | //! [CI](https://travis-ci.org/asajeffrey/josephine) 10 | //! ) 11 | //! 12 | //! This library allows Rust data to be attached to JavaScript objects: 13 | //! the lifetime of the Rust data is then the same as the JS object it is attached to. 14 | //! Since JS is garbage collected, it is safe to copy and discard references to 15 | //! JS managed data, and allows examples like doubly-linked lists which would 16 | //! otherwise require reference counting. Reference counting requires dynamic checks, 17 | //! for example getting mutable access to reference-counted data panics if the reference 18 | //! count is more than 1. 19 | //! 20 | //! The goals are: 21 | //! 22 | //! 1. Use JS to manage the lifetime of Rust data. 23 | //! 2. Allow references to JS managed data to be freely copied and discarded, relying on 24 | //! the garbage collector for safety. 25 | //! 3. Maintain Rust memory safety (for example no mutable aliasing), 26 | //! without requiring additional static analysis such as a lint. 27 | //! 4. Allow mutable and immutable access to Rust data via JS managed references, so 28 | //! we do not need to rely on interior mutability. 29 | //! 5. Provide a rooting API to ensure that JS managed data is not garbage collected 30 | //! while it is being used. 31 | //! 32 | //! To support safe access to JS managed data, the API uses a *JS context*, which 33 | //! is used as an access token to allow JS managed data to be accessed, allocated 34 | //! or deallocated. Mutable access to JS managed data requires mutable access to the 35 | //! JS context, which is how the API achieves memory safety even though JS managed 36 | //! references can be copied and discarded freely. 37 | //! 38 | //! JS managed memory is split into *compartments*, which are 39 | //! separately garbage collected, so the garbage collector can avoid 40 | //! scanning the entire heap. The API statically tracks compartments, to 41 | //! ensure that there are no direct references between compartments. 42 | //! 43 | //! The API is implemented as bindings to the SpiderMonkey JS engine, 44 | //! from which it borrows the garbage collector and the notions of compartment 45 | //! and JS context. The API allows calling into JavaScript 46 | //! from Rust, and calling back into Rust from JavaScript. These bindings are unsafe, 47 | //! and are intended for use by a trusted bindings generator. 48 | //! 49 | //! # JS-managed data 50 | //! 51 | //! Rust data can be given to JS to manage, and then accessed, 52 | //! using the JS context. The JS context is passed as a variable of type `JSContext`, 53 | //! where the type parameter `S` is used to track the state of the context. 54 | //! The context comes with the permissions it grants, such as `CanAlloc` 55 | //! and `CanAccess`. These permissions are modelled as traits, for example 56 | //! a context in state `S` can allocate memory when `S: CanAlloc`. 57 | //! 58 | //! JS managed memory is split into compartments. Each JS context has a notion of 59 | //! the current compartment, which is part of the state. A JS context in compartment 60 | //! `C` has type `JSCompartment` where `S: InCompartment` and `C: Compartment`. 61 | //! A reference to JS managed data in compartment `C`, where the Rust data being 62 | //! managed by JS has type `T`, is given type `JSManaged`. 63 | //! 64 | //! For example, we can give JS a Rust string to manage in compartment 65 | //! `C`: 66 | //! 67 | //! ```rust 68 | //! # use josephine::*; 69 | //! fn example(cx: &mut JSContext) where 70 | //! S: CanAlloc + InCompartment, 71 | //! C: Compartment, 72 | //! { 73 | //! let x: JSManaged = cx.manage(String::from("hello")); 74 | //! } 75 | //! ``` 76 | //! 77 | //! and then access it: 78 | //! 79 | //! ```rust 80 | //! # use josephine::*; 81 | //! fn example(cx: &mut JSContext, x: JSManaged) where 82 | //! S: CanAccess, 83 | //! C: Compartment, 84 | //! { 85 | //! println!("{} world", x.borrow(cx)); 86 | //! } 87 | //! ``` 88 | //! 89 | //! # Lifetimes of JS-managed data 90 | //! 91 | //! Unfortunately, combining these two examples is not memory-safe, due to 92 | //! garbage collection: 93 | //! 94 | //! ```rust,ignore 95 | //! # use josephine::*; 96 | //! fn unsafe_example(cx: &mut JSContext) where 97 | //! S: CanAlloc + CanAccess + InCompartment, 98 | //! C: Compartment, 99 | //! { 100 | //! let x: JSManaged = cx.manage(String::from("hello")); 101 | //! // Imagine something triggers GC here 102 | //! println!("{} world", x.borrow(cx)); 103 | //! } 104 | //! ``` 105 | //! 106 | //! This example is not safe, as there is nothing keeping `x` alive in JS, 107 | //! so if garbage collection is triggered, then `x` will be reclaimed 108 | //! which will drop the Rust data, and so the call to `x.borrow(cx)` will be a use-after-free. 109 | //! 110 | //! This example is not memory-safe, and fortunately fails to typecheck: 111 | //! 112 | //! ```text 113 | //! error[E0502]: cannot borrow `*cx` as immutable because it is also borrowed as mutable 114 | //! --> src/lib.rs:10:35 115 | //! | 116 | //! 8 | let x: JSManaged = cx.manage(String::from("hello")); 117 | //! | -- mutable borrow occurs here 118 | //! 9 | // Imagine something triggers GC here 119 | //! 10 | println!("{} world", x.borrow(cx)); 120 | //! | ^^ immutable borrow occurs here 121 | //! 11 | } 122 | //! | - mutable borrow ends here 123 | //! ``` 124 | //! 125 | //! To see why this example fails to typecheck, we can introduce explicit lifetimes: 126 | //! 127 | //! ```rust,ignore 128 | //! # use josephine::*; 129 | //! fn unsafe_example<'a, C, S>(cx: &'a mut JSContext) where 130 | //! S: CanAlloc + CanAccess + InCompartment, 131 | //! C: Compartment, 132 | //! { 133 | //! // x has type JSManaged<'b, C, String> 134 | //! let x = cx.manage(String::from("hello")); 135 | //! // Imagine something triggers GC here 136 | //! // x_ref has type &'c String 137 | //! let x_ref = x.borrow(cx); 138 | //! println!("{} world", x_ref); 139 | //! } 140 | //! ``` 141 | //! 142 | //! We can now see why this fails to typecheck: since `cx` is borrowed mutably at type 143 | //! `&'b mut JSContext`, then immutably at type `&'c mut JSContext` these lifetimes 144 | //! cannot overlap, but the call to `x.borrow(cx)` requires them to overlap. These contradicting 145 | //! constraints cause the example to fail to compile. 146 | //! 147 | //! # Rooting 148 | //! 149 | //! To fix this example, we need to make sure that `x` lives long enough. One way to do this is 150 | //! to root `x`, so that it will not be garbage collected. 151 | //! 152 | //! ```rust 153 | //! # use josephine::*; 154 | //! fn example(cx: &mut JSContext) where 155 | //! S: CanAlloc + CanAccess + InCompartment, 156 | //! C: Compartment, 157 | //! { 158 | //! // Declare a root which will be used to keep x alive during its lifetime 159 | //! let ref mut root = cx.new_root(); 160 | //! // Store a reference to x in the root 161 | //! let x = cx.manage(String::from("hello")).in_root(root); 162 | //! // This is what is keeping x alive ------^ 163 | //! // Imagine something triggers GC here 164 | //! // The root ensures that x survives GC, so is safe to use 165 | //! println!("{} world", x.borrow(cx)); 166 | //! } 167 | //! ``` 168 | //! 169 | //! To see why this example now typechecks, we again introduce explicit lifetimes: 170 | //! 171 | //! ```rust 172 | //! # use josephine::*; 173 | //! fn example<'a, C, S>(cx: &'a mut JSContext) where 174 | //! S: CanAlloc + CanAccess + InCompartment, 175 | //! C: Compartment, 176 | //! { 177 | //! // root has type JSRoot<'b, String> 178 | //! let ref mut root = cx.new_root(); 179 | //! // x has type JSManaged<'b, C, String> 180 | //! let x = { 181 | //! // x_unrooted has type JSManaged<'d, C, String> 182 | //! let x_unrooted = cx.manage(String::from("hello")); 183 | //! // By rooting it, its lifetime changes from 'd to 'b (the lifetime of the root) 184 | //! x_unrooted.in_root(root) 185 | //! }; 186 | //! // Imagine something triggers GC here 187 | //! // x_ref has type &'c String 188 | //! let x_ref = x.borrow(cx); 189 | //! println!("{} world", x_ref); 190 | //! } 191 | //! ``` 192 | //! 193 | //! The example typechecks because the 194 | //! constraints are that `'b` overlaps with `'c` and `'d`, and that 195 | //! `'c` and `'d` don't overlap. These constraints are satisfiable, so the 196 | //! example typechecks. 197 | //! 198 | //! # Mutating JS-managed data 199 | //! 200 | //! JS managed data can be accessed mutably as well as immutably. 201 | //! This is safe because mutably accessing JS manage data requires 202 | //! mutably borrowing the JS context, so there cannot be two simultaneous 203 | //! mutable accesses. 204 | //! 205 | //! ```rust 206 | //! # use josephine::*; 207 | //! fn example(cx: &mut JSContext, x: JSManaged) where 208 | //! S: CanAccess, 209 | //! C: Compartment, 210 | //! { 211 | //! println!("{} world", x.borrow(cx)); 212 | //! *x.borrow_mut(cx) = String::from("hi"); 213 | //! println!("{} world", x.borrow(cx)); 214 | //! } 215 | //! ``` 216 | //! 217 | //! An attempt to mutably access JS managed data more than once simultaneously 218 | //! results in an error from the borrow-checker, for example: 219 | //! 220 | //! ```rust,ignore 221 | //! # use josephine::*; use std::mem; 222 | //! fn unsafe_example(cx: &mut JSContext, x: JSManaged, y: JSManaged) where 223 | //! S: CanAccess, 224 | //! C: Compartment, 225 | //! { 226 | //! mem::swap(x.borrow_mut(cx), y.borrow_mut(cx)); 227 | //! } 228 | //! ``` 229 | //! 230 | //! ```text 231 | //! error[E0499]: cannot borrow `*cx` as mutable more than once at a time 232 | //! --> src/lib.rs:8:46 233 | //! | 234 | //! 8 | mem::swap(x.borrow_mut(cx), y.borrow_mut(cx)); 235 | //! | -- ^^ - first borrow ends here 236 | //! | | | 237 | //! | | second mutable borrow occurs here 238 | //! | first mutable borrow occurs here 239 | //! ``` 240 | //! 241 | //! # Snapshots 242 | //! 243 | //! Some cases of building JS managed data require rooting, but in some cases 244 | //! the rooting can be avoided, since the program does nothing to trigger 245 | //! garbage collection. In this case, we can snapshot the JS context after 246 | //! performing allocation. The snapshot supports accessing JS managed data, 247 | //! but does not support any calls that might trigger garbage collection. 248 | //! As a result, we know that any data which is live at the beginning of 249 | //! the snapshot is also live at the end. 250 | //! 251 | //! ```rust 252 | //! # use josephine::*; 253 | //! fn example(cx: &mut JSContext) where 254 | //! S: CanAlloc + CanAccess + InCompartment, 255 | //! C: Compartment, 256 | //! { 257 | //! let (ref cx, x) = cx.snapshot_manage(String::from("hello")); 258 | //! // Since the context is snapshotted it can't trigger GC 259 | //! println!("{} world", x.borrow(cx)); 260 | //! } 261 | //! ``` 262 | //! 263 | //! A program which tries to use a function which might trigger GC will 264 | //! not typecheck, as the snapshotted JS context state does not support 265 | //! the appropriate traits. For example: 266 | //! 267 | //! ```rust,ignore 268 | //! # use josephine::*; 269 | //! fn unsafe_example(cx: &mut JSContext) where 270 | //! S: CanAlloc + CanAccess + InCompartment, 271 | //! C: Compartment, 272 | //! { 273 | //! let (ref mut cx, x) = cx.snapshot_manage(String::from("hello")); 274 | //! cx.gc(); 275 | //! println!("{} world", x.borrow(cx)); 276 | //! } 277 | //! ``` 278 | //! 279 | //! In this program, the call to `cx.gc()` requires the state 280 | //! to support `CanAlloc`, which is not allowed by the snapshotted state. 281 | //! 282 | //! ```text 283 | //! error[E0277]: the trait bound `josephine::Snapshotted<'_, S>: josephine::CanAlloc` is not satisfied 284 | //! --> src/lib.rs:9:8 285 | //! | 286 | //! 9 | cx.gc(); 287 | //! | ^^ the trait `josephine::CanAlloc` is not implemented for `josephine::Snapshotted<'_, S>` 288 | //! ``` 289 | //! 290 | //! # Working with compartments 291 | //! 292 | //! To enter the compartment of a JS managed object, you can use `cx.enter_known_compartment(managed)`. 293 | //! This returns a context whose current compartment is that of the JS managed objecct. 294 | //! 295 | //! ```rust 296 | //! # use josephine::*; 297 | //! fn example(cx: &mut JSContext, x: JSManaged) where 298 | //! S: CanAccess + CanAlloc, 299 | //! C: Compartment, 300 | //! { 301 | //! // We can't allocate data without entering the comartment. 302 | //! // Commenting out the next line gives an error 303 | //! // the trait `josephine::InCompartment<_>` is not implemented for `S`. 304 | //! let ref mut cx = cx.enter_known_compartment(x); 305 | //! let ref mut root = cx.new_root(); 306 | //! let y = cx.manage(String::from("world")).in_root(root); 307 | //! println!("Hello, {}.", y.borrow(cx)); 308 | //! } 309 | //! ``` 310 | //! 311 | //! Working with named compartmens is fine when there is a fixed number of them, but not when 312 | //! the number of compartments is unbounded. For example, the type `Vec>` contains 313 | //! a vector of managed objects, all in the same compartment, but sometimes you need a vector of 314 | //! objects in different compartments. This is what *wildcards* are for (borrowed from 315 | //! [Java wildcards](https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html) 316 | //! which solve a similar problem). 317 | //! 318 | //! The wildcard compartment is called `SOMEWHERE`, and `JSManaged` refers 319 | //! to JS managed data whose compartment is unknown. For example `Vec>` 320 | //! contains a vector of managed objects, which may all be in different compartments. 321 | //! 322 | //! To create a `JSManaged`, we use `managed.forget_compartment()`. 323 | //! 324 | //! ```rust 325 | //! # use josephine::*; 326 | //! fn example(cx: &mut JSContext) -> JSManaged where 327 | //! S: CanAlloc + InCompartment, 328 | //! C: Compartment, 329 | //! { 330 | //! cx.manage(String::from("hello")).forget_compartment() 331 | //! } 332 | //! ``` 333 | //! 334 | //! To access data with a wildcard compartment, first enter the compartment 335 | //! using `cx.enter_unknown_compartment(managed)`. 336 | //! 337 | //! ```rust 338 | //! # use josephine::*; 339 | //! fn example(cx: &mut JSContext, x: JSManaged) where 340 | //! S: CanAccess, 341 | //! { 342 | //! // We can't access x without first entering its compartment. 343 | //! // Commenting out the next two lines gives an error 344 | //! // the trait `josephine::Compartment` is not implemented for `josephine::SOMEWHERE`. 345 | //! let ref mut cx = cx.enter_unknown_compartment(x); 346 | //! let x = cx.entered(); 347 | //! println!("Hello, {}.", x.borrow(cx)); 348 | //! } 349 | //! ``` 350 | //! 351 | //! Sometimes you need to check to see if some JS managed data is in the current compartment or not. 352 | //! This is done with `managed.in_compartment(cx)`, which returns an `Option>` 353 | //! when the context's current compartment is `C`. The result is `Some(managed)` if `managed` was in 354 | //! compartment `C`, and `None` if it was in a different compartment. 355 | //! 356 | //! ```rust 357 | //! # use josephine::*; 358 | //! fn example(cx: &mut JSContext, x: JSManaged) where 359 | //! S: CanAccess + InCompartment, 360 | //! C: Compartment, 361 | //! { 362 | //! if let Some(x) = x.in_compartment(cx) { 363 | //! println!("Hello, {}.", x.borrow(cx)); 364 | //! } 365 | //! } 366 | //! ``` 367 | //! 368 | //! # User-defined types 369 | //! 370 | //! There are more types to manage than just `String`! 371 | //! 372 | //! To be managed by `JSManageable`, a type should implement the following traits: 373 | //! 374 | //! * `JSTraceable`: values of the type can be *traced*, that is can tell the garbage 375 | //! collector which objects are reachable from them. 376 | //! * `JSLifetime`: values of the type have their lifetime managed by JS. 377 | //! * `JSCompartmental`: values of the type have their compartment managed by JS. 378 | //! * `JSInitializable`: this type knows how to initialize a JS object which is used 379 | //! to manage its lifetime. 380 | //! 381 | //! Each of these traits can be derived. The fields of a `JSTraceable` type should be 382 | //! `JSTraceable`, and similarly for `JSLifetime` and `JSCompartmental`. This requirement 383 | //! is *not* true for `JSInitializable`. 384 | //! 385 | //! A typical use is to define two types: the *native* type `NativeThing` 386 | //! and then the *managed* type `Thing<'a, C>` which is just a `JSManaged<'a, C, NativeThing>`. 387 | //! 388 | //! ```rust 389 | //! # #[macro_use] extern crate josephine; 390 | //! # use josephine::*; 391 | //! #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 392 | //! struct NativeName { name: String } 393 | //! 394 | //! #[derive(Clone, Copy, Debug, Eq, PartialEq, JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 395 | //! pub struct Name<'a, C> (JSManaged<'a, C, NativeName>); 396 | //! 397 | //! impl<'a, C:'a> Name<'a, C> { 398 | //! pub fn new(cx: &'a mut JSContext, name: &str) -> Name<'a, C> where 399 | //! S: CanAlloc + InCompartment, 400 | //! C: Compartment, 401 | //! { 402 | //! Name(cx.manage(NativeName { name: String::from(name) })) 403 | //! } 404 | //! pub fn name(self, cx: &'a JSContext) -> &'a str where 405 | //! S: CanAccess, 406 | //! C: Compartment, 407 | //! { 408 | //! &*self.0.borrow(cx).name 409 | //! } 410 | //! } 411 | //! 412 | //! # fn main() { 413 | //! # let ref mut cx = JSContext::new().expect("Creating a JSContext failed"); 414 | //! # let ref mut cx = cx.create_compartment().global_manage(37); 415 | //! let ref mut root = cx.new_root(); 416 | //! let hello = Name::new(cx, "hello").in_root(root); 417 | //! assert_eq!(hello.name(cx), "hello"); 418 | //! # } 419 | //! ``` 420 | //! 421 | //! Sometimes the native type will itself contain references to JS-managed data, 422 | //! so will need to be parameterized on a lifetime and compartment. 423 | //! 424 | //! ```rust 425 | //! # #[macro_use] extern crate josephine; 426 | //! # use josephine::*; 427 | //! # #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 428 | //! # struct NativeName { name: String } 429 | //! # 430 | //! # #[derive(Clone, Copy, Debug, Eq, PartialEq, JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 431 | //! # pub struct Name<'a, C> (JSManaged<'a, C, NativeName>); 432 | //! # 433 | //! # impl<'a, C> Name<'a, C> { 434 | //! # pub fn new(cx: &'a mut JSContext, name: &str) -> Name<'a, C> where 435 | //! # S: CanAlloc + InCompartment, 436 | //! # C: Compartment, 437 | //! # { 438 | //! # Name(cx.manage(NativeName { name: String::from(name) })) 439 | //! # } 440 | //! # pub fn name(self, cx: &'a JSContext) -> &'a str where 441 | //! # S: CanAccess, 442 | //! # C: Compartment, 443 | //! # { 444 | //! # &*self.0.borrow(cx).name 445 | //! # } 446 | //! # } 447 | //! # 448 | //! #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 449 | //! struct NativeNames<'a, C> { names: Vec> } 450 | //! 451 | //! #[derive(Clone, Copy, Debug, JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 452 | //! pub struct Names<'a, C> (JSManaged<'a, C, NativeNames<'a, C>>); 453 | //! impl<'a, C:'a> Names<'a, C> { 454 | //! pub fn new(cx: &'a mut JSContext) -> Names<'a, C> where 455 | //! S: CanAlloc + InCompartment, 456 | //! C: Compartment, 457 | //! { 458 | //! Names(cx.manage(NativeNames { names: vec![] })) 459 | //! } 460 | //! pub fn push_str(self, cx: &'a mut JSContext, name: &str) where 461 | //! S: CanAccess + CanAlloc + InCompartment, 462 | //! C: Compartment, 463 | //! { 464 | //! let ref mut root = cx.new_root(); 465 | //! let name = Name::new(cx, name).in_root(root); 466 | //! self.0.borrow_mut(cx).names.push(name); 467 | //! } 468 | //! pub fn get(self, cx: &'a JSContext, index: usize) -> Option> where 469 | //! S: CanAccess, 470 | //! C: Compartment, 471 | //! { 472 | //! self.0.borrow(cx).names.get(index).cloned() 473 | //! } 474 | //! } 475 | //! 476 | //! # fn main() { 477 | //! # let ref mut cx = JSContext::new().expect("Creating a JSContext failed"); 478 | //! # let ref mut cx = cx.create_compartment().global_manage(37); 479 | //! let ref mut root = cx.new_root(); 480 | //! let hello_world = Names::new(cx).in_root(root); 481 | //! hello_world.push_str(cx, "hello"); 482 | //! hello_world.push_str(cx, "world"); 483 | //! assert_eq!(hello_world.get(cx, 0).map(|name| name.name(cx)), Some("hello")); 484 | //! assert_eq!(hello_world.get(cx, 1).map(|name| name.name(cx)), Some("world")); 485 | //! # } 486 | //! ``` 487 | //! 488 | //! # Calling between JS and Rust 489 | //! 490 | //! Josephine exposes an unsafe API to allow JS to call Rust and back again. 491 | //! This is just a thin wrapper round the SpiderMonkey API. 492 | //! See the [`ffi`](ffi/index.html) module for details. 493 | //! 494 | //! # Globals 495 | //! 496 | //! Each JS compartment has a special object called a *global*. 497 | //! The compartment can be created using `cx.create_compartment()`, 498 | //! and the global can be given native data to manage with `cx.global_manage(data)`. 499 | //! The global can be accessed with `cx.global()`. 500 | //! 501 | //! ```rust 502 | //! # #[macro_use] extern crate josephine; 503 | //! # use josephine::*; 504 | //! #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 505 | //! pub struct NativeMyGlobal { name: String } 506 | //! type MyGlobal<'a, C> = JSManaged<'a, C, NativeMyGlobal>; 507 | //! 508 | //! fn example<'a, S>(cx: &'a mut JSContext) -> MyGlobal<'a, SOMEWHERE> where 509 | //! S: CanAccess + CanAlloc, 510 | //! { 511 | //! let cx = cx.create_compartment(); 512 | //! let name = String::from("Alice"); 513 | //! let cx = cx.global_manage(NativeMyGlobal { name: name }); 514 | //! cx.global().forget_compartment() 515 | //! } 516 | //! # fn main() {} 517 | //! ``` 518 | //! 519 | //! In some cases, the global contains some JS-managed data, which is why the initialization 520 | //! is split into two steps: creating the compartment, and 521 | //! providing the JS-managed data for the global, for example: 522 | //! 523 | //! ```rust 524 | //! # #[macro_use] extern crate josephine; 525 | //! # use josephine::*; 526 | //! #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 527 | //! pub struct NativeMyGlobal<'a, C> { name: JSManaged<'a, C, String> } 528 | //! type MyGlobal<'a, C> = JSManaged<'a, C, NativeMyGlobal<'a, C>>; 529 | //! 530 | //! fn example<'a, S>(cx: &'a mut JSContext) -> MyGlobal<'a, SOMEWHERE> where 531 | //! S: CanAccess + CanAlloc, 532 | //! { 533 | //! let mut cx = cx.create_compartment(); 534 | //! let ref mut root = cx.new_root(); 535 | //! let name = cx.manage(String::from("Alice")).in_root(root); 536 | //! let ref cx = cx.global_manage(NativeMyGlobal { name: name }); 537 | //! cx.global().forget_compartment() 538 | //! } 539 | //! # fn main() {} 540 | //! ``` 541 | //! 542 | //! # Bootstrapping 543 | //! 544 | //! The JSContext is built using `JSContext::new`. 545 | //! 546 | //! ```rust 547 | //! # #[macro_use] extern crate josephine; 548 | //! # use josephine::*; 549 | //! #[derive(JSInitializable, JSTraceable, JSLifetime, JSCompartmental)] 550 | //! pub struct NativeMyGlobal { name: String } 551 | //! type MyGlobal<'a, C> = JSManaged<'a, C, NativeMyGlobal>; 552 | //! 553 | //! fn example<'a, S>(cx: &'a mut JSContext) -> MyGlobal<'a, SOMEWHERE> where 554 | //! S: CanAccess + CanAlloc, 555 | //! { 556 | //! let cx = cx.create_compartment(); 557 | //! let name = String::from("Alice"); 558 | //! let cx = cx.global_manage(NativeMyGlobal { name: name }); 559 | //! cx.global().forget_compartment() 560 | //! } 561 | //! fn main() { 562 | //! let ref mut cx = JSContext::new().expect("Creating a JSContext failed"); 563 | //! example(cx); 564 | //! } 565 | //! ``` 566 | 567 | #![feature(associated_type_defaults)] 568 | #![feature(const_fn)] 569 | #![feature(const_ptr_null)] 570 | #![feature(generic_param_attrs)] 571 | #![feature(dropck_eyepatch)] 572 | #![feature(refcell_replace_swap)] 573 | 574 | extern crate mozjs; 575 | extern crate libc; 576 | #[macro_use] extern crate log; 577 | 578 | pub mod context; 579 | pub use context::JSContext; 580 | pub use context::CanAccess; 581 | pub use context::CanAlloc; 582 | pub use context::InCompartment; 583 | pub use context::Initialized; 584 | pub use context::IsInitialized; 585 | pub use context::IsInitializing; 586 | pub use context::IsSnapshot; 587 | 588 | pub mod compartment; 589 | pub use compartment::SOMEWHERE; 590 | pub use compartment::Compartment; 591 | pub use compartment::JSCompartmental; 592 | 593 | pub mod ffi; 594 | pub use ffi::JSInitializable; 595 | 596 | pub mod js { 597 | pub use mozjs::glue; 598 | pub use mozjs::jsval; 599 | pub use mozjs::rust; 600 | 601 | #[cfg(feature = "smup")] 602 | pub use mozjs::heap; 603 | #[cfg(not(feature = "smup"))] 604 | pub use mozjs::jsapi as heap; 605 | 606 | #[cfg(feature = "smup")] 607 | pub use mozjs::jsapi; 608 | #[cfg(not(feature = "smup"))] 609 | pub mod jsapi { 610 | pub use mozjs::*; 611 | pub use mozjs::jsapi::*; 612 | pub use mozjs::jsapi as JS; 613 | pub use mozjs::jsapi as js; 614 | } 615 | } 616 | 617 | pub mod managed; 618 | pub use managed::JSManaged; 619 | 620 | pub mod root; 621 | pub use root::JSRoot; 622 | pub use root::JSLifetime; 623 | 624 | pub mod string; 625 | pub use string::JSString; 626 | 627 | pub mod trace; 628 | pub use trace::JSTraceable; 629 | 630 | // Re-export the derive macros 631 | #[allow(unused_imports)] #[macro_use] extern crate josephine_derive; 632 | #[doc(hidden)] 633 | pub use josephine_derive::*; 634 | -------------------------------------------------------------------------------- /src/managed.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::CanAccess; 6 | use super::CanAlloc; 7 | use super::Compartment; 8 | use super::InCompartment; 9 | use super::IsSnapshot; 10 | use super::JSContext; 11 | use super::JSInitializable; 12 | use super::JSLifetime; 13 | use super::JSTraceable; 14 | use super::JSCompartmental; 15 | use super::SOMEWHERE; 16 | use super::ffi::JSEvaluateErr; 17 | use super::ffi::JSInitializer; 18 | use super::ffi::UNSAFE; 19 | 20 | use js::glue::CallObjectTracer; 21 | 22 | use js::heap::Heap; 23 | 24 | use js::jsapi::JS::CurrentGlobalOrNull; 25 | use js::jsapi::JS::GCTraceKindToAscii; 26 | use js::jsapi::JS::HandleValue; 27 | use js::jsapi::JS::TraceKind; 28 | use js::jsapi::JS::Value; 29 | 30 | use js::jsapi::js::GetGlobalForObjectCrossCompartment; 31 | 32 | use js::jsapi::JSObject; 33 | use js::jsapi::JSTracer; 34 | use js::jsapi::JS_GetReservedSlot; 35 | use js::jsapi::JS_IsNative; 36 | use js::jsapi::JS_NewObjectWithGivenProto; 37 | use js::jsapi::JS_SetReservedSlot; 38 | 39 | use js::jsval::ObjectValue; 40 | use js::jsval::PrivateValue; 41 | 42 | use libc; 43 | 44 | use std::any::TypeId; 45 | use std::fmt; 46 | use std::fmt::Debug; 47 | use std::mem; 48 | use std::marker::PhantomData; 49 | 50 | /// The type of JS-managed data in a JS compartment `C`, with lifetime `'a` and type `T`. 51 | /// 52 | /// If the user has access to a `JSManaged`, then the JS-managed 53 | /// data is live for the given lifetime. 54 | pub struct JSManaged<'a, C, T> { 55 | js_object: *mut Heap<*mut JSObject>, 56 | raw: *mut (), 57 | marker: PhantomData<(&'a(), C, T)> 58 | } 59 | 60 | impl<'a, C, T> Clone for JSManaged<'a, C, T> { 61 | fn clone(&self) -> Self { 62 | JSManaged { 63 | js_object: self.js_object, 64 | raw: self.raw, 65 | marker: PhantomData, 66 | } 67 | } 68 | } 69 | 70 | impl<'a, C, T> Copy for JSManaged<'a, C, T> { 71 | } 72 | 73 | impl<'a, C, T> Debug for JSManaged<'a, C, T> { 74 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 75 | fmt.debug_struct("JSManaged") 76 | .field("js_object", &self.js_object) 77 | .field("raw", &self.raw) 78 | .finish() 79 | } 80 | } 81 | 82 | impl<'a, C, T> PartialEq for JSManaged<'a, C, T> { 83 | fn eq(&self, other: &Self) -> bool { 84 | self.raw == other.raw 85 | } 86 | } 87 | 88 | impl<'a, C, T> Eq for JSManaged<'a, C, T> { 89 | } 90 | 91 | unsafe impl<'a, C, T> JSTraceable for JSManaged<'a, C, T> where 92 | T: JSTraceable, 93 | { 94 | unsafe fn trace(&self, trc: *mut JSTracer) { 95 | debug!("Tracing JSManaged {:p}.", self.js_object); 96 | CallObjectTracer(trc, self.js_object, GCTraceKindToAscii(TraceKind::Object)); 97 | } 98 | } 99 | 100 | impl<'a, C, T> JSManaged<'a, C, T> { 101 | pub fn new(cx: &'a mut JSContext, value: T) -> JSManaged<'a, C, T::Aged> where 102 | S: CanAlloc + InCompartment, 103 | C: Compartment, 104 | T: JSTraceable + JSInitializable + JSLifetime<'a>, 105 | { 106 | debug!("Managing native data."); 107 | let jsapi_context = cx.cx(); 108 | let prototype = cx.prototype_of::(); 109 | 110 | let boxed_jsobject = Box::new(Heap::default()); 111 | debug!("Boxed object {:p}", boxed_jsobject); 112 | let unboxed_jsobject = unsafe { JS_NewObjectWithGivenProto(jsapi_context, T::Init::classp(), prototype) }; 113 | debug!("Unboxed object {:p}", unboxed_jsobject); 114 | assert!(!unboxed_jsobject.is_null()); 115 | boxed_jsobject.set(unboxed_jsobject); 116 | 117 | // Save a pointer to the native value in a private slot 118 | let boxed_value: Box = Box::new(Some(value)); 119 | let fat_value: [*const libc::c_void; 2] = unsafe { mem::transmute(boxed_value) }; 120 | #[cfg(feature = "smup")] 121 | unsafe { 122 | JS_SetReservedSlot(boxed_jsobject.get(), 0, &PrivateValue(fat_value[0])); 123 | JS_SetReservedSlot(boxed_jsobject.get(), 1, &PrivateValue(fat_value[1])); 124 | } 125 | #[cfg(not(feature = "smup"))] 126 | unsafe { 127 | JS_SetReservedSlot(boxed_jsobject.get(), 0, PrivateValue(fat_value[0])); 128 | JS_SetReservedSlot(boxed_jsobject.get(), 1, PrivateValue(fat_value[1])); 129 | } 130 | 131 | // TODO: can we be sure that this won't trigger GC? Or do we need to root the boxed object? 132 | debug!("Initializing JS object."); 133 | unsafe { T::Init::js_init_object(jsapi_context, boxed_jsobject.handle()) }; 134 | 135 | debug!("Managed native data."); 136 | JSManaged { 137 | js_object: Box::into_raw(boxed_jsobject), 138 | raw: fat_value[0] as *mut (), 139 | marker: PhantomData, 140 | } 141 | } 142 | 143 | pub unsafe fn from_raw(js_object: *mut Heap<*mut JSObject>, raw: *mut ()) -> JSManaged<'a, C, T> { 144 | JSManaged { 145 | js_object: js_object, 146 | raw: raw, 147 | marker: PhantomData, 148 | } 149 | } 150 | 151 | /// Read-only access to JS-managed data. 152 | pub fn get<'b, S>(self, _: &'b JSContext) -> T::Aged where 153 | S: CanAccess, 154 | C: Compartment, 155 | T: JSLifetime<'b>, 156 | T::Aged: Copy, 157 | 'a: 'b, 158 | { 159 | let result = unsafe { *(self.raw as *mut Option) }; 160 | result.unwrap() 161 | } 162 | 163 | pub fn borrow<'b, S>(self, _: &'b JSContext) -> &'b T::Aged where 164 | S: CanAccess, 165 | C: Compartment, 166 | T: JSLifetime<'b>, 167 | 'a: 'b, 168 | { 169 | let result = unsafe { &*(self.raw as *mut Option) }; 170 | result.as_ref().unwrap() 171 | } 172 | 173 | /// Read-write access to JS-managed data. 174 | pub fn borrow_mut<'b, S>(self, _: &'b mut JSContext) -> &'b mut T::Aged where 175 | S: CanAccess, 176 | C: Compartment, 177 | T: JSLifetime<'b>, 178 | 'a: 'b, 179 | { 180 | let result = unsafe { &mut *(self.raw as *mut Option) }; 181 | result.as_mut().unwrap() 182 | } 183 | 184 | /// Change the compartment of JS-managed data. 185 | pub unsafe fn change_compartment(self) -> JSManaged<'a, D, T::ChangeCompartment> where 186 | T: JSCompartmental, 187 | { 188 | JSManaged { 189 | js_object: self.js_object, 190 | raw: self.raw, 191 | marker: PhantomData, 192 | } 193 | } 194 | 195 | /// Change the lifetime of JS-managed data. 196 | pub unsafe fn change_lifetime<'b>(self) -> JSManaged<'b, C, T::Aged> where 197 | T: JSLifetime<'b>, 198 | { 199 | JSManaged { 200 | js_object: self.js_object, 201 | raw: self.raw, 202 | marker: PhantomData, 203 | } 204 | } 205 | 206 | /// It's safe to extend the lifetime of JS-managed data if it has been snapshotted. 207 | pub fn extend_lifetime<'b, 'c, S>(self, _: &'c JSContext) -> JSManaged<'b, C, T::Aged> where 208 | S: IsSnapshot<'b>, 209 | T: JSLifetime<'b>, 210 | { 211 | unsafe { self.change_lifetime() } 212 | } 213 | 214 | /// Forget about which compartment the managed data is in. 215 | /// This is safe because when we mutate data in compartment `C` we require 216 | /// `C: Compartment`, which means it is never `SOMEWHERE`. 217 | pub fn forget_compartment(self) -> JSManaged<'a, SOMEWHERE, T::ChangeCompartment> where 218 | T: JSCompartmental, 219 | { 220 | unsafe { self.change_compartment() } 221 | } 222 | 223 | /// Check to see if the current object is in the same compartment as another. 224 | pub fn in_compartment(self, cx: &JSContext) -> Option> where 225 | T: JSCompartmental, 226 | S: InCompartment, 227 | { 228 | // The jsapi rust bindings don't expose cx.compartment() 229 | // so we compare globals rather than compartments. 230 | let self_global = unsafe { GetGlobalForObjectCrossCompartment(self.to_jsobject()) }; 231 | let cx_global = unsafe { CurrentGlobalOrNull(cx.cx()) }; 232 | if self_global == cx_global { 233 | Some(unsafe { self.change_compartment() }) 234 | } else { 235 | None 236 | } 237 | } 238 | 239 | pub fn to_raw_native(self) -> *mut Option { 240 | self.raw as *mut Option 241 | } 242 | 243 | pub fn to_heap_object(self) -> *mut Heap<*mut JSObject> { 244 | self.js_object 245 | } 246 | 247 | pub fn to_jsobject(self) -> *mut JSObject { 248 | unsafe { &*self.to_heap_object() }.get() 249 | } 250 | 251 | pub fn to_jsval(self) -> Value { 252 | ObjectValue(self.to_jsobject()) 253 | } 254 | } 255 | 256 | /// A trait for Rust data which can be managed 257 | 258 | // TODO: this trait shouldn't be public 259 | 260 | pub trait JSManageable: JSTraceable { 261 | unsafe fn class_id(&self) -> TypeId; 262 | } 263 | 264 | impl JSManageable for Option where 265 | T: JSTraceable + JSInitializable 266 | { 267 | unsafe fn class_id(&self) -> TypeId { 268 | TypeId::of::() 269 | } 270 | } 271 | 272 | pub unsafe fn jsmanaged_called_from_js<'a, T>(js_value: HandleValue) -> Result, JSEvaluateErr> where 273 | T: JSInitializable, 274 | { 275 | if !js_value.is_object() { 276 | return Err(JSEvaluateErr::NotAnObject); 277 | } 278 | 279 | let js_object = js_value.to_object(); 280 | 281 | if !JS_IsNative(js_object) { 282 | return Err(JSEvaluateErr::NotJSManaged); 283 | } 284 | 285 | let slot0 = JS_GetReservedSlot(js_object, 0); 286 | let slot1 = JS_GetReservedSlot(js_object, 1); 287 | if slot0.is_undefined() || slot1.is_undefined() { 288 | return Err(JSEvaluateErr::NotJSManaged); 289 | } 290 | // This unsafe if there are raw uses of jsapi that are doing 291 | // other things with the reserved slots. 292 | let native: &mut JSManageable = mem::transmute([ slot0.to_private(), slot1.to_private() ]); 293 | 294 | // TODO: inheritance 295 | if TypeId::of::() != native.class_id() { 296 | return Err(JSEvaluateErr::WrongClass); 297 | } 298 | let raw = native as *mut _ as *mut (); 299 | 300 | // TODO: these boxes never get deallocated, which is a space leak! 301 | let boxed = Box::new(Heap::default()); 302 | boxed.set(js_object); 303 | 304 | Ok(JSManaged { 305 | js_object: Box::into_raw(boxed), 306 | raw: raw, 307 | marker: PhantomData, 308 | }) 309 | } 310 | -------------------------------------------------------------------------------- /src/root.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::JSContext; 6 | use super::JSManaged; 7 | use super::JSTraceable; 8 | 9 | use js::jsapi::JSTracer; 10 | 11 | use std::cell::UnsafeCell; 12 | use std::marker::PhantomData; 13 | use std::mem; 14 | use std::ops::Deref; 15 | use std::ops::DerefMut; 16 | use std::os::raw::c_void; 17 | use std::ptr; 18 | 19 | /// A stack allocated root containing data of type `T` with lifetime `'a`. 20 | pub struct JSRoot<'a, T: 'a> { 21 | value: Option, 22 | pin: JSUntypedPinnedRoot, 23 | marker: PhantomData<&'a ()>, // NOTE: this is variant in `'a`. 24 | } 25 | 26 | const DUMMY: *mut JSTraceable = &() as *const JSTraceable as *mut JSTraceable; 27 | 28 | impl<'a, T:'a> JSRoot<'a, T> { 29 | pub fn new(_cx: &mut JSContext) -> JSRoot<'a, T> { 30 | JSRoot { 31 | value: None, 32 | pin: JSUntypedPinnedRoot { 33 | value: DUMMY, 34 | next: ptr::null_mut(), 35 | prev: ptr::null_mut(), 36 | }, 37 | marker: PhantomData, 38 | } 39 | } 40 | } 41 | 42 | /// A stack allocated root that has been pinned, so the backing store can't move. 43 | pub struct JSPinnedRoot<'a, T: 'a> (&'a mut JSRoot<'a, T>); 44 | 45 | /// A doubly linked list with all the pinned roots. 46 | #[derive(Eq, PartialEq)] 47 | pub struct JSPinnedRoots(*mut JSUntypedPinnedRoot); 48 | 49 | impl JSPinnedRoots { 50 | unsafe fn insert(&mut self, root: &mut JSUntypedPinnedRoot) { 51 | debug!("Adding root {:p}.", root); 52 | root.next = self.0; 53 | root.prev = ptr::null_mut(); 54 | if let Some(next) = root.next.as_mut() { 55 | next.prev = root; 56 | } 57 | self.0 = root; 58 | } 59 | 60 | unsafe fn remove(&mut self, root: &mut JSUntypedPinnedRoot) { 61 | if let Some(next) = root.next.as_mut() { 62 | debug!("Removing root.next.prev for {:p}.", root); 63 | next.prev = root.prev; 64 | } 65 | if let Some(prev) = root.prev.as_mut() { 66 | debug!("Removing root.prev.next for {:p}.", root); 67 | prev.next = root.next; 68 | } else if self.0 == root { 69 | debug!("Removing root {:p} from rootset.", root); 70 | self.0 = root.next; 71 | } 72 | root.value = DUMMY; 73 | root.next = ptr::null_mut(); 74 | root.prev = ptr::null_mut(); 75 | } 76 | } 77 | 78 | /// The thread-local list of all roots 79 | thread_local! { static ROOTS: UnsafeCell = UnsafeCell::new(JSPinnedRoots(ptr::null_mut())); } 80 | 81 | unsafe fn thread_local_roots() -> *mut JSPinnedRoots { 82 | ROOTS.with(UnsafeCell::get) 83 | } 84 | 85 | pub unsafe extern "C" fn trace_thread_local_roots(trc: *mut JSTracer, _: *mut c_void) { 86 | debug!("Tracing roots."); 87 | 88 | if let Some(roots) = thread_local_roots().as_mut() { 89 | let mut curr = roots.0; 90 | while let Some(root) = curr.as_ref() { 91 | debug!("Tracing root {:p}.", root); 92 | (&*root.value).trace(trc); 93 | curr = root.next; 94 | } 95 | } 96 | 97 | debug!("Done tracing roots."); 98 | } 99 | 100 | /// A stack allocated root that has been pinned, but we don't have a type for the contents 101 | struct JSUntypedPinnedRoot { 102 | value: *mut JSTraceable, 103 | next: *mut JSUntypedPinnedRoot, 104 | prev: *mut JSUntypedPinnedRoot, 105 | } 106 | 107 | impl Drop for JSUntypedPinnedRoot { 108 | fn drop(&mut self) { 109 | unsafe { (&mut *thread_local_roots()).remove(self); } 110 | } 111 | } 112 | 113 | /// Data which can be rooted. 114 | 115 | pub unsafe trait JSLifetime<'a> { 116 | type Aged; 117 | 118 | unsafe fn change_lifetime(self) -> Self::Aged where Self: Sized { 119 | let result = mem::transmute_copy(&self); 120 | mem::forget(self); 121 | result 122 | } 123 | 124 | fn contract_lifetime(self) -> Self::Aged where Self: 'a + Sized { 125 | unsafe { self.change_lifetime() } 126 | } 127 | 128 | fn in_root(self, root: &'a mut JSRoot<'a, Self::Aged>) -> Self::Aged where 129 | Self: Sized, 130 | Self::Aged: Copy + JSTraceable, 131 | { 132 | root.set(self) 133 | } 134 | } 135 | 136 | unsafe impl<'a> JSLifetime<'a> for String { type Aged = String; } 137 | unsafe impl<'a> JSLifetime<'a> for usize { type Aged = usize; } 138 | unsafe impl<'a, T> JSLifetime<'a> for Option where T: JSLifetime<'a> { type Aged = Option; } 139 | unsafe impl<'a, T> JSLifetime<'a> for Vec where T: JSLifetime<'a> { type Aged = Vec; } 140 | 141 | unsafe impl<'a, 'b, C, T> JSLifetime<'a> for JSManaged<'b, C, T> where 142 | T: JSLifetime<'a>, 143 | { 144 | type Aged = JSManaged<'a, C, T::Aged>; 145 | } 146 | 147 | impl<'a, T> JSRoot<'a, T> { 148 | // This uses Sgeo's trick to stop the JSRoot being forgotten by `mem::forget`. 149 | // The pin takes a `&'a mut JSRoot<'a, T>`, so borrows the root for the 150 | // duration of `'a`, so the type is no longer valid after the pin is dropped. 151 | pub fn pin(&'a mut self, value: U) -> &'a T where 152 | T: JSTraceable, 153 | U: JSLifetime<'a, Aged=T>, 154 | { 155 | let roots = unsafe { &mut *thread_local_roots() }; 156 | self.value = Some(unsafe { value.change_lifetime() }); 157 | self.pin.value = self.value.as_mut_ptr(); 158 | unsafe { roots.insert(&mut self.pin) }; 159 | self.value.as_ref().unwrap() 160 | } 161 | 162 | pub fn set(&'a mut self, value: U) -> T where 163 | T: Copy + JSTraceable, 164 | U: JSLifetime<'a, Aged=T>, 165 | { 166 | *self.pin(value) 167 | } 168 | 169 | pub unsafe fn unpin(&mut self) { 170 | let roots = &mut *thread_local_roots(); 171 | roots.remove(&mut self.pin); 172 | } 173 | } 174 | 175 | impl<'a, T> Deref for JSPinnedRoot<'a, T> { 176 | type Target = T; 177 | fn deref(&self) -> &T { 178 | self.0.value.as_ref().unwrap() 179 | } 180 | } 181 | 182 | impl<'a, T> DerefMut for JSPinnedRoot<'a, T> { 183 | fn deref_mut(&mut self) -> &mut T { 184 | self.0.value.as_mut().unwrap() 185 | } 186 | } 187 | 188 | unsafe impl<#[may_dangle] 'a, #[may_dangle] T> Drop for JSRoot<'a, T> { 189 | fn drop(&mut self) { 190 | unsafe { self.unpin() } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use js::jsapi::JS::InitSelfHostedCode; 6 | use js::jsapi::JS_BeginRequest; 7 | use js::jsapi::JS_EndRequest; 8 | use js::jsapi::JS_ShutDown; 9 | use js::jsapi::JS_NewRuntime; 10 | use js::jsapi::JSRuntime; 11 | use js::jsapi::JS_DestroyRuntime; 12 | use js::jsapi::JS_GetContext; 13 | use js::jsapi::JS_Init; 14 | 15 | use std::ptr; 16 | use std::rc::Rc; 17 | 18 | /// A JS runtime owned by Rust 19 | pub struct OwnedJSRuntime(*mut JSRuntime, Rc); 20 | 21 | const DEFAULT_HEAPSIZE: u32 = 32_u32 * 1024_u32 * 1024_u32; 22 | const CHUNK_SIZE: u32 = 1 << 20; 23 | 24 | impl OwnedJSRuntime { 25 | pub fn new() -> OwnedJSRuntime { 26 | let js_init = OWNED_JSINIT.with(|init| init.clone()); 27 | let js_runtime = unsafe { JS_NewRuntime(DEFAULT_HEAPSIZE, CHUNK_SIZE, ptr::null_mut()) }; 28 | let js_context = unsafe { JS_GetContext(js_runtime) }; 29 | unsafe { InitSelfHostedCode(js_context) }; 30 | unsafe { JS_BeginRequest(js_context) }; 31 | OwnedJSRuntime(js_runtime, js_init) 32 | } 33 | 34 | pub fn rt(&self) -> *mut JSRuntime { 35 | self.0 36 | } 37 | } 38 | 39 | impl Drop for OwnedJSRuntime { 40 | fn drop(&mut self) { 41 | unsafe { JS_EndRequest(JS_GetContext(self.0)) }; 42 | unsafe { JS_DestroyRuntime(self.0) }; 43 | } 44 | } 45 | 46 | /// A capability to create JSRuntimes, owned by Rust. 47 | struct OwnedJSInit; 48 | 49 | thread_local! { static OWNED_JSINIT: Rc = { unsafe { JS_Init() }; Rc::new(OwnedJSInit) }; } 50 | 51 | impl Drop for OwnedJSInit { 52 | fn drop(&mut self) { 53 | unsafe { JS_ShutDown() }; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use super::CanAlloc; 6 | use super::InCompartment; 7 | use super::JSTraceable; 8 | use super::JSContext; 9 | use super::JSLifetime; 10 | use super::ffi::JSEvaluateErr; 11 | use super::ffi::UNSAFE; 12 | 13 | use js::glue::CallStringTracer; 14 | 15 | use js::jsapi; 16 | use js::jsapi::JS::GCTraceKindToAscii; 17 | use js::jsapi::JS::HandleValue; 18 | use js::heap::Heap; 19 | use js::jsapi::JS_FlattenString; 20 | use js::jsapi::JS_GetLatin1FlatStringChars; 21 | use js::jsapi::JS_GetStringLength; 22 | use js::jsapi::JS_GetTwoByteFlatStringChars; 23 | use js::jsapi::JS_NewStringCopyN; 24 | use js::jsapi::JS_NewUCStringCopyN; 25 | use js::jsapi::JS_StringHasLatin1Chars; 26 | use js::jsapi::JSTracer; 27 | use js::jsapi::JS::TraceKind; 28 | 29 | use js::jsapi::JS::Value; 30 | 31 | use js::jsval::StringValue; 32 | 33 | use std::char; 34 | use std::fmt; 35 | use std::fmt::Display; 36 | use std::fmt::Write; 37 | use std::marker::PhantomData; 38 | use std::ptr; 39 | use std::slice; 40 | use std::str; 41 | 42 | /// The type of JS-managed strings in the same zone as compartment `C`, with lifetime `a`. 43 | /// Rust is much happier with flat string representations, so we flatten 44 | /// strings when they come into Rust. 45 | pub struct JSString<'a, C> { 46 | // This string is always flattened, but we're not allowed to build a `Heap<*mut JSFlatString>`. 47 | js_string: *mut Heap<*mut jsapi::JSString>, 48 | marker: PhantomData<(&'a(), C)>, 49 | } 50 | 51 | impl<'a, C> Clone for JSString<'a, C> { 52 | fn clone(&self) -> JSString<'a, C> { 53 | JSString { 54 | js_string: self.js_string, 55 | marker: PhantomData, 56 | } 57 | } 58 | } 59 | 60 | impl<'a, C> Copy for JSString<'a, C> {} 61 | 62 | impl<'a, C> JSString<'a, C> { 63 | pub fn to_jsstring(self) -> *mut jsapi::JSString { 64 | unsafe { &*self.js_string }.get() 65 | } 66 | 67 | pub fn to_jsflatstring(self) -> *mut jsapi::JSFlatString { 68 | self.to_jsstring() as *mut jsapi::JSFlatString 69 | } 70 | 71 | pub fn to_jsval(self) -> Value { 72 | StringValue(unsafe { &*self.to_jsstring() }) 73 | } 74 | 75 | pub fn len(self) -> usize { 76 | unsafe { JS_GetStringLength(self.to_jsstring()) } 77 | } 78 | 79 | pub fn has_latin1_chars(self) -> bool { 80 | unsafe { JS_StringHasLatin1Chars(self.to_jsstring()) } 81 | } 82 | 83 | pub unsafe fn get_latin1_chars(self) -> &'a str { 84 | let nogc = ptr::null_mut(); // TODO: build an AutoCheckCannotGC? 85 | let raw = JS_GetLatin1FlatStringChars(nogc, self.to_jsflatstring()); 86 | str::from_utf8_unchecked(slice::from_raw_parts(raw, self.len())) 87 | } 88 | 89 | pub unsafe fn get_two_byte_chars(self) -> &'a [u16] { 90 | let nogc = ptr::null_mut(); // TODO: build an AutoCheckCannotGC? 91 | let raw = JS_GetTwoByteFlatStringChars(nogc, self.to_jsflatstring()); 92 | slice::from_raw_parts(raw, self.len()) 93 | } 94 | 95 | pub fn contents(self) -> JSStringContents<'a> { 96 | if self.has_latin1_chars() { 97 | JSStringContents::Latin1(unsafe { self.get_latin1_chars() }) 98 | } else { 99 | JSStringContents::TwoByte(unsafe { self.get_two_byte_chars() }) 100 | } 101 | } 102 | 103 | pub unsafe fn from_jsstring(js_string: *mut jsapi::JSString) -> JSString<'a, C> { 104 | // TODO: these boxes never get deallocated, which is a space leak! 105 | let boxed = Box::new(Heap::default()); 106 | boxed.set(js_string); 107 | JSString { 108 | js_string: Box::into_raw(boxed), 109 | marker: PhantomData, 110 | } 111 | } 112 | 113 | pub unsafe fn from_latin1_unchecked(cx: &'a mut JSContext, string: &str) -> JSString<'a, C> where 114 | S: CanAlloc + InCompartment, 115 | { 116 | JSString::from_jsstring(JS_NewStringCopyN(cx.cx(), string as *const str as *const i8, string.len())) 117 | } 118 | 119 | pub fn from_latin1(cx: &'a mut JSContext, string: &str) -> Option> where 120 | S: CanAlloc + InCompartment, 121 | { 122 | if string.bytes().all(|byte| byte < 128) { 123 | Some(unsafe { JSString::from_latin1_unchecked(cx, string) }) 124 | } else { 125 | None 126 | } 127 | } 128 | 129 | pub fn from_twobyte(cx: &'a mut JSContext, slice: &[u16]) -> JSString<'a, C> where 130 | S: CanAlloc + InCompartment, 131 | { 132 | unsafe { JSString::from_jsstring(JS_NewUCStringCopyN(cx.cx(), &slice[0] as *const u16, slice.len())) } 133 | } 134 | 135 | pub fn from_str(cx: &'a mut JSContext, string: &str) -> JSString<'a, C> where 136 | S: CanAlloc + InCompartment, 137 | { 138 | if string.bytes().all(|byte| byte < 128) { 139 | unsafe { JSString::from_latin1_unchecked(cx, string) } 140 | } else { 141 | let utf16: Vec = string.encode_utf16().collect(); 142 | JSString::from_twobyte(cx, &*utf16) 143 | } 144 | } 145 | 146 | pub fn clone_in(self, cx: &'a mut JSContext) -> JSString<'a, D> where 147 | S: CanAlloc + InCompartment, 148 | { 149 | match self.contents() { 150 | JSStringContents::Latin1(string) => unsafe { JSString::from_latin1_unchecked(cx, string) }, 151 | JSStringContents::TwoByte(slice) => JSString::from_twobyte(cx, slice), 152 | } 153 | } 154 | } 155 | 156 | impl<'a, C> Display for JSString<'a, C> { 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 | self.contents().fmt(f) 159 | } 160 | } 161 | 162 | unsafe impl<'a, C> JSTraceable for JSString<'a, C> { 163 | unsafe fn trace(&self, trc: *mut JSTracer) { 164 | debug!("Tracing JSString {:p}.", self.js_string); 165 | CallStringTracer(trc, self.js_string, GCTraceKindToAscii(TraceKind::String)); 166 | } 167 | } 168 | 169 | unsafe impl<'a, 'b, C> JSLifetime<'a> for JSString<'b, C> { 170 | type Aged = JSString<'a, C>; 171 | } 172 | 173 | pub enum JSStringContents<'a> { 174 | Latin1(&'a str), 175 | TwoByte(&'a[u16]), 176 | } 177 | 178 | impl<'a> Display for JSStringContents<'a> { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | match *self { 181 | JSStringContents::Latin1(ref string) => f.write_str(string), 182 | JSStringContents::TwoByte(ref slice) => char::decode_utf16(slice.iter().cloned()) 183 | .map(|ch| ch.unwrap_or(char::REPLACEMENT_CHARACTER)) 184 | .map(|ch| f.write_char(ch)) 185 | .find(Result::is_err) 186 | .unwrap_or(Ok(())) 187 | } 188 | } 189 | } 190 | 191 | pub unsafe fn jsstring_called_from_js<'a>(cx: *mut jsapi::JSContext, value: HandleValue) -> Result, JSEvaluateErr> { 192 | if !value.is_string() { 193 | return Err(JSEvaluateErr::NotAString); 194 | } 195 | 196 | let flattened = JS_FlattenString(cx, value.to_string()); 197 | 198 | // TODO: these boxes never get deallocated, which is a space leak! 199 | let boxed = Box::new(Heap::default()); 200 | boxed.set(flattened as *mut jsapi::JSString); 201 | 202 | Ok(JSString { 203 | js_string: Box::into_raw(boxed), 204 | marker: PhantomData, 205 | }) 206 | } 207 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | pub use js::jsapi::JSTracer; 6 | 7 | use std::mem; 8 | 9 | /// A trait for Rust data that can be traced. 10 | pub unsafe trait JSTraceable { 11 | unsafe fn trace(&self, trc: *mut JSTracer); 12 | 13 | fn as_ptr(&self) -> *const JSTraceable where Self: Sized { 14 | unsafe { mem::transmute(self as &JSTraceable) } 15 | } 16 | 17 | fn as_mut_ptr(&mut self) -> *mut JSTraceable where Self: Sized { 18 | unsafe { mem::transmute(self as &mut JSTraceable) } 19 | } 20 | } 21 | 22 | unsafe impl JSTraceable for String { 23 | unsafe fn trace(&self, _trc: *mut JSTracer) {} 24 | } 25 | 26 | unsafe impl JSTraceable for usize { 27 | unsafe fn trace(&self, _trc: *mut JSTracer) {} 28 | } 29 | 30 | unsafe impl JSTraceable for () { 31 | unsafe fn trace(&self, _trc: *mut JSTracer) {} 32 | } 33 | 34 | unsafe impl JSTraceable for Option where T: JSTraceable { 35 | unsafe fn trace(&self, trc: *mut JSTracer) { 36 | if let Some(ref val) = *self { 37 | val.trace(trc); 38 | } 39 | } 40 | } 41 | 42 | unsafe impl JSTraceable for Vec where T: JSTraceable { 43 | unsafe fn trace(&self, trc: *mut JSTracer) { 44 | for val in self { 45 | val.trace(trc); 46 | } 47 | } 48 | } 49 | 50 | // etc. 51 | --------------------------------------------------------------------------------