From 2259f1816055f3014f23468b3d62756257a64650 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Sat, 19 Jul 2025 12:27:33 -0700 Subject: [PATCH] Migration from ESR 128 to 140 - Update build instructions - Update bytecode doc generator from upstream - Regenerate bytecode docs - Replace output examples in debugging tips with more recent output - Mention ReportUncatchableException in exceptions doc - Add migration guide - Port WeakRef example to ESR 140 (the only example that actually needs any code changes) --- docs/Building SpiderMonkey.md | 14 +- docs/Bytecodes.md | 556 +++++++++++++++++++++------------- docs/Debugging Tips.md | 74 +++-- docs/Exceptions.md | 6 +- docs/Migration Guide.md | 141 +++++++++ examples/README.md | 8 +- examples/weakref.cpp | 8 +- meson.build | 4 +- tools/jsopcode.py | 268 ++++++++-------- tools/make_opcode_doc.py | 86 ++++-- 10 files changed, 754 insertions(+), 411 deletions(-) diff --git a/docs/Building SpiderMonkey.md b/docs/Building SpiderMonkey.md index 36adc19..5ef62ee 100644 --- a/docs/Building SpiderMonkey.md +++ b/docs/Building SpiderMonkey.md @@ -5,27 +5,27 @@ Use these instructions to build your own copy of SpiderMonkey. ## Prerequisites ## You will need a **C++ compiler** that can handle the C++17 standard, -**Rust** version [1.76][minimum-rust-version] or later, **GNU Make**, +**Rust** version [1.82][minimum-rust-version] or later, **GNU Make**, **zlib**, and **libffi**. These can usually be installed with a package manager. > **NOTE** SpiderMonkey also requires ICU of at least version -> [73.1][minimum-icu-version], but it will build a bundled copy by +> [76.1][minimum-icu-version], but it will build a bundled copy by > default. > If you have a new enough copy installed on your system, you can add > `--with-system-icu` in the build instructions below, for a shorter > build time. -[minimum-rust-version]: https://searchfox.org/mozilla-esr128/rev/2d28d1b9e757a35095de45c818a0432e031f339d/python/mozboot/mozboot/util.py#14 -[minimum-icu-version]: https://searchfox.org/mozilla-esr128/rev/2d28d1b9e757a35095de45c818a0432e031f339d/js/moz.configure#1308 +[minimum-rust-version]: https://searchfox.org/mozilla-esr140/rev/ccf322da35382830f6797111a12c7d28fd8f2258/python/mozboot/mozboot/util.py#16 +[minimum-icu-version]: https://searchfox.org/mozilla-esr140/rev/ccf322da35382830f6797111a12c7d28fd8f2258/js/moz.configure#1196 ## Getting the source code ## Currently, the most reliable way to get the SpiderMonkey source code is to download the Firefox source. -At the time of writing, the latest source for Firefox ESR 128, which -contains the source for SpiderMonkey ESR 128, can be found here: -https://ftp.mozilla.org/pub/firefox/releases/128.1.0esr/source/ +At the time of writing, the latest source for Firefox ESR 140, which +contains the source for SpiderMonkey ESR 140, can be found here: +https://ftp.mozilla.org/pub/firefox/releases/140.0esr/source/ The ESR releases have a major release approximately once a year with security patches released throughout the year. diff --git a/docs/Bytecodes.md b/docs/Bytecodes.md index 5e07133..c986f15 100644 --- a/docs/Bytecodes.md +++ b/docs/Bytecodes.md @@ -1,7 +1,7 @@ # Bytecode Listing # This document is automatically generated from -[`Opcodes.h`](https://searchfox.org/mozilla-esr102/source/js/src/vm/Opcodes.h) by +[`Opcodes.h`](https://searchfox.org/mozilla-esr140/source/js/src/vm/Opcodes.h) by [`make_opcode_doc.py`](../tools/make_opcode_doc.py). ## Background ## @@ -180,100 +180,10 @@ Push a well-known symbol. -##### `InitRecord` - -**Operands:** `(uint32_t length)` - -**Stack:** ⇒ `rval` - -Initialize a new record, preallocating `length` memory slots. `length` can still grow -if needed, for example when using the spread operator. - -Implements: [RecordLiteral Evaluation][1] step 1. - -[1]: https://tc39.es/proposal-record-tuple/#sec-record-initializer-runtime-semantics-evaluation - - - -##### `AddRecordProperty` - - -**Stack:** `record, key, value` ⇒ `record` - -Add the last element in the stack to the preceding tuple. - -Implements: [AddPropertyIntoRecordEntriesList][1]. - -[1]: https://tc39.es/proposal-record-tuple/#sec-addpropertyintorecordentrieslist - - - -##### `AddRecordSpread` - - -**Stack:** `record, value` ⇒ `record` - -Add the last element in the stack to the preceding tuple. - -Implements: [RecordPropertyDefinitionEvaluation][1] for - RecordPropertyDefinition : ... AssignmentExpression - -[1]: https://tc39.es/proposal-record-tuple/#sec-addpropertyintorecordentrieslist - - - -##### `FinishRecord` - - -**Stack:** `record` ⇒ `record` - -Mark a record as "initialized", going from "write-only" mode to -"read-only" mode. - - - #### Tuple literals #### -##### `InitTuple` - -**Operands:** `(uint32_t length)` - -**Stack:** ⇒ `rval` - -Initialize a new tuple, preallocating `length` memory slots. `length` can still grow -if needed, for example when using the spread operator. - -Implements: [TupleLiteral Evaluation][1] step 1. - -[1]: https://tc39.es/proposal-record-tuple/#sec-tuple-initializer-runtime-semantics-evaluation - - - -##### `AddTupleElement` - - -**Stack:** `tuple, element` ⇒ `tuple` - -Add the last element in the stack to the preceding tuple. - -Implements: [AddValueToTupleSequenceList][1]. - -[1]: https://tc39.es/proposal-record-tuple/#sec-addvaluetotuplesequencelist - - - -##### `FinishTuple` - - -**Stack:** `tuple` ⇒ `tuple` - -Mark a tuple as "initialized", going from "write-only" mode to -"read-only" mode. - - - ### Expressions ### #### Unary operators #### @@ -662,6 +572,43 @@ sequence `GetLocal "x"; One; Sub; SetLocal "x"` does not give us. +##### `TypeofEq` + +**Operands:** `(TypeofEqOperand operand)` + +**Stack:** `val` ⇒ `(typeof val CMP "type")` + +A compound opcode for `typeof val === "type"` or `typeof val !== "type"`, +where `val` is single identifier. + +Infallible. The result is always a boolean that depends on the type of +`val` and `"type"` string, and the comparison operator. + + +**Format:** JOF_IC + +##### `StrictConstantEq`, `StrictConstantNe` + +**Operands:** `(ConstantCompareOperand operand)` + +**Stack:** `val` ⇒ `(val OP constant)` + +A compound opcode for strict equality comparisons with constant +operands. example: `val === null`, `val !== true`. Takes in a single +operand which encodes the type of the constant and a payload +if applicable. + + + +##### `NopIsAssignOp` + + + +No-op instruction for bytecode decompiler to hint that the previous + binary operator is compound assignment. + + + ##### `IsNullOrUndefined` @@ -739,6 +686,7 @@ Push the `import.meta` object. This must be used only in module code. +**Format:** JOF_IC ### Objects ### @@ -748,6 +696,7 @@ This must be used only in module code. ##### `NewInit` +**Operands:** `(uint8_t propertyCount)` **Stack:** ⇒ `obj` @@ -828,7 +777,7 @@ Implements: [CreateDataPropertyOrThrow][1] as used in [2]: https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-propertydefinitionevaluation -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPINIT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPINIT, JOF_IC ##### `InitHiddenProp` @@ -846,7 +795,7 @@ Implements: [PropertyDefinitionEvaluation][1] for methods, steps 3 and [1]: https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPINIT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPINIT, JOF_IC ##### `InitLockedProp` @@ -865,7 +814,7 @@ false. [1]: https://tc39.es/ecma262/#sec-makeconstructor -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPINIT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPINIT, JOF_IC ##### `InitElem`, `InitHiddenElem`, `InitLockedElem` @@ -881,14 +830,14 @@ object literals like `{0: val}` and `{[id]: val}`, and methods like `*[Symbol.iterator]() {}`. `JSOp::InitHiddenElem` is the same but defines a non-enumerable property, -for class methods. +for class methods and private fields. `JSOp::InitLockedElem` is the same but defines a non-enumerable, non-writable, non-configurable property, for private class methods. [1]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow -**Format:** JOF_ELEM, JOF_PROPINIT, JOF_IC +**Format:** JOF_PROPINIT, JOF_IC ##### `InitPropGetter`, `InitHiddenPropGetter` @@ -905,7 +854,7 @@ Define an accessor property on `obj` with the given `getter`. property, for getters in classes. -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPINIT +**Format:** JOF_ATOM, JOF_PROPINIT ##### `InitElemGetter`, `InitHiddenElemGetter` @@ -922,7 +871,7 @@ This is used to implement getters like `get [id]() {}` or `get 0() {}`. property, for getters in classes. -**Format:** JOF_ELEM, JOF_PROPINIT +**Format:** JOF_PROPINIT ##### `InitPropSetter`, `InitHiddenPropSetter` @@ -940,7 +889,7 @@ This is used to implement ordinary setters like `set foo(v) {}`. property, for setters in classes. -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPINIT +**Format:** JOF_ATOM, JOF_PROPINIT ##### `InitElemSetter`, `InitHiddenElemSetter` @@ -957,7 +906,7 @@ keys. property, for setters in classes. -**Format:** JOF_ELEM, JOF_PROPINIT +**Format:** JOF_PROPINIT #### Accessing properties #### @@ -978,7 +927,7 @@ Implements: [GetV][1], [GetValue][2] step 5. [2]: https://tc39.es/ecma262/#sec-getvalue -**Format:** JOF_ATOM, JOF_PROP, JOF_IC +**Format:** JOF_ATOM, JOF_IC ##### `GetElem` @@ -993,7 +942,7 @@ Implements: [GetV][1], [GetValue][2] step 5. [2]: https://tc39.es/ecma262/#sec-getvalue -**Format:** JOF_ELEM, JOF_IC +**Format:** JOF_IC ##### `SetProp` @@ -1012,7 +961,7 @@ Implements: [PutValue][1] step 6 for non-strict code. [1]: https://tc39.es/ecma262/#sec-putvalue -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC ##### `StrictSetProp` @@ -1025,7 +974,7 @@ Like `JSOp::SetProp`, but for strict mode code. Throw a TypeError if no setter, or if `obj` is a primitive value. -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC ##### `SetElem` @@ -1039,7 +988,7 @@ Implements: [PutValue][1] step 6 for non-strict code. [1]: https://tc39.es/ecma262/#sec-putvalue -**Format:** JOF_ELEM, JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC +**Format:** JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC ##### `StrictSetElem` @@ -1051,7 +1000,7 @@ Like `JSOp::SetElem`, but for strict mode code. Throw a TypeError if no setter, or if `obj` is a primitive value. -**Format:** JOF_ELEM, JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC +**Format:** JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC ##### `DelProp` @@ -1070,7 +1019,7 @@ Implements: [`delete obj.propname`][1] step 5 in non-strict code. [1]: https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation -**Format:** JOF_ATOM, JOF_PROP, JOF_CHECKSLOPPY +**Format:** JOF_ATOM, JOF_CHECKSLOPPY ##### `StrictDelProp` @@ -1082,7 +1031,7 @@ Like `JSOp::DelProp`, but for strict mode code. Push `true` on success, else throw a TypeError. -**Format:** JOF_ATOM, JOF_PROP, JOF_CHECKSTRICT +**Format:** JOF_ATOM, JOF_CHECKSTRICT ##### `DelElem` @@ -1099,7 +1048,7 @@ Implements: [`delete obj[key]`][1] step 5 in non-strict code. [1]: https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation -**Format:** JOF_ELEM, JOF_CHECKSLOPPY +**Format:** JOF_CHECKSLOPPY ##### `StrictDelElem` @@ -1110,7 +1059,7 @@ Like `JSOp::DelElem, but for strict mode code. Push `true` on success, else throw a TypeError. -**Format:** JOF_ELEM, JOF_CHECKSTRICT +**Format:** JOF_CHECKSTRICT ##### `HasOwn` @@ -1199,7 +1148,7 @@ method. [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation -**Format:** JOF_ATOM, JOF_PROP, JOF_IC +**Format:** JOF_ATOM, JOF_IC ##### `GetElemSuper` @@ -1218,7 +1167,7 @@ method); [`Reflect.get(obj, key, receiver)`][3]. [3]: https://tc39.es/ecma262/#sec-reflect.get -**Format:** JOF_ELEM, JOF_IC +**Format:** JOF_IC ##### `SetPropSuper` @@ -1237,7 +1186,7 @@ the enclosing method. [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPSET, JOF_CHECKSLOPPY +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSLOPPY ##### `StrictSetPropSuper` @@ -1248,7 +1197,7 @@ the enclosing method. Like `JSOp::SetPropSuper`, but for strict mode code. -**Format:** JOF_ATOM, JOF_PROP, JOF_PROPSET, JOF_CHECKSTRICT +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSTRICT ##### `SetElemSuper` @@ -1266,7 +1215,7 @@ the enclosing method. [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation -**Format:** JOF_ELEM, JOF_PROPSET, JOF_CHECKSLOPPY +**Format:** JOF_PROPSET, JOF_CHECKSLOPPY ##### `StrictSetElemSuper` @@ -1276,7 +1225,7 @@ the enclosing method. Like `JSOp::SetElemSuper`, but for strict mode code. -**Format:** JOF_ELEM, JOF_PROPSET, JOF_CHECKSTRICT +**Format:** JOF_PROPSET, JOF_CHECKSTRICT #### Enumeration #### @@ -1363,6 +1312,39 @@ Exit a for-in loop, closing the iterator. +##### `CloseIter` + +**Operands:** `(CompletionKind kind)` + +**Stack:** `iter` ⇒ + +If the iterator object on top of the stack has a `return` method, +call that method. If the method exists but does not return an object, +and `kind` is not `CompletionKind::Throw`, throw a TypeError. (If +`kind` is `Throw`, the error we are already throwing takes precedence.) + +`iter` must be an object conforming to the [Iterator][1] interface. + +Implements: [IteratorClose][2] + +[1]: https://tc39.es/ecma262/#sec-iterator-interface +[2]: https://tc39.es/ecma262/#sec-iteratorclose + +**Format:** JOF_IC + +##### `OptimizeGetIterator` + + +**Stack:** `iterable` ⇒ `is_optimizable` + +If we can optimize iteration for `iterable`, meaning that it is a packed +array and nothing important has been tampered with, then we replace it +with `true`, otherwise we replace it with `false`. This is similar in +operation to OptimizeSpreadCall. + + +**Format:** JOF_IC + ##### `CheckIsObj` **Operands:** `(CheckIsObjectKind kind)` @@ -1474,7 +1456,7 @@ common case where *nextIndex* is known. [1]: https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation -**Format:** JOF_ELEM, JOF_PROPINIT +**Format:** JOF_PROPINIT ##### `InitElemInc` @@ -1493,7 +1475,8 @@ This never calls setters or proxy traps. except by `JSOp::InitElemArray` and `JSOp::InitElemInc`. `index` must be an integer, `0 <= index <= INT32_MAX`. If `index` is -`INT32_MAX`, this throws a RangeError. +`INT32_MAX`, this throws a RangeError. Unlike `InitElemArray`, it is not +necessary that the `array` length > `index`. This instruction is used when an array literal contains a *SpreadElement*. In `[a, ...b, c]`, `InitElemArray 0` is used to put @@ -1506,7 +1489,7 @@ CreateDataProperty, set the array length, and/or increment *nextIndex*. [1]: https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation -**Format:** JOF_ELEM, JOF_PROPINIT, JOF_IC +**Format:** JOF_PROPINIT, JOF_IC ##### `Hole` @@ -1555,6 +1538,7 @@ Pushes the current global's %BuiltinObject%. `BuiltinObjectKind::None`). +**Format:** JOF_IC ### Functions ### @@ -1582,7 +1566,7 @@ Implements: [InstantiateFunctionObject][1], [Evaluation for [2]: https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation -**Format:** JOF_OBJECT +**Format:** JOF_OBJECT, JOF_USES_ENV, JOF_IC ##### `SetFunName` @@ -1652,13 +1636,13 @@ Implements: [ClassDefinitionEvaluation][1] steps 6.e.ii, 6.g.iii, and [1]: https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation -**Format:** JOF_OBJECT +**Format:** JOF_OBJECT, JOF_USES_ENV #### Calls #### -##### `Call`, `CallIter`, `CallIgnoresRv` +##### `Call`, `CallContent`, `CallIter`, `CallContentIter`, `CallIgnoresRv` **Operands:** `(uint16_t argc)` @@ -1667,11 +1651,19 @@ Implements: [ClassDefinitionEvaluation][1] steps 6.e.ii, 6.g.iii, and Invoke `callee` with `this` and `args`, and push the return value. Throw a TypeError if `callee` isn't a function. +`JSOp::CallContent` is for `callContentFunction` in self-hosted JS, and +this is for handling it differently in debugger's `onNativeCall` hook. +`onNativeCall` hook disables all JITs, and `JSOp::CallContent` is +treated exactly the same as `JSOP::Call` in JIT. + `JSOp::CallIter` is used for implicit calls to @@iterator methods, to ensure error messages are formatted with `JSMSG_NOT_ITERABLE` ("x is not iterable") rather than `JSMSG_NOT_FUNCTION` ("x[Symbol.iterator] is not a function"). The `argc` operand must be 0 for this variation. +`JSOp::CallContentIter` is `JSOp::CallContent` variant of +`JSOp::CallIter`. + `JSOp::CallIgnoresRv` hints to the VM that the return value is ignored. This allows alternate faster implementations to be used that avoid unnecesary allocations. @@ -1785,29 +1777,21 @@ See `JSOp::SpreadCall` for restrictions on `args`. ##### `ImplicitThis` -**Operands:** `(uint32_t nameIndex)` -**Stack:** ⇒ `this` +**Stack:** `env` ⇒ `this` Push the implicit `this` value for an unqualified function call, like -`foo()`. `nameIndex` gives the name of the function we're calling. +`foo()`. The result is always `undefined` except when the name refers to a `with` binding. For example, in `with (date) { getFullYear(); }`, the implicit `this` passed to `getFullYear` is `date`, not `undefined`. -This walks the run-time environment chain looking for the environment -record that contains the function. If the function call definitely -refers to a local binding, use `JSOp::Undefined`. - -Implements: [EvaluateCall][1] step 1.b. But not entirely correctly. -See [bug 1166408][2]. +Implements: [EvaluateCall][1] step 1.b. [1]: https://tc39.es/ecma262/#sec-evaluatecall -[2]: https://bugzilla.mozilla.org/show_bug.cgi?id=1166408 -**Format:** JOF_ATOM ##### `CallSiteObj` @@ -1817,16 +1801,10 @@ See [bug 1166408][2]. Push the call site object for a tagged template call. -`script->getObject(objectIndex)` is the call site object; -`script->getObject(objectIndex + 1)` is the raw object. - -The first time this instruction runs for a given template, it assembles -the final value, defining the `.raw` property on the call site object -and freezing both objects. +`script->getObject(objectIndex)` is the call site object. -Implements: [GetTemplateObject][1], steps 4 and 12-16. - -[1]: https://tc39.es/ecma262/#sec-gettemplateobject +The call site object will already have the `.raw` property defined on it +and will be frozen. **Format:** JOF_OBJECT @@ -1843,7 +1821,7 @@ This magic value is a required argument to the `JSOp::New` and -##### `New`, `SuperCall` +##### `New`, `NewContent`, `SuperCall` **Operands:** `(uint16_t argc)` @@ -1858,6 +1836,9 @@ the return value. Throw a TypeError if `callee` isn't a constructor. *SuperCall* expressions, to allow JITs to distinguish them from `new` expressions. +`JSOp::NewContent` is for `constructContentFunction` in self-hosted JS. +See the comment for `JSOp::CallContent` for more details. + Implements: [EvaluateConstruct][1] steps 7 and 8. [1]: https://tc39.es/ecma262/#sec-evaluatenew @@ -1931,6 +1912,7 @@ object for the current frame (that is, this instruction must execute at most once per generator or async call). +**Format:** JOF_USES_ENV ##### `InitialYield` @@ -2072,18 +2054,36 @@ Implements: [Await][1], steps 2-9. ##### `AsyncResolve` -**Operands:** `(AsyncFunctionResolveKind fulfillOrReject)` -**Stack:** `valueOrReason, gen` ⇒ `promise` +**Stack:** `value, gen` ⇒ `promise` + +Resolve the current async function's result promise with 'value'. + +This instruction must appear only in non-generator async function +scripts. `gen` must be the internal generator object for the current +frame. This instruction must run at most once per async function call, +as resolving an already resolved/rejected promise is not permitted. + +The result `promise` is the async function's result promise, +`gen->as().promise()`. + +Implements: [AsyncFunctionStart][1], step 4.d.i. and 4.e.i. + +[1]: https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + + + +##### `AsyncReject` + + +**Stack:** `reason, stack, gen` ⇒ `promise` -Resolve or reject the current async function's result promise with -'valueOrReason'. +Reject the current async function's result promise with 'reason'. This instruction must appear only in non-generator async function scripts. `gen` must be the internal generator object for the current frame. This instruction must run at most once per async function call, -as resolving/rejecting an already resolved/rejected promise is not -permitted. +as rejecting an already resolved/rejected promise is not permitted. The result `promise` is the async function's result promise, `gen->as().promise()`. @@ -2530,14 +2530,42 @@ environment chain and resumes execution at the top of the `catch` or Implements: [*ThrowStatement* Evaluation][1], step 3. -This is also used in for-of loops. If the body of the loop throws an -exception, we catch it, close the iterator, then use `JSOp::Throw` to -rethrow. - [1]: https://tc39.es/ecma262/#sec-throw-statement-runtime-semantics-evaluation +##### `ThrowWithStack` + + +**Stack:** `exc, stack` ⇒ + +Throw `exc`. (ノಠ益ಠ)ノ彡┴──┴ + +This sets the pending exception to `exc`, the pending exception stack +to `stack`, and then jumps to error-handling code. If we're in a `try` +block, error handling adjusts the stack and environment chain and resumes +execution at the top of the `catch` or `finally` block. Otherwise it +starts unwinding the stack. + +This is used in for-of loops. If the body of the loop throws an +exception, we catch it, close the iterator, then use +`JSOp::ThrowWithStack` to rethrow. + + + +##### `CreateSuppressedError` + + +**Stack:** `error, suppressed` ⇒ `suppressedError` + +Create a suppressed error object and push it on the stack. + +Implements: [DisposeResources ( disposeCapability, completion )][1], step 3.e.iii.1.a-f. + +[1] https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposeresources + + + ##### `ThrowMsg` **Operands:** `(ThrowMsgKind msgNumber)` @@ -2563,7 +2591,7 @@ The number of arguments in the error message must be 0. Throws a runtime TypeError for invalid assignment to a `const` binding. -**Format:** JOF_ATOM, JOF_NAME +**Format:** JOF_ATOM ##### `Try` @@ -2601,8 +2629,23 @@ This must be used only in the fixed sequence of instructions following a `JSTRY_CATCH` span (see "Bytecode Invariants" above), as that's the only way instructions would run with an exception pending. -Used to implement catch-blocks, including the implicit ones generated as -part of for-of iteration. +Used to implement catch-blocks. + + + +##### `ExceptionAndStack` + + +**Stack:** ⇒ `exception, stack` + +Push and clear the pending exception. ┬──┬◡ノ(° -°ノ) + +This must be used only in the fixed sequence of instructions following a +`JSTRY_CATCH` span (see "Bytecode Invariants" above), as that's the only +way instructions would run with an exception pending. + +Used to implement implicit catch-blocks generated as part of for-of +iteration. @@ -2654,7 +2697,7 @@ this is how `const` bindings are initialized.) [2]: https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v -**Format:** JOF_LOCAL, JOF_NAME +**Format:** JOF_LOCAL ##### `InitGLexical` @@ -2671,7 +2714,7 @@ Like `JSOp::InitLexical` but for global lexicals. Unlike `InitLexical` this can't be used to mark a binding as uninitialized. -**Format:** JOF_ATOM, JOF_NAME, JOF_PROPINIT, JOF_GNAME, JOF_IC +**Format:** JOF_ATOM, JOF_PROPINIT, JOF_GNAME, JOF_IC ##### `InitAliasedLexical` @@ -2691,7 +2734,7 @@ initializing. argument `a` is initialized from inside a nested scope, so `hops == 1`. -**Format:** JOF_ENVCOORD, JOF_NAME, JOF_PROPINIT +**Format:** JOF_ENVCOORD, JOF_PROPINIT ##### `CheckLexical` @@ -2710,7 +2753,7 @@ Implements: [GetBindingValue][1] step 3 and [SetMutableBinding][2] step [2]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s -**Format:** JOF_LOCAL, JOF_NAME +**Format:** JOF_LOCAL ##### `CheckAliasedLexical` @@ -2727,7 +2770,7 @@ they're unnecessary. `JSOp::{Get,Set}{Name,GName}` all check for uninitialized lexicals and throw if needed. -**Format:** JOF_ENVCOORD, JOF_NAME +**Format:** JOF_ENVCOORD ##### `CheckThis` @@ -2749,7 +2792,7 @@ Implements: [GetThisBinding][1] step 3. -##### `BindGName` +##### `BindUnqualifiedGName` **Operands:** `(uint32_t nameIndex)` @@ -2757,10 +2800,23 @@ Implements: [GetThisBinding][1] step 3. Look up a name on the global lexical environment's chain and push the environment which contains a binding for that name. If no such binding -exists, push the global lexical environment. +exists, push the top-most variables object, which is the global object. + + +**Format:** JOF_ATOM, JOF_GNAME, JOF_IC + +##### `BindUnqualifiedName` + +**Operands:** `(uint32_t nameIndex)` + +**Stack:** ⇒ `env` + +Look up an unqualified name on the environment chain and push the +environment which contains a binding for that name. If no such binding +exists, push the first variables object along the environment chain. -**Format:** JOF_ATOM, JOF_NAME, JOF_GNAME, JOF_IC +**Format:** JOF_ATOM, JOF_IC, JOF_USES_ENV ##### `BindName` @@ -2770,10 +2826,10 @@ exists, push the global lexical environment. Look up a name on the environment chain and push the environment which contains a binding for that name. If no such binding exists, push the -global lexical environment. +global object. -**Format:** JOF_ATOM, JOF_NAME, JOF_IC +**Format:** JOF_ATOM, JOF_IC, JOF_USES_ENV #### Getting binding values #### @@ -2789,7 +2845,8 @@ Find a binding on the environment chain and push its value. If the binding is an uninitialized lexical, throw a ReferenceError. If no such binding exists, throw a ReferenceError unless the next -instruction is `JSOp::Typeof`, in which case push `undefined`. +instruction is `JSOp::Typeof` or `JSOp::TypeofEq` (see IsTypeOfNameOp), +in which case push `undefined`. Implements: [ResolveBinding][1] followed by [GetValue][2] (adjusted hackily for `typeof`). @@ -2801,7 +2858,7 @@ cases. Optimized instructions follow. [2]: https://tc39.es/ecma262/#sec-getvalue -**Format:** JOF_ATOM, JOF_NAME, JOF_IC +**Format:** JOF_ATOM, JOF_IC, JOF_USES_ENV ##### `GetGName` @@ -2827,7 +2884,7 @@ found (unless the next instruction is `JSOp::Typeof`) or if the binding is an uninitialized lexical. -**Format:** JOF_ATOM, JOF_NAME, JOF_GNAME, JOF_IC +**Format:** JOF_ATOM, JOF_GNAME, JOF_IC ##### `GetArg` @@ -2839,7 +2896,20 @@ Push the value of an argument that is stored in the stack frame or in an `ArgumentsObject`. -**Format:** JOF_QARG, JOF_NAME +**Format:** JOF_QARG + +##### `GetFrameArg` + +**Operands:** `(uint16_t argno)` + +**Stack:** ⇒ `arguments[argno]` + +Push the value of an argument that is stored in the stack frame. Like +`JSOp::GetArg`, but ignores the frame's `ArgumentsObject` and doesn't +assert the argument is unaliased. + + +**Format:** JOF_QARG ##### `GetLocal` @@ -2853,7 +2923,32 @@ If the variable is an uninitialized lexical, push `MagicValue(JS_UNINIITALIZED_LEXICAL)`. -**Format:** JOF_LOCAL, JOF_NAME +**Format:** JOF_LOCAL + +##### `ArgumentsLength` + + +**Stack:** ⇒ `arguments.length` + +Push the number of actual arguments as Int32Value. + +This is emitted for the ArgumentsLength() intrinsic in self-hosted code, +and if the script uses only arguments.length. + + + +##### `GetActualArg` + + +**Stack:** `index` ⇒ `arguments[index]` + +Push the value of an argument that is stored in the stack frame. The +value on top of the stack must be an Int32Value storing the index. The +index must be less than the number of actual arguments. + +This is emitted for the GetArgument(i) intrinsic in self-hosted code. + + ##### `GetAliasedVar` @@ -2880,7 +2975,7 @@ to non-strict `eval` or `with`) that might shadow the aliased binding. [1]: https://searchfox.org/mozilla-central/search?q=symbol:T_js%3A%3AEnvironmentCoordinate -**Format:** JOF_ENVCOORD, JOF_NAME +**Format:** JOF_ENVCOORD, JOF_USES_ENV ##### `GetAliasedDebugVar` @@ -2892,7 +2987,7 @@ Push the value of an aliased binding, which may have to bypass a DebugEnvironmen on the environment chain. -**Format:** JOF_DEBUGCOORD, JOF_NAME +**Format:** JOF_DEBUGCOORD ##### `GetImport` @@ -2903,7 +2998,7 @@ on the environment chain. Get the value of a module import by name and pushes it onto the stack. -**Format:** JOF_ATOM, JOF_NAME +**Format:** JOF_ATOM, JOF_IC ##### `GetBoundName` @@ -2915,22 +3010,25 @@ Get the value of a binding from the environment `env`. If the name is not bound in `env`, throw a ReferenceError. `env` must be an environment currently on the environment chain, pushed -by `JSOp::BindName` or `JSOp::BindVar`. - -Note: `JSOp::BindName` and `JSOp::GetBoundName` are the two halves of the -`JSOp::GetName` operation: finding and reading a variable. This -decomposed version is needed to implement the compound assignment and -increment/decrement operators, which get and then set a variable. The -spec says the variable lookup is done only once. If we did the lookup +by `JSOp::BindName`, `JSOp::BindUnqualifiedName`, or `JSOp::BindVar`. + +Note: `JSOp::Bind(Unqualified)Name` and `JSOp::GetBoundName` are the two +halves of the `JSOp::GetName` operation: finding and reading a variable. +This decomposed version is needed to implement: +1. The call operator, which gets a variable and its this-environment. +2. The compound assignment and increment/decrement operators, which get + and then set a variable. +The spec says the variable lookup is done only once. If we did the lookup twice, there would be observable bugs, thanks to dynamic scoping. We -could set the wrong variable or call proxy traps incorrectly. +could get the wrong this-environment resp. variable or call proxy traps +incorrectly. Implements: [GetValue][1] steps 4 and 6. [1]: https://tc39.es/ecma262/#sec-getvalue -**Format:** JOF_ATOM, JOF_NAME, JOF_IC +**Format:** JOF_ATOM, JOF_IC ##### `GetIntrinsic` @@ -2945,7 +3043,7 @@ Non-standard. Intrinsics are slots in the intrinsics holder object (see bindings in self-hosting code. -**Format:** JOF_ATOM, JOF_NAME, JOF_IC +**Format:** JOF_ATOM, JOF_IC ##### `Callee` @@ -2995,24 +3093,24 @@ Throw a ReferenceError if the binding is an uninitialized lexical. This can call setters and/or proxy traps. `env` must be an environment currently on the environment chain, -pushed by `JSOp::BindName` or `JSOp::BindVar`. +pushed by `JSOp::BindUnqualifiedName` or `JSOp::BindVar`. This is the fallback `Set` instruction that handles all unoptimized cases. Optimized instructions follow. Implements: [PutValue][1] steps 5 and 7 for unoptimized bindings. -Note: `JSOp::BindName` and `JSOp::SetName` are the two halves of simple -assignment: finding and setting a variable. They are two separate -instructions because, per spec, the "finding" part happens before -evaluating the right-hand side of the assignment, and the "setting" part -after. Optimized cases don't need a `Bind` instruction because the -"finding" is done statically. +Note: `JSOp::BindUnqualifiedName` and `JSOp::SetName` are the two halves +of simple assignment: finding and setting a variable. They are two +separate instructions because, per spec, the "finding" part happens +before evaluating the right-hand side of the assignment, and the +"setting" part after. Optimized cases don't need a `Bind` instruction +because the "finding" is done statically. [1]: https://tc39.es/ecma262/#sec-putvalue -**Format:** JOF_ATOM, JOF_NAME, JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSLOPPY, JOF_IC, JOF_USES_ENV ##### `StrictSetName` @@ -3029,7 +3127,7 @@ Implements: [PutValue][1] steps 5 and 7 for strict mode code. [1]: https://tc39.es/ecma262/#sec-putvalue -**Format:** JOF_ATOM, JOF_NAME, JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_CHECKSTRICT, JOF_IC, JOF_USES_ENV ##### `SetGName` @@ -3038,10 +3136,10 @@ Implements: [PutValue][1] steps 5 and 7 for strict mode code. **Stack:** `env, val` ⇒ `val` Like `JSOp::SetName`, but for assigning to globals. `env` must be an -environment pushed by `JSOp::BindGName`. +environment pushed by `JSOp::BindUnqualifiedGName`. -**Format:** JOF_ATOM, JOF_NAME, JOF_PROPSET, JOF_GNAME, JOF_CHECKSLOPPY, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_GNAME, JOF_CHECKSLOPPY, JOF_IC ##### `StrictSetGName` @@ -3050,10 +3148,10 @@ environment pushed by `JSOp::BindGName`. **Stack:** `env, val` ⇒ `val` Like `JSOp::StrictSetGName`, but for assigning to globals. `env` must be -an environment pushed by `JSOp::BindGName`. +an environment pushed by `JSOp::BindUnqualifiedGName`. -**Format:** JOF_ATOM, JOF_NAME, JOF_PROPSET, JOF_GNAME, JOF_CHECKSTRICT, JOF_IC +**Format:** JOF_ATOM, JOF_PROPSET, JOF_GNAME, JOF_CHECKSTRICT, JOF_IC ##### `SetArg` @@ -3065,7 +3163,7 @@ Assign `val` to an argument binding that's stored in the stack frame or in an `ArgumentsObject`. -**Format:** JOF_QARG, JOF_NAME +**Format:** JOF_QARG ##### `SetLocal` @@ -3076,7 +3174,7 @@ in an `ArgumentsObject`. Assign to an optimized local binding. -**Format:** JOF_LOCAL, JOF_NAME +**Format:** JOF_LOCAL ##### `SetAliasedVar` @@ -3093,7 +3191,7 @@ and has been initialized. [1]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s -**Format:** JOF_ENVCOORD, JOF_NAME, JOF_PROPSET +**Format:** JOF_ENVCOORD, JOF_PROPSET, JOF_USES_ENV ##### `SetIntrinsic` @@ -3109,7 +3207,7 @@ object, `GlobalObject::getIntrinsicsHolder`. (Self-hosted code doesn't have many global `var`s, but it has many `function`s.) -**Format:** JOF_ATOM, JOF_NAME +**Format:** JOF_ATOM #### Entering and leaving environments #### @@ -3158,7 +3256,7 @@ after a `JSOp::PopLexicalEnv`, then those must be correctly noted as [1]: https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation -**Format:** JOF_SCOPE +**Format:** JOF_SCOPE, JOF_USES_ENV ##### `PopLexicalEnv` @@ -3169,6 +3267,7 @@ Pop a lexical or class-body environment from the environment chain. See `JSOp::PushLexicalEnv` for the fine print. +**Format:** JOF_USES_ENV ##### `DebugLeaveLexicalEnv` @@ -3190,6 +3289,7 @@ or `JSOp::PopLexicalEnv` (if not). ##### `RecreateLexicalEnv` +**Operands:** `(uint32_t lexicalScopeIndex)` Replace the current block on the environment chain with a fresh block @@ -3200,9 +3300,11 @@ loop-head declares lexical variables that may be captured. The current environment must be a BlockLexicalEnvironmentObject. +**Format:** JOF_SCOPE ##### `FreshenLexicalEnv` +**Operands:** `(uint32_t lexicalScopeIndex)` Like `JSOp::RecreateLexicalEnv`, but the values of all the bindings are @@ -3210,6 +3312,7 @@ copied from the old block to the new one. This is used for C-style `for(let ...; ...; ...)` loops. +**Format:** JOF_SCOPE ##### `PushClassBodyEnv` @@ -3263,7 +3366,7 @@ not needed. [2]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation -**Format:** JOF_SCOPE +**Format:** JOF_SCOPE, JOF_USES_ENV ##### `EnterWith` @@ -3278,10 +3381,11 @@ Implements: [Evaluation of `with` statements][1], steps 2-6. Operations that may need to consult a WithEnvironment can't be correctly implemented using optimized instructions like `JSOp::GetLocal`. A script -must use the deoptimized `JSOp::GetName`, `BindName`, `SetName`, and -`DelName` instead. Since those instructions don't work correctly with -optimized locals and arguments, all bindings in scopes enclosing a -`with` statement are marked as "aliased" and deoptimized too. +must use the deoptimized `JSOp::GetName`, `BindUnqualifiedName`, +`BindName`,`SetName`, and `DelName` instead. Since those instructions +don't work correctly with optimized locals and arguments, all bindings in +scopes enclosing a `with` statement are marked as "aliased" and +deoptimized too. See `JSOp::PushLexicalEnv` for the fine print. @@ -3304,6 +3408,38 @@ Implements: [Evaluation of `with` statements][1], step 8. +##### `AddDisposable` + +**Operands:** `(UsingHint hint)` + +**Stack:** `v, method, needsClosure` ⇒ + +Append the object and method on the stack as a disposable to be disposed on +to the current lexical environment object. + +Implements: [AddDisposableResource ( disposeCapability, V, hint [ , method ] )][1], steps 3-4. + +[1] https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-adddisposableresource + + +**Format:** JOF_USES_ENV + +##### `TakeDisposeCapability` + + +**Stack:** ⇒ `disposeCapability` + +Get the dispose capability of the present environment object. +In case the dispose capability of the environment +has already been cleared or if no disposables have been +pushed to the capability, it shall push undefined as the dispose +capability. After extracting a non-empty dispose +capability, the dispose capability is cleared from the present +environment object by setting it to undefined value. + + +**Format:** JOF_USES_ENV + #### Creating and deleting bindings #### @@ -3324,6 +3460,7 @@ can't be optimized. [1]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation +**Format:** JOF_USES_ENV ##### `GlobalOrEvalDeclInstantiation` @@ -3346,7 +3483,7 @@ from index `0` thru `lastFun` contain only scopes and hoisted functions. [2]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation -**Format:** JOF_GCTHING +**Format:** JOF_GCTHING, JOF_USES_ENV ##### `DelName` @@ -3366,7 +3503,7 @@ strict mode code. [2]: https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors -**Format:** JOF_ATOM, JOF_NAME, JOF_CHECKSLOPPY +**Format:** JOF_ATOM, JOF_CHECKSLOPPY, JOF_USES_ENV #### Function environment setup #### @@ -3392,6 +3529,7 @@ The current script must be a function script. This instruction must execute at most once per function activation. +**Format:** JOF_USES_ENV ##### `Rest` diff --git a/docs/Debugging Tips.md b/docs/Debugging Tips.md index a4cfdb7..313a634 100644 --- a/docs/Debugging Tips.md +++ b/docs/Debugging Tips.md @@ -31,20 +31,45 @@ js> function f () { return 1; } js> dis(f); -flags: -loc op ------ -- +{ + "file": "typein", + "lineno": 1, + "column": 12, + "immutableFlags": [ + "IsFunction", + "HasMappedArgsObj" + ], + "functionName": "f", + "functionFlags": [ + "NORMAL_KIND", + "BASESCRIPT", + "CONSTRUCTOR" + ] +} +loc line op +----- ---- -- main: -00000: one -00001: return -00002: stop +00000: 2 One # 1 +00001: 3 Return # +00002: 3 RetRval # !!! UNREACHABLE !!! Source notes: - ofs line pc delta desc args ----- ---- ----- ------ -------- ------ - 0: 1 0 [ 0] newline - 1: 2 0 [ 0] colspan 2 - 3: 2 2 [ 2] colspan 9 + ofs line column pc delta desc args +---- ---- ------ ----- ------ ---------------- ------ + 0: 1 12 0 [ 0] newlinecolumn column 3 + 2: 2 3 0 [ 0] breakpoint-step-sep + 3: 2 3 1 [ 1] newline + 4: 3 1 2 [ 1] breakpoint + +Exception table: +kind stack start end + +Scope notes: + index parent start end + +GC things: + index type value + 0 Scope function {} -> global ``` In gdb, a function named `js::DisassembleAtPC()` can print the bytecode @@ -63,7 +88,7 @@ and in parentheses, the `JSScript` pointer and the `jsbytecode` pointer (PC) executed. ``` -$ gdb --args js102 +$ gdb --args js140 […] (gdb) b js::ReportOverRecursed (gdb) r @@ -73,20 +98,21 @@ js> function f(i) { } js> f(0) -Breakpoint 1, js::ReportOverRecursed (maybecx=0xfdca70) at /home/nicolas/mozilla/ionmonkey/js/src/jscntxt.cpp:495 -495 if (maybecx) +Thread 1 "js140" hit Breakpoint 1.1, js::ReportOverRecursed (maybecx=maybecx@entry=0x555558871160) + at /path/to/js/src/vm/JSContext.cpp:340 +340 MaybeReportOverRecursedForDifferentialTesting(); (gdb) call js::DumpBacktrace(maybecx) -#0 (nil) typein:2 (0x7fffef1231c0 @ 0) -#1 (nil) typein:2 (0x7fffef1231c0 @ 24) -#2 (nil) typein:3 (0x7fffef1231c0 @ 47) -#3 (nil) typein:2 (0x7fffef1231c0 @ 24) -#4 (nil) typein:3 (0x7fffef1231c0 @ 47) +#0 7fffffdfdf70 I typein:2 (309ac6b74060 @ 0) +#1 7fffffdfdfa0 I typein:2 (309ac6b74060 @ 27) +#2 7fffffdfdfd0 I typein:3 (309ac6b74060 @ 53) +#3 7fffffdfe000 I typein:2 (309ac6b74060 @ 27) +#4 7fffffdfe030 I typein:3 (309ac6b74060 @ 53) […] -#25157 0x7fffefbbc250 typein:2 (0x7fffef1231c0 @ 24) -#25158 0x7fffefbbc1c8 typein:3 (0x7fffef1231c0 @ 47) -#25159 0x7fffefbbc140 typein:2 (0x7fffef1231c0 @ 24) -#25160 0x7fffefbbc0b8 typein:3 (0x7fffef1231c0 @ 47) -#25161 0x7fffefbbc030 typein:5 (0x7fffef123280 @ 9) +#11873 5555589b4858 i typein:2 (322afad74060 @ 27) +#11874 5555589b47b8 i typein:3 (322afad74060 @ 53) +#11875 5555589b4718 i typein:2 (322afad74060 @ 27) +#11876 5555589b4678 i typein:3 (322afad74060 @ 53) +#11877 5555589b45e0 i typein:5 (322afad74100 @ 7) ``` Note, you can do the exact same exercise above using `lldb` (necessary diff --git a/docs/Exceptions.md b/docs/Exceptions.md index dd05929..093bcdf 100644 --- a/docs/Exceptions.md +++ b/docs/Exceptions.md @@ -52,6 +52,10 @@ The JavaScript stack is unwound in the normal way except that `catch` and `finally` blocks are ignored. The most recent JSAPI call returns `false` or `nullptr` to the application. +To avoid unintended uncatchable exceptions, debug builds of +SpiderMonkey require also calling `JS::ReportUncatchableException` when +throwing one intentionally. + An uncatchable error leaves the `JSContext` in a good state. It can be used again right away. The application does not have to do anything to “recover” from the error, as far as the JSAPI is concerned. @@ -64,6 +68,6 @@ Here is some example code that throws an uncatchable error. Log("The server room is on fire!"); /* Make sure the error is uncatchable. */ -JS_ClearPendingException(cx); +JS::ReportUncatchableException(cx); return false; ``` diff --git a/docs/Migration Guide.md b/docs/Migration Guide.md index 2f26589..1b6de98 100644 --- a/docs/Migration Guide.md +++ b/docs/Migration Guide.md @@ -3,6 +3,147 @@ This document describes how to port your code from using one ESR version of SpiderMonkey to the next ESR version. +## ESR 128 to ESR 140 ## + +### Scope chains ### + +Various script execution APIs take a scope chain argument, for when the +script is intended to be executed within one or more additional scopes +nested within the global object. +Previously, this scope chain was a vector of JSObject. + +Starting in this version, it is a `JS::EnvironmentChain`. +`JS::EnvironmentChain` has basically the same API as +`JS::RootedObjectVector` but its constructor takes an additional +argument, either `JS::SupportUnscopables::Yes` or +`JS::SupportUnscopables::No`. + +This additional argument determines whether the `[Symbol.unscopables]` +property on the scope objects should be taken into account, to exclude +some of the objects' properties. +Usually, embeddings are not using the `[Symbol.unscopables]` property, +so unless you know specifically that you need it, you can most likely +pass `JS::SupportUnscopables::No`. + +**Recommendation:** Rewrite your code like this: +```c++ +// old +JS::RootedObjectVector scope_chain{cx}; +// new +JS::EnvironmentChain scope_chain{cx, JS::SupportUnscopables::No}; +``` +And make sure to include ``. + +### Uncatchable exceptions ### + +If a JSNative callback returns false without reporting an exception, +that signifies an uncatchable exception. +Since it is easy to forget to report an exception, now debug builds of +SpiderMonkey will assert that `JS::ReportUncatchableException()` has +been called, to make sure the uncatchable exception is intentional. + +**Recommendation:** Your code will still work in release builds if you +don't do this, but make sure to call `JS::ReportUncatchableException()` +wherever an uncatchable exception is intended (for example, if your +interpreter uses an uncatchable exception for its "exit" command.) + +### Stashed pointers + +There is a new feature in ``: +`JS::NewObjectWithStashedPointer()` and `JS::ObjectGetStashedPointer()`. +These allow creating a JS object with a C++ pointer from the embedding +stashed in one of its private slots. + +The pointer can optionally have a destructor function called on it when +the object is finalized. + +**Recommendation:** A JS object with a stashed C++ pointer is often +needed in embedder code. +If you need the simple case where the object doesn't need any custom +`JSClassOps` behaviour, consider simplifying your code by using this +feature. + +### `Error.isError()` customization + +`Error.isError()` is a new static method on the `Error` object in +JavaScript. +If you define custom exception objects in your embedding that do not +inherit from `Error`, you may wish to make `Error.isError()` return true +when called on them. + +**Recommendation:** Consider whether any custom exception objects need +to return true when passed to `Error.isError()`. +In browsers, for example, this happens for `DOMException` objects. +If so, you will need to call `js::SetDOMCallbacks()` when setting up the +`JSContext`, and pass a `js::DOMCallbacks` struct with a callback +pointer as its `instanceClassIsError` member. + +This callback will need to determine whether the passed-in `JSClass` +pointer should be considered an error object or not. +Usually this is done by comparing it with the custom exception object's +`JSClass` by pointer equality, but other conditions are possible. +Note that the callback does not have access to the instance's `JSObject` +pointer itself, so the determination can only be made by class. + +You will also need to add `JSCLASS_IS_DOMJSCLASS` to the object's +`JSClass.flags`, in order for this callback to get called at all. + +### Module API changes + +There are a few changes to the module loading APIs in order to support +import attributes and JSON modules. + +`JS::CreateModuleRequest()` now takes an additional parameter to be able +to set the module type. This parameter can be either +`JS::ModuleType::JavaScript` or `JS::ModuleType::JSON`. + +Two functions that were previously always guaranteed to return, can now +fail (i.e., can return a null pointer signifying a pending exception.) +`JS::GetModuleScript()` can now fail if the module record passed to it +is a synthetic module. `JS::GetRequestedModuleSpecifier()` can now fail +if the requested module has an unsupported import attribute or the +module has an unknown type. + +### Stencil API changes + +The APIs previously referring to "incremental encoding" are now named to +refer to "collecting delazifications", and have moved to the +`` header. + +The data model of `JS::Stencil` now includes a list of delazifications, +meaning that it can be used to cache the delazifications and can be used +across threads. + +`JS::StartIncrementalEncoding()` is renamed to +`JS::StartCollectingDelazifications()`, and no longer takes ownership of +the stencil. +It also gains an `alreadyStarted` boolean out parameter. + +`JS::FinishIncrementalEncoding()` is renamed to +`JS::FinishCollectingDelazifications()`, and has gained a new overload +that returns the `JS::Stencil` as an out parameter, instead of +transcoding to a buffer. + +`JS::AbortIncrementalEncoding()` is simply renamed to +`JS::AbortCollectingDelazifications()`. + +### Other API changes ### + +This is a non-exhaustive list of minor API changes and renames. + +- `JS::JobQueue::getIncumbentGlobal()` → + `JS::JobQueue::getHostDefinedData()` (you may be implementing this + virtual function in a subclass in your code) +- `JS::Heap::address()` → `JS::Heap::unsafeAddress()` +- `JSSecurityCallbacks` has an extra `codeForEvalGets` field +- The `ConstUTF8CharsZ` overloads of `JS::UTF8CharsToNewTwoByteCharsZ()` + and `JS::LossyUTF8CharsToNewTwoByteCharsZ()` have been removed (the + `UTF8CharsZ` overloads can be used before the migration) +- `JS::LossyUTF8CharsToNewLatin1CharsZ()` → + `mozilla::LossyConvertUTF8ToLatin1()` (switch can be done before the + migration, but beware that the behaviour of non-Latin1 codepoints is + different) + ## ESR 115 to ESR 128 ## ### New API for column numbers ### diff --git a/examples/README.md b/examples/README.md index f669283..caad7c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,13 +5,13 @@ You need Meson 0.43.0 or later to build the examples. Installation instructions for Meson are [here](https://mesonbuild.com/Getting-meson.html). -You will also need SpiderMonkey ESR 102 installed where Meson can find +You will also need SpiderMonkey ESR 140 installed where Meson can find it. -Generally this means that the `mozjs-102.pc` file needs to be installed -in a location known to pkg-config, and the `libmozjs-102.so` file needs +Generally this means that the `mozjs-140.pc` file needs to be installed +in a location known to pkg-config, and the `libmozjs-140.so` file needs to be in the path for loading libraries. -Many Linux distributions have development packages for SpiderMonkey 102 +Many Linux distributions have development packages for SpiderMonkey 140 and if you just want to try the examples, installing that is the easiest way to get a build of SpiderMonkey. If you are on macOS or Windows, or want to do any development, read the diff --git a/examples/weakref.cpp b/examples/weakref.cpp index 7cb5d20..a0f9e6e 100644 --- a/examples/weakref.cpp +++ b/examples/weakref.cpp @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -56,8 +54,10 @@ class CustomJobQueue : public JS::JobQueue { ~CustomJobQueue() = default; // JS::JobQueue override - JSObject* getIncumbentGlobal(JSContext* cx) override { - return JS::CurrentGlobalOrNull(cx); + bool getHostDefinedData(JSContext* cx, + JS::MutableHandleObject data) const override { + data.set(JS::CurrentGlobalOrNull(cx)); + return true; } // JS::JobQueue override diff --git a/meson.build b/meson.build index 5938013..9a4c87e 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('spidermonkey-embedding-examples', 'cpp', version: 'esr128', +project('spidermonkey-embedding-examples', 'cpp', version: 'esr140', meson_version: '>= 0.43.0', default_options: ['cpp_std=c++20', 'warning_level=3']) @@ -7,7 +7,7 @@ cxx = meson.get_compiler('cpp') args = [] zlib = dependency('zlib') # (is already a SpiderMonkey dependency) -spidermonkey = dependency('mozjs-128') +spidermonkey = dependency('mozjs-140') readline = cxx.find_library('readline') # Check if SpiderMonkey was compiled with --enable-debug. If this is the case, diff --git a/tools/jsopcode.py b/tools/jsopcode.py index 9cc7ee5..e41a572 100644 --- a/tools/jsopcode.py +++ b/tools/jsopcode.py @@ -10,21 +10,21 @@ def codify(text): - text = re.sub(quoted_pat, '\\1\\2', text) - text = re.sub(js_pat, '\\1\\2', text) + text = re.sub(quoted_pat, "\\1\\2", text) + text = re.sub(js_pat, "\\1\\2", text) return text -space_star_space_pat = re.compile('^\s*\* ?', re.M) +space_star_space_pat = re.compile(r"^\s*\* ?", re.M) def get_comment_body(comment): - return re.sub(space_star_space_pat, '', comment).split('\n') + return re.sub(space_star_space_pat, "", comment).split("\n") quote_pat = re.compile('"([^"]+)"') -str_pat = re.compile('js_([^_]+)_str') +str_pat = re.compile("js_([^_]+)_str") def parse_name(s): @@ -37,34 +37,34 @@ def parse_name(s): return s -csv_pat = re.compile(', *') +csv_pat = re.compile(", *") def parse_csv(s): a = csv_pat.split(s) - if len(a) == 1 and a[0] == '': + if len(a) == 1 and a[0] == "": return [] return a def get_stack_count(stack): - if stack == '': + if stack == "": return 0 - if '...' in stack: + if "..." in stack: return -1 - return len(stack.split(',')) + return len(stack.split(",")) def parse_index(comment): index = [] current_types = None - category_name = '' - category_pat = re.compile('\[([^\]]+)\]') + category_name = "" + category_pat = re.compile(r"\[([^\]]+)\]") for line in get_comment_body(comment): m = category_pat.search(line) if m: category_name = m.group(1) - if category_name == 'Index': + if category_name == "Index": continue current_types = [] index.append((category_name, current_types)) @@ -75,6 +75,7 @@ def parse_index(comment): return index + # Holds the information stored in the comment with the following format: # /* # * {desc} @@ -87,12 +88,13 @@ def parse_index(comment): class CommentInfo: def __init__(self): - self.desc = '' - self.category_name = '' - self.type_name = '' - self.operands = '' - self.stack_uses = '' - self.stack_defs = '' + self.desc = "" + self.category_name = "" + self.type_name = "" + self.operands = "" + self.stack_uses = "" + self.stack_defs = "" + # Holds the information stored in the macro with the following format: # MACRO({op}, {op_snake}, {token}, {length}, {nuses}, {ndefs}, {format}) @@ -101,14 +103,14 @@ def __init__(self): class OpcodeInfo: def __init__(self, value, comment_info): - self.op = '' - self.op_snake = '' + self.op = "" + self.op_snake = "" self.value = value - self.token = '' - self.length = '' - self.nuses = '' - self.ndefs = '' - self.format_ = '' + self.token = "" + self.length = "" + self.nuses = "" + self.ndefs = "" + self.format_ = "" self.operands_array = [] self.stack_uses_array = [] @@ -128,16 +130,16 @@ def __init__(self, value, comment_info): # /* # * comment # */ - # MACRO(JSOP_SUB, ...) - # MACRO(JSOP_MUL, ...) - # MACRO(JSOP_DIV, ...) + # MACRO(Sub, ...) + # MACRO(Mul, ...) + # MACRO(Div, ...) self.group = [] - self.sort_key = '' + self.sort_key = "" def find_by_name(list, name): - for (n, body) in list: + for n, body in list: if n == name: return body @@ -147,53 +149,67 @@ def find_by_name(list, name): def add_to_index(index, opcode): types = find_by_name(index, opcode.category_name) if types is None: - raise Exception('Category is not listed in index: ' - '{name}'.format(name=opcode.category_name)) + raise Exception("Category is not listed in index: " f"{opcode.category_name}") opcodes = find_by_name(types, opcode.type_name) if opcodes is None: if opcode.type_name: - raise Exception('Type is not listed in {category}: ' - '{name}'.format(category=opcode.category_name, - name=opcode.type_name)) + raise Exception( + f"Type is not listed in {opcode.category_name}: " f"{opcode.type_name}" + ) types.append((opcode.type_name, [opcode])) return opcodes.append(opcode) -tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$') +tag_pat = re.compile(r"^\s*[A-Za-z]+:\s*|\s*$") def get_tag_value(line): - return re.sub(tag_pat, '', line) + return re.sub(tag_pat, "", line) RUST_OR_CPP_KEYWORDS = { - 'and', 'case', 'default', 'double', 'false', 'goto', 'in', 'new', 'not', 'or', 'return', - 'throw', 'true', 'try', 'typeof', 'void', + "and", + "case", + "default", + "double", + "false", + "goto", + "in", + "new", + "not", + "or", + "return", + "throw", + "true", + "try", + "typeof", + "void", } def get_opcodes(dir): - iter_pat = re.compile(r"/\*(.*?)\*/" # either a documentation comment... - r"|" - r"MACRO\(" # or a MACRO(...) call - r"(?P[^,]+),\s*" - r"(?P[^,]+),\s*" - r"(?P[^,]+,)\s*" - r"(?P[0-9\-]+),\s*" - r"(?P[0-9\-]+),\s*" - r"(?P[0-9\-]+),\s*" - r"(?P[^\)]+)" - r"\)", re.S) - stack_pat = re.compile(r"^(?P.*?)" - r"\s*=>\s*" - r"(?P.*?)$") + iter_pat = re.compile( + r"/\*(.*?)\*/" # either a documentation comment... + r"|" + r"MACRO\(" # or a MACRO(...) call + r"(?P[^,]+),\s*" + r"(?P[^,]+),\s*" + r"(?P[^,]+,)\s*" + r"(?P[0-9\-]+),\s*" + r"(?P[0-9\-]+),\s*" + r"(?P[0-9\-]+),\s*" + r"(?P[^\)]+)" + r"\)", + re.S, + ) + stack_pat = re.compile(r"^(?P.*?)" r"\s*=>\s*" r"(?P.*?)$") opcodes = dict() index = [] - with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r', encoding='utf-8') as f: + with open(f"{dir}/js/src/vm/Opcodes.h", encoding="utf-8") as f: data = f.read() comment_info = None @@ -205,49 +221,50 @@ def get_opcodes(dir): for m in re.finditer(iter_pat, data): comment = m.group(1) - op = m.group('op') + op = m.group("op") if comment: - if '[Index]' in comment: + if "[Index]" in comment: index = parse_index(comment) continue - if 'Operands:' not in comment: + if "Operands:" not in comment: continue group_head = None comment_info = CommentInfo() - state = 'desc' - stack = '' - desc = '' + state = "desc" + stack = "" + desc = "" for line in get_comment_body(comment): - if line.startswith(' Category:'): - state = 'category' + if line.startswith(" Category:"): + state = "category" comment_info.category_name = get_tag_value(line) - elif line.startswith(' Type:'): - state = 'type' + elif line.startswith(" Type:"): + state = "type" comment_info.type_name = get_tag_value(line) - elif line.startswith(' Operands:'): - state = 'operands' + elif line.startswith(" Operands:"): + state = "operands" comment_info.operands = get_tag_value(line) - elif line.startswith(' Stack:'): - state = 'stack' + elif line.startswith(" Stack:"): + state = "stack" stack = get_tag_value(line) - elif state == 'desc': + elif state == "desc": desc += line + "\n" - elif line.startswith(' '): + elif line.startswith(" "): if line.isspace(): pass - elif state == 'operands': - comment_info.operands += ' ' + line.strip() - elif state == 'stack': - stack += ' ' + line.strip() + elif state == "operands": + comment_info.operands += " " + line.strip() + elif state == "stack": + stack += " " + line.strip() else: - raise ValueError("unrecognized line in comment: {!r}\n\nfull comment was:\n{}" - .format(line, comment)) + raise ValueError( + f"unrecognized line in comment: {line!r}\n\nfull comment was:\n{comment}" + ) comment_info.desc = desc @@ -257,65 +274,58 @@ def get_opcodes(dir): m2 = stack_pat.search(stack) if m2: - comment_info.stack_uses = m2.group('uses') - comment_info.stack_defs = m2.group('defs') + comment_info.stack_uses = m2.group("uses") + comment_info.stack_defs = m2.group("defs") else: assert op is not None opcode = OpcodeInfo(next_opcode_value, comment_info) next_opcode_value += 1 opcode.op = op - opcode.op_snake = m.group('op_snake') - opcode.token = parse_name(m.group('token')) - opcode.length = m.group('length') - opcode.nuses = m.group('nuses') - opcode.ndefs = m.group('ndefs') - opcode.format_ = m.group('format').split('|') - - expected_snake = re.sub(r'(?