├── docs ├── README.md ├── Value.md ├── Exceptions.md ├── Building SpiderMonkey.md ├── Miscellaneous.md ├── JSAPI Introduction.md ├── Custom Objects.md ├── Garbage Collection.md ├── Debugging Tips.md └── GC Rooting Guide.md ├── .clang-format ├── tools ├── generic_lib.sh ├── get_sm.sh ├── make_opcode_doc.py ├── apply-format ├── git-pre-commit-format └── jsopcode.py ├── examples ├── boilerplate.h ├── hello.cpp ├── boilerplate.cpp ├── README.md ├── wasm.cpp ├── worker.cpp ├── modules.cpp ├── repl.cpp ├── resolve.cpp ├── tracing.cpp ├── weakref.cpp └── cookbook.cpp ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── .github └── workflows │ └── build.yml └── meson.build /docs/README.md: -------------------------------------------------------------------------------- 1 | # SpiderMonkey Library Documentation # 2 | 3 | In this directory you will find documentation pages with explanations of 4 | core SpiderMonkey concepts, and how-to pages. 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | 4 | # Prevent the loss of indentation with these macros 5 | MacroBlockBegin: "^JS_BEGIN_MACRO$" 6 | MacroBlockEnd: "^JS_END_MACRO$" 7 | 8 | SortIncludes: false 9 | ... 10 | -------------------------------------------------------------------------------- /tools/generic_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Make SpiderMonkey and this repo generic instead of version specific. 3 | # script shoud be run in mozjs dir with $1 for meson.build location 4 | sed -i 's/mozjs-$MOZILLA_SYMBOLVERSION/mozjs/g' old-configure.in 5 | sed -i --regexp-extended "s/dependency\(.mozjs.*$/dependency('mozjs')/gm" "$1" 6 | -------------------------------------------------------------------------------- /examples/boilerplate.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // See 'boilerplate.cpp' for documentation. 4 | 5 | namespace boilerplate { 6 | 7 | extern const JSClassOps DefaultGlobalClassOps; 8 | 9 | JSObject* CreateGlobal(JSContext* cx); 10 | 11 | void ReportAndClearException(JSContext* cx); 12 | 13 | bool RunExample(bool (*task)(JSContext*), bool initSelfHosting = true); 14 | 15 | } // namespace boilerplate 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Mozilla Foundation 2 | 3 | Licensed under the Apache License (Version 2.0), or the MIT license, 4 | (the "Licenses") at your option. You may not use this file except in 5 | compliance with one of the Licenses. You may obtain copies of the 6 | Licenses at: 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | http://opensource.org/licenses/MIT 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the Licenses is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the Licenses for the specific language governing permissions and 15 | limitations under the Licenses. 16 | -------------------------------------------------------------------------------- /tools/get_sm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Get latest mozjs tar in repo 3 | repo=mozilla-beta 4 | jobs=( $(curl "https://treeherder.mozilla.org/api/project/$repo/push/?full=true&count=50" | jq '.results[].id') ) 5 | for i in "${jobs[@]}" 6 | do 7 | task_id=$(curl "https://treeherder.mozilla.org/api/jobs/?push_id=$i" | jq -r '.results[] | select(.[] == "spidermonkey-sm-package-linux64/opt") | .[14]') 8 | echo "Task id $task_id" 9 | if [ ! -z "${task_id}" ]; then 10 | tar_file=$(curl "https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/$task_id/runs/0/artifacts" | jq -r '.artifacts[] | select(.name | contains("tar.xz")) | .name') 11 | echo "Tar at https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/$task_id/runs/0/artifacts/$tar_file" 12 | curl -L --output mozjs.tar.xz "https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/$task_id/runs/0/artifacts/$tar_file" 13 | break 14 | fi 15 | done 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpiderMonkey Embedding Resources # 2 | 3 | This repository contains documentation and examples for people who want 4 | to embed the SpiderMonkey JavaScript engine. 5 | 6 | The information on this `esr102` branch applies to SpiderMonkey 102.x, an 7 | Extended Support Release (ESR). 8 | For other versions of SpiderMonkey, check the other branches: `next` for 9 | information that will apply in the next as-yet-unreleased ESR, or 10 | earlier `esr` branches for previous versions. 11 | 12 | # Docs # 13 | 14 | Check the [`docs/`](docs/) directory for howtos and documentation, such as: 15 | 16 | - [Building SpiderMonkey for embedding](docs/Building%20SpiderMonkey.md ) 17 | - [A JSAPI Introduction](docs/JSAPI%20Introduction.md) 18 | - [The Migration Guide from previous versions](docs/Migration%20Guide.md) 19 | 20 | 21 | # Examples # 22 | 23 | The [`examples/`](examples/) directory contains code examples. 24 | See the [README in that directory](examples/README.md) for build 25 | instructions. 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Spidermonkey 2 | 3 | on: 4 | push: 5 | branches-ignore: [ esr* ] 6 | pull_request: 7 | branches-ignore: [ esr* ] 8 | schedule: 9 | # every fifth day 10 | - cron: "10 10 1,5,10,15,20,25 * *" 11 | 12 | env: 13 | SHELL: /bin/bash 14 | # ccache 15 | CCACHE: ccache 16 | # use clang/lld 17 | CXX: clang++ 18 | CC: clang 19 | LDFLAGS: -fuse-ld=lld 20 | LD_LIBRARY_PATH: /usr/local/lib 21 | 22 | jobs: 23 | build: 24 | 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Install deps 30 | run: | 31 | sudo apt install ccache llvm clang lld meson ninja-build -y 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | profile: minimal 35 | toolchain: stable 36 | override: true 37 | default: true 38 | - name: Get SM pkg 39 | run: ./tools/get_sm.sh 40 | - name: ccache cache files 41 | uses: actions/cache@v1.1.0 42 | with: 43 | path: ~/.ccache 44 | key: ${{ runner.os }}-${{ hashFiles('**/mozjs.tar.xz') }} 45 | - name: Build SpiderMonkey 46 | run: | 47 | mkdir -p /tmp/mozjs 48 | tar -xf mozjs.tar.xz -C /tmp/mozjs 49 | cd /tmp/mozjs 50 | cd $(ls -d */|head -n 1) 51 | cd js/src 52 | bash $GITHUB_WORKSPACE/tools/generic_lib.sh $GITHUB_WORKSPACE/meson.build 53 | mkdir _build 54 | cd _build 55 | ../configure --disable-jemalloc --with-system-zlib \ 56 | --with-intl-api --enable-debug --enable-optimize 57 | ccache -z 58 | make 59 | sudo make install 60 | ccache -s 61 | - name: Build Examples 62 | run: | 63 | meson _build || cat _build/meson-logs/meson-log.txt 64 | ninja -C _build 65 | -------------------------------------------------------------------------------- /examples/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "boilerplate.h" 8 | 9 | // This example illustrates the bare minimum you need to do to execute a 10 | // JavaScript program using embedded SpiderMonkey. It does no error handling and 11 | // simply exits if something goes wrong. 12 | // 13 | // See 'boilerplate.cpp' for the parts of this example that are reused in many 14 | // simple embedding examples. 15 | // 16 | // To use the interpreter you need to create a context and a global object, and 17 | // do some setup on both of these. You also need to enter a "realm" (environment 18 | // within one global object) before you can execute code. 19 | 20 | static bool ExecuteCodePrintResult(JSContext* cx, const char* code) { 21 | JS::CompileOptions options(cx); 22 | options.setFileAndLine("noname", 1); 23 | 24 | JS::SourceText source; 25 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 26 | return false; 27 | } 28 | 29 | JS::RootedValue rval(cx); 30 | if (!JS::Evaluate(cx, options, source, &rval)) return false; 31 | 32 | // There are many ways to display an arbitrary value as a result. In this 33 | // case, we know that the value is an ASCII string because of the expression 34 | // that we executed, so we can just print the string directly. 35 | printf("%s\n", JS_EncodeStringToASCII(cx, rval.toString()).get()); 36 | return true; 37 | } 38 | 39 | static bool HelloExample(JSContext* cx) { 40 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 41 | if (!global) { 42 | return false; 43 | } 44 | 45 | JSAutoRealm ar(cx, global); 46 | 47 | // The 'js' delimiter is meaningless, but it's useful for marking C++ raw 48 | // strings semantically. 49 | return ExecuteCodePrintResult(cx, R"js( 50 | `hello world, it is ${new Date()}` 51 | )js"); 52 | } 53 | 54 | int main(int argc, const char* argv[]) { 55 | if (!boilerplate::RunExample(HelloExample)) { 56 | return 1; 57 | } 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /docs/Value.md: -------------------------------------------------------------------------------- 1 | # JavaScript values # 2 | 3 | JavaScript is a dynamically typed language: variables and properties do 4 | not have a type that is fixed at compile time. 5 | How can a statically typed language, like C or C++, in which all 6 | variables have types, interact with JavaScript? 7 | The JSAPI provides a data type, `JS::Value`, which can contain 8 | JavaScript values of any type. 9 | A `JS::Value` can be a number, a string, a boolean value, a reference to 10 | an object (like an `Object`, `Array`, `Date`, or `Function`), or one of 11 | the special values `null` or `undefined`. 12 | 13 | For integers and boolean values, a `JS::Value` contains the value 14 | itself. 15 | In other cases, the `JS::Value` is a pointer to an object, string, or 16 | number. 17 | 18 | > **Warning:** Like C++ pointers, and unlike JavaScript `var`s, a 19 | > `JS::Value` is **not** automatically initialized to a safe value, and 20 | > **can** become a dangling pointer! 21 | > 22 | > A dangling pointer is a pointer that used to point to a valid object, 23 | > but no longer does because the object no longer exists. 24 | > Using a dangling pointer can crash a C++ program (or worse). 25 | > In the case of `JS::Value`, the JavaScript garbage collector recycles 26 | > objects, strings, and numbers that don't appear to be in use, and a 27 | > `JS::Value` by itself does not protect its referent from the garbage 28 | > collector. 29 | > See _Garbage collection_ below for crucial information on how to use 30 | > `JS::Value`s safely. 31 | 32 | `JS::Value` inclues member functions to test the JavaScript data type. 33 | These are `isObject()`, `isNumber()`, `isInt32()`, `isDouble()`, 34 | `isString()`, `isBoolean()`, `isSymbol()`, `isNull()`, and 35 | `isUndefined()`. 36 | 37 | If a `JS::Value` contains a `JSObject`, `double`, or `JSString`, you can 38 | cast it to its underlying data type using the `toObject()`, 39 | `toDouble()`, and `toString()` member functions, respectively. 40 | This is useful in some cases where your application or a JSAPI function 41 | requires a variable or argument of a specific data type, rather than a 42 | `JS::Value`. 43 | Similarly, you can create a `JS::Value` wrapping a `JSObject`, `double`, 44 | or `JSString` pointer to a `JS::Value` using 45 | `JS::ObjectValue(JSObject&)`, `JS::DoubleValue(double)`, or 46 | `JS::StringValue(JSString*)`. 47 | -------------------------------------------------------------------------------- /examples/boilerplate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "boilerplate.h" 7 | 8 | // This file contains boilerplate code used by a number of examples. Ideally 9 | // this should eventually become part of SpiderMonkey itself. 10 | 11 | // Create a simple Global object. A global object is the top-level 'this' value 12 | // in a script and is required in order to compile or execute JavaScript. 13 | JSObject* boilerplate::CreateGlobal(JSContext* cx) { 14 | JS::RealmOptions options; 15 | 16 | static JSClass BoilerplateGlobalClass = { 17 | "BoilerplateGlobal", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; 18 | 19 | return JS_NewGlobalObject(cx, &BoilerplateGlobalClass, nullptr, 20 | JS::FireOnNewGlobalHook, options); 21 | } 22 | 23 | // Helper to read current exception and dump to stderr. 24 | // 25 | // NOTE: This must be called with a JSAutoRealm (or equivalent) on the stack. 26 | void boilerplate::ReportAndClearException(JSContext* cx) { 27 | JS::ExceptionStack stack(cx); 28 | if (!JS::StealPendingExceptionStack(cx, &stack)) { 29 | fprintf(stderr, "Uncatchable exception thrown, out of memory or something"); 30 | exit(1); 31 | } 32 | 33 | JS::ErrorReportBuilder report(cx); 34 | if (!report.init(cx, stack, JS::ErrorReportBuilder::WithSideEffects)) { 35 | fprintf(stderr, "Couldn't build error report"); 36 | exit(1); 37 | } 38 | 39 | JS::PrintError(stderr, report, false); 40 | } 41 | 42 | // Initialize the JS environment, create a JSContext and run the example 43 | // function in that context. By default the self-hosting environment is 44 | // initialized as it is needed to run any JavaScript). If the 'initSelfHosting' 45 | // argument is false, we will not initialize self-hosting and instead leave 46 | // that to the caller. 47 | bool boilerplate::RunExample(bool (*task)(JSContext*), bool initSelfHosting) { 48 | if (!JS_Init()) { 49 | return false; 50 | } 51 | 52 | JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes); 53 | if (!cx) { 54 | return false; 55 | } 56 | 57 | if (initSelfHosting && !JS::InitSelfHostedCode(cx)) { 58 | return false; 59 | } 60 | 61 | if (!task(cx)) { 62 | return false; 63 | } 64 | 65 | JS_DestroyContext(cx); 66 | JS_ShutDown(); 67 | 68 | return true; 69 | } 70 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # SpiderMonkey Embedding Examples # 2 | 3 | ## Prerequsisites ## 4 | 5 | You need Meson 0.43.0 or later to build the examples. 6 | Installation instructions for Meson are [here](https://mesonbuild.com/Getting-meson.html). 7 | 8 | You will also need SpiderMonkey ESR 102 installed where Meson can find 9 | it. 10 | Generally this means that the `mozjs-102.pc` file needs to be installed 11 | in a location known to pkg-config, and the `libmozjs-102.so` file needs 12 | to be in the path for loading libraries. 13 | 14 | Many Linux distributions have development packages for SpiderMonkey 102 15 | and if you just want to try the examples, installing that is the easiest 16 | way to get a build of SpiderMonkey. 17 | If you are on macOS or Windows, or want to do any development, read the 18 | [Building SpiderMonkey for Embedders](../docs/Building%20SpiderMonkey.md) 19 | page. 20 | 21 | For the REPL example, you will need readline installed where Meson can 22 | find it, as well. 23 | 24 | ## To build ## 25 | 26 | To compile these examples, build in the toplevel directory: 27 | ```sh 28 | meson _build 29 | ninja -C _build 30 | ``` 31 | 32 | ## To contribute ## 33 | 34 | Install the clang-format commit hook: 35 | 36 | ```sh 37 | tools/git-pre-commit-format install 38 | ``` 39 | 40 | If adding a new example to the examples directory, make sure to build it 41 | in the `meson.build` file, and add a description of it to this 42 | `README.md` file. 43 | 44 | ## List of examples ## 45 | 46 | - **hello.cpp** - Simple Hello World program, shows how to do the bare 47 | minimum to embed SpiderMonkey and execute a single line of JS code. 48 | - **tracing.cpp** - Example of how to safely store pointers to 49 | garbage-collected things into your C++ data structures. 50 | - **cookbook.cpp** - Based on an old wiki page called "JSAPI Cookbook", 51 | this program doesn't do anything in particular but contains a lot of 52 | examples showing how to do common operations with SpiderMonkey. 53 | - **repl.cpp** - Best practices for creating a mini JavaScript 54 | interpreter, consisting of a read-eval-print loop. 55 | - **resolve.cpp** - Best practices for creating a JS class that uses 56 | lazy property resolution. 57 | Use this in cases where defining properties and methods in your class 58 | upfront might be slow. 59 | - **modules.cpp** - Example of how to load ES Module sources. 60 | -------------------------------------------------------------------------------- /docs/Exceptions.md: -------------------------------------------------------------------------------- 1 | # Errors and exceptions # 2 | 3 | Almost every JSAPI function that takes a `JSContext*` argument can fail. 4 | The system might run out of memory. 5 | There might be a syntax error in a script. 6 | Or a script might explicitly `throw` an exception. 7 | 8 | The JavaScript language has exceptions, and C++ has exceptions, but they 9 | are not the same thing. 10 | SpiderMonkey does not use C++ exceptions for anything. 11 | JSAPI functions never throw C++ exceptions, and when SpiderMonkey calls 12 | an application callback, the callback must not throw a C++ exception. 13 | 14 | See the `ReportError`, `ThrowValue`, and `ThrowError` examples in 15 | the [JSAPI Cookbook](../examples/cookbook.cpp) that show how to work 16 | with JavaScript exceptions in C++. 17 | 18 | ## Uncatchable errors ## 19 | 20 | Another way for a `JSNative` callback to report an error is like this: 21 | 22 | ```c++ 23 | if (!p) { 24 | JS_ReportOutOfMemory(cx); 25 | return false; 26 | } 27 | ``` 28 | 29 | This does something subtly different from what `JS_ReportErrorUTF8` and 30 | similar functions do. 31 | 32 | Most errors, including those raised by `JS_ReportErrorUTF8`, are 33 | represented 34 | as JavaScript exceptions and thus interact with the JavaScript 35 | exception-handling language features, `try`, `catch`, and `finally`. 36 | However, in some cases we do not want scripts to be able to `catch` an 37 | error; we want script execution to terminate right away. 38 | If the system runs out of memory in the middle of a script, we do not 39 | want `finally` blocks to execute, because almost anything a script does 40 | requires at least a little memory, and we have none. 41 | If a script has been running too long and we want to kill it, it's no 42 | good to throw an exception—the script could just `catch` it and keep 43 | going. 44 | 45 | Therefore `JS_ReportOutOfMemory(cx)` does _not_ set the pending 46 | exception. 47 | It is an uncatchable error. 48 | 49 | If SpiderMonkey runs out of memory, or a JSAPI callback returns `false` 50 | without an exception pending, this is treated as an uncatchable error. 51 | The JavaScript stack is unwound in the normal way except that `catch` 52 | and `finally` blocks are ignored. 53 | The most recent JSAPI call returns `false` or `nullptr` to the application. 54 | 55 | An uncatchable error leaves the `JSContext` in a good state. 56 | It can be used again right away. The application does not have to do 57 | anything to “recover” from the error, as far as the JSAPI is concerned. 58 | (Of course, if the error is that the system is out of memory, that 59 | problem remains to be dealt with.) 60 | 61 | Here is some example code that throws an uncatchable error. 62 | 63 | ```c++ 64 | Log("The server room is on fire!"); 65 | 66 | /* Make sure the error is uncatchable. */ 67 | JS_ClearPendingException(cx); 68 | return false; 69 | ``` 70 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('spidermonkey-embedding-examples', 'cpp', version: 'esr115', 2 | meson_version: '>= 0.43.0', 3 | default_options: ['cpp_std=c++20', 'warning_level=3']) 4 | 5 | cxx = meson.get_compiler('cpp') 6 | 7 | args = [] 8 | 9 | zlib = dependency('zlib') # (is already a SpiderMonkey dependency) 10 | spidermonkey = dependency('mozjs-115') 11 | readline = cxx.find_library('readline') 12 | 13 | # Check if SpiderMonkey was compiled with --enable-debug. If this is the case, 14 | # you must compile all your sources with -DDEBUG=1. 15 | # See https://bugzilla.mozilla.org/show_bug.cgi?id=1261161 16 | nondebug_spidermonkey = cxx.compiles(''' 17 | #include 18 | #ifdef JS_DEBUG 19 | #error debug yes, if we did not already error out due to DEBUG not being defined 20 | #endif 21 | ''', 22 | args: args, dependencies: spidermonkey, 23 | name: 'SpiderMonkey is a non-debug build') 24 | 25 | if not nondebug_spidermonkey 26 | args += '-DDEBUG=1' 27 | endif 28 | 29 | # Check if a minimal SpiderMonkey program compiles, links, and runs. If not, 30 | # it's most likely the case that SpiderMonkey was configured incorrectly, for 31 | # example by building mozglue as a shared library. 32 | minimal_program = cxx.run(''' 33 | #include 34 | int main(void) { 35 | if (!JS_Init()) return 1; 36 | JS_ShutDown(); 37 | return 0; 38 | } 39 | ''', 40 | args: args, dependencies: spidermonkey, 41 | name: 'SpiderMonkey sanity check') 42 | 43 | if not minimal_program.compiled() or minimal_program.returncode() != 0 44 | error(''' 45 | A minimal SpiderMonkey program could not be compiled, linked, or run. Most 46 | likely you should build it with a different configuration. Check the recommended 47 | configuration in this repository.''') 48 | endif 49 | 50 | add_project_arguments(args, language: 'cpp') 51 | 52 | if cxx.get_id() == 'gcc' or cxx.get_id() == 'clang' 53 | test_warning_args = [ 54 | # it's useful not to have to initialize all struct fields when defining a 55 | # JSClass, since some of them are padding. 56 | '-Wno-missing-field-initializers', 57 | 58 | # we use argument names in some cases for explanatory purposes 59 | '-Wno-unused-parameter', 60 | ] 61 | else 62 | test_warning_args = [] 63 | endif 64 | 65 | add_project_arguments(cxx.get_supported_arguments(test_warning_args), 66 | language: 'cpp') 67 | 68 | executable('hello', 'examples/hello.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey) 69 | executable('cookbook', 'examples/cookbook.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey) 70 | executable('repl', 'examples/repl.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey, readline]) 71 | executable('tracing', 'examples/tracing.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey) 72 | executable('resolve', 'examples/resolve.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey, zlib]) 73 | executable('modules', 'examples/modules.cpp', 'examples/boilerplate.cpp', dependencies: [spidermonkey]) 74 | executable('weakref', 'examples/weakref.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey) 75 | executable('worker', 'examples/worker.cpp', 'examples/boilerplate.cpp', dependencies: spidermonkey) 76 | -------------------------------------------------------------------------------- /docs/Building SpiderMonkey.md: -------------------------------------------------------------------------------- 1 | # Building SpiderMonkey for Embedders # 2 | 3 | Use these instructions to build your own copy of SpiderMonkey. 4 | 5 | ## Prerequisites ## 6 | 7 | You will need a **C++ compiler** that can handle the C++17 standard, 8 | **Rust** version [1.66][minimum-rust-version] or later, **GNU Make**, 9 | **zlib**, and **libffi**. 10 | These can usually be installed with a package manager. 11 | 12 | > **NOTE** SpiderMonkey also requires ICU of at least version 13 | > [73.1][minimum-icu-version], but it will build a bundled copy by 14 | > default. 15 | > If you have a new enough copy installed on your system, you can add 16 | > `--with-system-icu` in the build instructions below, for a shorter 17 | > build time. 18 | 19 | [minimum-rust-version]: https://searchfox.org/mozilla-esr115/rev/61b47de1faebf23626e519b2464b461589fbea3e/python/mozboot/mozboot/util.py#14 20 | [minimum-icu-version]: https://searchfox.org/mozilla-esr115/rev/61b47de1faebf23626e519b2464b461589fbea3e/js/moz.configure#1107 21 | 22 | ## Getting the source code ## 23 | 24 | Currently, the most reliable way to get the SpiderMonkey source code is 25 | to download the Firefox source. 26 | At the time of writing, the latest source for Firefox ESR 115, which 27 | contains the source for SpiderMonkey ESR 115, can be found here: 28 | https://ftp.mozilla.org/pub/firefox/releases/115.1.0esr/source/ 29 | 30 | The ESR releases have a major release approximately once a year with 31 | security patches released throughout the year. 32 | It is recommended that embedders track ESR to have reasonable API 33 | stability. 34 | The master branch of SpiderMonkey experiences a fair amount of breaking 35 | changes unfortunately, driven by the needs of the Firefox browser. 36 | 37 | > **NOTE** Mozilla may be able to provide separate source packages for 38 | > SpiderMonkey in the future, but this is difficult for a number of 39 | > reasons. 40 | 41 | ## Building SpiderMonkey ## 42 | 43 | First you should decide where you want to install SpiderMonkey. 44 | By default, it will install into `/usr/local`. 45 | You might want to pick some other location if `/usr/local` is not 46 | writable to you without superuser permissions, for example. 47 | 48 | ```sh 49 | cd js/src 50 | mkdir _build 51 | cd _build 52 | ../configure --disable-jemalloc --with-system-zlib \ 53 | --with-intl-api --enable-debug --enable-optimize 54 | make 55 | make install # sudo if necessary 56 | ``` 57 | 58 | Add `--prefix=/my/installation/dir` to the `configure` line if you chose 59 | a different installation location. 60 | (Where `/my/installation/dir` is your chosen location: for example, 61 | `--prefix=/opt/spidermonkey`.) 62 | 63 | If you are building a package for production, omit the `--enable-debug`. 64 | 65 | If you picked a different location to install into, and that location is 66 | not a standard place where libraries are loaded from, you may need to 67 | execute the following when you want to use the SpiderMonkey libraries, 68 | for example when building the examples from this repository. 69 | 70 | ```sh 71 | export PKG_CONFIG_PATH=/my/installation/dir/lib/pkgconfig 72 | export LD_LIBRARY_PATH=/my/installation/dir/lib 73 | ``` 74 | 75 | ### Disabling jemalloc ### 76 | 77 | One important configuration when getting started is the 78 | `--disable-jemalloc` flag. 79 | This will cause SpiderMonkey to use the system allocator functions 80 | instead of a custom build of jemalloc. 81 | The custom configuration, which is the default, is intended for a 82 | browser environment and requires linking the final application with a 83 | matching version of a library called mozglue. 84 | If one accidentally builds SpiderMonkey for their embedding without 85 | including the `--disable-jemalloc` flag, they usually quickly encounter 86 | strange crashes as items allocated in jemalloc allocator are freed on 87 | system allocator. 88 | -------------------------------------------------------------------------------- /examples/wasm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "boilerplate.h" 10 | 11 | // This example illustrates usage of WebAssembly JS API via embedded 12 | // SpiderMonkey. It does no error handling and simply exits if something 13 | // goes wrong. 14 | // 15 | // See 'boilerplate.cpp' for the parts of this example that are reused in many 16 | // simple embedding examples. 17 | // 18 | // To use the WebAssembly JIT you need to create a context and a global object, 19 | // and do some setup on both of these. You also need to enter a "realm" 20 | // (environment within one global object) before you can execute code. 21 | 22 | /* 23 | hi.wat: 24 | (module 25 | (import "env" "bar" (func $bar (param i32) (result i32))) 26 | (func (export "foo") (result i32) 27 | i32.const 42 28 | call $bar 29 | )) 30 | */ 31 | unsigned char hi_wasm[] = { 32 | 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x02, 0x60, 33 | 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x00, 0x01, 0x7f, 0x02, 0x0b, 0x01, 0x03, 34 | 0x65, 0x6e, 0x76, 0x03, 0x62, 0x61, 0x72, 0x00, 0x00, 0x03, 0x02, 0x01, 35 | 0x01, 0x07, 0x07, 0x01, 0x03, 0x66, 0x6f, 0x6f, 0x00, 0x01, 0x0a, 0x08, 36 | 0x01, 0x06, 0x00, 0x41, 0x2a, 0x10, 0x00, 0x0b 37 | }; 38 | unsigned int hi_wasm_len = 56; 39 | 40 | static bool BarFunc(JSContext* cx, unsigned argc, JS::Value* vp) { 41 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 42 | args.rval().setInt32(args[0].toInt32()); 43 | return true; 44 | } 45 | 46 | static bool WasmExample(JSContext* cx) { 47 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 48 | if (!global) { 49 | return false; 50 | } 51 | 52 | JSAutoRealm ar(cx, global); 53 | 54 | // Get WebAssembly.Module and WebAssembly.Instance constructors. 55 | JS::RootedValue wasm(cx); 56 | JS::RootedValue wasmModule(cx); 57 | JS::RootedValue wasmInstance(cx); 58 | if (!JS_GetProperty(cx, global, "WebAssembly", &wasm)) return false; 59 | JS::RootedObject wasmObj(cx, &wasm.toObject()); 60 | if (!JS_GetProperty(cx, wasmObj, "Module", &wasmModule)) return false; 61 | if (!JS_GetProperty(cx, wasmObj, "Instance", &wasmInstance)) return false; 62 | 63 | 64 | // Construct Wasm module from bytes. 65 | JS::RootedObject module_(cx); 66 | { 67 | JSObject* arrayBuffer = JS::NewArrayBufferWithUserOwnedContents(cx, hi_wasm_len, hi_wasm); 68 | if (!arrayBuffer) return false; 69 | JS::RootedValueArray<1> args(cx); 70 | args[0].setObject(*arrayBuffer); 71 | 72 | if (!Construct(cx, wasmModule, args, &module_)) return false; 73 | } 74 | 75 | // Construct Wasm module instance with required imports. 76 | JS::RootedObject instance_(cx); 77 | { 78 | // Build "env" imports object. 79 | JS::RootedObject envImportObj(cx, JS_NewPlainObject(cx)); 80 | if (!envImportObj) return false; 81 | if (!JS_DefineFunction(cx, envImportObj, "bar", BarFunc, 1, 0)) return false; 82 | JS::RootedValue envImport(cx, JS::ObjectValue(*envImportObj)); 83 | // Build imports bag. 84 | JS::RootedObject imports(cx, JS_NewPlainObject(cx)); 85 | if (!imports) return false; 86 | if (!JS_SetProperty(cx, imports, "env", envImport)) return false; 87 | 88 | JS::RootedValueArray<2> args(cx); 89 | args[0].setObject(*module_.get()); // module 90 | args[1].setObject(*imports.get());// imports 91 | 92 | if (!Construct(cx, wasmInstance, args, &instance_)) return false; 93 | } 94 | 95 | // Find `foo` method in exports. 96 | JS::RootedValue exports(cx); 97 | if (!JS_GetProperty(cx, instance_, "exports", &exports)) return false; 98 | JS::RootedObject exportsObj(cx, &exports.toObject()); 99 | JS::RootedValue foo(cx); 100 | if (!JS_GetProperty(cx, exportsObj, "foo", &foo)) return false; 101 | 102 | JS::RootedValue rval(cx); 103 | if (!Call(cx, JS::UndefinedHandleValue, foo, JS::HandleValueArray::empty(), &rval)) 104 | return false; 105 | 106 | printf("The answer is %d\n", rval.toInt32()); 107 | return true; 108 | } 109 | 110 | int main(int argc, const char* argv[]) { 111 | if (!boilerplate::RunExample(WasmExample)) { 112 | return 1; 113 | } 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /docs/Miscellaneous.md: -------------------------------------------------------------------------------- 1 | # Unicode # 2 | 3 | To pass Unicode data between JavaScript and native code, represent the 4 | data in UTF-16 in memory. 5 | JavaScript strings, property names, and programs are all made up of 6 | `char16_t`s. 7 | 8 | Many JSAPI functions operate on null-terminated, 8-bit `char` strings. 9 | These functions convert their `char*` arguments to 16-bit strings by 10 | zero-extending each 8-bit `char` to 16 bits. 11 | 12 | The JSAPI provides `char16_t`-based versions of many API functions that 13 | operate on strings, object properties, and JavaScript code. 14 | 15 | `char16_t`-based functions work exactly like their `char`-based 16 | namesakes, except that where traditional functions take a `char*` 17 | argument, the Unicode versions take a `char16_t*` argument, usually with 18 | a separate argument specifying the length of the string in `char16_t`s. 19 | 20 | # Compiled scripts # 21 | 22 | The easiest way to run a script is to use `JS::Evaluate`, which 23 | compiles and executes the script in one go. 24 | 25 | But sometimes an application needs to run a script many times. In this 26 | case, it may be faster to compile the script once and execute it 27 | multiple times. 28 | 29 | The JSAPI provides a type, `JSScript`, that represents a compiled 30 | script. 31 | The life cycle of a `JSScript` looks like this: 32 | 33 | - The application compiles some JavaScript code using `JS::Compile`. 34 | This function returns a pointer to a new `JSScript`. 35 | - The application calls `JS_ExecuteScript` 36 | any number of times. It is safe to use a `JSScript` in multiple 37 | different contexts, but only within the `JSContext` and global in 38 | which it was created. 39 | 40 | Here is some example code using a compiled script: 41 | 42 | ```c++ 43 | /* 44 | * Compile a script and execute it repeatedly until an 45 | * error occurs. (If this ever returns, it returns false. 46 | * If there's no error it just keeps going.) 47 | */ 48 | bool 49 | compileAndRepeat(JSContext* cx, const char* filename) 50 | { 51 | JS::RootedScript script(cx); 52 | 53 | JS::CompileOptions options; 54 | options.setUTF8(true); 55 | if (!JS::Compile(cx, options, filename, &script)) 56 | return false; /* compilation error */ 57 | 58 | for (;;) { 59 | JS::RootedValue result(cx); 60 | 61 | if (!JS_ExecuteScript(cx, script, &result)) 62 | break; 63 | JS_MaybeGC(cx); 64 | } 65 | 66 | return false; 67 | } 68 | ``` 69 | 70 | # Security # 71 | 72 | Many applications use SpiderMonkey to run untrusted code. 73 | In designing this kind of application, it's important to think through 74 | the security concerns ahead of time. 75 | Your application will need to do several things. 76 | 77 | - **Deploy security updates** - Firefox automatically installs updates, 78 | so security fixes are deployed as soon as they are available. 79 | Unless you also regularly deploy SpiderMonkey security updates, a 80 | determined hacker could use publicly known bugs in the engine to 81 | attack your application. 82 | Note that the kind of attack we're talking about here is where a 83 | hacker uses JavaScript to attack the C++ code of the engine itself (or 84 | your embedding). 85 | The rest of the items in this list talk about security issues that 86 | arise within JavaScript itself, even if the engine is working properly. 87 | - **Block simple denial-of-service attacks** - A program like 88 | `while(true){}` should not hang your application. 89 | To stop execution of scripts that run too long, use 90 | `JS_AddInterruptCallback`. 91 | Likewise, a function like `function f(){f();}` should not crash your 92 | application with a stack overflow. 93 | To block that, use `JS_SetNativeStackQuota`. 94 | - **Control access to sensitive data** - Your application might expose 95 | data to some scripts that other scripts should not be able to see. 96 | For example, you might let your customers write scripts that operate 97 | on their own data, but not other customers' data. 98 | These access rules must be enforced somehow. 99 | - **Control access to dangerous functionality** - Suppose your 100 | application has a method `deleteUserAccount()` which is meant to be 101 | used by administrators only. 102 | Obviously if untrusted code can use that method, you have a security 103 | problem. 104 | -------------------------------------------------------------------------------- /examples/worker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "boilerplate.h" 13 | 14 | // This example illustrates usage of SpiderMonkey in multiple threads. It does 15 | // no error handling and simply exits if something goes wrong. 16 | // 17 | // See 'boilerplate.cpp' for the parts of this example that are reused in many 18 | // simple embedding examples. 19 | // 20 | // To use SpiderMonkey API in multiple threads, you need to create a JSContext 21 | // in the thread, using the main thread's JSRuntime as a parent, and initialize 22 | // self-hosted code, and create its own global. 23 | 24 | static bool ExecuteCode(JSContext* cx, const char* code) { 25 | JS::CompileOptions options(cx); 26 | options.setFileAndLine("noname", 1); 27 | 28 | JS::SourceText source; 29 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 30 | return false; 31 | } 32 | 33 | JS::Rooted rval(cx); 34 | if (!JS::Evaluate(cx, options, source, &rval)) { 35 | return false; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | static bool Print(JSContext* cx, unsigned argc, JS::Value* vp) { 42 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 43 | 44 | JS::Rooted arg(cx, args.get(0)); 45 | JS::Rooted str(cx, JS::ToString(cx, arg)); 46 | if (!str) { 47 | return false; 48 | } 49 | 50 | JS::UniqueChars chars = JS_EncodeStringToUTF8(cx, str); 51 | fprintf(stderr, "%s\n", chars.get()); 52 | 53 | args.rval().setUndefined(); 54 | return true; 55 | } 56 | 57 | static bool Sleep(JSContext* cx, unsigned argc, JS::Value* vp) { 58 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 59 | 60 | JS::Rooted arg(cx, args.get(0)); 61 | int32_t ms; 62 | if (!JS::ToInt32(cx, arg, &ms)) { 63 | return false; 64 | } 65 | 66 | std::this_thread::sleep_for(std::chrono::milliseconds(ms)); 67 | 68 | args.rval().setUndefined(); 69 | return true; 70 | } 71 | 72 | bool DefineFunctions(JSContext* cx, JS::Handle global) { 73 | if (!JS_DefineFunction(cx, global, "print", &Print, 0, 0)) { 74 | return false; 75 | } 76 | if (!JS_DefineFunction(cx, global, "sleep", &Sleep, 0, 0)) { 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | static void WorkerMain(JSRuntime* parentRuntime) { 84 | JSContext* cx = JS_NewContext(8L * 1024L * 1024L, parentRuntime); 85 | 86 | if (!JS::InitSelfHostedCode(cx)) { 87 | fprintf(stderr, "Error: Failed during JS::InitSelfHostedCode\n"); 88 | return; 89 | } 90 | 91 | { 92 | JS::Rooted global(cx, boilerplate::CreateGlobal(cx)); 93 | if (!global) { 94 | fprintf(stderr, "Error: Failed during boilerplate::CreateGlobal\n"); 95 | return; 96 | } 97 | 98 | JSAutoRealm ar(cx, global); 99 | 100 | if (!DefineFunctions(cx, global)) { 101 | boilerplate::ReportAndClearException(cx); 102 | return; 103 | } 104 | 105 | if (!ExecuteCode(cx, R"js( 106 | for (let i = 0; i < 10; i++) { 107 | print(`in worker thread, it is ${new Date()}`); 108 | sleep(1000); 109 | } 110 | )js")) { 111 | boilerplate::ReportAndClearException(cx); 112 | return; 113 | } 114 | } 115 | 116 | JS_DestroyContext(cx); 117 | 118 | return; 119 | } 120 | 121 | static bool WorkerExample(JSContext* cx) { 122 | JS::Rooted global(cx, boilerplate::CreateGlobal(cx)); 123 | if (!global) { 124 | return false; 125 | } 126 | 127 | std::thread thread1(WorkerMain, JS_GetRuntime(cx)); 128 | std::thread thread2(WorkerMain, JS_GetRuntime(cx)); 129 | 130 | JSAutoRealm ar(cx, global); 131 | 132 | if (!DefineFunctions(cx, global)) { 133 | boilerplate::ReportAndClearException(cx); 134 | return false; 135 | } 136 | 137 | if (!ExecuteCode(cx, R"js( 138 | for (let i = 0; i < 10; i++) { 139 | print(`in main thread, it is ${new Date()}`); 140 | sleep(1000); 141 | } 142 | )js")) { 143 | boilerplate::ReportAndClearException(cx); 144 | return false; 145 | } 146 | 147 | thread1.join(); 148 | thread2.join(); 149 | 150 | return true; 151 | } 152 | 153 | int main(int argc, const char* argv[]) { 154 | if (!boilerplate::RunExample(WorkerExample)) { 155 | return 1; 156 | } 157 | return 0; 158 | } 159 | -------------------------------------------------------------------------------- /docs/JSAPI Introduction.md: -------------------------------------------------------------------------------- 1 | In order to run any JavaScript code in SpiderMonkey, an application must 2 | have two key elements: a `JSContext`, and a global object. 3 | This section describes what these things are. 4 | The next section explains how to set them up, using JSAPI functions. 5 | 6 | **Contexts.** A `JSContext`, or _context_, is like a little machine that 7 | can do many things involving JavaScript code and objects. 8 | It can compile and execute scripts, get and set object properties, call 9 | JavaScript functions, convert JavaScript data from one type to another, 10 | create objects, and so on. 11 | Almost all JSAPI functions require a `JSContext*` as the first argument. 12 | Use `JS_NewContext` and `JS_DestroyContext` to create and destroy a 13 | context. 14 | 15 | Use `JS_SetContextPrivate` and `JS_GetContextPrivate` to associate 16 | application-specific data with a context. 17 | 18 | When your application is done with SpiderMonkey altogether, use 19 | `JS_ShutDown` to free any remaining cached resources. 20 | (This is a mere nicety if the process is about to exit anyway. But as 21 | that is not always the case, calling `JS_Shutdown` is a good habit to 22 | get into.) 23 | 24 | **Global objects.** Then, the _global object_ contains all the 25 | classes, functions, and variables that are available for JavaScript code 26 | to use. 27 | Whenever JavaScript code does something like 28 | `window.open("http://www.mozilla.org/")`, it is accessing a global 29 | property, in this case `window`. 30 | JSAPI applications have full control over what global properties scripts 31 | can see. 32 | The application starts out by creating an object and populating it with 33 | the standard JavaScript classes, like `Array` and `Object`. 34 | Then it adds whatever custom classes, functions, and variables (like 35 | `window`) the application wants to provide; see [Custom 36 | objects](#custom-objects) below. 37 | Each time the application runs a JS script (using, for example, 38 | `JS::Evaluate`), it provides the global object for that script to 39 | use. 40 | As the script runs, it can create global functions and variables of its 41 | own. 42 | All of these functions, classes, and variables are stored as properties 43 | of the global object. 44 | To create this object, you first need a `JSClass` with the 45 | `JSCLASS_GLOBAL_FLAGS` option. 46 | The example `hello.cpp` defines a very basic `JSClass` (named 47 | `globalClass`) with no methods or properties of its own. 48 | Use `JS_NewGlobalObject` to create a global object. 49 | Use `JS_InitStandardClasses` to populate it with the standard JavaScript 50 | globals. 51 | 52 | # Executing C++ code in JavaScript # 53 | 54 | C++ functions that can be called by JavaScript code have the type 55 | `JSNative`, which is a typedef for 56 | `bool (*)(JSContext* cx, unsigned argc, JS::Value* vp`. 57 | Each `JSNative` has the same signature, regardless of what arguments it 58 | expects to receive from JavaScript. 59 | 60 | The JavaScript arguments to the function are given in `argc` and `vp`. 61 | This signature is obsolescent and in the future will be replaced with 62 | a `JS::CallArgs& args` parameter. 63 | For now, the first line of your `JSNative` function should always be 64 | 65 | ```c++ 66 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 67 | ``` 68 | 69 | `args` functions as an array of those arguments. 70 | The arguments do not have native C++ types like `int` and `float`; 71 | rather, they are `JS::Value`s, JavaScript values. 72 | The native function uses conversion functions such as `JS::ToNumber()` 73 | to convert the arguments 74 | to C++ types and store them in local variables. 75 | The native function must store its JavaScript return value in 76 | `args.rval()`. 77 | 78 | On success, a `JSNative` must call `args.rval()` and return `true`. 79 | The value stored in `args.rval()` is returned to the JavaScript caller. 80 | 81 | On failure, a `JSNative` calls an error-reporting function, e.g. 82 | `JS_ReportErrorUTF8`, and returns `false`. 83 | This causes a JavaScript exception to be thrown. 84 | The caller can catch the exception using a JavaScript `try/catch` 85 | statement. 86 | 87 | To make native functions callable from JavaScript, declare a table of 88 | `JSFunctionSpec`s describing the functions. 89 | Then call `JS_DefineFunctions`. 90 | 91 | ```c++ 92 | static JSFunctionSpec myjs_global_functions[] = { 93 | JS_FN("rand", myjs_rand, 0, 0), 94 | JS_FN("srand", myjs_srand, 0, 0), 95 | JS_FN("system", myjs_system, 1, 0), 96 | JS_FS_END 97 | }; 98 | 99 | ... 100 | if (!JS_DefineFunctions(cx, global, myjs_global_functions)) 101 | return false; 102 | ... 103 | ``` 104 | 105 | Once the functions are defined in `global`, any script that uses 106 | `global` as the global object can call them, just as any web page can 107 | call `alert`. 108 | -------------------------------------------------------------------------------- /tools/make_opcode_doc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S python3 -B 2 | # coding: utf-8 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 | # You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | """ Usage: make_opcode_doc.py PATH_TO_SPIDERMONKEY_SOURCE 8 | 9 | This script generates SpiderMonkey bytecode documentation 10 | from js/src/vm/Opcodes.h. 11 | 12 | Output is written to docs/Bytecode.md. 13 | """ 14 | 15 | import sys 16 | import os 17 | from xml.sax.saxutils import escape 18 | 19 | SOURCE_BASE = 'https://searchfox.org/mozilla-esr102/source' 20 | 21 | FORMAT_TO_IGNORE = { 22 | "JOF_BYTE", 23 | "JOF_UINT8", 24 | "JOF_UINT16", 25 | "JOF_UINT24", 26 | "JOF_UINT32", 27 | "JOF_INT8", 28 | "JOF_INT32", 29 | "JOF_TABLESWITCH", 30 | "JOF_REGEXP", 31 | "JOF_DOUBLE", 32 | "JOF_LOOPHEAD", 33 | "JOF_BIGINT", 34 | } 35 | 36 | 37 | def format_format(format): 38 | format = [flag for flag in format if flag not in FORMAT_TO_IGNORE] 39 | if len(format) == 0: 40 | return '' 41 | return '**Format:** {format}\n'.format(format=', '.join(format)) 42 | 43 | 44 | def maybe_escape(value, format_str, fallback=""): 45 | if value: 46 | return format_str.format(escape(value)) 47 | return fallback 48 | 49 | 50 | OPCODE_FORMAT = """\ 51 | ##### {names} 52 | 53 | {operands} 54 | {stack} 55 | {desc} 56 | {format}\ 57 | """ 58 | 59 | 60 | def print_opcode(opcode, out): 61 | opcodes = [opcode] + opcode.group 62 | names = ', '.join(maybe_escape(code.op, "`{}`") for code in opcodes) 63 | operands = maybe_escape(opcode.operands, "**Operands:** `({})`\n") 64 | stack_uses = maybe_escape(opcode.stack_uses, "`{}` ") 65 | stack_defs = maybe_escape(opcode.stack_defs, " `{}`") 66 | if stack_uses or stack_defs: 67 | stack = "**Stack:** {}⇒{}\n".format(stack_uses, stack_defs) 68 | else: 69 | stack = "" 70 | 71 | print(OPCODE_FORMAT.format( 72 | id=opcodes[0].op, 73 | names=names, 74 | operands=operands, 75 | stack=stack, 76 | desc=opcode.desc, 77 | format=format_format(opcode.format_), 78 | ), file=out) 79 | 80 | 81 | id_cache = dict() 82 | id_count = dict() 83 | 84 | 85 | def print_doc(index, out): 86 | print("""# Bytecode Listing # 87 | 88 | This document is automatically generated from 89 | [`Opcodes.h`]({source_base}/js/src/vm/Opcodes.h) by 90 | [`make_opcode_doc.py`](../tools/make_opcode_doc.py). 91 | 92 | ## Background ## 93 | 94 | SpiderMonkey bytecodes are the canonical form of code representation that is 95 | used in the JavaScript engine. 96 | The JavaScript frontend constructs an AST from the source text, then emits 97 | stack-based bytecodes from that AST as a part of the `JSScript` data structure. 98 | Bytecodes can reference atoms and objects (typically by array index) which are 99 | also contained in the `JSScript` data structure. 100 | 101 | Within the engine, all bytecode executed within a stack frame — even global 102 | (top-level) and eval code — has a stack frame associated with it. 103 | A frame on the stack has space for JavaScript Values (the tagged value format) 104 | in a few different categories. 105 | The space for a single JavaScript value is called a "slot", so the categories 106 | are: 107 | 108 | - Argument slots: holds the actual arguments passed to the current frame. 109 | - Local slots: holds the local variables used by the current code. 110 | - Expression slots: holds the temporary space that you need to calculate 111 | expressions on a stack. For example, in `(a + b) + c` you would push a, then 112 | push b, then add, then push c, then add, which requires a maximum depth of 113 | two expression slots. 114 | 115 | There are also some slots reserved for dedicated functionality, holding values 116 | like `this` and the callee / return value. 117 | 118 | There is always a "Top of Stack" (TOS) that corresponds to the latest value 119 | pushed onto the expression stack. 120 | All bytecodes implicitly operate in terms of this location. 121 | 122 | 123 | """.format(source_base=SOURCE_BASE), file=out) 124 | 125 | for (category_name, types) in index: 126 | print('### {name} ###\n'.format(name=category_name), file=out) 127 | for (type_name, opcodes) in types: 128 | if type_name: 129 | print('#### {name} ####\n'.format(name=type_name), file=out) 130 | print('\n', file=out) 131 | for opcode in opcodes: 132 | print_opcode(opcode, out) 133 | 134 | 135 | if __name__ == '__main__': 136 | if len(sys.argv) < 2: 137 | print("Usage: make_opcode_doc.py PATH_TO_SPIDERMONKEY_SOURCE", 138 | file=sys.stderr) 139 | sys.exit(1) 140 | dir = sys.argv[1] 141 | 142 | thisdir = os.path.dirname(os.path.realpath(__file__)) 143 | sys.path.insert(0, thisdir) 144 | import jsopcode 145 | 146 | try: 147 | index, _ = jsopcode.get_opcodes(dir) 148 | except Exception as e: 149 | print("Error: {}".format(' '.join(map(str, e.args))), file=sys.stderr) 150 | sys.exit(1) 151 | 152 | with open(os.path.join(thisdir, '..', 'docs', 'Bytecodes.md'), 'w') as out: 153 | print_doc(index, out) 154 | -------------------------------------------------------------------------------- /docs/Custom Objects.md: -------------------------------------------------------------------------------- 1 | # Creating and initializing custom objects # 2 | 3 | In addition to using the engine's built-in objects, you will create, 4 | initialize, and use your own JS objects. 5 | This is especially true if you are using the JS engine with scripts to 6 | automate your application. 7 | Custom JS objects can provide direct program services, or they can serve 8 | as interfaces to your program's services. 9 | For example, a custom JS object that provides direct service might be 10 | one that handles all of an application's network access, or might serve 11 | as an intermediary broker of database services. 12 | Or a JS object that mirrors data and functions that already exist in the 13 | application may provide an object-oriented interface to C code that is 14 | not otherwise, strictly-speaking, object-oriented itself. 15 | Such a custom object acts as an interface to the application itself, 16 | passing values from the application to the user, and receiving and 17 | processing user input before returning it to the application. 18 | Such an object might also be used to provide access control to the 19 | underlying functions of the application. 20 | 21 | There are two ways to create custom objects that the JS engine can use: 22 | 23 | - Write a JS script that creates an object, its properties, methods, and 24 | constructor, and then pass the script to the JS engine at run time. 25 | - Embed code in your application that defines the object's properties 26 | and methods, call the engine to initialize a new object, and then set 27 | the object's properties through additional engine calls. 28 | An advantage of this method is that your application can contain 29 | native methods that directly manipulate the object embedding. 30 | 31 | In either case, if you create an object and then want it to persist in 32 | the run time where it can be used by other scripts, you must root the 33 | object. 34 | The easiest way is to install it as a property on the global object with 35 | `JS_DefineProperty`. 36 | This way, it's accessible from other scripts that you run, and the JS 37 | engine will keep track of it and clean it up only when the global object 38 | is cleaned up. 39 | 40 | ## Creating an object from a script ## 41 | 42 | One reason to create a custom JS object from a script is when you only 43 | need an object to exist as long as the script that uses it is executing. 44 | To create objects that persist across script calls, you can embed the 45 | object code in your application instead of using a script. 46 | 47 | **Note**: You can also use scripts to create persistent objects, too. 48 | 49 | To create a custom object using a script: 50 | 51 | 1. Define and spec the object. 52 | What is it intended to do? 53 | What are its data members (properties)? 54 | What are its methods (functions)? 55 | Does it require a run time constructor function? 56 | 2. Code the JS script that defines and creates the object. 57 | For example: `function myfun(){ var x = newObject(); . . . }` 58 | **NOTE**: Object scripting using JavaScript occurs outside the 59 | context of embedding the JS engine in your applications. 60 | Embed the appropriate JS engine call(s) in your application to 61 | compile and execute the script. 62 | You have two choices: 1.) compile and execute a script with a single 63 | call to `JS::Evaluate`, or 2.) compile the script once with a call to 64 | `JS::Compile`, and then execute it repeatedly with individual calls 65 | to `JS_ExecuteScript`. 66 | 67 | An object you create using a script only can be made available only 68 | during the lifetime of the script, or can be created to persist after 69 | the script completes execution. 70 | Ordinarily, once script execution is complete, its objects are 71 | destroyed. 72 | In many cases, this behavior is just what your application needs. 73 | In other cases, however, you will want object persistence across 74 | scripts, or for the lifetime of your application. 75 | In these cases you need to embed object creation code directly in your 76 | application, or you need to tie the object directly to the global object 77 | so that it persists as long as the global object itself persists. 78 | 79 | ## Custom objects ## 80 | 81 | An application can create a custom object without bothering with a 82 | `JSClass`: 83 | 84 | 1. Implement the getters, setters, and methods for your custom object in 85 | C++. 86 | Write a `JSNative` for each getter, setter, and method. 87 | 2. Declare a `JSPropertySpec` array containing information about your 88 | custom object's properties, including getters and setters. 89 | 3. Declare a `JSFunctionSpec` array containing information about your 90 | custom object's methods. 91 | 4. Call `JS_NewPlainObject` to create the object. 92 | 5. Call `JS_DefineProperties` to define the object's properties. 93 | 6. Call `JS_DefineFunctions` to define the object's methods. 94 | 95 | `JS_SetProperty` can also be used to create properties on an object. 96 | The properties it creates do not have getters or setters; they are 97 | ordinary JavaScript properties. 98 | 99 | ## Providing private data for objects ## 100 | 101 | Like contexts, you can associate large quantities of data with an object 102 | without having to store the data on the object itself where it's 103 | accessible to JS code. 104 | 105 | Objects have 'reserved slots' which protect their contents from garbage 106 | collection, while keeping them invisible to JS code. 107 | These slots are numbered, and can hold any [`JS::Value`](./Value.md). 108 | Use `JS::SetReservedSlot()` and `JS::GetReservedSlot()` to access them. 109 | 110 | You can also store a C++ pointer in a reserved slot, by stuffing it into 111 | a `JS::PrivateValue()`. 112 | Your application is responsible for creating and managing this optional private data. 113 | 114 | To create private data and associate it with an object: 115 | 116 | - Specify the number of reserved slots that you'll require in the 117 | object's `JSClass` flags, with `JSCLASS_HAS_RESERVED_SLOTS(n)`. 118 | It's customary to define an enum for the slots. 119 | - Establish the private data as you would a normal C void pointer 120 | variable. 121 | - Call `JS::SetReservedSlot()`, specify the object for which to 122 | establish private data, the reserved slot number that you've chosen to 123 | store the private data, and the pointer to the data. 124 | 125 | For example: 126 | 127 | ```c++ 128 | enum { PRIVATE_DATA, SLOT_COUNT }; 129 | 130 | // ... 131 | 132 | const JSClass MyClass = { 133 | "MyClass", 134 | JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT), 135 | &MyClassOps, 136 | }; 137 | 138 | // ... 139 | 140 | JS::SetReservedSlot(obj, PRIVATE_DATA, JS::PrivateValue(pdata)); 141 | ``` 142 | 143 | To unset the pointer, store `JS::UndefinedValue()` in the slot: 144 | 145 | ```c++ 146 | // This removes the pointer from the reserved slot: 147 | JS::SetReservedSlot(obj, PRIVATE_DATA, JS::UndefinedValue()); 148 | ``` 149 | 150 | There is a convenience function to retrieve a pointer from a reserved 151 | slot at a later time, and cast it to the correct type: 152 | `JS::GetMaybePtrFromReservedSlot()`. 153 | This function returns the pointer to an object's private data: 154 | 155 | ```c++ 156 | pdata = JS::GetMaybePtrFromReservedSlot(obj, PRIVATE_DATA); 157 | ``` 158 | -------------------------------------------------------------------------------- /examples/modules.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "boilerplate.h" 13 | 14 | // This examples demonstrates how to compile ES modules in an embedding. 15 | // 16 | // See 'boilerplate.cpp' for the parts of this example that are reused in many 17 | // simple embedding examples. 18 | 19 | // Translates source code into a JSObject representing the compiled module. This 20 | // module is not yet linked/instantiated. 21 | static JSObject* CompileExampleModule(JSContext* cx, const char* filename, 22 | const char* code) { 23 | JS::CompileOptions options(cx); 24 | options.setFileAndLine(filename, 1); 25 | 26 | JS::SourceText source; 27 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 28 | return nullptr; 29 | } 30 | 31 | // Compile the module source to bytecode. 32 | // 33 | // NOTE: This generates a JSObject instead of a JSScript. This contains 34 | // additional metadata to resolve imports/exports. This object should not be 35 | // exposed to other JS code or unexpected behaviour may occur. 36 | return JS::CompileModule(cx, options, source); 37 | } 38 | 39 | // Maintain a registry of imported modules. The ResolveHook may be called 40 | // multiple times for the same specifier and we need to return the same compiled 41 | // module. 42 | // 43 | // NOTE: This example assumes only one JSContext/GlobalObject is used, but in 44 | // general the registry needs to be distinct for each GlobalObject. 45 | static std::map moduleRegistry; 46 | 47 | // Callback for embedding to provide modules for import statements. This example 48 | // hardcodes sources, but an embedding would normally load files here. 49 | static JSObject* ExampleResolveHook(JSContext* cx, 50 | JS::HandleValue modulePrivate, 51 | JS::HandleObject moduleRequest) { 52 | // Extract module specifier string. 53 | JS::Rooted specifierString( 54 | cx, JS::GetModuleRequestSpecifier(cx, moduleRequest)); 55 | if (!specifierString) { 56 | return nullptr; 57 | } 58 | 59 | // Convert specifier to a std::u16char for simplicity. 60 | JS::UniqueTwoByteChars specChars(JS_CopyStringCharsZ(cx, specifierString)); 61 | if (!specChars) { 62 | return nullptr; 63 | } 64 | std::u16string filename(specChars.get()); 65 | 66 | // If we already resolved before, return same module. 67 | auto search = moduleRegistry.find(filename); 68 | if (search != moduleRegistry.end()) { 69 | return search->second; 70 | } 71 | 72 | JS::RootedObject mod(cx); 73 | 74 | if (filename == u"a") { 75 | mod = CompileExampleModule(cx, "a", "export const C1 = 1;"); 76 | if (!mod) { 77 | return nullptr; 78 | } 79 | } 80 | 81 | if (filename == u"b") { 82 | mod = CompileExampleModule(cx, "b", "export const C2 = 2;"); 83 | if (!mod) { 84 | return nullptr; 85 | } 86 | } 87 | 88 | // Register result in table. 89 | if (mod) { 90 | moduleRegistry.emplace(filename, JS::PersistentRootedObject(cx, mod)); 91 | return mod; 92 | } 93 | 94 | JS_ReportErrorASCII(cx, "Cannot resolve import specifier"); 95 | return nullptr; 96 | } 97 | 98 | // Callback for embedding to implement an asynchronous dynamic import. This must 99 | // do the same thing as the module resolve hook, but also link and evaluate the 100 | // module, and it must always call JS::FinishDynamicModuleImport when done. 101 | static bool ExampleDynamicImportHook(JSContext* cx, 102 | JS::Handle referencingPrivate, 103 | JS::Handle moduleRequest, 104 | JS::Handle promise) { 105 | JS::Rooted mod{ 106 | cx, ExampleResolveHook(cx, referencingPrivate, moduleRequest)}; 107 | if (!mod || !JS::ModuleLink(cx, mod)) { 108 | return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate, 109 | moduleRequest, promise); 110 | } 111 | 112 | JS::Rooted rval{cx}; 113 | if (!JS::ModuleEvaluate(cx, mod, &rval)) { 114 | return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate, 115 | moduleRequest, promise); 116 | } 117 | if (rval.isObject()) { 118 | JS::Rooted evaluationPromise{cx, &rval.toObject()}; 119 | return JS::FinishDynamicModuleImport( 120 | cx, evaluationPromise, referencingPrivate, moduleRequest, promise); 121 | } 122 | return JS::FinishDynamicModuleImport(cx, nullptr, referencingPrivate, 123 | moduleRequest, promise); 124 | } 125 | 126 | static bool ModuleExample(JSContext* cx) { 127 | // In order to use dynamic imports, we need a job queue. We can use the 128 | // default SpiderMonkey job queue for this example, but a more sophisticated 129 | // embedding would use a custom job queue to schedule its own tasks. 130 | if (!js::UseInternalJobQueues(cx)) return false; 131 | 132 | // We must instantiate self-hosting *after* setting up job queue. 133 | if (!JS::InitSelfHostedCode(cx)) return false; 134 | 135 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 136 | if (!global) { 137 | return false; 138 | } 139 | 140 | JSAutoRealm ar(cx, global); 141 | 142 | // Register a hook in order to provide modules 143 | JSRuntime* rt = JS_GetRuntime(cx); 144 | JS::SetModuleResolveHook(rt, ExampleResolveHook); 145 | JS::SetModuleDynamicImportHook(rt, ExampleDynamicImportHook); 146 | 147 | // Compile the top module. 148 | static const char top_module_source[] = R"js( 149 | import {C1} from 'a'; 150 | const {C2} = await import('b'); 151 | )js"; 152 | JS::Rooted mod{cx, 153 | CompileExampleModule(cx, "top", top_module_source)}; 154 | if (!mod) { 155 | boilerplate::ReportAndClearException(cx); 156 | return false; 157 | } 158 | 159 | // Resolve imports by loading and compiling additional scripts. 160 | if (!JS::ModuleLink(cx, mod)) { 161 | boilerplate::ReportAndClearException(cx); 162 | return false; 163 | } 164 | 165 | // Result value, used for top-level await. 166 | JS::RootedValue rval(cx); 167 | 168 | // Execute the module bytecode. 169 | if (!JS::ModuleEvaluate(cx, mod, &rval)) { 170 | boilerplate::ReportAndClearException(cx); 171 | return false; 172 | } 173 | 174 | js::RunJobs(cx); 175 | if (rval.isObject()) { 176 | JS::Rooted evaluationPromise{cx, &rval.toObject()}; 177 | if (!JS::ThrowOnModuleEvaluationFailure( 178 | cx, evaluationPromise, 179 | JS::ModuleErrorBehaviour::ThrowModuleErrorsSync)) { 180 | boilerplate::ReportAndClearException(cx); 181 | return false; 182 | } 183 | } 184 | 185 | return true; 186 | } 187 | 188 | int main(int argc, const char* argv[]) { 189 | if (!boilerplate::RunExample(ModuleExample, /* initSelfHosting = */ false)) { 190 | return 1; 191 | } 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /examples/repl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include "boilerplate.h" 26 | 27 | /* This is a longer example that illustrates how to build a simple 28 | * REPL (Read-Eval-Print Loop). */ 29 | 30 | /* NOTE: This example assumes that it's okay to print UTF-8 encoded text to 31 | * stdout and stderr. On Linux and macOS this will usually be the case. On 32 | * Windows you may have to set your terminal's codepage to UTF-8. */ 33 | 34 | class ReplGlobal { 35 | enum Slots { GlobalSlot, SlotCount }; 36 | 37 | bool m_shouldQuit : 1; 38 | 39 | ReplGlobal(void) : m_shouldQuit(false) {} 40 | 41 | static ReplGlobal* priv(JSObject* global) { 42 | auto* retval = JS::GetMaybePtrFromReservedSlot(global, GlobalSlot); 43 | assert(retval); 44 | return retval; 45 | } 46 | 47 | static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) { 48 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 49 | JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(&args.callee())); 50 | if (!global) return false; 51 | 52 | // Return an "uncatchable" exception, by returning false without setting an 53 | // exception to be pending. We distinguish it from any other uncatchable 54 | // that the JS engine might throw, by setting m_shouldQuit 55 | priv(global)->m_shouldQuit = true; 56 | js::StopDrainingJobQueue(cx); 57 | return false; 58 | } 59 | 60 | static const JSClass klass; 61 | 62 | static constexpr JSFunctionSpec functions[] = { 63 | JS_FN("quit", &ReplGlobal::quit, 0, 0), JS_FS_END}; 64 | 65 | public: 66 | static JSObject* create(JSContext* cx); 67 | static void loop(JSContext* cx, JS::HandleObject global); 68 | }; 69 | constexpr JSFunctionSpec ReplGlobal::functions[]; 70 | 71 | /* The class of the global object. */ 72 | const JSClass ReplGlobal::klass = { 73 | "ReplGlobal", 74 | JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(ReplGlobal::SlotCount), 75 | &JS::DefaultGlobalClassOps 76 | }; 77 | 78 | std::string FormatString(JSContext* cx, JS::HandleString string) { 79 | std::string buf = "\""; 80 | 81 | JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, string)); 82 | if (!chars) { 83 | JS_ClearPendingException(cx); 84 | return "[invalid string]"; 85 | } 86 | 87 | buf += chars.get(); 88 | buf += '"'; 89 | return buf; 90 | } 91 | 92 | std::string FormatResult(JSContext* cx, JS::HandleValue value) { 93 | JS::RootedString str(cx); 94 | 95 | /* Special case format for strings */ 96 | if (value.isString()) { 97 | str = value.toString(); 98 | return FormatString(cx, str); 99 | } 100 | 101 | str = JS::ToString(cx, value); 102 | 103 | if (!str) { 104 | JS_ClearPendingException(cx); 105 | str = JS_ValueToSource(cx, value); 106 | } 107 | 108 | if (!str) { 109 | JS_ClearPendingException(cx); 110 | if (value.isObject()) { 111 | const JSClass* klass = JS::GetClass(&value.toObject()); 112 | if (klass) 113 | str = JS_NewStringCopyZ(cx, klass->name); 114 | else 115 | return "[unknown object]"; 116 | } else { 117 | return "[unknown non-object]"; 118 | } 119 | } 120 | 121 | if (!str) { 122 | JS_ClearPendingException(cx); 123 | return "[invalid class]"; 124 | } 125 | 126 | JS::UniqueChars bytes(JS_EncodeStringToUTF8(cx, str)); 127 | if (!bytes) { 128 | JS_ClearPendingException(cx); 129 | return "[invalid string]"; 130 | } 131 | 132 | return bytes.get(); 133 | } 134 | 135 | JSObject* ReplGlobal::create(JSContext* cx) { 136 | JS::RealmOptions options; 137 | JS::RootedObject global(cx, 138 | JS_NewGlobalObject(cx, &ReplGlobal::klass, nullptr, 139 | JS::FireOnNewGlobalHook, options)); 140 | 141 | ReplGlobal* priv = new ReplGlobal(); 142 | JS::SetReservedSlot(global, GlobalSlot, JS::PrivateValue(priv)); 143 | 144 | // Define any extra global functions that we want in our environment. 145 | JSAutoRealm ar(cx, global); 146 | if (!JS_DefineFunctions(cx, global, ReplGlobal::functions)) return nullptr; 147 | 148 | return global; 149 | } 150 | 151 | bool EvalAndPrint(JSContext* cx, const std::string& buffer, unsigned lineno) { 152 | JS::CompileOptions options(cx); 153 | options.setFileAndLine("typein", lineno); 154 | 155 | JS::SourceText source; 156 | if (!source.init(cx, buffer.c_str(), buffer.size(), 157 | JS::SourceOwnership::Borrowed)) { 158 | return false; 159 | } 160 | 161 | JS::RootedValue result(cx); 162 | if (!JS::Evaluate(cx, options, source, &result)) return false; 163 | 164 | JS_MaybeGC(cx); 165 | 166 | if (result.isUndefined()) return true; 167 | 168 | std::string display_str = FormatResult(cx, result); 169 | if (!display_str.empty()) std::cout << display_str << '\n'; 170 | return true; 171 | } 172 | 173 | void ReplGlobal::loop(JSContext* cx, JS::HandleObject global) { 174 | bool eof = false; 175 | unsigned lineno = 1; 176 | do { 177 | // Accumulate lines until we get a 'compilable unit' - one that either 178 | // generates an error (before running out of source) or that compiles 179 | // cleanly. This should be whenever we get a complete statement that 180 | // coincides with the end of a line. 181 | unsigned startline = lineno; 182 | std::string buffer; 183 | 184 | do { 185 | const char* prompt = startline == lineno ? "js> " : "... "; 186 | char* line = readline(prompt); 187 | if (!line) { 188 | eof = true; 189 | break; 190 | } 191 | if (line[0] != '\0') add_history(line); 192 | buffer += line; 193 | lineno++; 194 | } while (!JS_Utf8BufferIsCompilableUnit(cx, global, buffer.c_str(), 195 | buffer.length())); 196 | 197 | if (!EvalAndPrint(cx, buffer, startline)) { 198 | if (!priv(global)->m_shouldQuit) { 199 | boilerplate::ReportAndClearException(cx); 200 | } 201 | } 202 | 203 | js::RunJobs(cx); 204 | } while (!eof && !priv(global)->m_shouldQuit); 205 | } 206 | 207 | static bool RunREPL(JSContext* cx) { 208 | // In order to use Promises in the REPL, we need a job queue to process 209 | // events after each line of input is processed. 210 | // 211 | // A more sophisticated embedding would schedule it's own tasks and use 212 | // JS::SetEnqueuePromiseJobCallback(), JS::SetGetIncumbentGlobalCallback(), 213 | // and JS::SetPromiseRejectionTrackerCallback(). 214 | if (!js::UseInternalJobQueues(cx)) return false; 215 | 216 | // We must instantiate self-hosting *after* setting up job queue. 217 | if (!JS::InitSelfHostedCode(cx)) return false; 218 | 219 | JS::RootedObject global(cx, ReplGlobal::create(cx)); 220 | if (!global) return false; 221 | 222 | JSAutoRealm ar(cx, global); 223 | 224 | JS::SetWarningReporter(cx, [](JSContext* cx, JSErrorReport* report) { 225 | JS::PrintError(stderr, report, true); 226 | }); 227 | 228 | ReplGlobal::loop(cx, global); 229 | 230 | std::cout << '\n'; 231 | return true; 232 | } 233 | 234 | int main(int argc, const char* argv[]) { 235 | if (!boilerplate::RunExample(RunREPL, /* initSelfHosting = */ false)) 236 | return 1; 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /examples/resolve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "boilerplate.h" 16 | 17 | namespace zlib { 18 | #include 19 | } 20 | 21 | /* This example illustrates how to set up a class with a custom resolve hook, in 22 | * order to do lazy property resolution. 23 | * 24 | * We'll use the CRC-32 checksum API from zlib as an example. Not because it's 25 | * an incredibly useful API, but zlib is already a dependency of SpiderMonkey, 26 | * so it's likely to be installed anywhere these examples are being compiled. 27 | * 28 | * There will be two properties that can resolve lazily: an `update()` method, 29 | * and a `checksum` property. */ 30 | 31 | class Crc { 32 | enum Slots { CrcSlot, SlotCount }; 33 | 34 | unsigned long m_crc; 35 | 36 | Crc(void) : m_crc(zlib::crc32(0L, nullptr, 0)) {} 37 | 38 | bool updateImpl(JSContext* cx, const JS::CallArgs& args) { 39 | if (!args.requireAtLeast(cx, "update", 1)) return false; 40 | 41 | if (!args[0].isObject() || !JS_IsUint8Array(&args[0].toObject())) { 42 | JS_ReportErrorASCII(cx, "argument to update() should be a Uint8Array"); 43 | return false; 44 | } 45 | 46 | JSObject* buffer = &args[0].toObject(); 47 | 48 | size_t len = JS_GetTypedArrayLength(buffer); 49 | if (len > std::numeric_limits::max()) { 50 | JS_ReportErrorASCII(cx, "array has too many bytes"); 51 | return false; 52 | } 53 | 54 | { 55 | bool isSharedMemory; 56 | JS::AutoAssertNoGC nogc; 57 | uint8_t* data = JS_GetUint8ArrayData(buffer, &isSharedMemory, nogc); 58 | 59 | m_crc = zlib::crc32(m_crc, data, unsigned(len)); 60 | } 61 | 62 | args.rval().setUndefined(); 63 | return true; 64 | } 65 | 66 | bool getChecksumImpl(JSContext* cx, const JS::CallArgs& args) { 67 | args.rval().setNumber(uint32_t(m_crc)); 68 | return true; 69 | } 70 | 71 | static Crc* getPriv(JSObject* obj) { 72 | return JS::GetMaybePtrFromReservedSlot(obj, CrcSlot); 73 | } 74 | 75 | static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp) { 76 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 77 | 78 | if (!args.isConstructing()) { 79 | JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, 80 | JSMSG_CANT_CALL_CLASS_CONSTRUCTOR); 81 | return false; 82 | } 83 | 84 | JS::RootedObject newObj(cx, 85 | JS_NewObjectForConstructor(cx, &Crc::klass, args)); 86 | if (!newObj) return false; 87 | 88 | Crc* priv = new Crc(); 89 | JS::SetReservedSlot(newObj, CrcSlot, JS::PrivateValue(priv)); 90 | 91 | args.rval().setObject(*newObj); 92 | return true; 93 | } 94 | 95 | static bool update(JSContext* cx, unsigned argc, JS::Value* vp) { 96 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 97 | JS::RootedObject thisObj(cx); 98 | if (!args.computeThis(cx, &thisObj)) return false; 99 | if (!JS_InstanceOf(cx, thisObj, &Crc::klass, &args)) return false; 100 | return getPriv(thisObj)->updateImpl(cx, args); 101 | } 102 | 103 | static bool getChecksum(JSContext* cx, unsigned argc, JS::Value* vp) { 104 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 105 | JS::RootedObject thisObj(cx); 106 | if (!args.computeThis(cx, &thisObj)) return false; 107 | if (!JS_InstanceOf(cx, thisObj, &Crc::klass, &args)) return false; 108 | return getPriv(thisObj)->getChecksumImpl(cx, args); 109 | } 110 | 111 | static bool newEnumerate(JSContext* cx, JS::HandleObject obj, 112 | JS::MutableHandleIdVector properties, 113 | bool enumerableOnly) { 114 | jsid idUpdate = 115 | JS::PropertyKey::fromPinnedString(JS_AtomizeAndPinString(cx, "update")); 116 | if (!properties.append(idUpdate)) return false; 117 | 118 | jsid idChecksum = JS::PropertyKey::fromPinnedString( 119 | JS_AtomizeAndPinString(cx, "checksum")); 120 | if (!properties.append(idChecksum)) return false; 121 | 122 | return true; 123 | } 124 | 125 | static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, 126 | bool* resolved) { 127 | if (!id.isString()) { 128 | *resolved = false; 129 | return true; 130 | } 131 | 132 | JSLinearString* str = id.toLinearString(); 133 | 134 | if (JS_LinearStringEqualsAscii(str, "update")) { 135 | if (!JS_DefineFunctionById(cx, obj, id, &Crc::update, 1, 136 | JSPROP_ENUMERATE)) 137 | return false; 138 | *resolved = true; 139 | return true; 140 | } 141 | 142 | if (JS_LinearStringEqualsAscii(str, "checksum")) { 143 | if (!JS_DefinePropertyById(cx, obj, id, &Crc::getChecksum, nullptr, 144 | JSPROP_ENUMERATE)) 145 | return false; 146 | *resolved = true; 147 | return true; 148 | } 149 | 150 | *resolved = false; 151 | return true; 152 | } 153 | 154 | static bool mayResolve(const JSAtomState& names, jsid id, 155 | JSObject* maybeObj) { 156 | if (!id.isString()) return false; 157 | 158 | JSLinearString* str = id.toLinearString(); 159 | return JS_LinearStringEqualsAscii(str, "update") || 160 | JS_LinearStringEqualsAscii(str, "checksum"); 161 | } 162 | 163 | static void finalize(JS::GCContext* gcx, JSObject* obj) { 164 | Crc* priv = getPriv(obj); 165 | if (priv) { 166 | delete priv; 167 | JS::SetReservedSlot(obj, CrcSlot, JS::UndefinedValue()); 168 | } 169 | } 170 | 171 | // Note that this vtable applies both to the prototype and instances. The 172 | // operations must distinguish between the two. 173 | static constexpr JSClassOps classOps = { 174 | nullptr, // addProperty 175 | nullptr, // deleteProperty 176 | nullptr, // enumerate 177 | &Crc::newEnumerate, 178 | &Crc::resolve, 179 | &Crc::mayResolve, 180 | &Crc::finalize, 181 | nullptr, // call 182 | nullptr, // construct 183 | nullptr, // trace 184 | }; 185 | 186 | static constexpr JSClass klass = { 187 | "Crc", 188 | JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE, 189 | &Crc::classOps, 190 | }; 191 | 192 | public: 193 | static bool DefinePrototype(JSContext* cx) { 194 | JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); 195 | return JS_InitClass(cx, 196 | global, // the object in which to define the class 197 | nullptr, // the prototype object, Crc.prototype 198 | // (in our case, a plain JS object because 199 | // calling "Crc.prototype.update" does not 200 | // make sense) 201 | nullptr, // the prototype of the parent class (in our 202 | // case, Object.prototype) 203 | Crc::klass.name, // "Crc", the constructor name 204 | &Crc::constructor, 205 | 0, // constructor and num. args 206 | // The four nullptrs below are for arrays where you 207 | // would list predefined (not lazy) methods and 208 | // properties, static and non-static 209 | nullptr, nullptr, nullptr, nullptr); 210 | } 211 | }; 212 | constexpr JSClassOps Crc::classOps; 213 | constexpr JSClass Crc::klass; 214 | 215 | static const char* testProgram = R"js( 216 | const crc = new Crc(); 217 | crc.update(new Uint8Array([1, 2, 3, 4, 5])); 218 | crc.checksum; 219 | )js"; 220 | 221 | /**** BOILERPLATE *************************************************************/ 222 | // Below here, the code is very similar to what is found in hello.cpp 223 | 224 | static bool ExecuteCodePrintResult(JSContext* cx, const char* code) { 225 | JS::CompileOptions options(cx); 226 | options.setFileAndLine("noname", 1); 227 | 228 | JS::SourceText source; 229 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 230 | return false; 231 | } 232 | 233 | JS::RootedValue rval(cx); 234 | if (!JS::Evaluate(cx, options, source, &rval)) return false; 235 | 236 | JS::RootedString rval_str(cx, JS::ToString(cx, rval)); 237 | if (!rval_str) return false; 238 | 239 | // The printed value will be a number, so we know it will be an ASCII string 240 | // that we can just print directly. 241 | std::cout << JS_EncodeStringToASCII(cx, rval_str).get() << '\n'; 242 | return true; 243 | } 244 | 245 | static void die(const char* why) { 246 | std::cerr << "fatal error: " << why; 247 | exit(1); 248 | } 249 | 250 | void LogException(JSContext* cx) { 251 | JS::RootedValue exception(cx); 252 | if (!JS_GetPendingException(cx, &exception)) 253 | die("Uncatchable exception thrown, out of memory or something"); 254 | 255 | JS_ClearPendingException(cx); 256 | 257 | JS::RootedString exc_str(cx, JS::ToString(cx, exception)); 258 | if (!exc_str) die("Exception thrown, could not be converted to string"); 259 | 260 | std::cout << "Exception thrown: " << JS_EncodeStringToUTF8(cx, exc_str).get() 261 | << '\n'; 262 | } 263 | 264 | static bool ResolveExample(JSContext* cx) { 265 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 266 | if (!global) { 267 | return false; 268 | } 269 | 270 | JSAutoRealm ar(cx, global); 271 | 272 | if (!Crc::DefinePrototype(cx)) { 273 | LogException(cx); 274 | return false; 275 | } 276 | 277 | if (!ExecuteCodePrintResult(cx, testProgram)) { 278 | LogException(cx); 279 | return false; 280 | } 281 | 282 | return true; 283 | } 284 | 285 | int main(int argc, const char* argv[]) { 286 | if (!boilerplate::RunExample(ResolveExample)) return 1; 287 | return 0; 288 | } 289 | -------------------------------------------------------------------------------- /examples/tracing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "boilerplate.h" 8 | 9 | // This example illustrates how to safely store GC pointers in the embedding's 10 | // data structures, and vice versa, by implementing appropriate tracing 11 | // mechanisms. 12 | // 13 | // This example covers using strong references where C++ keeps the JS objects 14 | // alive. Weak references use a different implementation strategy that is not 15 | // covered here. 16 | 17 | //////////////////////////////////////////////////////////// 18 | 19 | // An example C++ type that stores an arbitrary JS value. 20 | struct SafeBox { 21 | // Arbitrary JS value that will need to be traced. The JS::Heap type has a 22 | // constructor, destructor and write barriers to properly register the 23 | // pointer with the GC as needed. 24 | JS::Heap stashed; 25 | 26 | // The JS::Heap type is also compatible with C++ containers that properly 27 | // construct/move/destory elements. 28 | std::vector> container; 29 | 30 | // If we provide a trace method, we can then be used in a JS::Rooted or 31 | // similar and the GC will be able to successfuly trace us. 32 | void trace(JSTracer* trc) { 33 | JS::TraceEdge(trc, &stashed, "stashed value"); 34 | 35 | // We can also trace containers as long as we use references. 36 | for (auto& elem : container) { 37 | JS::TraceEdge(trc, &elem, "container value"); 38 | } 39 | } 40 | }; 41 | 42 | static bool CustomTypeExample(JSContext* cx) { 43 | // If we use SafeBox as a stack object, then a JS::Rooted is enough. 44 | JS::Rooted stackSafe(cx); 45 | 46 | // We can also use js::UniquePtr if SafeBox should be allocated on heap. 47 | JS::Rooted> heapSafe(cx, js::MakeUnique()); 48 | 49 | // NOTE: A JS::Rooted is a compile error. If one wanted to support 50 | // rooting bare non-GC pointers then both JS::MapTypeToRootKind and 51 | // JS::GCPolicy need to be defined for SafeBox*. This should be avoided in 52 | // favor of using a smart-pointer when possible. 53 | 54 | return true; 55 | } 56 | 57 | //////////////////////////////////////////////////////////// 58 | 59 | // By defining a JS::GCPolicy we can support tracing existing types that we 60 | // cannot add a trace method to such as std::shared_ptr. 61 | // 62 | // In this example, we forward methods to GCPolicy of target type and provide 63 | // reasonable behaviours when we have no current target. 64 | template 65 | struct JS::GCPolicy> { 66 | static void trace(JSTracer* trc, std::shared_ptr* tp, const char* name) { 67 | if (T* target = tp->get()) { 68 | GCPolicy::trace(trc, target, name); 69 | } 70 | } 71 | static bool needsSweep(std::shared_ptr* tp) { 72 | if (T* target = tp->get()) { 73 | return GCPolicy::needsSweep(target); 74 | } 75 | return false; 76 | } 77 | static bool isValid(const std::shared_ptr& t) { 78 | if (T* target = t.get()) { 79 | return GCPolicy::isValid(*target); 80 | } 81 | return true; 82 | } 83 | }; 84 | 85 | static bool ExistingTypeExample(JSContext* cx) { 86 | // We can use std::shared_ptr too since we defined a GCPolicy above. 87 | JS::Rooted> sharedSafe(cx, 88 | std::make_shared()); 89 | return true; 90 | } 91 | 92 | //////////////////////////////////////////////////////////// 93 | 94 | // When an embedding wishes to keep GC things alive when the JavaScript no 95 | // longer has direct references, it must provide GC roots for the various 96 | // tracing mechanisms to search from. This is done using the PersistentRooted 97 | // type. 98 | // 99 | // Each PersistentRooted will register / unregister with the GC root-list. This 100 | // can be a performance overhead if you rapidly create / destroy C++ objects. 101 | // If you have an array of C++ objects it is preferable to root the container 102 | // rather than putting a PersistentRooted in each element. See the 103 | // SafeBox::container field in the example above. 104 | 105 | // A global PersistentRooted is created before SpiderMonkey has initialized so 106 | // we must be careful to not create any JS::Heap fields during construction. 107 | JS::PersistentRooted> globalPtrSafe; 108 | mozilla::Maybe> globalMaybeSafe; 109 | 110 | static bool GlobalRootExample(JSContext* cx) { 111 | // Initialize the root with cx and allocate a fresh SafeBox. 112 | globalPtrSafe.init(cx, js::MakeUnique()); 113 | 114 | // If we want to avoid a heap allocation, we can wrap the PersistentRooted in 115 | // a Maybe. When we emplace the variable, we pass 'cx' for the constructor of 116 | // PersistentRooted. 117 | globalMaybeSafe.emplace(cx); 118 | 119 | // IMPORTANT: When using global PersistentRooteds they *must* be cleared 120 | // before shutdown by calling 'reset()'. 121 | globalMaybeSafe.reset(); 122 | globalPtrSafe.reset(); 123 | 124 | return true; 125 | } 126 | 127 | //////////////////////////////////////////////////////////// 128 | 129 | // Instead of the global variable example above, it is often preferable to 130 | // store PersistentRooted inside embedding data structures. By passing cx to 131 | // the roots during the constructor we can automatically register the roots. 132 | // 133 | // NOTE: The techniques used in the GlobalRootExample with Maybe and UniquePtr 134 | // can also be applied here. 135 | 136 | struct EmbeddingContext { 137 | JS::PersistentRooted memberSafe; 138 | JS::PersistentRooted memberObjPtr; 139 | 140 | explicit EmbeddingContext(JSContext* cx) : memberSafe(cx), memberObjPtr(cx) {} 141 | }; 142 | 143 | static bool EmbeddingRootExample(JSContext* cx) { 144 | js::UniquePtr ec{new EmbeddingContext(cx)}; 145 | 146 | return true; 147 | } 148 | 149 | //////////////////////////////////////////////////////////// 150 | 151 | // The other way around: to store a pointer to a C++ struct from a JS object, 152 | // use a JSClass with a trace hook. Note that this is only required if the C++ 153 | // struct can reach a GC pointer. 154 | 155 | struct CustomObject { 156 | enum Slots { OwnedBoxSlot, UnownedBoxSlot, SlotCount }; 157 | 158 | // When the CustomObject is collected, delete the stored box. 159 | static void finalize(JS::GCContext* gcx, JSObject* obj); 160 | 161 | // When a CustomObject is traced, it must trace the stored box. 162 | static void trace(JSTracer* trc, JSObject* obj); 163 | 164 | static constexpr JSClassOps classOps = { 165 | // Use .finalize if CustomObject owns the C++ object and should delete it 166 | // when the CustomObject dies. 167 | .finalize = finalize, 168 | 169 | // Use .trace whenever the JS object points to a C++ object that can reach 170 | // other JS objects. 171 | .trace = trace 172 | }; 173 | 174 | static constexpr JSClass clasp = { 175 | .name = "Custom", 176 | .flags = 177 | JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE, 178 | .cOps = &classOps}; 179 | 180 | static JSObject* create(JSContext* cx, SafeBox* ownedBox1, SafeBox* unownedBox2); 181 | 182 | // Full type of JSObject is not known, so we can't inherit. 183 | static CustomObject* fromObject(JSObject* obj) { 184 | return reinterpret_cast(obj); 185 | } 186 | static JSObject* asObject(CustomObject* custom) { 187 | return reinterpret_cast(custom); 188 | } 189 | 190 | // Retrieve the owned SafeBox* from the reserved slot. 191 | SafeBox* ownedBox() { 192 | JSObject* obj = CustomObject::asObject(this); 193 | return JS::GetMaybePtrFromReservedSlot(obj, OwnedBoxSlot); 194 | } 195 | 196 | // Retrieve the unowned SafeBox* from the reserved slot. 197 | SafeBox* unownedBox() { 198 | JSObject* obj = CustomObject::asObject(this); 199 | return JS::GetMaybePtrFromReservedSlot(obj, UnownedBoxSlot); 200 | } 201 | }; 202 | 203 | JSObject* CustomObject::create(JSContext* cx, SafeBox* box1, SafeBox* box2) { 204 | JS::Rooted obj(cx, JS_NewObject(cx, &clasp)); 205 | if (!obj) { 206 | return nullptr; 207 | } 208 | JS_SetReservedSlot(obj, OwnedBoxSlot, JS::PrivateValue(box1)); 209 | JS_SetReservedSlot(obj, UnownedBoxSlot, JS::PrivateValue(box2)); 210 | return obj; 211 | } 212 | 213 | void CustomObject::finalize(JS::GCContext* gcx, JSObject* obj) { 214 | delete CustomObject::fromObject(obj)->ownedBox(); 215 | // Do NOT delete unownedBox(). 216 | } 217 | 218 | void CustomObject::trace(JSTracer* trc, JSObject* obj) { 219 | // Must trace both boxes, owned or not, in order to keep any referred-to GC 220 | // things alive. 221 | SafeBox* b = CustomObject::fromObject(obj)->ownedBox(); 222 | if (b) { 223 | b->trace(trc); 224 | } 225 | b = CustomObject::fromObject(obj)->unownedBox(); 226 | if (b) { 227 | b->trace(trc); 228 | } 229 | } 230 | 231 | static bool CustomObjectExample(JSContext* cx) { 232 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 233 | if (!global) { 234 | return false; 235 | } 236 | 237 | JSAutoRealm ar(cx, global); 238 | 239 | SafeBox* box = new SafeBox(); 240 | static SafeBox eternalBox{}; 241 | JSObject* obj = CustomObject::create(cx, box, &eternalBox); 242 | if (!obj) { 243 | return false; 244 | } 245 | 246 | // The CustomObject will be collected, since it hasn't been stored anywhere 247 | // else, and the SafeBox allocated just above will be destroyed. 248 | JS_GC(cx); 249 | 250 | return true; 251 | } 252 | //////////////////////////////////////////////////////////// 253 | 254 | static bool TracingExample(JSContext* cx) { 255 | if (!CustomTypeExample(cx)) { 256 | return false; 257 | } 258 | if (!ExistingTypeExample(cx)) { 259 | return false; 260 | } 261 | if (!GlobalRootExample(cx)) { 262 | return false; 263 | } 264 | if (!EmbeddingRootExample(cx)) { 265 | return false; 266 | } 267 | if (!CustomObjectExample(cx)) { 268 | return false; 269 | } 270 | 271 | return true; 272 | } 273 | 274 | int main(int argc, const char* argv[]) { 275 | if (!boilerplate::RunExample(TracingExample)) { 276 | return 1; 277 | } 278 | return 0; 279 | } 280 | -------------------------------------------------------------------------------- /tools/apply-format: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # Copyright 2018 Undo Ltd. 4 | # 5 | # https://github.com/barisione/clang-format-hooks 6 | 7 | # Force variable declaration before access. 8 | set -u 9 | # Make any failure in piped commands be reflected in the exit code. 10 | set -o pipefail 11 | 12 | readonly bash_source="${BASH_SOURCE[0]:-$0}" 13 | 14 | ################## 15 | # Misc functions # 16 | ################## 17 | 18 | function error_exit() { 19 | for str in "$@"; do 20 | echo -n "$str" >&2 21 | done 22 | echo >&2 23 | 24 | exit 1 25 | } 26 | 27 | 28 | ######################## 29 | # Command line parsing # 30 | ######################## 31 | 32 | function show_help() { 33 | if [ -t 1 ] && hash tput 2> /dev/null; then 34 | local -r b=$(tput bold) 35 | local -r i=$(tput sitm) 36 | local -r n=$(tput sgr0) 37 | else 38 | local -r b= 39 | local -r i= 40 | local -r n= 41 | fi 42 | 43 | cat << EOF 44 | ${b}SYNOPSIS${n} 45 | 46 | To reformat git diffs: 47 | 48 | ${i}$bash_source [OPTIONS] [FILES-OR-GIT-DIFF-OPTIONS]${n} 49 | 50 | To reformat whole files, including unchanged parts: 51 | 52 | ${i}$bash_source [-f | --whole-file] FILES${n} 53 | 54 | ${b}DESCRIPTION${n} 55 | 56 | Reformat C or C++ code to match a specified formatting style. 57 | 58 | This command can either work on diffs, to reformat only changed parts of 59 | the code, or on whole files (if -f or --whole-file is used). 60 | 61 | ${b}FILES-OR-GIT-DIFF-OPTIONS${n} 62 | List of files to consider when applying clang-format to a diff. This is 63 | passed to "git diff" as is, so it can also include extra git options or 64 | revisions. 65 | For example, to apply clang-format on the changes made in the last few 66 | revisions you could use: 67 | ${i}\$ $bash_source HEAD~3${n} 68 | 69 | ${b}FILES${n} 70 | List of files to completely reformat. 71 | 72 | ${b}-f, --whole-file${n} 73 | Reformat the specified files completely (including parts you didn't 74 | change). 75 | The patch is printed on stdout by default. Use -i if you want to modify 76 | the files on disk. 77 | 78 | ${b}--staged, --cached${n} 79 | Reformat only code which is staged for commit. 80 | The patch is printed on stdout by default. Use -i if you want to modify 81 | the files on disk. 82 | 83 | ${b}-i${n} 84 | Reformat the code and apply the changes to the files on disk (instead 85 | of just printing the patch on stdout). 86 | 87 | ${b}--apply-to-staged${n} 88 | This is like specifying both --staged and -i, but the formatting 89 | changes are also staged for commit (so you can just use "git commit" 90 | to commit what you planned to, but formatted correctly). 91 | 92 | ${b}--style STYLE${n} 93 | The style to use for reformatting code. 94 | If no style is specified, then it's assumed there's a .clang-format 95 | file in the current directory or one of its parents. 96 | 97 | ${b}--help, -h, -?${n} 98 | Show this help. 99 | EOF 100 | } 101 | 102 | # getopts doesn't support long options. 103 | # getopt mangles stuff. 104 | # So we parse manually... 105 | declare positionals=() 106 | declare has_positionals=false 107 | declare whole_file=false 108 | declare apply_to_staged=false 109 | declare staged=false 110 | declare in_place=false 111 | declare style=file 112 | while [ $# -gt 0 ]; do 113 | declare arg="$1" 114 | shift # Past option. 115 | case "$arg" in 116 | -h | -\? | --help ) 117 | show_help 118 | exit 0 119 | ;; 120 | -f | --whole-file ) 121 | whole_file=true 122 | ;; 123 | --apply-to-staged ) 124 | apply_to_staged=true 125 | ;; 126 | --cached | --staged ) 127 | staged=true 128 | ;; 129 | -i ) 130 | in_place=true 131 | ;; 132 | --style=* ) 133 | style="${arg//--style=/}" 134 | ;; 135 | --style ) 136 | [ $# -gt 0 ] || \ 137 | error_exit "No argument for --style option." 138 | style="$1" 139 | shift 140 | ;; 141 | -- ) 142 | # Stop processing further arguments. 143 | if [ $# -gt 0 ]; then 144 | positionals+=("$@") 145 | has_positionals=true 146 | fi 147 | break 148 | ;; 149 | -* ) 150 | error_exit "Unknown argument: $arg" 151 | ;; 152 | *) 153 | positionals+=("$arg") 154 | ;; 155 | esac 156 | done 157 | 158 | # Restore positional arguments, access them from "$@". 159 | if [ ${#positionals[@]} -gt 0 ]; then 160 | set -- "${positionals[@]}" 161 | has_positionals=true 162 | fi 163 | 164 | [ -n "$style" ] || \ 165 | error_exit "If you use --style you need to specify a valid style." 166 | 167 | ####################################### 168 | # Detection of clang-format & friends # 169 | ####################################### 170 | 171 | # clang-format. 172 | declare format="${CLANG_FORMAT:-}" 173 | if [ -z "$format" ]; then 174 | format=$(type -p clang-format) 175 | fi 176 | 177 | if [ -z "$format" ]; then 178 | error_exit \ 179 | $'You need to install clang-format.\n' \ 180 | $'\n' \ 181 | $'On Ubuntu/Debian this is available in the clang-format package or, in\n' \ 182 | $'older distro versions, clang-format-VERSION.\n' \ 183 | $'On Fedora it\'s available in the clang package.\n' \ 184 | $'You can also specify your own path for clang-format by setting the\n' \ 185 | $'$CLANG_FORMAT environment variable.' 186 | fi 187 | 188 | # clang-format-diff. 189 | if [ "$whole_file" = false ]; then 190 | invalid="/dev/null/invalid/path" 191 | if [ "${OSTYPE:-}" = "linux-gnu" ]; then 192 | readonly sort_version=-V 193 | else 194 | # On macOS, sort doesn't have -V. 195 | readonly sort_version=-n 196 | fi 197 | declare paths_to_try=() 198 | # .deb packages directly from upstream. 199 | # We try these first as they are probably newer than the system ones. 200 | while read -r f; do 201 | paths_to_try+=("$f") 202 | done < <(compgen -G "/usr/share/clang/clang-format-*/clang-format-diff.py" | sort "$sort_version" -r) 203 | # LLVM official releases (just untarred in /usr/local). 204 | while read -r f; do 205 | paths_to_try+=("$f") 206 | done < <(compgen -G "/usr/local/clang+llvm*/share/clang/clang-format-diff.py" | sort "$sort_version" -r) 207 | # Maybe it's in the $PATH already? This is true for Ubuntu and Debian. 208 | paths_to_try+=( \ 209 | "$(type -p clang-format-diff 2> /dev/null || echo "$invalid")" \ 210 | "$(type -p clang-format-diff.py 2> /dev/null || echo "$invalid")" \ 211 | ) 212 | # Fedora. 213 | paths_to_try+=( \ 214 | /usr/share/clang/clang-format-diff.py \ 215 | ) 216 | # Gentoo. 217 | while read -r f; do 218 | paths_to_try+=("$f") 219 | done < <(compgen -G "/usr/lib/llvm/*/share/clang/clang-format-diff.py" | sort -n -r) 220 | # Homebrew. 221 | while read -r f; do 222 | paths_to_try+=("$f") 223 | done < <(compgen -G "/usr/local/Cellar/clang-format/*/share/clang/clang-format-diff.py" | sort -n -r) 224 | 225 | declare format_diff= 226 | 227 | # Did the user specify a path? 228 | if [ -n "${CLANG_FORMAT_DIFF:-}" ]; then 229 | format_diff="$CLANG_FORMAT_DIFF" 230 | else 231 | for path in "${paths_to_try[@]}"; do 232 | if [ -e "$path" ]; then 233 | # Found! 234 | format_diff="$path" 235 | if [ ! -x "$format_diff" ]; then 236 | format_diff="python $format_diff" 237 | fi 238 | break 239 | fi 240 | done 241 | fi 242 | 243 | if [ -z "$format_diff" ]; then 244 | error_exit \ 245 | $'Cannot find clang-format-diff which should be shipped as part of the same\n' \ 246 | $'package where clang-format is.\n' \ 247 | $'\n' \ 248 | $'Please find out where clang-format-diff is in your distro and report an issue\n' \ 249 | $'at https://github.com/barisione/clang-format-hooks/issues with details about\n' \ 250 | $'your operating system and setup.\n' \ 251 | $'\n' \ 252 | $'You can also specify your own path for clang-format-diff by setting the\n' \ 253 | $'$CLANG_FORMAT_DIFF environment variable, for instance:\n' \ 254 | $'\n' \ 255 | $' CLANG_FORMAT_DIFF="python /.../clang-format-diff.py" \\\n' \ 256 | $' ' "$bash_source" 257 | fi 258 | 259 | readonly format_diff 260 | fi 261 | 262 | 263 | ############################ 264 | # Actually run the command # 265 | ############################ 266 | 267 | if [ "$whole_file" = true ]; then 268 | 269 | [ "$has_positionals" = true ] || \ 270 | error_exit "No files to reformat specified." 271 | [ "$staged" = false ] || \ 272 | error_exit "--staged/--cached only make sense when applying to a diff." 273 | 274 | read -r -a format_args <<< "$format" 275 | format_args+=("-style=file") 276 | [ "$in_place" = true ] && format_args+=("-i") 277 | 278 | "${format_args[@]}" "$@" 279 | 280 | else # Diff-only. 281 | 282 | if [ "$apply_to_staged" = true ]; then 283 | [ "$staged" = false ] || \ 284 | error_exit "You don't need --staged/--cached with --apply-to-staged." 285 | [ "$in_place" = false ] || \ 286 | error_exit "You don't need -i with --apply-to-staged." 287 | staged=true 288 | readonly patch_dest=$(mktemp) 289 | trap '{ rm -f "$patch_dest"; }' EXIT 290 | else 291 | readonly patch_dest=/dev/stdout 292 | fi 293 | 294 | declare git_args=(git diff -U0 --no-color) 295 | [ "$staged" = true ] && git_args+=("--staged") 296 | 297 | # $format_diff may contain a command ("python") and the script to excute, so we 298 | # need to split it. 299 | read -r -a format_diff_args <<< "$format_diff" 300 | [ "$in_place" = true ] && format_diff_args+=("-i") 301 | 302 | "${git_args[@]}" "$@" \ 303 | | "${format_diff_args[@]}" \ 304 | -p1 \ 305 | -style="$style" \ 306 | -iregex='^.*\.(c|cpp|cxx|cc|h|m|mm|js|java)$' \ 307 | > "$patch_dest" \ 308 | || exit 1 309 | 310 | if [ "$apply_to_staged" = true ]; then 311 | if [ ! -s "$patch_dest" ]; then 312 | echo "No formatting changes to apply." 313 | exit 0 314 | fi 315 | patch -p0 < "$patch_dest" || \ 316 | error_exit "Cannot apply patch to local files." 317 | git apply -p0 --cached < "$patch_dest" || \ 318 | error_exit "Cannot apply patch to git staged changes." 319 | fi 320 | 321 | fi 322 | -------------------------------------------------------------------------------- /examples/weakref.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "boilerplate.h" 13 | 14 | // This example illustrates what you have to do in your embedding to make 15 | // WeakRef and FinalizationRegistry work. Without notifying SpiderMonkey when to 16 | // clear out WeakRefs and run FinalizationRegistry callbacks, they will appear 17 | // not to work correctly. 18 | // 19 | // See 'boilerplate.cpp' for the parts of this example that are reused in many 20 | // simple embedding examples. 21 | 22 | // This function silently ignores errors in a way that production code probably 23 | // wouldn't. 24 | static void LogPendingException(JSContext* cx) { 25 | // Nothing we can do about uncatchable exceptions. 26 | if (!JS_IsExceptionPending(cx)) return; 27 | 28 | JS::ExceptionStack exnStack{cx}; 29 | if (!JS::StealPendingExceptionStack(cx, &exnStack)) return; 30 | 31 | JS::ErrorReportBuilder builder{cx}; 32 | if (!builder.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) { 33 | return; 34 | } 35 | JS::PrintError(stderr, builder, /* reportWarnings = */ false); 36 | } 37 | 38 | // This example integrates the FinalizationRegistry job queue together with the 39 | // Promise job handling, since that's a logical place that you might put it in 40 | // your embedding. 41 | // 42 | // However, it's not necessary to use JS::JobQueue and it's not necessary to 43 | // handle Promise jobs, in order to have FinalizationRegistry work. You do need 44 | // to have some kind of job queue, but it can be very minimal. It doesn't have 45 | // to be based on JS::JobQueue. The only requirement is that the enqueued 46 | // cleanup functions must be run "some time in the future". 47 | // 48 | // To approximate a minimal job queue, you might remove m_queue from this class 49 | // and remove the inheritance from JS::JobQueue and its overridden methods. 50 | class CustomJobQueue : public JS::JobQueue { 51 | public: 52 | explicit CustomJobQueue(JSContext* cx) 53 | : m_queue(cx, js::SystemAllocPolicy{}), 54 | m_finalizationRegistryCallbacks(cx), 55 | m_draining(false) {} 56 | ~CustomJobQueue() = default; 57 | 58 | // JS::JobQueue override 59 | JSObject* getIncumbentGlobal(JSContext* cx) override { 60 | return JS::CurrentGlobalOrNull(cx); 61 | } 62 | 63 | // JS::JobQueue override 64 | bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise, 65 | JS::HandleObject job, JS::HandleObject allocationSite, 66 | JS::HandleObject incumbentGlobal) override { 67 | if (!m_queue.append(job)) { 68 | JS_ReportOutOfMemory(cx); 69 | return false; 70 | } 71 | 72 | JS::JobQueueMayNotBeEmpty(cx); 73 | return true; 74 | } 75 | 76 | // JS::JobQueue override 77 | void runJobs(JSContext* cx) override { 78 | // Ignore nested calls of runJobs. 79 | if (m_draining) { 80 | return; 81 | } 82 | 83 | m_draining = true; 84 | 85 | JS::Rooted job{cx}; 86 | JS::Rooted unused_rval{cx}; 87 | 88 | while (true) { 89 | // Execute jobs in a loop until we've reached the end of the queue. 90 | while (!m_queue.empty()) { 91 | job = m_queue[0]; 92 | m_queue.erase(m_queue.begin()); // In production code, use a FIFO queue 93 | 94 | // If the next job is the last job in the job queue, allow skipping the 95 | // standard job queuing behavior. 96 | if (m_queue.empty()) { 97 | JS::JobQueueIsEmpty(cx); 98 | } 99 | 100 | JSAutoRealm ar{cx, job}; 101 | if (!JS::Call(cx, JS::UndefinedHandleValue, job, 102 | JS::HandleValueArray::empty(), &unused_rval)) { 103 | // We can't throw the exception here, because there is nowhere to 104 | // catch it. So, log it. 105 | LogPendingException(cx); 106 | } 107 | } 108 | 109 | // FinalizationRegistry callbacks may queue more jobs, so only stop 110 | // running jobs if there were no FinalizationRegistry callbacks to run. 111 | if (!maybeRunFinalizationRegistryCallbacks(cx)) break; 112 | } 113 | 114 | m_draining = false; 115 | m_queue.clear(); 116 | } 117 | 118 | // JS::JobQueue override 119 | bool empty() const override { return m_queue.empty(); } 120 | 121 | void queueFinalizationRegistryCallback(JSFunction* callback) { 122 | mozilla::Unused << m_finalizationRegistryCallbacks.append(callback); 123 | } 124 | 125 | private: 126 | using JobQueueStorage = JS::GCVector; 127 | JS::PersistentRooted m_queue; 128 | 129 | using FunctionVector = JS::GCVector; 130 | JS::PersistentRooted m_finalizationRegistryCallbacks; 131 | 132 | // True if we are in the midst of draining jobs from this queue. We use this 133 | // to avoid re-entry (nested calls simply return immediately). 134 | bool m_draining : 1; 135 | 136 | class SavedQueue : public JobQueue::SavedJobQueue { 137 | public: 138 | SavedQueue(JSContext* cx, CustomJobQueue* jobQueue) 139 | : m_jobQueue(jobQueue), 140 | m_saved(cx, std::move(jobQueue->m_queue.get())), 141 | m_draining(jobQueue->m_draining) {} 142 | 143 | ~SavedQueue() { 144 | m_jobQueue->m_queue = std::move(m_saved.get()); 145 | m_jobQueue->m_draining = m_draining; 146 | } 147 | 148 | private: 149 | CustomJobQueue* m_jobQueue; 150 | JS::PersistentRooted m_saved; 151 | bool m_draining : 1; 152 | }; 153 | 154 | // JS::JobQueue override 155 | js::UniquePtr saveJobQueue( 156 | JSContext* cx) override { 157 | auto saved = js::MakeUnique(cx, this); 158 | if (!saved) { 159 | // When MakeUnique's allocation fails, the SavedQueue constructor is never 160 | // called, so this->queue is still initialized. (The move doesn't occur 161 | // until the constructor gets called.) 162 | JS_ReportOutOfMemory(cx); 163 | return nullptr; 164 | } 165 | 166 | m_queue.clear(); 167 | m_draining = false; 168 | return saved; 169 | } 170 | 171 | bool maybeRunFinalizationRegistryCallbacks(JSContext* cx) { 172 | bool ranCallbacks = false; 173 | 174 | JS::Rooted callbacks{cx}; 175 | std::swap(callbacks.get(), m_finalizationRegistryCallbacks.get()); 176 | for (JSFunction* f : callbacks) { 177 | JS::ExposeObjectToActiveJS(JS_GetFunctionObject(f)); 178 | 179 | JSAutoRealm ar{cx, JS_GetFunctionObject(f)}; 180 | JS::Rooted func{cx, f}; 181 | JS::Rooted unused_rval{cx}; 182 | if (!JS_CallFunction(cx, nullptr, func, JS::HandleValueArray::empty(), 183 | &unused_rval)) { 184 | LogPendingException(cx); 185 | } 186 | 187 | ranCallbacks = true; 188 | } 189 | 190 | return ranCallbacks; 191 | } 192 | }; 193 | 194 | static void CleanupFinalizationRegistry(JSFunction* callback, 195 | JSObject* incumbent_global 196 | [[maybe_unused]], 197 | void* user_data) { 198 | // Queue a cleanup task to run after each job has been run. 199 | // We only have one global so ignore the incumbent global parameter. 200 | auto* jobQueue = static_cast(user_data); 201 | jobQueue->queueFinalizationRegistryCallback(callback); 202 | } 203 | 204 | static bool GC(JSContext* cx, unsigned argc, JS::Value* vp) { 205 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 206 | 207 | JS_GC(cx, JS::GCReason::API); 208 | 209 | args.rval().setUndefined(); 210 | return true; 211 | } 212 | 213 | static bool RunJobs(JSContext* cx, unsigned argc, JS::Value* vp) { 214 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 215 | 216 | // This calls JS::ClearKeptObjects() after draining the job queue. If you're 217 | // not using js::RunJobs(), you'll have to call it yourself -- otherwise, the 218 | // WeakRefs will never be emptied. 219 | js::RunJobs(cx); 220 | 221 | args.rval().setUndefined(); 222 | return true; 223 | } 224 | 225 | static bool ExecuteCode(JSContext* cx, const char* code) { 226 | JS::CompileOptions options{cx}; 227 | options.setFileAndLine("noname", 1); 228 | 229 | JS::SourceText source; 230 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 231 | return false; 232 | } 233 | 234 | JS::Rooted rval{cx}; 235 | return JS::Evaluate(cx, options, source, &rval); 236 | } 237 | 238 | static bool WeakRefExample(JSContext* cx) { 239 | // Using WeakRefs and FinalizationRegistry requires a job queue. The built-in 240 | // job queue used in repl.cpp is not sufficient, because it does not provide 241 | // any way to queue FinalizationRegistry cleanup callbacks. 242 | CustomJobQueue jobQueue{cx}; 243 | JS::SetJobQueue(cx, &jobQueue); 244 | 245 | // Without this, FinalizationRegistry callbacks will never be called. The 246 | // embedding has to decide when to schedule them. 247 | JS::SetHostCleanupFinalizationRegistryCallback( 248 | cx, CleanupFinalizationRegistry, &jobQueue); 249 | 250 | JS::RealmOptions options; 251 | options.creationOptions().setWeakRefsEnabled( 252 | JS::WeakRefSpecifier::EnabledWithoutCleanupSome); 253 | 254 | static JSClass GlobalClass = {"WeakRefsGlobal", JSCLASS_GLOBAL_FLAGS, 255 | &JS::DefaultGlobalClassOps}; 256 | 257 | JS::Rooted global{ 258 | cx, JS_NewGlobalObject(cx, &GlobalClass, nullptr, JS::FireOnNewGlobalHook, 259 | options)}; 260 | if (!global) return false; 261 | 262 | JSAutoRealm ar{cx, global}; 263 | 264 | if (!JS_DefineFunction(cx, global, "gc", &GC, 0, 0) || 265 | !JS_DefineFunction(cx, global, "runJobs", &RunJobs, 0, 0)) { 266 | boilerplate::ReportAndClearException(cx); 267 | return false; 268 | } 269 | 270 | if (!ExecuteCode(cx, R"js( 271 | let valueFinalized; 272 | const registry = new FinalizationRegistry( 273 | heldValue => (valueFinalized = heldValue)); 274 | let obj = {}; 275 | const weakRef = new WeakRef(obj); 276 | registry.register(obj, "marker"); 277 | 278 | obj = null; 279 | 280 | runJobs(); // Makes weakRef eligible for clearing 281 | gc(); // Clears weakRef, collects obj which is no longer live, and 282 | // enqueues finalization registry cleanup 283 | 284 | if (weakRef.deref() !== undefined) throw new Error("WeakRef"); 285 | 286 | runJobs(); // Runs finalization registry cleanup 287 | 288 | if (valueFinalized !== "marker") throw new Error("FinalizationRegistry"); 289 | )js")) { 290 | boilerplate::ReportAndClearException(cx); 291 | return false; 292 | } 293 | 294 | return true; 295 | } 296 | 297 | int main(int argc, const char* argv[]) { 298 | if (!boilerplate::RunExample(WeakRefExample)) { 299 | return 1; 300 | } 301 | return 0; 302 | } 303 | -------------------------------------------------------------------------------- /tools/git-pre-commit-format: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # Copyright 2018 Undo Ltd. 4 | # 5 | # https://github.com/barisione/clang-format-hooks 6 | 7 | # Force variable declaration before access. 8 | set -u 9 | # Make any failure in piped commands be reflected in the exit code. 10 | set -o pipefail 11 | 12 | readonly bash_source="${BASH_SOURCE[0]:-$0}" 13 | 14 | if [ -t 1 ] && hash tput 2> /dev/null; then 15 | readonly b=$(tput bold) 16 | readonly i=$(tput sitm) 17 | readonly n=$(tput sgr0) 18 | else 19 | readonly b= 20 | readonly i= 21 | readonly n= 22 | fi 23 | 24 | function error_exit() { 25 | for str in "$@"; do 26 | echo -n "$b$str$n" >&2 27 | done 28 | echo >&2 29 | 30 | exit 1 31 | } 32 | 33 | # realpath is not available everywhere. 34 | function realpath() { 35 | if [ "${OSTYPE:-}" = "linux-gnu" ]; then 36 | readlink -m "$@" 37 | else 38 | # Python should always be available on macOS. 39 | # We use sys.stdout.write instead of print so it's compatible with both Python 2 and 3. 40 | python -c "import sys; import os.path; sys.stdout.write(os.path.realpath('''$1''') + '\\n')" 41 | fi 42 | } 43 | 44 | # realpath --relative-to is only available on recent Linux distros. 45 | # This function behaves identical to Python's os.path.relpath() and doesn't need files to exist. 46 | function rel_realpath() { 47 | local -r path=$(realpath "$1") 48 | local -r rel_to=$(realpath "${2:-$PWD}") 49 | 50 | # Split the paths into components. 51 | IFS='/' read -r -a path_parts <<< "$path" 52 | IFS='/' read -r -a rel_to_parts <<< "$rel_to" 53 | 54 | # Search for the first different component. 55 | for ((idx=1; idx<${#path_parts[@]}; idx++)); do 56 | if [ "${path_parts[idx]}" != "${rel_to_parts[idx]:-}" ]; then 57 | break 58 | fi 59 | done 60 | 61 | result=() 62 | # Add the required ".." to the $result array. 63 | local -r first_different_idx="$idx" 64 | for ((idx=first_different_idx; idx<${#rel_to_parts[@]}; idx++)); do 65 | result+=("..") 66 | done 67 | # Add the required components from $path. 68 | for ((idx=first_different_idx; idx<${#path_parts[@]}; idx++)); do 69 | result+=("${path_parts[idx]}") 70 | done 71 | 72 | if [ "${#result[@]}" -gt 0 ]; then 73 | # Join the array with a "/" as separator. 74 | echo "$(export IFS='/'; echo "${result[*]}")" 75 | else 76 | echo . 77 | fi 78 | } 79 | 80 | # Find the top-level git directory (taking into account we could be in a submodule). 81 | declare git_test_dir=. 82 | declare top_dir 83 | 84 | while true; do 85 | top_dir=$(cd "$git_test_dir" && git rev-parse --show-toplevel) || \ 86 | error_exit "You need to be in the git repository to run this script." 87 | 88 | [ -e "$top_dir/.git" ] || \ 89 | error_exit "No .git directory in $top_dir." 90 | 91 | if [ -d "$top_dir/.git" ]; then 92 | # We are done! top_dir is the root git directory. 93 | break 94 | elif [ -f "$top_dir/.git" ]; then 95 | # We are in a submodule or git work-tree if .git is a file! 96 | if [ -z "$(git rev-parse --show-superproject-working-tree)" ]; then 97 | # The --show-superproject-working-tree option is available and we 98 | # are in a work tree. 99 | gitdir=$(<"$top_dir/.git") 100 | gitdir=${gitdir#gitdir: } 101 | topdir_basename=${gitdir##*/} 102 | git_test_dir=${gitdir%/worktrees/$topdir_basename} 103 | break 104 | fi 105 | # If show-superproject-working-tree returns non-empty string, either: 106 | # 107 | # 1) --show-superproject-working-tree is not defined for this version of git 108 | # 109 | # 2) --show-superproject-working-tree is defined and we are in a submodule 110 | # 111 | # In the first case we will assume it is not a work tree because people 112 | # using that advanced technology will be using a recent version of git. 113 | # 114 | # In second case, we could use the value returned by 115 | # --show-superproject-working-tree directly but we do not here because 116 | # that would require extra work. 117 | # 118 | git_test_dir="$git_test_dir/.." 119 | fi 120 | done 121 | 122 | readonly top_dir 123 | 124 | hook_path="$top_dir/.git/hooks/pre-commit" 125 | readonly hook_path 126 | 127 | me=$(realpath "$bash_source") || exit 1 128 | readonly me 129 | 130 | me_relative_to_hook=$(rel_realpath "$me" "$(dirname "$hook_path")") || exit 1 131 | readonly me_relative_to_hook 132 | 133 | my_dir=$(dirname "$me") || exit 1 134 | readonly my_dir 135 | 136 | apply_format="$my_dir/apply-format" 137 | readonly apply_format 138 | 139 | apply_format_relative_to_top_dir=$(rel_realpath "$apply_format" "$top_dir") || exit 1 140 | readonly apply_format_relative_to_top_dir 141 | 142 | function is_installed() { 143 | if [ ! -e "$hook_path" ]; then 144 | echo nothing 145 | else 146 | existing_hook_target=$(realpath "$hook_path") || exit 1 147 | readonly existing_hook_target 148 | 149 | if [ "$existing_hook_target" = "$me" ]; then 150 | # Already installed. 151 | echo installed 152 | else 153 | # There's a hook, but it's not us. 154 | echo different 155 | fi 156 | fi 157 | } 158 | 159 | function install() { 160 | if ln -s "$me_relative_to_hook" "$hook_path" 2> /dev/null; then 161 | echo "Pre-commit hook installed." 162 | else 163 | local -r res=$(is_installed) 164 | if [ "$res" = installed ]; then 165 | error_exit "The hook is already installed." 166 | elif [ "$res" = different ]; then 167 | error_exit "There's already an existing pre-commit hook, but for something else." 168 | elif [ "$res" = nothing ]; then 169 | error_exit "There's no pre-commit hook, but we couldn't create a symlink." 170 | else 171 | error_exit "Unexpected failure." 172 | fi 173 | fi 174 | } 175 | 176 | function uninstall() { 177 | local -r res=$(is_installed) 178 | if [ "$res" = installed ]; then 179 | rm "$hook_path" || \ 180 | error_exit "Couldn't remove the pre-commit hook." 181 | elif [ "$res" = different ]; then 182 | error_exit "There's a pre-commit hook installed, but for something else. Not removing." 183 | elif [ "$res" = nothing ]; then 184 | error_exit "There's no pre-commit hook, nothing to uninstall." 185 | else 186 | error_exit "Unexpected failure detecting the pre-commit hook status." 187 | fi 188 | } 189 | 190 | function show_help() { 191 | cat << EOF 192 | ${b}SYNOPSIS${n} 193 | 194 | $bash_source [install|uninstall] 195 | 196 | ${b}DESCRIPTION${n} 197 | 198 | Git hook to verify and fix formatting before committing. 199 | 200 | The script is invoked automatically when you commit, so you need to call it 201 | directly only to set up the hook or remove it. 202 | 203 | To setup the hook run this script passing "install" on the command line. 204 | To remove the hook run passing "uninstall". 205 | 206 | ${b}CONFIGURATION${n} 207 | 208 | You can configure the hook using the "git config" command. 209 | 210 | ${b}hooks.clangFormatDiffInteractive${n} (default: true) 211 | By default, the hook requires user input. If you don't run git from a 212 | terminal, you can disable the interactive prompt with: 213 | ${i}\$ git config hooks.clangFormatDiffInteractive false${n} 214 | 215 | ${b}hooks.clangFormatDiffStyle${n} (default: file) 216 | Unless a different style is specified, the hook expects a file named 217 | .clang-format to exist in the repository. This file should contain the 218 | configuration for clang-format. 219 | You can specify a different style (in this example, the WebKit one) 220 | with: 221 | ${i}\$ git config hooks.clangFormatDiffStyle WebKit${n} 222 | EOF 223 | } 224 | 225 | if [ $# = 1 ]; then 226 | case "$1" in 227 | -h | -\? | --help ) 228 | show_help 229 | exit 0 230 | ;; 231 | install ) 232 | install 233 | exit 0 234 | ;; 235 | uninstall ) 236 | uninstall 237 | exit 0 238 | ;; 239 | esac 240 | fi 241 | 242 | [ $# = 0 ] || error_exit "Invalid arguments: $*" 243 | 244 | 245 | # This is a real run of the hook, not a install/uninstall run. 246 | 247 | if [ -z "${GIT_DIR:-}" ] && [ -z "${GIT_INDEX_FILE:-}" ]; then 248 | error_exit \ 249 | $'It looks like you invoked this script directly, but it\'s supposed to be used\n' \ 250 | $'as a pre-commit git hook.\n' \ 251 | $'\n' \ 252 | $'To install the hook try:\n' \ 253 | $' ' "$bash_source" $' install\n' \ 254 | $'\n' \ 255 | $'For more details on this script try:\n' \ 256 | $' ' "$bash_source" $' --help\n' 257 | fi 258 | 259 | [ -x "$apply_format" ] || \ 260 | error_exit \ 261 | $'Cannot find the apply-format script.\n' \ 262 | $'I expected it here:\n' \ 263 | $' ' "$apply_format" 264 | 265 | readonly style=$(cd "$top_dir" && git config hooks.clangFormatDiffStyle || echo file) 266 | 267 | readonly patch=$(mktemp) 268 | trap '{ rm -f "$patch"; }' EXIT 269 | "$apply_format" --style="$style" --cached > "$patch" || \ 270 | error_exit $'\nThe apply-format script failed.' 271 | 272 | if [ "$(wc -l < "$patch")" -eq 0 ]; then 273 | echo "The staged content is formatted correctly." 274 | exit 0 275 | fi 276 | 277 | 278 | # The code is not formatted correctly. 279 | 280 | interactive=$(cd "$top_dir" && git config --bool hooks.clangFormatDiffInteractive) 281 | if [ "$interactive" != false ]; then 282 | # Interactive is the default, so anything that is not false is converted to 283 | # true, including possibly invalid values. 284 | interactive=true 285 | fi 286 | readonly interactive 287 | 288 | if [ "$interactive" = false ]; then 289 | echo "${b}The staged content is not formatted correctly.${n}" 290 | echo "You can fix the formatting with:" 291 | echo " ${i}\$ ./$apply_format_relative_to_top_dir --apply-to-staged${n}" 292 | echo 293 | echo "You can also make this script interactive (if you use git from a terminal) with:" 294 | echo " ${i}\$ git config hooks.clangFormatDiffInteractive true${n}" 295 | exit 1 296 | fi 297 | 298 | if hash colordiff 2> /dev/null; then 299 | colordiff < "$patch" 300 | else 301 | echo "${b}(Install colordiff to see this diff in color!)${n}" 302 | echo 303 | cat "$patch" 304 | fi 305 | 306 | echo 307 | echo "${b}The staged content is not formatted correctly.${n}" 308 | echo "The patch shown above can be applied automatically to fix the formatting." 309 | echo 310 | 311 | echo "You can:" 312 | echo " [a]: Apply the patch" 313 | echo " [f]: Force and commit anyway (not recommended!)" 314 | echo " [c]: Cancel the commit" 315 | echo " [?]: Show help" 316 | echo 317 | 318 | readonly tty=${PRE_COMMIT_HOOK_TTY:-/dev/tty} 319 | 320 | while true; do 321 | echo -n "What would you like to do? [a/f/c/?] " 322 | read -r answer < "$tty" 323 | case "$answer" in 324 | 325 | [aA] ) 326 | patch -p0 < "$patch" || \ 327 | error_exit \ 328 | $'\n' \ 329 | $'Cannot apply patch to local files.\n' \ 330 | $'Have you modified the file locally after starting the commit?' 331 | git apply -p0 --cached < "$patch" || \ 332 | error_exit \ 333 | $'\n' \ 334 | $'Cannot apply patch to git staged changes.\n' \ 335 | $'This may happen if you have some overlapping unstaged changes. To solve\n' \ 336 | $'you need to stage or reset changes manually.' 337 | ;; 338 | 339 | [fF] ) 340 | echo 341 | echo "Will commit anyway!" 342 | echo "You can always abort by quitting your editor with no commit message." 343 | echo 344 | echo -n "Press return to continue." 345 | read -r < "$tty" 346 | exit 0 347 | ;; 348 | 349 | [cC] ) 350 | error_exit "Commit aborted as requested." 351 | ;; 352 | 353 | \? ) 354 | echo 355 | show_help 356 | echo 357 | continue 358 | ;; 359 | 360 | * ) 361 | echo 'Invalid answer. Type "a", "f" or "c".' 362 | echo 363 | continue 364 | 365 | esac 366 | break 367 | done 368 | -------------------------------------------------------------------------------- /tools/jsopcode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 -B 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 | # You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | import re 7 | 8 | quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'") 9 | js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)") 10 | 11 | 12 | def codify(text): 13 | text = re.sub(quoted_pat, '\\1\\2', text) 14 | text = re.sub(js_pat, '\\1\\2', text) 15 | 16 | return text 17 | 18 | 19 | space_star_space_pat = re.compile('^\s*\* ?', re.M) 20 | 21 | 22 | def get_comment_body(comment): 23 | return re.sub(space_star_space_pat, '', comment).split('\n') 24 | 25 | 26 | quote_pat = re.compile('"([^"]+)"') 27 | str_pat = re.compile('js_([^_]+)_str') 28 | 29 | 30 | def parse_name(s): 31 | m = quote_pat.search(s) 32 | if m: 33 | return m.group(1) 34 | m = str_pat.search(s) 35 | if m: 36 | return m.group(1) 37 | return s 38 | 39 | 40 | csv_pat = re.compile(', *') 41 | 42 | 43 | def parse_csv(s): 44 | a = csv_pat.split(s) 45 | if len(a) == 1 and a[0] == '': 46 | return [] 47 | return a 48 | 49 | 50 | def get_stack_count(stack): 51 | if stack == '': 52 | return 0 53 | if '...' in stack: 54 | return -1 55 | return len(stack.split(',')) 56 | 57 | 58 | def parse_index(comment): 59 | index = [] 60 | current_types = None 61 | category_name = '' 62 | category_pat = re.compile('\[([^\]]+)\]') 63 | for line in get_comment_body(comment): 64 | m = category_pat.search(line) 65 | if m: 66 | category_name = m.group(1) 67 | if category_name == 'Index': 68 | continue 69 | current_types = [] 70 | index.append((category_name, current_types)) 71 | else: 72 | type_name = line.strip() 73 | if type_name and current_types is not None: 74 | current_types.append((type_name, [])) 75 | 76 | return index 77 | 78 | # Holds the information stored in the comment with the following format: 79 | # /* 80 | # * {desc} 81 | # * Category: {category_name} 82 | # * Type: {type_name} 83 | # * Operands: {operands} 84 | # * Stack: {stack_uses} => {stack_defs} 85 | # */ 86 | 87 | 88 | class CommentInfo: 89 | def __init__(self): 90 | self.desc = '' 91 | self.category_name = '' 92 | self.type_name = '' 93 | self.operands = '' 94 | self.stack_uses = '' 95 | self.stack_defs = '' 96 | 97 | # Holds the information stored in the macro with the following format: 98 | # MACRO({op}, {op_snake}, {token}, {length}, {nuses}, {ndefs}, {format}) 99 | # and the information from CommentInfo. 100 | 101 | 102 | class OpcodeInfo: 103 | def __init__(self, value, comment_info): 104 | self.op = '' 105 | self.op_snake = '' 106 | self.value = value 107 | self.token = '' 108 | self.length = '' 109 | self.nuses = '' 110 | self.ndefs = '' 111 | self.format_ = '' 112 | 113 | self.operands_array = [] 114 | self.stack_uses_array = [] 115 | self.stack_defs_array = [] 116 | 117 | self.desc = comment_info.desc 118 | self.category_name = comment_info.category_name 119 | self.type_name = comment_info.type_name 120 | self.operands = comment_info.operands 121 | self.operands_array = comment_info.operands_array 122 | self.stack_uses = comment_info.stack_uses 123 | self.stack_uses_array = comment_info.stack_uses_array 124 | self.stack_defs = comment_info.stack_defs 125 | self.stack_defs_array = comment_info.stack_defs_array 126 | 127 | # List of OpcodeInfo that corresponds to macros after this. 128 | # /* 129 | # * comment 130 | # */ 131 | # MACRO(JSOP_SUB, ...) 132 | # MACRO(JSOP_MUL, ...) 133 | # MACRO(JSOP_DIV, ...) 134 | self.group = [] 135 | 136 | self.sort_key = '' 137 | 138 | 139 | def find_by_name(list, name): 140 | for (n, body) in list: 141 | if n == name: 142 | return body 143 | 144 | return None 145 | 146 | 147 | def add_to_index(index, opcode): 148 | types = find_by_name(index, opcode.category_name) 149 | if types is None: 150 | raise Exception('Category is not listed in index: ' 151 | '{name}'.format(name=opcode.category_name)) 152 | opcodes = find_by_name(types, opcode.type_name) 153 | if opcodes is None: 154 | if opcode.type_name: 155 | raise Exception('Type is not listed in {category}: ' 156 | '{name}'.format(category=opcode.category_name, 157 | name=opcode.type_name)) 158 | types.append((opcode.type_name, [opcode])) 159 | return 160 | 161 | opcodes.append(opcode) 162 | 163 | 164 | tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$') 165 | 166 | 167 | def get_tag_value(line): 168 | return re.sub(tag_pat, '', line) 169 | 170 | 171 | RUST_OR_CPP_KEYWORDS = { 172 | 'and', 'case', 'default', 'double', 'false', 'goto', 'in', 'new', 'not', 'or', 'return', 173 | 'throw', 'true', 'try', 'typeof', 'void', 174 | } 175 | 176 | 177 | def get_opcodes(dir): 178 | iter_pat = re.compile(r"/\*(.*?)\*/" # either a documentation comment... 179 | r"|" 180 | r"MACRO\(" # or a MACRO(...) call 181 | r"(?P[^,]+),\s*" 182 | r"(?P[^,]+),\s*" 183 | r"(?P[^,]+,)\s*" 184 | r"(?P[0-9\-]+),\s*" 185 | r"(?P[0-9\-]+),\s*" 186 | r"(?P[0-9\-]+),\s*" 187 | r"(?P[^\)]+)" 188 | r"\)", re.S) 189 | stack_pat = re.compile(r"^(?P.*?)" 190 | r"\s*=>\s*" 191 | r"(?P.*?)$") 192 | 193 | opcodes = dict() 194 | index = [] 195 | 196 | with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r', encoding='utf-8') as f: 197 | data = f.read() 198 | 199 | comment_info = None 200 | opcode = None 201 | 202 | # The first opcode after the comment. 203 | group_head = None 204 | next_opcode_value = 0 205 | 206 | for m in re.finditer(iter_pat, data): 207 | comment = m.group(1) 208 | op = m.group('op') 209 | 210 | if comment: 211 | if '[Index]' in comment: 212 | index = parse_index(comment) 213 | continue 214 | 215 | if 'Operands:' not in comment: 216 | continue 217 | 218 | group_head = None 219 | 220 | comment_info = CommentInfo() 221 | 222 | state = 'desc' 223 | stack = '' 224 | desc = '' 225 | 226 | for line in get_comment_body(comment): 227 | if line.startswith(' Category:'): 228 | state = 'category' 229 | comment_info.category_name = get_tag_value(line) 230 | elif line.startswith(' Type:'): 231 | state = 'type' 232 | comment_info.type_name = get_tag_value(line) 233 | elif line.startswith(' Operands:'): 234 | state = 'operands' 235 | comment_info.operands = get_tag_value(line) 236 | elif line.startswith(' Stack:'): 237 | state = 'stack' 238 | stack = get_tag_value(line) 239 | elif state == 'desc': 240 | desc += line + "\n" 241 | elif line.startswith(' '): 242 | if line.isspace(): 243 | pass 244 | elif state == 'operands': 245 | comment_info.operands += ' ' + line.strip() 246 | elif state == 'stack': 247 | stack += ' ' + line.strip() 248 | else: 249 | raise ValueError("unrecognized line in comment: {!r}\n\nfull comment was:\n{}" 250 | .format(line, comment)) 251 | 252 | comment_info.desc = desc 253 | 254 | comment_info.operands_array = parse_csv(comment_info.operands) 255 | comment_info.stack_uses_array = parse_csv(comment_info.stack_uses) 256 | comment_info.stack_defs_array = parse_csv(comment_info.stack_defs) 257 | 258 | m2 = stack_pat.search(stack) 259 | if m2: 260 | comment_info.stack_uses = m2.group('uses') 261 | comment_info.stack_defs = m2.group('defs') 262 | else: 263 | assert op is not None 264 | opcode = OpcodeInfo(next_opcode_value, comment_info) 265 | next_opcode_value += 1 266 | 267 | opcode.op = op 268 | opcode.op_snake = m.group('op_snake') 269 | opcode.token = parse_name(m.group('token')) 270 | opcode.length = m.group('length') 271 | opcode.nuses = m.group('nuses') 272 | opcode.ndefs = m.group('ndefs') 273 | opcode.format_ = m.group('format').split('|') 274 | 275 | expected_snake = re.sub(r'(?needsIncrementalBarrier()`, then `writeBarrierPre()` marks 254 | `old`. 255 | That's it. 256 | 257 | The class `Heap` is provided to make it easy to invoke write 258 | barriers. 259 | A `Heap` encapsulates a `T` and invokes the write barrier whenever 260 | assigned to. 261 | Thus, object fields of GC-pointer type should normally be defined as 262 | type `Heap`. 263 | The class `HeapSlot` (and the related class `HeapSlotArray`) is the 264 | same, but for object slots. 265 | 266 | Object private fields must be handled specially. 267 | The private field itself is opaque to the engine, but it may point to 268 | things that need to be marked, e.g., an array of JSObject pointers. 269 | In this example, if the private field is overwritten, the JSObject 270 | pointers could be 'lost', so a write barrier must be invoked to mark 271 | them. 272 | If you need to do this in your objects, invoke the JSObject's class 273 | trace hook before the private field is set. 274 | 275 | Another detail is that write barriers can be skipped when initializing 276 | fields of newly allocated objects, because no pointer is being 277 | overwritten. 278 | 279 | ## Dictionary of terms ## 280 | 281 | **black**: In common CS terminology, an object is black during the mark 282 | phase if it has been marked and its children are gray (have been queued 283 | for marking). 284 | An object is black after the mark phase if it has been marked. In SpiderMonkey, an object is black if its black mark bit is set. 285 | 286 | **gray**: In common CS terminology, an object is gray during the mark 287 | phase if it has been queued for marking. 288 | In SpiderMonkey, an object is queued for marking if it is on the mark 289 | stack and is not black. 290 | Thus, the gray objects from CS literature are not represented 291 | explicitly. 292 | See also: **gray**, below. 293 | 294 | **gray**: SpiderMonkey instead uses "gray" to refer to objects that are 295 | not black, but are reachable from "gray roots". 296 | Gray roots are used by the Gecko cycle collector to find cycles that 297 | pass through the JS heap. 298 | 299 | **handle**: In our GC, a Handle is a pointer that has elsewhere been 300 | registered by a root. 301 | In other words, it is an updatable pointer to a GC thing (it is 302 | essentially a `Cell**` that the GC knows about.) 303 | 304 | **root**: A starting point to the GC graph traversal, a root is known to 305 | be alive for some external reason (one other than being reachable by 306 | some other part of the GC heap.) 307 | 308 | **weak pointer**: In common CS terminology, a weak pointer is one that 309 | doesn't keep the pointed-to value live for GC purposes. 310 | Typically, a weak pointer value has a `get()` method that returns a null 311 | pointer if the object has been GC'd. 312 | In SpiderMonkey, a weak pointer is a pointer to an object that can be 313 | GC'd that is not marked through. 314 | Thus, there is no `get()` method and no protection against the 315 | pointed-to value getting GC'd — the programmer must ensure this 316 | mechanism themselves. 317 | 318 | **white**: In common CS terminology, an object is white during the mark 319 | phase if it has not been seen yet. 320 | An object is white after the mark phase if it has not been marked. 321 | In SpiderMonkey, an object is white if it is not gray or black; i.e., it 322 | is not marked and it is not on the mark stack. 323 | -------------------------------------------------------------------------------- /docs/Debugging Tips.md: -------------------------------------------------------------------------------- 1 | # Debugging tips # 2 | 3 | This page lists a few tips to help you investigate issues related to use 4 | of the SpiderMonkey API. 5 | 6 | Many tips listed here are dealing with the `js102` JavaScript shell built 7 | during the SpiderMonkey build process. 8 | If you want to implement these functions in your own code, you can look 9 | at how they are implemented in the source code of the JS shell. 10 | 11 | Many of these tips only apply to debug builds; they will not function in 12 | a release build. 13 | 14 | ## Getting help from JS shell ## 15 | 16 | Use the `help()` function to get the list of all primitive functions of 17 | the shell with their description. 18 | Note that some functions have been moved under an `os` object, and 19 | `help(os)` will give brief help on just the members of that "namespace". 20 | A regular expression can also be passed to `help()`, for example 21 | `help(/gc/i)`. 22 | 23 | ## Getting the bytecode of a function ## 24 | 25 | The shell has a small function named `dis()` to dump the bytecode of a 26 | function with its source notes. 27 | Without arguments, it will dump the bytecode of its caller. 28 | 29 | ``` 30 | js> function f () { 31 | return 1; 32 | } 33 | js> dis(f); 34 | flags: 35 | loc op 36 | ----- -- 37 | main: 38 | 00000: one 39 | 00001: return 40 | 00002: stop 41 | 42 | Source notes: 43 | ofs line pc delta desc args 44 | ---- ---- ----- ------ -------- ------ 45 | 0: 1 0 [ 0] newline 46 | 1: 2 0 [ 0] colspan 2 47 | 3: 2 2 [ 2] colspan 9 48 | ``` 49 | 50 | In gdb, a function named `js::DisassembleAtPC()` can print the bytecode 51 | of a script. 52 | Some variants of this function such as `js::DumpScript()` are convenient 53 | for debugging. 54 | 55 | ## Printing the JS stack from gdb ## 56 | 57 | A function named `js::DumpBacktrace()` prints a backtrace à la gdb for 58 | the JS stack. 59 | The backtrace contains in the following order: the stack depth; the 60 | interpreter frame pointer (see the `StackFrame` class) or `(nil)` if 61 | compiled with IonMonkey; the file and line number of the call location; 62 | and in parentheses, the `JSScript` pointer and the `jsbytecode` pointer 63 | (PC) executed. 64 | 65 | ``` 66 | $ gdb --args js102 67 | […] 68 | (gdb) b js::ReportOverRecursed 69 | (gdb) r 70 | js> function f(i) { 71 | if (i % 2) f(i + 1); 72 | else f(i + 3); 73 | } 74 | js> f(0) 75 | 76 | Breakpoint 1, js::ReportOverRecursed (maybecx=0xfdca70) at /home/nicolas/mozilla/ionmonkey/js/src/jscntxt.cpp:495 77 | 495 if (maybecx) 78 | (gdb) call js::DumpBacktrace(maybecx) 79 | #0 (nil) typein:2 (0x7fffef1231c0 @ 0) 80 | #1 (nil) typein:2 (0x7fffef1231c0 @ 24) 81 | #2 (nil) typein:3 (0x7fffef1231c0 @ 47) 82 | #3 (nil) typein:2 (0x7fffef1231c0 @ 24) 83 | #4 (nil) typein:3 (0x7fffef1231c0 @ 47) 84 | […] 85 | #25157 0x7fffefbbc250 typein:2 (0x7fffef1231c0 @ 24) 86 | #25158 0x7fffefbbc1c8 typein:3 (0x7fffef1231c0 @ 47) 87 | #25159 0x7fffefbbc140 typein:2 (0x7fffef1231c0 @ 24) 88 | #25160 0x7fffefbbc0b8 typein:3 (0x7fffef1231c0 @ 47) 89 | #25161 0x7fffefbbc030 typein:5 (0x7fffef123280 @ 9) 90 | ``` 91 | 92 | Note, you can do the exact same exercise above using `lldb` (necessary 93 | on macOS after Apple removed gdb) by running `lldb -f js102` then 94 | following the remaining steps. 95 | 96 | We also have a gdb unwinder. 97 | This unwinder is able to read the frames created by the JIT, and to 98 | display the frames which are after these JIT frames. 99 | 100 | ``` 101 | $ gdb --args out/dist/bin/js ./foo.js 102 | […] 103 | SpiderMonkey unwinder is disabled by default, to enable it type: 104 | enable unwinder .* SpiderMonkey 105 | (gdb) b js::math_cos 106 | (gdb) run 107 | […] 108 | #0 js::math_cos (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338 109 | 338 CallArgs args = CallArgsFromVp(argc, vp); 110 | (gdb) enable unwinder .* SpiderMonkey 111 | (gdb) backtrace 10 112 | #0 0x0000000000f89979 in js::math_cos(JSContext*, unsigned int, JS::Value*) (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338 113 | #1 0x0000000000ca9c6e in js::CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) (cx=0x14f2640, native=0xf89960 , args=...) at js/src/jscntxtinlines.h:235 114 | #2 0x0000000000c87625 in js::Invoke(JSContext*, JS::CallArgs const&, js::MaybeConstruct) (cx=0x14f2640, args=..., construct=js::NO_CONSTRUCT) at js/src/vm/Interpreter.cpp:476 115 | #3 0x000000000069bdcf in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICCall_Fallback*, uint32_t, JS::Value*, JS::MutableHandleValue) (cx=0x14f2640, frame=0x7fffffff6ad8, stub_=0x1798838, argc=1, vp=0x7fffffff6a88, res=JSVAL_VOID) at js/src/jit/BaselineIC.cpp:6113 116 | #4 0x00007ffff7f41395 in <> () 117 | #5 0x00007ffff7f42223 in <> () 118 | #6 0x00007ffff7f4423d in <> () 119 | #7 0x00007ffff7f4222e in <> () 120 | #8 0x00007ffff7f4326a in <> () 121 | #9 0x00007ffff7f38d5f in <> () 122 | #10 0x00000000006a86de in EnterBaseline(JSContext*, js::jit::EnterJitData&) (cx=0x14f2640, data=...) at js/src/jit/BaselineJIT.cpp:150 123 | ``` 124 | 125 | Note, when you enable the unwinder, the current version of gdb (7.10.1) 126 | does not flush the backtrace. 127 | Therefore, the JIT frames do not appear until you settle on the next 128 | breakpoint. 129 | To work around this issue you can use the recording feature of gdb, to 130 | step one instruction, and settle back to where you came from with the 131 | following set of gdb commands: 132 | 133 | ``` 134 | (gdb) record full 135 | (gdb) si 136 | (gdb) record goto 0 137 | (gdb) record stop 138 | ``` 139 | 140 | If you have a core file, you can use the gdb unwinder the same way, or 141 | do everything from the command line as follows: 142 | 143 | ``` 144 | $ gdb -ex 'enable unwinder .* SpiderMonkey' -ex 'bt 0' -ex 'thread apply all backtrace' -ex 'quit' out/dist/bin/js corefile 145 | ``` 146 | 147 | The gdb unwinder is supposed to be loaded by `dist/bin/js-gdb.py` and 148 | load python scripts which are located in `js/src/gdb/mozilla` under gdb. 149 | If gdb does not load the unwinder by default, you can force it to, by 150 | using the `source` command with the js-gdb.py file. 151 | 152 | ## Dumping the JavaScript heap ## 153 | 154 | From the shell, you can call the `dumpHeap()` function to dump out all 155 | GC things (reachable and unreachable) that are present in the heap. 156 | By default the function writes to stdout, but a filename can be 157 | specified as an argument. 158 | 159 | Example output might look as follows: 160 | 161 | ``` 162 | 0x1234abcd B global object 163 | ========== 164 | # zone 0x56789123 165 | # compartment http://gmail.com [in zone 0x56789123] 166 | # compartment http://gmail.com/iframe [in zone 0x56789123] 167 | # arena allockind=3 size=64 168 | 0x1234abcd B object 169 | > 0x1234abcd B prop1 170 | > 0xabcd1234 W prop2 171 | 0xabcd1234 W object 172 | > 0xdeadbeef B prop3 173 | # arena allockind=5 size=72 174 | 0xdeadbeef W object 175 | > 0xabcd1234 W prop4 176 | ``` 177 | 178 | The output is textual. 179 | The first section of the file contains a list of roots, one per line. 180 | Each root has the form `0xabcd1234 `, where 181 | `` is the marking color of the given GC thing (`B` for black, `G` 182 | for gray, `W` for white) and `` is a string. 183 | The list of roots ends with a line containing `==========`. 184 | 185 | After the roots come a series of zones. 186 | A zone starts with several "comment lines" that start with hashes. 187 | The first comment declares the zone. 188 | It is followed by lines listing each compartment within the zone. 189 | After all the compartments come arenas, which is where the GC things are 190 | actually stored. 191 | Each arena is followed by all the GC things in the arena. 192 | A GC thing starts with a line giving its address, its color, and the 193 | thing kind (object, function, whatever). 194 | After this come a list of addresses that the GC thing points to, each 195 | one starting with `>`. 196 | 197 | It's also possible to dump the JavaScript heap from C++ code (or from 198 | gdb) using the `js::DumpHeap()` function. 199 | It is part of `jsfriendapi.h` and it is available in release builds. 200 | 201 | ## Dumping garbage collection statistics ## 202 | 203 | The environment variable `MOZ_GCTIMER` controls text dumping of GC 204 | statistics. 205 | `MOZ_GCTIMER` may be `none`, `stderr`, `stdout`, or a filename. 206 | If a filename is given, data is appended to that file. 207 | 208 | ## Setting a breakpoint in the generated code from gdb ## 209 | 210 | To set a breakpoint the generated code of a specific `JSScript` compiled 211 | with IonMonkey, set a breakpoint on the instruction you are interested 212 | in. 213 | If you have no precise idea which function you are looking at, you can 214 | set a breakpoint on the `js::ion::CodeGenerator::visitStart()` function. 215 | Optionally, a condition on the `ins->id()` of the LIR instruction can be 216 | added to select precisely the instruction you are looking for. 217 | Once the breakpoint is on the `CodeGenerator` function of the LIR 218 | instruction, add a command to generate a static breakpoint in the 219 | generated code. 220 | 221 | ``` 222 | $ gdb --args js 223 | […] 224 | (gdb) b js::ion::CodeGenerator::visitStart 225 | (gdb) command 226 | >call masm.breakpoint() 227 | >continue 228 | >end 229 | (gdb) r 230 | js> function f(a, b) { return a + b; } 231 | js> for (var i = 0; i < 100000; i++) f(i, i + 1); 232 | 233 | Breakpoint 1, js::ion::CodeGenerator::visitStart (this=0x101ed20, lir=0x10234e0) 234 | at /home/nicolas/mozilla/ionmonkey/js/src/ion/CodeGenerator.cpp:609 235 | 609 } 236 | 237 | Program received signal SIGTRAP, Trace/breakpoint trap. 238 | 0x00007ffff7fb165a in ?? () 239 | (gdb) 240 | ``` 241 | 242 | Once you hit the generated breakpoint, you can replace it by a gdb 243 | breakpoint to make it conditional, the procedure is to first replace 244 | the generated breakpoint by a nop instruction, and to set a breakpoint 245 | at the address of the nop. 246 | 247 | ``` 248 | (gdb) x /5i $pc - 1 249 | 0x7ffff7fb1659: int3 250 | => 0x7ffff7fb165a: mov 0x28(%rsp),%rax 251 | 0x7ffff7fb165f: mov %eax,%ecx 252 | 0x7ffff7fb1661: mov 0x30(%rsp),%rdx 253 | 0x7ffff7fb1666: mov %edx,%ebx 254 | 255 | (gdb) # replace the int3 by a nop 256 | (gdb) set *(unsigned char *) ($pc - 1) = 0x90 257 | (gdb) x /1i $pc - 1 258 | 0x7ffff7fb1659: nop 259 | 260 | (gdb) # set a breakpoint at the previous location 261 | (gdb) b *0x7ffff7fb1659 262 | Breakpoint 2 at 0x7ffff7fb1659 263 | ``` 264 | 265 | ## Printing Ion generated assembly code from gdb ## 266 | 267 | If you want to look at the assembly code generated by IonMonkey, you can follow this procedure: 268 | 269 | - Place a breakpoint in `CodeGenerator.cpp` on the 270 | `CodeGenerator::link()` method. 271 | - Step next a few times, so that the `code` variable gets generated. 272 | - Print `code->code_`, which is the address of the code. 273 | - Disassemble code read at this address (using `x/Ni address`, where _N_ 274 | is the number of instructions you would like to see.) 275 | 276 | Here is an example. 277 | It might be simpler to use the `CodeGenerator::link()` line number 278 | instead of the full qualified name to put the breakpoint. 279 | Let's say that the line number of this function is 4780, for instance: 280 | 281 | ``` 282 | (gdb) b CodeGenerator.cpp:4780 283 | Breakpoint 1 at 0x84cade0: file /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp, line 4780. 284 | (gdb) r 285 | Starting program: /home/code/mozilla-central/js/src/32-release/js -f /home/code/jaeger.js 286 | [Thread debugging using libthread_db enabled] 287 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 288 | [New Thread 0xf7903b40 (LWP 12563)] 289 | [New Thread 0xf6bdeb40 (LWP 12564)] 290 | Run#0 291 | 292 | Breakpoint 1, js::ion::CodeGenerator::link (this=0x86badf8) 293 | at /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp:4780 294 | 4780 { 295 | (gdb) n 296 | 4781 JSContext *cx = GetIonContext()->cx; 297 | (gdb) n 298 | 4783 Linker linker(masm); 299 | (gdb) n 300 | 4784 IonCode *code = linker.newCode(cx, JSC::ION_CODE); 301 | (gdb) n 302 | 4785 if (!code) 303 | (gdb) p code->code_ 304 | $1 = (uint8_t *) 0xf7fd25a8 "\201", 305 | (gdb) x/2i 0xf7fd25a8 306 | 0xf7fd25a8: sub $0x80,%esp 307 | 0xf7fd25ae: mov 0x94(%esp),%ecx 308 | ``` 309 | 310 | On ARM, the compiled JS code will always be ARM machine code, whereas 311 | SpiderMonkey itself is frequently Thumb2. 312 | Since there isn't debug info for the JITted code, you will need to tell 313 | gdb that you are looking at ARM code: 314 | 315 | ``` 316 | (gdb) set arm force-mode arm 317 | ``` 318 | 319 | Or you can wrap the `x` command in your own command: 320 | 321 | ``` 322 | def xi 323 | set arm force-mode arm 324 | eval "x/%di %d", $arg0, $arg1 325 | set arm force-mode auto 326 | end 327 | ``` 328 | 329 | ## Printing asm.js/wasm generated assembly code from gdb ## 330 | 331 | - Set a breakpoint on `js::wasm::Instance::callExport()` (defined in 332 | `WasmInstance.cpp` as of November 18th 2016). 333 | This will trigger for _any_ asm.js/wasm call, so you should find a way 334 | to set this breakpoint for only the generated codes you want to look 335 | at. 336 | - Run the program. 337 | - Do `next` in gdb until you reach the definition of the `funcPtr`: 338 | ``` 339 | // Call the per-exported-function trampoline created by GenerateEntry. 340 | auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset()); 341 | if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData_)) 342 | return false; 343 | ``` 344 | - After it's set, `x/64i funcPtr` will show you the trampoline code. 345 | There should be a call to an address at some point; that's what we're 346 | targeting. 347 | Copy that address. 348 | ``` 349 | 0x7ffff7ff6000: push %r15 350 | 0x7ffff7ff6002: push %r14 351 | 0x7ffff7ff6004: push %r13 352 | 0x7ffff7ff6006: push %r12 353 | 0x7ffff7ff6008: push %rbp 354 | 0x7ffff7ff6009: push %rbx 355 | 0x7ffff7ff600a: movabs $0xea4f80,%r10 356 | 0x7ffff7ff6014: mov 0x178(%r10),%r10 357 | 0x7ffff7ff601b: mov %rsp,0x40(%r10) 358 | 0x7ffff7ff601f: mov (%rsi),%r15 359 | 0x7ffff7ff6022: mov %rdi,%r10 360 | 0x7ffff7ff6025: push %r10 361 | 0x7ffff7ff6027: test $0xf,%spl 362 | 0x7ffff7ff602b: je 0x7ffff7ff6032 363 | 0x7ffff7ff6031: int3 364 | 0x7ffff7ff6032: callq 0x7ffff7ff5000 <------ right here 365 | ``` 366 | - `x/64i address` (in this case: `x/64i 0x7ffff7ff6032`). 367 | - If you want to put a breakpoint at the function's entry, you can do: 368 | `b *address` (for instance here, `b* 0x7ffff7ff6032`). 369 | Then you can display the instructions around pc with `x/20i $pc`, and 370 | execute instruction by instruction with `stepi`. 371 | 372 | -------------------------------------------------------------------------------- /docs/GC Rooting Guide.md: -------------------------------------------------------------------------------- 1 | ## Introduction ## 2 | 3 | This guide explains the basics of interacting with SpiderMonkey's 4 | GC as a SpiderMonkey API user. 5 | Since SpiderMonkey has a moving GC, it is very important that it knows 6 | about each and every pointer to a GC thing in the system. 7 | SpiderMonkey's rooting API tries to make this task as simple as 8 | possible. 9 | 10 | ## What is a GC thing pointer? ## 11 | 12 | "GC thing" is the term used to refer to memory allocated and managed by 13 | the SpiderMonkey garbage collector. 14 | The main types of GC thing pointer are: 15 | 16 | - `JS::Value` 17 | - `JSObject*` 18 | - `JSString*` 19 | - `JSScript*` 20 | - `JS::PropertyKey (aka jsid) 21 | - `JSFunction*` 22 | - `JS::Symbol*` 23 | 24 | Note that `JS::Value` and `JS::PropertyKey` can contain pointers internally even 25 | though they are not normal pointer types, hence their inclusion in this 26 | list. 27 | 28 | If you use these types directly, or create classes, structs or arrays 29 | that contain them, you must follow the rules set out in this guide. 30 | If you do not your program will not work correctly — if it works at all. 31 | 32 | ## GC thing pointers on the stack ## 33 | 34 | ### `JS::Rooted` ### 35 | 36 | All GC thing pointers stored **on the stack** (i.e., local variables and 37 | parameters to functions) must use the `JS::Rooted` class. 38 | This is a template class where the template parameter is the type of the 39 | GC thing it contains. 40 | From the user perspective, a `JS::Rooted` instance behaves exactly as 41 | if it were the underlying pointer. 42 | 43 | `JS::Rooted` must be constructed with a `JSContext*`, and optionally an initial value. 44 | 45 | There are typedefs available for the main types, though they are now 46 | deprecated in both Gecko and SpiderMonkey. If you see eg `JS::RootedObject`, 47 | it is an alias for `JS::Rooted`. 48 | 49 | SpiderMonkey makes it easy to remember to use the `JS::Rooted` type 50 | instead of a raw pointer because all of the API methods that may GC take 51 | a `JS::Handle`, as described below, and `JS::Rooted` autoconverts 52 | to `JS::Handle` but a bare pointer does not. 53 | 54 | ### `JS::Handle` ### 55 | 56 | All GC thing pointers that are parameters to a function must be wrapped 57 | in `JS::Handle`. A `JS::Handle` is a reference to a 58 | `JS::Rooted`, and is created implicitly by referencing a 59 | `JS::Rooted`: It is not valid to create a `JS::Handle` manually 60 | (the whole point of a Handle is that it only reference pointers that the 61 | GC knows about so it can update them when they move). 62 | Like `JS::Rooted`, a `JS::Handle` can be used as if it were the 63 | underlying pointer. 64 | 65 | Since only a `JS::Rooted` will cast to a `JS::Handle`, the 66 | compiler will enforce correct rooting of any parameters passed to a 67 | function that may trigger GC. 68 | `JS::Handle` exists because creating and destroying a `JS::Rooted` 69 | is not free (though it only costs a few cycles). 70 | Thus, it makes more sense to only root the GC thing once and reuse it 71 | through an indirect reference. 72 | Like a reference, a `JS::Handle` is immutable: it can only ever refer to 73 | the `JS::Rooted` that it was created for. 74 | 75 | You should use `JS::Handle` for all function parameters taking GC 76 | thing pointers (except out-parameters, which are described below). 77 | For example, instead of: 78 | 79 | ```c++ 80 | JSObject* 81 | someFunction(JSContext* cx, JSObject* obj) { 82 | // ... 83 | } 84 | ``` 85 | 86 | You should write: 87 | 88 | ```c++ 89 | JSObject* 90 | someFunction(JSContext* cx, JS::Handle obj) { 91 | // ... 92 | } 93 | ``` 94 | 95 | There is also a static constructor method `Handle::fromMarkedLocation()` 96 | that creates a `JS::Handle` from an arbitrary location. 97 | This is used to make `JS::Handle`s for things that aren't explicitly 98 | rooted themselves, but are always reachable from the stack roots. 99 | Every use of these should be commented to explain why they are 100 | guaranteed to be rooted. 101 | 102 | ### JS::MutableHandle ### 103 | 104 | All GC thing pointers that are used as out-parameters must be wrapped in 105 | a `JS::MutableHandle`. 106 | A `JS::MutableHandle` is a reference to a `JS::Rooted` that, 107 | unlike a normal handle, may modify the underlying `JS::Rooted`. 108 | All `JS::MutableHandle`s are created through an explicit `&` — 109 | address-of operator — on a `JS::Rooted` instance. 110 | `JS::MutableHandle` is exactly like a `JS::Handle` except that it 111 | adds a `.set(T &t)` method and must be created from a `JS::Rooted` 112 | explicitly. 113 | 114 | `JS::MutableHandle` should be used for all out-parameters, for 115 | example instead of: 116 | 117 | ```c++ 118 | bool 119 | maybeGetValue(JSContext* cx, JS::Value* valueOut) { 120 | // ... 121 | if (!wasError) 122 | *valueOut = resultValue; 123 | return wasError; 124 | } 125 | 126 | void 127 | otherFunction(JSContext* cx) { 128 | JS::Value value; 129 | bool success = maybeGetValue(cx, &value); 130 | // ... 131 | } 132 | ``` 133 | 134 | You should write: 135 | 136 | ```c++ 137 | bool 138 | maybeGetValue(JSContext* cx, JS::MutableHandle valueOut) { 139 | // ... 140 | if (!wasError) 141 | valueOut.set(resultValue); 142 | return wasError; 143 | } 144 | 145 | void 146 | otherFunction(JSContext* cx) { 147 | JS::RootedValue value(cx); 148 | bool success = maybeGetValue(cx, &value); 149 | // ... 150 | } 151 | ``` 152 | 153 | ### Return values ### 154 | 155 | It's ok to return raw pointers! 156 | These do not need to be wrapped in any of rooting classes, but they 157 | should be immediately used to initialize a `JS::Rooted` if there is 158 | any code that could GC before the end of the containing function; a raw 159 | pointer must never be stored on the stack during a GC. 160 | 161 | ### AutoRooters ### 162 | 163 | GC thing pointers that appear as part of stack-allocated aggregates 164 | (array, structure, class, union) should use `JS::Rooted` when 165 | possible. 166 | 167 | There are some situations when using `JS::Rooted` is not possible, or 168 | is undesirable for performance reasons. 169 | To cover these cases, there are various `AutoRooter` classes that can be 170 | used. 171 | 172 | Here are the main AutoRooters defined: 173 | 174 | | Type | AutoRooter class | 175 | | --------------------------- | ---------------------- | 176 | | `JS::AutoVector` | `JS::AutoValueVector` | 177 | | `JS::AutoVector` | `JS::AutoIdVector` | 178 | | `JS::AutoVector` | `JS::AutoObjectVector` | 179 | 180 | If your case is not covered by one of these, it is possible to write 181 | your own by deriving from `JS::CustomAutoRooter` and overriding the 182 | virtual `trace()` method. 183 | The implementation should trace all the GC things contained in the 184 | object by calling `JS::TraceEdge`. 185 | 186 | ### Common Pitfalls ### 187 | 188 | The C++ type system allows us to eliminate the possibility of most 189 | common errors; however, there are still a few things that you can get 190 | wrong that the compiler cannot help you with. 191 | There is basically never a good reason to do any of these. 192 | If you think you do need to do one of these, ask on one of 193 | SpiderMonkey's support forums: maybe we've already solved your problem 194 | using a different mechanism. 195 | 196 | - Storing a `JS::Rooted` on the heap. 197 | It would be very easy to violate the LIFO constraint if you did this. 198 | Use `JS::Heap` (see below) if you store a GC thing pointer on the 199 | heap. 200 | - Storing a `JS::Handle` on the heap. 201 | It is very easy for the handle to outlive its root if you do this. 202 | - Returning a `JS::Handle` from a function. 203 | If you do this, a handle may outlive its root. 204 | 205 | ### Performance Tweaking ### 206 | 207 | If the extra overhead of exact rooting does end up adding an 208 | unacceptable cost to a specific code path, there are some tricks you can 209 | use to get better performance at the cost of more complex code. 210 | 211 | - Move `JS::Rooted` declarations above loops. 212 | Modern C++ compilers are not smart enough to do LICM on 213 | `JS::Rooted`, so forward declaring a single `JS::Rooted` above 214 | the loop and re-using it on every iteration can save some cycles. 215 | - Raw pointers. 216 | If you are 100% sure that there is no way for SpiderMonkey to GC while 217 | the pointer is on the stack, this is an option. Note: During any JSAPI 218 | call, SpiderMonkey can 219 | GC because of any error, GC because of timers, GC because we are low 220 | on memory, GC because of environment variables, etc. 221 | This is not a terribly safe option for embedder code, so only consider 222 | this as a very last resort. 223 | 224 | ## GC thing pointers on the heap ## 225 | 226 | ### `JS::Heap` ### 227 | 228 | GC thing pointers on the heap must be wrapped in a `JS::Heap`. 229 | The only exception to this is if they are added as roots with the 230 | `JS::PersistentRooted` class, but don't do this unless it's really 231 | necessary. 232 | **`JS::Heap` pointers must also continue to be traced in the normal 233 | way**, and how to do that is explained below. 234 | That is, wrapping the value in `JS::Heap` only protects the pointer 235 | from becoming invalid when the GC thing it points to gets moved. 236 | It does not protect the GC thing from being collected by the GC! 237 | Tracing is what protects from collection. 238 | 239 | `JS::Heap` doesn't require a `JSContext*`, and can be constructed 240 | with or without an initial value parameter. 241 | Like the other template classes, it functions as if it were the GC thing 242 | pointer itself. 243 | 244 | One consequence of having different rooting requirements for heap and 245 | stack data is that a single structure containing GC thing pointers 246 | cannot be used on both the stack and the heap. 247 | In this case, separate structures must be created for the stack and the 248 | heap. 249 | 250 | For example, instead of this: 251 | 252 | ```c++ 253 | struct HeapStruct 254 | { 255 | JSObject* mSomeObject; 256 | JS::Value mSomeValue; 257 | }; 258 | ``` 259 | 260 | You should write: 261 | 262 | ```c++ 263 | struct HeapStruct 264 | { 265 | JS::Heap mSomeObject; 266 | JS::Heap mSomeValue; 267 | }; 268 | ``` 269 | 270 | (Note that below you will find that you will probably want to define a `trace` 271 | method as well.) 272 | 273 | ### Tracing ### 274 | 275 | #### Simple JSClasses and Proxies #### 276 | 277 | All GC pointers stored on the heap must be traced. 278 | For simple JSClasses without a private struct, or subclasses of 279 | `js::BaseProxyHandler`, this is normally done by storing them in 280 | reserved slots, which are automatically traced by the GC. 281 | 282 | ```c++ 283 | JSClass FooClass = { 284 | "FooPrototype", 285 | JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1), 286 | &FooClassOps 287 | }; 288 | JS::Rooted obj(cx, JS_NewObject(cx, &FooClass)); 289 | JS::Rooted v(cx, JS::ObjectValue(*otherGCThing)); 290 | js::SetReservedSlot(obj, 0, v); 291 | ``` 292 | 293 | #### JSClass #### 294 | 295 | When defining a JSClass with a private struct that contains `Heap` 296 | fields, [set the trace 297 | hook](https://searchfox.org/mozilla-central/search?q=JSTraceOp) 298 | to invoke a function that traces those fields. 299 | 300 | #### General Classes/Structures #### 301 | 302 | For a regular `struct` or `class`, tracing must be triggered manually. 303 | The usual way is to define a 304 | `void trace(JSTracer* trc, const char* name)` method on the class — 305 | which is already enough be able to create a `JS::Rooted` on 306 | the stack — and then arrange for it to be called during tracing. 307 | If a pointer to your structure is stored in the private field of a 308 | JSObject, the usual way would be to define a trace hook on the JSClass 309 | (see above) that casts the private pointer to your structure and invokes 310 | `trace()` on it: 311 | 312 | ```c++ 313 | class MyClass { 314 | enum { PRIVATE_DATA, SLOT_COUNT }; 315 | 316 | Heap str; 317 | 318 | public: 319 | static void trace(JSTracer* trc, JSObject* obj) { 320 | auto* mine = JS::GetMaybePtrFromReservedSlot(obj, PRIVATE_DATA); 321 | mine->trace(trc, "MyClass private field"); 322 | } 323 | 324 | void trace(JSTracer* trc, const char* name) { 325 | JS::TraceEdge(trc, &str, "my string"); 326 | } 327 | }; 328 | ``` 329 | 330 | If a pointer to your structure is stored in some other structure, then 331 | its `trace()` method should invoke yours: 332 | 333 | ```c++ 334 | struct MyOwningStruct { 335 | MyClass* mything; 336 | void trace(JSTracer* trc, const char* name) { 337 | if (mything) 338 | mything->trace(trc, "my thing"); 339 | } 340 | }; 341 | ``` 342 | 343 | If the toplevel structure is not stored in a `JSObject`, then how it 344 | gets traced depends on why it should be alive. 345 | The simplest approach is to use `JS::PersistentRooted` (usable on 346 | anything with a trace method with the appropriate signature): 347 | 348 | ```c++ 349 | JS::PersistentRooted immortalStruct; 350 | ``` 351 | 352 | But note that `JS::PersistentRooted` in a struct or class is a rather 353 | dangerous thing to use — it will keep a GC thing alive, and most GC 354 | things end up keeping their global alive, so if your class/struct is 355 | reachable in any way from that global, then nothing reachable from that 356 | global will ever be cleaned up by the GC. 357 | 358 | It's also possible to add a custom tracer using 359 | `JS_AddExtraGCRootsTracer()`. 360 | Each tracer that gets added needs to be removed again later with 361 | `JS_RemoveExtraGCRootsTracer()`. 362 | Obviously it's faster to add (and later remove) one function that gets called during GC and loops over many objects than adding (and removing) many objects to the GC root set. 363 | 364 | ## Testing Rooting ## 365 | 366 | ### JS_GC_ZEAL (increased GC frequency) ### 367 | 368 | This is a debugging feature to increase the frequency of garbage 369 | collections. 370 | It should reveal issues that would only show up in rare cases under 371 | normal circumstances. 372 | If the feature is enabled in the SpiderMonkey build (`--enable-gczeal`), 373 | you can set the environment variable `JS_GC_ZEAL` to configure 374 | debugging. 375 | Set it to -1 to print a table of possible settings (or look up that 376 | table in GCEnum.h). 377 | 378 | The most useful settings probably are: 379 | 380 | - `Alloc`: Collect every N allocations (default: 100) 381 | - `VerifierPre`: Verify pre write barriers between instructions 382 | - `GenerationalGC`: Collect the nursery every N nursery allocations 383 | - `IncrementalMarkingValidator`: Verify incremental marking 384 | 385 | You can append a number separated by a comma to specify N (like 386 | `Alloc,1` to GC after every allocation or `GenerationalGC,10` to do a 387 | minor GC every 10 nursery allocations). 388 | With some settings the program gets extremely slow. 389 | 390 | ### Static rooting analysis ### 391 | 392 | The static rooting analysis uses a [GCC 393 | plugin](https://hg.mozilla.org/users/sfink_mozilla.com/sixgill) to 394 | gather information about types that can contain GC pointers and calls 395 | that can cause a GC, and will statically (at compile time) 396 | analyse this data for rooting hazards. 397 | 398 | The main differences to dynamic rooting analysis are: 399 | 400 | - Covers all compiled code at once during compile time. There's no need to actually execute these codepaths. 401 | - Setup is more complicated 402 | - Only covers stack based rooting 403 | - There can be false positives (false alarms, where it will claim there is a situation that cannot actually arise) 404 | 405 | More information and instructions (possibly outdated) on [this wiki 406 | page](https://trac.wildfiregames.com/wiki/StaticRootingAnalysis). 407 | 408 | ## Summary ## 409 | 410 | - Use `JS::Rooted` typedefs for local variables on the stack. 411 | - Use `JS::Handle` typedefs for function parameters. 412 | - Use `JS::MutableHandle` typedefs for function out-parameters. 413 | - Use an implicit cast from `JS::Rooted` to get a `JS::Handle`. 414 | - Use an explicit address-of-operator on `JS::Rooted` to get a 415 | `JS::MutableHandle`. 416 | - Return raw pointers from functions. 417 | - Use `JS::Rooted` fields when possible for aggregates, otherwise use 418 | an AutoRooter. 419 | - Use `JS::Heap` members for heap data. **Note: `Heap` are not 420 | "rooted": they must be traced!** 421 | - Do not use `JS::Rooted`, `JS::Handle` or `JS::MutableHandle` 422 | on the heap. 423 | - Do not use `JS::Rooted` for function parameters. 424 | - Use `JS::PersistentRooted` for things that are alive until the 425 | process exits (or until you manually delete the PersistentRooted for a 426 | reason not based on GC finalization.) 427 | -------------------------------------------------------------------------------- /examples/cookbook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "boilerplate.h" 17 | 18 | // This example program shows the SpiderMonkey JSAPI equivalent for a handful 19 | // of common JavaScript idioms. 20 | 21 | /**** BASICS ******************************************************************/ 22 | 23 | ///// Working with Values ////////////////////////////////////////////////////// 24 | 25 | /* The basic, undifferentiated value type in the JSAPI is `JS::Value`. 26 | * To query whether a value has a particular type, use a correspondingly named 27 | * member testing function: 28 | * 29 | * // JavaScript 30 | * var isString = typeof v === "string"; 31 | * var isNumber = typeof v === "number"; 32 | * var isNull = v === null; 33 | * var isBoolean = typeof v === "boolean"; 34 | * var isObject = typeof v === "object" && v !== null; 35 | * var isSymbol = typeof v === "symbol"; 36 | * var isFunction = typeof v === "function"; 37 | * var isUndefined = typeof v === "undefined"; 38 | */ 39 | static void GetTypeOfValue(JSContext* cx, JS::HandleValue v) { 40 | bool isString = v.isString(); 41 | bool isNumber = v.isNumber(); 42 | bool isInt32 = 43 | v.isInt32(); // NOTE: Internal representation, not numeric value 44 | bool isNull = v.isNull(); 45 | bool isBoolean = v.isBoolean(); 46 | bool isObject = 47 | v.isObject(); // NOTE: not broken like typeof === "object" is :-) 48 | bool isSymbol = v.isSymbol(); 49 | bool isFunction = v.isObject() && JS::IsCallable(&v.toObject()); 50 | bool isUndefined = v.isUndefined(); 51 | 52 | // Avoid compiler warnings 53 | mozilla::Unused << isString; 54 | mozilla::Unused << isNumber; 55 | mozilla::Unused << isInt32; 56 | mozilla::Unused << isNull; 57 | mozilla::Unused << isBoolean; 58 | mozilla::Unused << isObject; 59 | mozilla::Unused << isSymbol; 60 | mozilla::Unused << isFunction; 61 | mozilla::Unused << isUndefined; 62 | } 63 | 64 | /* To set a value use a correspondingly named member mutator function, or assign 65 | * the result of the correspondingly named standalone function: 66 | * 67 | * // JavaScript 68 | * var v; 69 | * 70 | * v = 0; 71 | * v = 0.5; 72 | * v = someString; 73 | * v = null; 74 | * v = undefined; 75 | * v = false; 76 | * v = {}; 77 | * v = new Symbol(someString); 78 | */ 79 | static bool SetValue(JSContext* cx) { 80 | JS::RootedValue v(cx); 81 | JS::RootedString someString(cx, JS_NewStringCopyZ(cx, "my string")); 82 | if (!someString) return false; 83 | JS::RootedObject obj(cx, JS_NewPlainObject(cx)); 84 | if (!obj) return false; 85 | JS::RootedSymbol symbol(cx, JS::NewSymbol(cx, someString)); 86 | if (!symbol) return false; 87 | 88 | v.setInt32(0); 89 | v.setDouble(0.5); 90 | v.setNumber(0u); 91 | v.setNumber(0.5); 92 | v.setString(someString); 93 | v.setNull(); 94 | v.setUndefined(); 95 | v.setBoolean(false); 96 | v.setObject(*obj); 97 | v.setSymbol(symbol); 98 | 99 | // or: 100 | 101 | v = JS::Int32Value(0); 102 | v = JS::DoubleValue(0.5); 103 | v = JS::NumberValue(0); 104 | v = JS::NumberValue(0.5); 105 | v = JS::StringValue(someString); 106 | v = JS::NullValue(); 107 | v = JS::UndefinedValue(); 108 | v = JS::BooleanValue(false); 109 | v = JS::ObjectValue(*obj); 110 | v = JS::SymbolValue(symbol); 111 | 112 | return true; 113 | } 114 | 115 | ///// Finding the global object //////////////////////////////////////////////// 116 | 117 | /* Sometimes in a C++ function called from JavaScript, you will need to have 118 | * access to the global object. 119 | * 120 | * // JavaScript 121 | * var global = this; 122 | * 123 | * There is a function, JS::CurrentGlobalOrNull(cx), that makes a best guess, 124 | * and sometimes that is the best that can be done. 125 | * But in a JSNative the correct way to do this is: 126 | */ 127 | static bool FindGlobalObject(JSContext* cx, unsigned argc, JS::Value* vp) { 128 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 129 | JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(&args.callee())); 130 | if (!global) return false; 131 | 132 | // For comparison, here's how to do it with JS::CurrentGlobalOrNull(): 133 | JS::RootedObject global2(cx, JS::CurrentGlobalOrNull(cx)); 134 | 135 | if (global != global2) { 136 | JS_ReportErrorASCII(cx, "Globals did not agree"); 137 | return false; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | ///// Defining a function ////////////////////////////////////////////////////// 144 | 145 | /* // JavaScript 146 | * function justForFun() { 147 | * return null; 148 | * } 149 | * 150 | * To define many JSAPI functions at once, use JS_DefineFunctions(). 151 | */ 152 | static bool JustForFun(JSContext* cx, unsigned argc, JS::Value* vp) { 153 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 154 | args.rval().setNull(); 155 | return true; 156 | } 157 | 158 | /* Add this to your JSContext setup code. 159 | * This makes your C++ function visible as a global function in JavaScript. 160 | */ 161 | static bool DefineGlobalFunction(JSContext* cx, JS::HandleObject global) { 162 | if (!JS_DefineFunction(cx, global, "justForFun", &JustForFun, 0, 0)) 163 | return false; 164 | 165 | // Really, "if (!x) return false; else return true;" is bad style, just 166 | // "return x;" instead, but normally you might have other code between the 167 | // "return false" and "return true". 168 | return true; 169 | } 170 | 171 | ///// Creating an array //////////////////////////////////////////////////////// 172 | 173 | /* // JavaScript 174 | * var x = []; // or "x = Array()", or "x = new Array" 175 | */ 176 | static bool CreateArray(JSContext* cx) { 177 | JS::RootedObject x(cx, JS::NewArrayObject(cx, 0)); 178 | if (!x) return false; 179 | 180 | return true; 181 | } 182 | 183 | ///// Creating an object /////////////////////////////////////////////////////// 184 | 185 | /* // JavaScript 186 | * var x = {}; // or "x = Object()", or "x = new Object" 187 | */ 188 | static bool CreateObject(JSContext* cx) { 189 | JS::RootedObject x(cx, JS_NewPlainObject(cx)); 190 | if (!x) return false; 191 | 192 | // or: 193 | x = JS_NewObject(cx, /* clasp = */ nullptr); 194 | if (!x) return false; 195 | 196 | return true; 197 | } 198 | 199 | ///// Constructing an object with new ////////////////////////////////////////// 200 | 201 | /* // JavaScript 202 | * var person = new Person("Dave", 24); 203 | * 204 | * It looks so simple in JavaScript, but a JSAPI application has to do three 205 | * things here: 206 | * 207 | * - look up the constructor, Person 208 | * - prepare the arguments ("Dave", 24) 209 | * - call JS::Construct to simulate the new keyword 210 | */ 211 | static bool ConstructObjectWithNew(JSContext* cx, JS::HandleObject global) { 212 | // Step 1 - Get the value of `Person` and check that it is an object. 213 | JS::RootedValue constructor_val(cx); 214 | if (!JS_GetProperty(cx, global, "Person", &constructor_val)) return false; 215 | if (!constructor_val.isObject()) { 216 | JS_ReportErrorASCII(cx, "Person is not a constructor"); 217 | return false; 218 | } 219 | JS::RootedObject constructor(cx, &constructor_val.toObject()); 220 | 221 | // Step 2 - Set up the arguments. 222 | JS::RootedString name_str(cx, JS_NewStringCopyZ(cx, "Dave")); 223 | if (!name_str) return false; 224 | 225 | JS::RootedValueArray<2> args(cx); 226 | args[0].setString(name_str); 227 | args[1].setInt32(24); 228 | 229 | // Step 3 - Call `new Person(...args)`, passing the arguments. 230 | JS::RootedObject obj(cx); 231 | if (!JS::Construct(cx, constructor_val, args, &obj)) return false; 232 | if (!obj) return false; 233 | 234 | // (If your constructor doesn't take any arguments, you can skip the second 235 | // step and call step 3 like this:) 236 | if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) 237 | return false; 238 | 239 | return true; 240 | } 241 | 242 | static bool PersonConstructor(JSContext* cx, unsigned argc, JS::Value* vp) { 243 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 244 | JS::RootedObject newObject(cx, JS_NewPlainObject(cx)); 245 | if (!newObject) return false; 246 | args.rval().setObject(*newObject); 247 | return true; 248 | } 249 | 250 | ///// Calling a global JS function ///////////////////////////////////////////// 251 | 252 | /* // JavaScript 253 | * var r = foo(); // where f is a global function 254 | * 255 | * Suppose the script defines a global JavaScript 256 | * function foo() and we want to call it from C. 257 | */ 258 | static bool CallGlobalFunction(JSContext* cx, JS::HandleObject global) { 259 | JS::RootedValue r(cx); 260 | if (!JS_CallFunctionName(cx, global, "foo", JS::HandleValueArray::empty(), 261 | &r)) { 262 | return false; 263 | } 264 | 265 | return true; 266 | } 267 | 268 | ///// Calling a JS function via a local variable /////////////////////////////// 269 | 270 | /* // JavaScript 271 | * var r = f(); // where f is a local variable 272 | * 273 | * Suppose f is a local C variable of type JS::Value. 274 | */ 275 | static bool CallLocalFunctionVariable(JSContext* cx, JS::HandleValue f) { 276 | JS::RootedValue r(cx); 277 | if (!JS_CallFunctionValue(cx, nullptr, f, JS::HandleValueArray::empty(), &r)) 278 | return false; 279 | 280 | return true; 281 | } 282 | 283 | ///// Returning an integer ///////////////////////////////////////////////////// 284 | 285 | /* // JavaScript 286 | * return 23; 287 | * 288 | * Warning: This only works for integers that fit in 32 bits. 289 | * Otherwise, use setNumber or setDouble (see the next example). 290 | */ 291 | static bool ReturnInteger(JSContext* cx, unsigned argc, JS::Value* vp) { 292 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 293 | args.rval().setInt32(23); 294 | return true; 295 | } 296 | 297 | ///// Returning a floating-point number //////////////////////////////////////// 298 | 299 | /* // JavaScript 300 | * return 3.14159; 301 | */ 302 | 303 | static bool ReturnFloat(JSContext* cx, unsigned argc, JS::Value* vp) { 304 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 305 | args.rval().setDouble(3.14159); 306 | return true; 307 | } 308 | 309 | /**** EXCEPTION HANDLING ******************************************************/ 310 | 311 | ///// `throw` ////////////////////////////////////////////////////////////////// 312 | 313 | /* The most common idiom is to create a new Error object and throw that. 314 | * JS_ReportError{ASCII,Latin1,UTF8} does this. Note that JavaScript exceptions 315 | * are not the same thing as C++ exceptions. The JSAPI code also has to return 316 | * false to signal failure to the caller. 317 | * 318 | * // JavaScript 319 | * throw new Error("Failed to grow " + varietal + ": too many greenflies."); 320 | * 321 | * To internationalize your error messages, and to throw other error types, such 322 | * as SyntaxError or TypeError, use JS_ReportErrorNumber{ASCII,Latin1,UTF8} 323 | * instead. 324 | */ 325 | static bool ReportError(JSContext* cx, const char* varietal) { 326 | JS_ReportErrorASCII(cx, "Failed to grow %s: too many greenflies.", varietal); 327 | return false; 328 | } 329 | 330 | /* JavaScript also supports throwing any value at all, not just Error objects. 331 | * Use JS_SetPendingException to throw an arbitrary JS::Value from C/C++. 332 | * 333 | * // JavaScript 334 | * throw exc; 335 | */ 336 | static bool ThrowValue(JSContext* cx, JS::HandleValue exc) { 337 | JS_SetPendingException(cx, exc); 338 | return false; 339 | } 340 | 341 | /* When JS_ReportError creates a new Error object, it sets the fileName and 342 | * lineNumber properties to the line of JavaScript code currently at the top of 343 | * the stack. This is usually the line of code that called your native function, 344 | * so it's usually what you want. JSAPI code can override this by creating the 345 | * Error object directly and passing additional arguments to the constructor: 346 | * 347 | * // JavaScript 348 | * throw new Error(message, filename, lineno); 349 | * 350 | * An example use would be to pass the filename and line number in the C++ code 351 | * instead: 352 | * 353 | * return ThrowError(cx, global, message, __FILE__, __LINE__, column); 354 | */ 355 | static bool ThrowError(JSContext* cx, JS::HandleObject global, 356 | const char* message, const char* filename, 357 | int32_t lineno, int32_t colno = 0) { 358 | JS::RootedString messageStr(cx, JS_NewStringCopyZ(cx, message)); 359 | if (!messageStr) return false; 360 | JS::RootedString filenameStr(cx, JS_NewStringCopyZ(cx, filename)); 361 | if (!filenameStr) return false; 362 | 363 | JS::Rooted stack(cx); 364 | if (!JS::CaptureCurrentStack(cx, &stack)) { 365 | return false; 366 | } 367 | 368 | JS::Rooted exc(cx); 369 | if (!JS::CreateError(cx, JSEXN_ERR, stack, filenameStr, lineno, colno, 370 | nullptr, messageStr, JS::NothingHandleValue, &exc)) { 371 | return false; 372 | } 373 | 374 | JS_SetPendingException(cx, exc); 375 | return false; 376 | } 377 | 378 | #define THROW_ERROR(cx, global, message) \ 379 | ThrowError(cx, global, message, __FILE__, __LINE__) 380 | 381 | ///// `catch` ////////////////////////////////////////////////////////////////// 382 | 383 | /* // JavaScript 384 | * try { 385 | * // try some stuff here; for example: 386 | * foo(); 387 | * bar(); 388 | * } catch (exc) { 389 | * // do error-handling stuff here 390 | * } 391 | */ 392 | static bool CatchError(JSContext* cx, JS::HandleObject global) { 393 | JS::RootedValue r(cx); 394 | // try some stuff here; for example: 395 | if (!JS_CallFunctionName(cx, global, "foo", JS::HandleValueArray::empty(), 396 | &r)) 397 | goto catch_block; // instead of returning false 398 | if (!JS_CallFunctionName(cx, global, "bar", JS::HandleValueArray::empty(), 399 | &r)) 400 | goto catch_block; // instead of returning false 401 | return true; 402 | 403 | catch_block: 404 | JS::RootedValue exc(cx); 405 | if (!JS_GetPendingException(cx, &exc)) return false; 406 | JS_ClearPendingException(cx); 407 | // do error-handling stuff here 408 | return true; 409 | } 410 | 411 | ///// `finally` //////////////////////////////////////////////////////////////// 412 | 413 | /* // JavaScript 414 | * try { 415 | * foo(); 416 | * bar(); 417 | * } finally { 418 | * cleanup(); 419 | * } 420 | * 421 | * If your C/C++ cleanup code doesn't call back into the JSAPI, this is 422 | * straightforward: 423 | */ 424 | static bool FinallyBlock(JSContext* cx, JS::HandleObject global) { 425 | bool success = false; 426 | JS::RootedValue r(cx); 427 | 428 | if (!JS_CallFunctionName(cx, global, "foo", JS::HandleValueArray::empty(), 429 | &r)) 430 | goto finally_block; // instead of returning false immediately 431 | if (!JS_CallFunctionName(cx, global, "bar", JS::HandleValueArray::empty(), 432 | &r)) 433 | goto finally_block; 434 | success = true; 435 | // Intentionally fall through to the finally block. 436 | 437 | finally_block: 438 | /* cleanup(); */ 439 | return success; 440 | } 441 | 442 | /* However, if cleanup() is actually a JavaScript function, there's a catch. 443 | * When an error occurs, the JSContext's pending exception is set. If this 444 | * happens in foo() or bar() in the above example, the pending exception will 445 | * still be set when you call cleanup(), which would be bad. To avoid this, your 446 | * JSAPI code implementing the finally block must: 447 | * 448 | * - save the old exception, if any 449 | * - clear the pending exception so that your cleanup code can run 450 | * - do your cleanup 451 | * - restore the old exception, if any 452 | * - return false if an exception occurred, so that the exception is propagated 453 | * up. 454 | */ 455 | static bool ReentrantFinallyBlock(JSContext* cx, JS::HandleObject global) { 456 | bool success = false; 457 | JS::RootedValue r(cx); 458 | 459 | if (!JS_CallFunctionName(cx, global, "foo", JS::HandleValueArray::empty(), 460 | &r)) 461 | goto finally_block; // instead of returning false immediately 462 | if (!JS_CallFunctionName(cx, global, "bar", JS::HandleValueArray::empty(), 463 | &r)) 464 | goto finally_block; 465 | success = true; 466 | // Intentionally fall through to the finally block. 467 | 468 | finally_block: 469 | /* Temporarily set aside any exception currently pending. 470 | * It will be automatically restored when we return, unless we call 471 | * savedState.drop(). */ 472 | JS::AutoSaveExceptionState savedState(cx); 473 | 474 | if (!JS_CallFunctionName(cx, global, "cleanup", JS::HandleValueArray::empty(), 475 | &r)) { 476 | // The new error replaces the previous one, so discard the saved exception 477 | // state. 478 | savedState.drop(); 479 | return false; 480 | } 481 | return success; 482 | } 483 | 484 | /**** OBJECT PROPERTIES *******************************************************/ 485 | 486 | ///// Getting a property /////////////////////////////////////////////////////// 487 | 488 | /* // JavaScript 489 | * var x = y.myprop; 490 | * 491 | * The JSAPI function that does this is JS_GetProperty. It requires a JSObject* 492 | * argument. Since JavaScript values are usually stored in JS::Value variables, 493 | * a cast or conversion is usually needed. 494 | * 495 | * In cases where it is certain that y is an object (that is, not a boolean, 496 | * number, string, null, or undefined), this is fairly straightforward. Use 497 | * JS::Value::toObject() to cast y to type JSObject*. 498 | */ 499 | static bool GetProperty(JSContext* cx, JS::HandleValue y) { 500 | JS::RootedValue x(cx); 501 | 502 | assert(y.isObject()); 503 | JS::RootedObject yobj(cx, &y.toObject()); 504 | if (!JS_GetProperty(cx, yobj, "myprop", &x)) return false; 505 | 506 | return true; 507 | } 508 | 509 | /* That code will crash if y is not an object. That's often unacceptable. An 510 | * alternative would be to simulate the behavior of the JavaScript . notation, 511 | * which will "work" but tends to silently hide errors (as for example would 512 | * JavaScript `var x = 4; return x.myprop;`). 513 | */ 514 | static bool GetPropertySafe(JSContext* cx, JS::HandleObject global, 515 | JS::HandleValue y) { 516 | JS::RootedObject yobj(cx); 517 | if (!JS_ValueToObject(cx, y, &yobj)) return false; 518 | 519 | JS::RootedValue x(cx); 520 | if (!JS_GetProperty(cx, yobj, "myprop", &x)) return false; 521 | 522 | return true; 523 | } 524 | 525 | ///// Setting a property /////////////////////////////////////////////////////// 526 | 527 | /* // JavaScript 528 | * y.myprop = x; 529 | * 530 | * See "Getting a property", above, concerning the case where y is not an 531 | * object. 532 | */ 533 | static bool SetProperty(JSContext* cx, JS::HandleValue y, JS::HandleValue x) { 534 | JS::RootedObject yobj(cx); 535 | if (!JS_ValueToObject(cx, y, &yobj)) return false; 536 | if (!JS_SetProperty(cx, yobj, "myprop", x)) return false; 537 | 538 | return true; 539 | } 540 | 541 | ///// Checking for a property ////////////////////////////////////////////////// 542 | 543 | /* // JavaScript 544 | * if ("myprop" in y) { 545 | * // then do something 546 | * } 547 | * 548 | * In the case where y is not an object, here we just proceed as if the property 549 | * did not exist. Compare "Getting a property", above. 550 | */ 551 | static bool CheckProperty(JSContext* cx, JS::HandleValue y) { 552 | bool found; 553 | 554 | if (!y.isObject()) { 555 | found = false; 556 | } else { 557 | JS::RootedObject yobj(cx, &y.toObject()); 558 | if (!JS_HasProperty(cx, yobj, "myprop", &found)) return false; 559 | } 560 | if (found) { 561 | // then do something 562 | } 563 | 564 | return true; 565 | } 566 | 567 | ///// Defining a constant property ///////////////////////////////////////////// 568 | 569 | /* This is the first of three examples involving the built-in function 570 | * Object.defineProperty(), which gives JavaScript code fine-grained control 571 | * over the behavior of individual properties of any object. 572 | * 573 | * You can use this function to create a constant property, one that can't be 574 | * overwritten or deleted. Specify writable: false to make the property 575 | * read-only and configurable: false to prevent it from being deleted or 576 | * redefined. The flag enumerable: true causes this property to be seen by 577 | * for-in loops. 578 | * 579 | * // JavaScript 580 | * Object.defineProperty(obj, "const_prop", { 581 | * value: 123, 582 | * writable: false, 583 | * enumerable: true, 584 | * configurable: false, 585 | * }); 586 | * 587 | * The analogous JSAPI function is JS_DefineProperty. The property attribute 588 | * JSPROP_READONLY corresponds to writeable: false, JSPROP_ENUMERATE to 589 | * enumerable: true, and JSPROP_PERMANENT to configurable: false. To get the 590 | * opposite behavior for any of these settings, simply omit the property 591 | * attribute bits you don't want. 592 | */ 593 | static bool DefineConstantProperty(JSContext* cx, JS::HandleObject obj) { 594 | // You can pass the integer directly instead of creating a JS::Int32Value(), 595 | // as there are overloads for common types 596 | if (!JS_DefineProperty( 597 | cx, obj, "const-prop", 123, 598 | JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) { 599 | return false; 600 | } 601 | 602 | return true; 603 | } 604 | 605 | ///// Defining a property with a getter and setter ///////////////////////////// 606 | 607 | /* Object.defineProperty() can be used to define properties in terms of two 608 | * accessor functions. 609 | * 610 | * // JavaScript 611 | * Object.defineProperty(obj, "getter_setter_prop", { 612 | * get: GetPropFunc, 613 | * set: SetPropFunc, 614 | * enumerable: true, 615 | * }); 616 | * 617 | * In the JSAPI version, GetPropFunc and SetPropFunc are C/C++ functions of type 618 | * JSNative. 619 | */ 620 | static bool GetPropFunc(JSContext* cx, unsigned argc, JS::Value* vp) { 621 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 622 | args.rval().setInt32(42); 623 | return true; 624 | } 625 | 626 | static bool SetPropFunc(JSContext* cx, unsigned argc, JS::Value* vp) { 627 | return true; 628 | } 629 | 630 | static bool DefineGetterSetterProperty(JSContext* cx, JS::HandleObject obj) { 631 | if (!JS_DefineProperty(cx, obj, "getter_setter_prop", GetPropFunc, 632 | SetPropFunc, JSPROP_ENUMERATE)) { 633 | return false; 634 | } 635 | 636 | return true; 637 | } 638 | 639 | ///// Defining a read-only property with only a getter ///////////////////////// 640 | 641 | /* // JavaScript 642 | * Object.defineProperty(obj, "read_only_prop", { 643 | * get: GetPropFunc, 644 | * enumerable: true, 645 | * }); 646 | * 647 | * In the JSAPI version, to signify that the property is read-only, pass nullptr 648 | * for the setter. 649 | */ 650 | static bool DefineReadOnlyProperty(JSContext* cx, JS::HandleObject obj) { 651 | if (!JS_DefineProperty(cx, obj, "read_only_prop", GetPropFunc, 652 | nullptr, /* setter */ 653 | JSPROP_ENUMERATE)) { 654 | return false; 655 | } 656 | 657 | return true; 658 | } 659 | 660 | /**** WORKING WITH THE PROTOTYPE CHAIN ****************************************/ 661 | 662 | ///// Defining a native read-only property on the String.prototype ///////////// 663 | 664 | /* // JavaScript 665 | * Object.defineProperty(String.prototype, "md5sum", { 666 | * get: GetMD5Func, 667 | * enumerable: true, 668 | * }); 669 | * 670 | * The following trick couldn't work if someone has replaced the global String 671 | * object with something. 672 | */ 673 | static bool GetMD5Func(JSContext* cx, unsigned argc, JS::Value* vp) { 674 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 675 | // Implement your MD5 hashing here... 676 | JSString* hashstr = JS_NewStringCopyZ(cx, "d41d8cd98f00b204e9800998ecf8427e"); 677 | if (!hashstr) return false; 678 | args.rval().setString(hashstr); 679 | return true; 680 | } 681 | 682 | static bool ModifyStringPrototype(JSContext* cx, JS::HandleObject global) { 683 | JS::RootedValue val(cx); 684 | 685 | // Get the String constructor from the global object. 686 | if (!JS_GetProperty(cx, global, "String", &val)) return false; 687 | if (val.isPrimitive()) 688 | return THROW_ERROR(cx, global, "String is not an object"); 689 | JS::RootedObject string(cx, &val.toObject()); 690 | 691 | // Get String.prototype. 692 | if (!JS_GetProperty(cx, string, "prototype", &val)) return false; 693 | if (val.isPrimitive()) 694 | return THROW_ERROR(cx, global, "String.prototype is not an object"); 695 | JS::RootedObject string_prototype(cx, &val.toObject()); 696 | 697 | // ...and now we can add some new functionality to all strings. 698 | if (!JS_DefineProperty(cx, string_prototype, "md5sum", GetMD5Func, nullptr, 699 | JSPROP_ENUMERATE)) { 700 | return false; 701 | } 702 | 703 | return true; 704 | } 705 | 706 | /**** Defining classes ********************************************************/ 707 | 708 | /* This defines a constructor function, a prototype object, and properties of 709 | * the prototype and of the constructor, all with one API call. 710 | * 711 | * Initialize a class by defining its constructor function, prototype, and 712 | * per-instance and per-class properties. 713 | * The latter are called "static" below by analogy to Java. 714 | * They are defined in the constructor object's scope, so that 715 | * `MyClass.myStaticProp` works along with `new MyClass()`. 716 | * 717 | * `JS_InitClass` takes a lot of arguments, but you can pass `nullptr` for 718 | * any of the last four if there are no such properties or methods. 719 | * 720 | * Note that you do not need to call `JS_InitClass` to make a new instance 721 | * of that class—otherwise there would be a chicken-and-egg problem making 722 | * the global object—but you should call `JS_InitClass` if you require a 723 | * constructor function for script authors to call via `new`, and/or a 724 | * class prototype object (`MyClass.prototype`) for authors to extend with 725 | * new properties at run time. 726 | * In general, if you want to support multiple instances that share 727 | * behavior, use `JS_InitClass`. 728 | * 729 | * // JavaScript: 730 | * class MyClass { 731 | * constructor(a, b) { 732 | * this._a = a; 733 | * this._b = b; 734 | * } 735 | * get prop() { return 42; } 736 | * method() { return this.a + this.b; } 737 | * static get static_prop() { return 'static'; } 738 | * static static_method(a, b) { return a + b; } 739 | * } 740 | */ 741 | static JSClass myClass = {"MyClass", JSCLASS_HAS_RESERVED_SLOTS(2), nullptr}; 742 | 743 | enum MyClassSlots { SlotA, SlotB }; 744 | 745 | static bool MyClassPropGetter(JSContext* cx, unsigned argc, JS::Value* vp) { 746 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 747 | args.rval().setInt32(42); 748 | return true; 749 | } 750 | 751 | static bool MyClassMethod(JSContext* cx, unsigned argc, JS::Value* vp) { 752 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 753 | JS::RootedObject thisObj(cx); 754 | if (!args.computeThis(cx, &thisObj)) return false; 755 | 756 | JS::RootedValue v_a(cx, JS::GetReservedSlot(thisObj, SlotA)); 757 | JS::RootedValue v_b(cx, JS::GetReservedSlot(thisObj, SlotB)); 758 | 759 | double a, b; 760 | if (!JS::ToNumber(cx, v_a, &a) || !JS::ToNumber(cx, v_b, &b)) return false; 761 | 762 | args.rval().setDouble(a + b); 763 | return true; 764 | } 765 | 766 | static bool MyClassStaticPropGetter(JSContext* cx, unsigned argc, 767 | JS::Value* vp) { 768 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 769 | JSString* str = JS_NewStringCopyZ(cx, "static"); 770 | if (!str) return false; 771 | args.rval().setString(str); 772 | return true; 773 | } 774 | 775 | static bool MyClassStaticMethod(JSContext* cx, unsigned argc, JS::Value* vp) { 776 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 777 | if (!args.requireAtLeast(cx, "static_method", 2)) return false; 778 | 779 | double a, b; 780 | if (!JS::ToNumber(cx, args[0], &a) || !JS::ToNumber(cx, args[1], &b)) 781 | return false; 782 | 783 | args.rval().setDouble(a + b); 784 | return true; 785 | } 786 | 787 | static JSPropertySpec MyClassProperties[] = { 788 | JS_PSG("prop", MyClassPropGetter, JSPROP_ENUMERATE), JS_PS_END}; 789 | 790 | static JSFunctionSpec MyClassMethods[] = { 791 | JS_FN("method", MyClassMethod, 0, JSPROP_ENUMERATE), JS_FS_END}; 792 | 793 | static JSPropertySpec MyClassStaticProperties[] = { 794 | JS_PSG("static_prop", MyClassStaticPropGetter, JSPROP_ENUMERATE), 795 | JS_PS_END}; 796 | 797 | static JSFunctionSpec MyClassStaticMethods[] = { 798 | JS_FN("static_method", MyClassStaticMethod, 2, JSPROP_ENUMERATE), 799 | JS_FS_END}; 800 | 801 | static bool MyClassConstructor(JSContext* cx, unsigned argc, JS::Value* vp) { 802 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 803 | if (!args.requireAtLeast(cx, "MyClass", 2)) return false; 804 | if (!args.isConstructing()) { 805 | JS_ReportErrorASCII(cx, "You must call this constructor with 'new'"); 806 | return false; 807 | } 808 | JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &myClass, args)); 809 | if (!thisObj) return false; 810 | 811 | // Slightly different from the 'private' properties in the JS example, here 812 | // we use reserved slots to store the a and b values. These are not accessible 813 | // from JavaScript. 814 | JS::SetReservedSlot(thisObj, SlotA, args[0]); 815 | JS::SetReservedSlot(thisObj, SlotB, args[1]); 816 | 817 | args.rval().setObject(*thisObj); 818 | return true; 819 | } 820 | 821 | static bool DefineMyClass(JSContext* cx, JS::HandleObject global) { 822 | JS::RootedObject protoObj( 823 | cx, JS_InitClass(cx, global, nullptr, nullptr, myClass.name, 824 | // native constructor function and min arg count 825 | MyClassConstructor, 2, 826 | 827 | // prototype object properties and methods -- these will 828 | // be "inherited" by all instances through delegation up 829 | // the instance's prototype link. 830 | MyClassProperties, MyClassMethods, 831 | 832 | // class constructor properties and methods 833 | MyClassStaticProperties, MyClassStaticMethods)); 834 | if (!protoObj) return false; 835 | 836 | // You can add anything else here to protoObj (which is available as 837 | // MyClass.prototype in JavaScript). For example, call JS_DefineProperty() to 838 | // add data properties to the prototype. 839 | 840 | return true; 841 | } 842 | 843 | /**** WANTED ******************************************************************/ 844 | 845 | /* Simulating `for` and `for...of`. 846 | * Actually outputting errors. 847 | * Create global variable __dirname to retrieve the current JavaScript file 848 | * name, like in NodeJS 849 | * Custom error reporter 850 | */ 851 | 852 | /**** BOILERPLATE *************************************************************/ 853 | 854 | static bool GenericJSNative(JSContext* cx, unsigned argc, JS::Value* vp) { 855 | return true; 856 | } 857 | 858 | static bool ThrowJSNative(JSContext* cx, unsigned argc, JS::Value* vp) { 859 | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 860 | JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(&args.callee())); 861 | if (!global) return false; 862 | return THROW_ERROR(cx, global, "Error message"); 863 | } 864 | 865 | static JSFunctionSpec globalFunctions[] = { 866 | JS_FN("findGlobalObject", FindGlobalObject, 0, 0), 867 | JS_FN("Person", PersonConstructor, 2, JSFUN_CONSTRUCTOR), 868 | JS_FN("foo", GenericJSNative, 0, 0), 869 | JS_FN("returnInteger", ReturnInteger, 0, 0), 870 | JS_FN("returnFloat", ReturnFloat, 0, 0), 871 | JS_FN("bar", ThrowJSNative, 0, 0), 872 | JS_FN("cleanup", GenericJSNative, 0, 0), 873 | JS_FS_END}; 874 | 875 | static bool ExecuteCode(JSContext* cx, const char* code) { 876 | JS::CompileOptions options(cx); 877 | options.setFileAndLine("noname", 1); 878 | 879 | JS::SourceText source; 880 | if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) { 881 | return false; 882 | } 883 | 884 | JS::RootedValue unused(cx); 885 | return JS::Evaluate(cx, options, source, &unused); 886 | } 887 | 888 | class AutoReportException { 889 | JSContext* m_cx; 890 | 891 | public: 892 | explicit AutoReportException(JSContext* cx) : m_cx(cx) {} 893 | 894 | ~AutoReportException(void) { 895 | if (!JS_IsExceptionPending(m_cx)) return; 896 | 897 | JS::RootedValue v_exn(m_cx); 898 | mozilla::Unused << JS_GetPendingException(m_cx, &v_exn); 899 | JS_ClearPendingException(m_cx); 900 | 901 | JS::RootedString message(m_cx, JS::ToString(m_cx, v_exn)); 902 | if (!message) { 903 | std::cerr << "(could not convert thrown exception to string)\n"; 904 | } else { 905 | JS::UniqueChars message_utf8(JS_EncodeStringToUTF8(m_cx, message)); 906 | std::cerr << message_utf8.get() << '\n'; 907 | } 908 | 909 | JS_ClearPendingException(m_cx); 910 | } 911 | }; 912 | 913 | /* Execute each of the examples; many don't do anything but it's good to be able 914 | * to exercise the code to make sure it hasn't bitrotted. */ 915 | static bool RunCookbook(JSContext* cx) { 916 | JS::RootedObject global(cx, boilerplate::CreateGlobal(cx)); 917 | if (!global) return false; 918 | 919 | JSAutoRealm ar(cx, global); 920 | 921 | // Define some helper methods on our new global. 922 | if (!JS_DefineFunctions(cx, global, globalFunctions)) return false; 923 | 924 | AutoReportException autoreport(cx); 925 | 926 | // Execute each of the JSAPI recipe functions we defined: 927 | 928 | JS::RootedValue v(cx, JS::NullValue()); 929 | GetTypeOfValue(cx, v); 930 | if (!SetValue(cx)) return false; 931 | 932 | if (!DefineGlobalFunction(cx, global) || !CreateArray(cx) || 933 | !CreateObject(cx) || !ConstructObjectWithNew(cx, global) || 934 | !CallGlobalFunction(cx, global)) { 935 | return false; 936 | } 937 | 938 | JS::RootedValue f(cx); 939 | JSFunction* newFunction = JS_NewFunction(cx, JustForFun, 0, 0, "f"); 940 | if (!newFunction) return false; 941 | f.setObject(*JS_GetFunctionObject(newFunction)); 942 | 943 | if (!CallLocalFunctionVariable(cx, f)) return false; 944 | 945 | if (ReportError(cx, "cabernet sauvignon")) return false; 946 | JS_ClearPendingException(cx); 947 | 948 | JS::RootedValue exc(cx, JS::NumberValue(42)); 949 | if (ThrowValue(cx, exc)) return false; 950 | JS_ClearPendingException(cx); 951 | 952 | if (THROW_ERROR(cx, global, "an error message")) return false; 953 | JS_ClearPendingException(cx); 954 | 955 | if (!CatchError(cx, global)) return false; 956 | 957 | if (FinallyBlock(cx, global)) return false; 958 | JS_ClearPendingException(cx); 959 | 960 | if (ReentrantFinallyBlock(cx, global)) return false; 961 | JS_ClearPendingException(cx); 962 | 963 | JS::RootedObject obj(cx, JS_NewPlainObject(cx)); 964 | if (!obj) return false; 965 | JS::RootedValue v_obj(cx, JS::ObjectValue(*obj)); 966 | JS::RootedValue v_prop(cx, JS::Int32Value(42)); 967 | if (!SetProperty(cx, v_obj, v_prop)) return false; 968 | if (!CheckProperty(cx, v_obj)) return false; 969 | if (!GetProperty(cx, v_obj)) return false; 970 | if (!GetPropertySafe(cx, global, v_obj)) return false; 971 | if (!DefineConstantProperty(cx, obj)) return false; 972 | if (!DefineGetterSetterProperty(cx, obj)) return false; 973 | if (!DefineReadOnlyProperty(cx, obj)) return false; 974 | if (!ModifyStringPrototype(cx, global)) return false; 975 | 976 | if (!DefineMyClass(cx, global)) return false; 977 | if (!ExecuteCode(cx, R"js( 978 | const m = new MyClass(1, 2); 979 | m.method(); 980 | m.prop; 981 | MyClass.static_prop; 982 | MyClass.static_method(2, 3); 983 | )js")) 984 | return false; 985 | 986 | // Also execute each of the JSNative functions we defined: 987 | return ExecuteCode(cx, R"js( 988 | justForFun(); 989 | findGlobalObject(); 990 | returnInteger(); 991 | returnFloat(); 992 | ''.md5sum 993 | )js"); 994 | } 995 | 996 | int main(int argc, const char* argv[]) { 997 | if (!boilerplate::RunExample(RunCookbook)) return 1; 998 | return 0; 999 | } 1000 | --------------------------------------------------------------------------------