From 2259f1816055f3014f23468b3d62756257a64650 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Sat, 19 Jul 2025 12:27:33 -0700 Subject: [PATCH 1/2] 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'(? Date: Mon, 26 Jan 2026 14:36:36 -0800 Subject: [PATCH 2/2] Change version from ESR 140.0 to ESR 140.7.0 in doc ESR 140.0 may not be usable due to this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1973993 which is fixed in ESR 140.7.0. --- docs/Building SpiderMonkey.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Building SpiderMonkey.md b/docs/Building SpiderMonkey.md index 5ef62ee..3aea60c 100644 --- a/docs/Building SpiderMonkey.md +++ b/docs/Building SpiderMonkey.md @@ -25,7 +25,7 @@ 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 140, which contains the source for SpiderMonkey ESR 140, can be found here: -https://ftp.mozilla.org/pub/firefox/releases/140.0esr/source/ +https://ftp.mozilla.org/pub/firefox/releases/140.7.0esr/source/ The ESR releases have a major release approximately once a year with security patches released throughout the year.