author | Phil Ringnalda <philringnalda@gmail.com> |
Sun, 26 Feb 2017 10:48:26 -0800 | |
changeset 344978 | 7ef1e9abd296a8edc39b7efc8d637767ba2f77ed |
parent 344977 | aa2b14b08dbb728ab6b9868e43a66842626f1833 (current diff) |
parent 344976 | 712e84866cf557b5ed88c7b991dd508ec3d550ef (diff) |
child 344979 | 8e332422e57c39ee72bc7493875fad72f23dc640 |
child 345001 | 99239977e6c3a9428a037a9b1fd20f832adc5b09 |
push id | 37994 |
push user | philringnalda@gmail.com |
push date | Sun, 26 Feb 2017 18:59:10 +0000 |
treeherder | autoland@8e332422e57c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 54.0a1 |
first release with | nightly mac
7ef1e9abd296
/
54.0a1
/
20170227030203
/
files
nightly win32
7ef1e9abd296
/
54.0a1
/
20170227030203
/
files
nightly win64
7ef1e9abd296
/
54.0a1
/
20170227030203
/
files
nightly linux32
nightly linux64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly mac
54.0a1
/
20170227030203
/
pushlog to previous
nightly win32
54.0a1
/
20170227030203
/
pushlog to previous
nightly win64
54.0a1
/
20170227030203
/
pushlog to previous
|
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp +++ b/accessible/windows/msaa/DocAccessibleWrap.cpp @@ -122,16 +122,20 @@ DocAccessibleWrap::Shutdown() //////////////////////////////////////////////////////////////////////////////// // DocAccessible public void* DocAccessibleWrap::GetNativeWindow() const { if (XRE_IsContentProcess()) { DocAccessibleChild* ipcDoc = IPCDoc(); + if (!ipcDoc) { + return nullptr; + } + HWND hWnd = ipcDoc->GetEmulatedWindowHandle(); if (hWnd) { return hWnd; } auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager()); MOZ_ASSERT(tab); if (!tab) {
--- a/devtools/client/inspector/layout/components/App.js +++ b/devtools/client/inspector/layout/components/App.js @@ -27,16 +27,17 @@ const App = createClass({ propTypes: { boxModel: PropTypes.shape(Types.boxModel).isRequired, getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired, setSelectedNode: PropTypes.func.isRequired, showBoxModelProperties: PropTypes.bool.isRequired, + showGridOutline: PropTypes.bool.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelEditor: PropTypes.func.isRequired, onShowBoxModelHighlighter: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, },
--- a/devtools/client/inspector/layout/components/Grid.js +++ b/devtools/client/inspector/layout/components/Grid.js @@ -4,72 +4,86 @@ "use strict"; const { addons, createClass, createFactory, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); const GridDisplaySettings = createFactory(require("./GridDisplaySettings")); const GridList = createFactory(require("./GridList")); +const GridOutline = createFactory(require("./GridOutline")); const Types = require("../types"); const { getStr } = require("../utils/l10n"); module.exports = createClass({ displayName: "Grid", propTypes: { getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired, setSelectedNode: PropTypes.func.isRequired, + showGridOutline: PropTypes.bool.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, }, mixins: [ addons.PureRenderMixin ], render() { let { getSwatchColorPickerTooltip, grids, highlighterSettings, setSelectedNode, + showGridOutline, onHideBoxModelHighlighter, onSetGridOverlayColor, onShowBoxModelHighlighterForNode, onToggleGridHighlighter, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, } = this.props; return grids.length ? dom.div( { id: "layout-grid-container", }, - GridList({ - getSwatchColorPickerTooltip, - grids, - setSelectedNode, - onHideBoxModelHighlighter, - onSetGridOverlayColor, - onShowBoxModelHighlighterForNode, - onToggleGridHighlighter, - }), - GridDisplaySettings({ - highlighterSettings, - onToggleShowGridLineNumbers, - onToggleShowInfiniteLines, - }) + showGridOutline ? + GridOutline({ + grids, + }) + : + null, + dom.div( + { + className: "grid-content", + }, + GridList({ + getSwatchColorPickerTooltip, + grids, + setSelectedNode, + onHideBoxModelHighlighter, + onSetGridOverlayColor, + onShowBoxModelHighlighterForNode, + onToggleGridHighlighter, + }), + GridDisplaySettings({ + highlighterSettings, + onToggleShowGridLineNumbers, + onToggleShowInfiniteLines, + }) + ) ) : dom.div( { className: "layout-no-grids", }, getStr("layout.noGrids") );
new file mode 100644 --- /dev/null +++ b/devtools/client/inspector/layout/components/GridOutline.js @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { addons, createClass, DOM: dom, PropTypes } = + require("devtools/client/shared/vendor/react"); + +const Types = require("../types"); + +// Move SVG grid to the right 100 units, so that it is not flushed against the edge of +// layout border +const TRANSLATE_X = -100; +const TRANSLATE_Y = 0; + +const VIEWPORT_HEIGHT = 100; +const VIEWPORT_WIDTH = 450; + +module.exports = createClass({ + + displayName: "GridOutline", + + propTypes: { + grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, + }, + + mixins: [ addons.PureRenderMixin ], + + getInitialState() { + return { + selectedGrids: [], + }; + }, + + componentWillReceiveProps({ grids }) { + this.setState({ + selectedGrids: grids.filter(grid => grid.highlighted), + }); + }, + + renderGridCell(columnNumber, rowNumber, color) { + return dom.rect( + { + className: "grid-outline-cell", + x: columnNumber, + y: rowNumber, + width: 10, + height: 10, + stroke: color, + } + ); + }, + + renderGridFragment({ color, gridFragments }) { + // TODO: We are drawing the first fragment since only one is currently being stored. + // In future we will need to iterate over all fragments of a grid. + const { rows, cols } = gridFragments[0]; + const numOfColLines = cols.lines.length - 1; + const numOfRowLines = rows.lines.length - 1; + const rectangles = []; + + // Draw a rectangle that acts as a border for the grid outline + const border = this.renderGridOutlineBorder(numOfRowLines, numOfColLines, color); + rectangles.push(border); + + let x = 1; + let y = 1; + const width = 10; + const height = 10; + + // Draw the cells within the grid outline border + for (let row = 0; row < numOfRowLines; row++) { + for (let col = 0; col < numOfColLines; col++) { + rectangles.push(this.renderGridCell(x, y, color)); + x += width; + } + + x = 1; + y += height; + } + + return rectangles; + }, + + renderGridOutline(gridFragments) { + return dom.g( + { + className: "grid-cell-group", + }, + gridFragments.map(gridFragment => this.renderGridFragment(gridFragment)) + ); + }, + + renderGridOutlineBorder(numberOfRows, numberOfCols, color) { + return dom.rect( + { + className: "grid-outline-border", + x: 1, + y: 1, + width: numberOfCols * 10, + height: numberOfRows * 10, + stroke: color, + } + ); + }, + + render() { + return this.state.selectedGrids.length ? + dom.svg( + { + className: "grid-outline", + width: "100%", + height: 100, + viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${VIEWPORT_WIDTH} ${VIEWPORT_HEIGHT}`, + }, + this.renderGridOutline(this.state.selectedGrids) + ) + : + null; + }, + +});
--- a/devtools/client/inspector/layout/components/moz.build +++ b/devtools/client/inspector/layout/components/moz.build @@ -7,9 +7,10 @@ DevToolsModules( 'Accordion.css', 'Accordion.js', 'App.js', 'Grid.js', 'GridDisplaySettings.js', 'GridItem.js', 'GridList.js', + 'GridOutline.js', )
--- a/devtools/client/inspector/layout/layout.js +++ b/devtools/client/inspector/layout/layout.js @@ -24,16 +24,17 @@ const { const App = createFactory(require("./components/App")); const { LocalizationHelper } = require("devtools/shared/l10n"); const INSPECTOR_L10N = new LocalizationHelper("devtools/client/locales/inspector.properties"); const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers"; +const SHOW_GRID_OUTLINE_PREF = "devtools.gridinspector.showGridOutline"; const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines"; // Default grid colors. const GRID_COLORS = [ "#05E4EE", "#BB9DFF", "#FFB53B", "#71F362", @@ -109,16 +110,22 @@ LayoutView.prototype = { }, /** * Shows the box model properties under the box model if true, otherwise, hidden by * default. */ showBoxModelProperties: true, + /** + * Shows the grid outline if user preferences are set to true, otherwise, hidden by + * default. + */ + showGridOutline: Services.prefs.getBoolPref(SHOW_GRID_OUTLINE_PREF), + onHideBoxModelHighlighter, /** * Handler for a change in the grid overlay color picker for a grid container. * * @param {NodeFront} node * The NodeFront of the grid container element for which the grid color is * being updated.
--- a/devtools/client/preferences/devtools.js +++ b/devtools/client/preferences/devtools.js @@ -69,16 +69,17 @@ pref("devtools.inspector.colorWidget.ena // Enable the Font Inspector pref("devtools.fontinspector.enabled", true); // Enable the Layout View pref("devtools.layoutview.enabled", false); // Grid highlighter preferences pref("devtools.gridinspector.showGridLineNumbers", false); +pref("devtools.gridinspector.showGridOutline", false); pref("devtools.gridinspector.showInfiniteLines", false); // By how many times eyedropper will magnify pixels pref("devtools.eyedropper.zoom", 6); // Enable to collapse attributes that are too long. pref("devtools.markup.collapseAttributes", true);
--- a/devtools/client/themes/layout.css +++ b/devtools/client/themes/layout.css @@ -35,20 +35,49 @@ } /** * Grid Container */ #layout-grid-container { display: flex; + flex-direction: column; margin: 5px; } /** + * Grid Content + */ + +.grid-content { + display: flex; + flex: 1; +} + +/** + * Grid Outline + */ + +#grid-outline { + margin: 5px auto; +} + +.grid-outline-border { + stroke-width: 0.75; + fill: none; +} + +.grid-outline-cell { + fill: none; + pointer-events: all; + stroke-dasharray: 0.5, 2; +} + +/** * Container when no grids are present */ .layout-no-grids { font-style: italic; text-align: center; padding: 0.5em; }
--- a/devtools/client/webconsole/test/browser.ini +++ b/devtools/client/webconsole/test/browser.ini @@ -176,16 +176,17 @@ skip-if = (os == 'linux' && bits == 32 & [browser_console_error_source_click.js] [browser_console_filters.js] [browser_console_iframe_messages.js] [browser_console_keyboard_accessibility.js] [browser_console_log_inspectable_object.js] [browser_console_native_getters.js] [browser_console_navigation_marker.js] [browser_console_netlogging.js] +skip-if = true # Bug 1298364 [browser_console_nsiconsolemessage.js] [browser_console_optimized_out_vars.js] [browser_console_private_browsing.js] skip-if = e10s # Bug 1042253 - webconsole e10s tests [browser_console_server_logging.js] [browser_console_variables_view.js] [browser_console_variables_view_filter.js] [browser_console_variables_view_dom_nodes.js]
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -2829,19 +2829,17 @@ nsGlobalWindow::SetNewDocument(nsIDocume AutoJSAPI jsapi; jsapi.Init(); JSContext *cx = jsapi.cx(); // Check if we're anywhere near the stack limit before we reach the // transplanting code, since it has no good way to handle errors. This uses // the untrusted script limit, which is not strictly necessary since no // actual script should run. - bool overrecursed = false; - JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true); - if (overrecursed) { + if (!js::CheckRecursionLimitConservativeDontReport(cx)) { NS_WARNING("Overrecursion in SetNewDocument"); return NS_ERROR_FAILURE; } if (!mDoc) { // First document load. // Get our private root. If it is equal to us, then we need to
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2114,17 +2114,19 @@ nsresult ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) { js::AssertSameCompartment(aCx, aObjArg); // Check if we're anywhere near the stack limit before we reach the // transplanting code, since it has no good way to handle errors. This uses // the untrusted script limit, which is not strictly necessary since no // actual script should run. - JS_CHECK_RECURSION_CONSERVATIVE(aCx, return NS_ERROR_FAILURE); + if (!js::CheckRecursionLimitConservative(aCx)) { + return NS_ERROR_FAILURE; + } JS::Rooted<JSObject*> aObj(aCx, aObjArg); const DOMJSClass* domClass = GetDOMClass(aObj); // DOM things are always parented to globals. JS::Rooted<JSObject*> oldParent(aCx, js::GetGlobalForObjectCrossCompartment(aObj)); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(oldParent) == oldParent);
--- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -109,17 +109,19 @@ js::obj_propertyIsEnumerable(JSContext* return true; } #if JS_HAS_TOSOURCE static bool obj_toSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - JS_CHECK_RECURSION(cx, return false); + + if (!CheckRecursionLimit(cx)) + return false; RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; JSString* str = ObjectToSource(cx, obj); if (!str) return false;
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2367,17 +2367,19 @@ ASTSerializer::classDefinition(ParseNode return optExpression(pn->pn_kid2, &heritage) && statement(pn->pn_kid3, &classBody) && builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst); } bool ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; + switch (pn->getKind()) { case PNK_FUNCTION: case PNK_VAR: return declaration(pn, dst); case PNK_LET: case PNK_CONST: return declaration(pn, dst); @@ -2806,17 +2808,19 @@ ASTSerializer::generatorExpression(Parse return expression(next->pn_kid->pn_left, &body) && builder.generatorExpression(body, blocks, filter, isLegacy, &pn->pn_pos, dst); } bool ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; + switch (pn->getKind()) { case PNK_FUNCTION: { ASTType type = pn->pn_funbox->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR; return function(pn, type, dst); } case PNK_COMMA: @@ -3369,17 +3373,19 @@ ASTSerializer::objectPattern(ParseNode* } return builder.objectPattern(elts, &pn->pn_pos, dst); } bool ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; + switch (pn->getKind()) { case PNK_OBJECT: return objectPattern(pn, dst); case PNK_ARRAY: return arrayPattern(pn, dst); default:
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -239,19 +239,19 @@ class LoopControl : public BreakableCont MOZ_ASSERT(is<LoopControl>()); LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing()); stackDepth_ = bce->stackDepth; loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; int loopSlots; - if (loopKind == StatementKind::Spread) + if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop) loopSlots = 3; - else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) + else if (loopKind == StatementKind::ForInLoop) loopSlots = 2; else loopSlots = 0; MOZ_ASSERT(loopSlots <= stackDepth_); if (enclosingLoop) { canIonOsr_ = (enclosingLoop->canIonOsr_ && @@ -273,74 +273,16 @@ class LoopControl : public BreakableCont MOZ_ASSERT(continueTarget.offset != -1); if (!patchBreaks(bce)) return false; bce->patchJumpsToTarget(continues, continueTarget); return true; } }; -class ForOfLoopControl : public LoopControl -{ - // The stack depth of the iterator. - int32_t iterDepth_; - - // for-of loops, when throwing from non-iterator code (i.e. from the body - // or from evaluating the LHS of the loop condition), need to call - // IteratorClose. If IteratorClose itself throws, we must not re-call - // IteratorClose. Since non-local jumps like break and return call - // IteratorClose, whenever a non-local jump is emitted, we must terminate - // the current JSTRY_ITERCLOSE note to skip the non-local jump code, then - // start a new one. - // - // Visually, - // - // for (x of y) { - // ... instantiate ForOfLoopControl - // ... + <-- iterCloseTryStart_ points to right before - // ... assignment to loop variable - // ... ^ - // ... | - // if (...) v - // + call finishIterCloseTryNote before |break| - // above range is noted with JSTRY_ITERCLOSE - // - // break; <-- break and IteratorClose are not inside - // JSTRY_ITERCLOSE note - // - // call startNewIterCloseTryNote after |break| - // + <-- next iterCloseTryStart_ points here - // ... | - // ... ~ - // } - ptrdiff_t iterCloseTryStart_; - - public: - ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth) - : LoopControl(bce, StatementKind::ForOfLoop), - iterDepth_(iterDepth), - iterCloseTryStart_(-1) - { - MOZ_ASSERT(bce->stackDepth >= iterDepth); - } - - MOZ_MUST_USE bool finishIterCloseTryNote(BytecodeEmitter* bce) { - ptrdiff_t end = bce->offset(); - MOZ_ASSERT(end >= iterCloseTryStart_); - if (end != iterCloseTryStart_) - return bce->tryNoteList.append(JSTRY_ITERCLOSE, iterDepth_, iterCloseTryStart_, end); - return true; - } - - void startNewIterCloseTryNote(BytecodeEmitter* bce) { - MOZ_ASSERT(bce->offset() > iterCloseTryStart_); - iterCloseTryStart_ = bce->offset(); - } -}; - class TryFinallyControl : public BytecodeEmitter::NestableControl { bool emittingSubroutine_; public: // The subroutine when emitting a finally block. JumpList gosubs; @@ -2052,16 +1994,154 @@ class MOZ_STACK_CLASS IfThenElseEmitter } int32_t popped() const { return -pushed_; } #endif }; +class ForOfLoopControl : public LoopControl +{ + // The stack depth of the iterator. + int32_t iterDepth_; + + // for-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. This is done by enclosing non-iterator code with + // try-catch and call IteratorClose in `catch` block. + // If IteratorClose itself throws, we must not re-call IteratorClose. Since + // non-local jumps like break and return call IteratorClose, whenever a + // non-local jump is emitted, we must tell catch block not to perform + // IteratorClose. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, clear iterator on the stack to tell + // // catch block not to perform IteratorClose. + // tmpIterator = iterator; + // iterator = undefined; + // IteratorClose(tmpIterator, { break }); + // break; + // } + // ... + // } catch (e) { + // // Just throw again when iterator is cleared by non-local jump. + // if (iterator === undefined) + // throw e; + // IteratorClose(iterator, { throw, e }); + // } + // } + Maybe<TryEmitter> tryCatch_; + + bool allowSelfHosted_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + allowSelfHosted_(allowSelfHosted) + { + } + + bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal); + + if (!tryCatch_->emitTry()) + return false; + return true; + } + + bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { + if (!tryCatch_->emitCatch()) // ITER ... + return false; + + if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION + return false; + unsigned slotFromTop = bce->stackDepth - iterDepth_; + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + + // If ITER is undefined, it means the exception is thrown by + // IteratorClose for non-local jump, and we should't perform + // IteratorClose again here. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF + return false; + if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE + return false; + + IfThenElseEmitter ifIteratorIsNotClosed(bce); + if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION + return false; + + MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_)); + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION + return false; + + if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION + return false; + + if (!bce->emit1(JSOP_THROW)) // ITER ... + return false; + + if (!tryCatch_->emitEnd()) + return false; + + tryCatch_.reset(); + return true; + } + + bool emitIteratorClose(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + return bce->emitIteratorClose(completionKind, allowSelfHosted_); + } + + bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { + // Pop unnecessary values from the stack. Effectively this means + // leaving try-catch block. However, the performing IteratorClose can + // reach the depth for try-catch, and effectively re-enter the + // try-catch block. + if (!bce->emit1(JSOP_POP)) // ITER RESULT + return false; + if (!bce->emit1(JSOP_POP)) // ITER + return false; + + // Clear ITER slot on the stack to tell catch block to avoid performing + // IteratorClose again. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF + return false; + if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER + return false; + + if (!emitIteratorClose(bce)) // UNDEF + return false; + + if (isTarget) { + // At the level of the target block, there's bytecode after the + // loop that will pop the iterator and the value, so push + // undefineds to balance the stack. + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF + return false; + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF + return false; + } else { + if (!bce->emit1(JSOP_POP)) // + return false; + } + + return true; + } +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc, HandleScript script, Handle<LazyScript*> lazyScript, uint32_t lineNum, EmitterMode emitterMode) : sc(sc), cx(sc->context), parent(parent), script(cx, script), @@ -2410,16 +2490,22 @@ BytecodeEmitter::emitDupAt(unsigned slot } bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } +bool +BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) +{ + return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind)); +} + static inline unsigned LengthOfSetLine(unsigned line) { return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); } /* Updates line number notes, not column notes. */ bool @@ -2651,20 +2737,18 @@ NonLocalExitControl::leaveScope(Bytecode bool NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target) { using NestableControl = BytecodeEmitter::NestableControl; using EmitterScope = BytecodeEmitter::EmitterScope; EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; - bool hasForOfLoopsWithIteratorClose = false; - - // IteratorClose is handled specially in the exception unwinder. For - // 'continue', 'break', and 'return' statements, emit IteratorClose + + // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->flushPops(&npops)) return false; @@ -2691,41 +2775,32 @@ NonLocalExitControl::prepareForNonLocalJ /* * There's a [exception or hole, retsub pc-index] pair and the * possible return value on the stack that we need to pop. */ npops += 3; } else { if (!flushPops(bce_)) return false; - if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) + if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ... return false; } break; } case StatementKind::ForOfLoop: - if (!flushPops(bce_)) - return false; - - // The iterator and the current value are on the stack. - // if (emitIteratorClose) { - hasForOfLoopsWithIteratorClose = true; - if (!control->as<ForOfLoopControl>().finishIterCloseTryNote(bce_)) + if (!flushPops(bce_)) return false; - if (!bce_->emit1(JSOP_POP)) // ... ITER - return false; - if (!bce_->emitIteratorClose()) // ... + + ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... return false; } else { - if (!bce_->emit1(JSOP_POP)) // ... ITER - return false; - if (!bce_->emit1(JSOP_POP)) // ... - return false; + npops += 3; } break; case StatementKind::ForInLoop: if (!flushPops(bce_)) return false; // The iterator and the current value are on the stack. @@ -2738,51 +2813,28 @@ NonLocalExitControl::prepareForNonLocalJ default: break; } } if (!flushPops(bce_)) return false; - if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget) { - hasForOfLoopsWithIteratorClose = true; - if (!target->as<ForOfLoopControl>().finishIterCloseTryNote(bce_)) - return false; - - // The iterator and the current value are on the stack. At the level - // of the target block, there's bytecode after the loop that will pop - // the iterator and the value, so duplicate the iterator and call - // IteratorClose. - if (!bce_->emitDupAt(1)) // ... ITER RESULT ITER - return false; - if (!bce_->emitIteratorClose()) // ... ITER RESULT + if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { + ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF return false; } EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } - // See comment in ForOfLoopControl. - if (hasForOfLoopsWithIteratorClose) { - for (NestableControl* control = bce_->innermostNestableControl; - control != target; - control = control->enclosing()) - { - if (control->is<ForOfLoopControl>()) - control->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_); - } - - if (target && target->is<ForOfLoopControl>() && emitIteratorCloseAtTarget) - target->as<ForOfLoopControl>().startNewIterCloseTryNote(bce_); - } - return true; } } // anonymous namespace bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { @@ -2986,17 +3038,18 @@ BytecodeEmitter::strictifySetNameOp(JSOp default:; } return op; } bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; restart: switch (pn->getKind()) { // Trivial cases with no side effects. case PNK_NOP: case PNK_STRING: case PNK_TEMPLATE_STRING: @@ -5176,17 +5229,17 @@ BytecodeEmitter::emitSetOrInitializeDest if (!emit1(JSOP_POP)) return false; } return true; } bool -BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) +BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); if (!emit1(JSOP_DUP)) // ... ITER ITER return false; if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // ... ITER NEXT @@ -5197,17 +5250,18 @@ BytecodeEmitter::emitIteratorNext(ParseN return false; if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT return false; checkTypeSet(JSOP_CALL); return true; } bool -BytecodeEmitter::emitIteratorClose(bool allowSelfHosted) +BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); // Generate inline logic corresponding to IteratorClose (ES 7.4.6). // // Callers need to ensure that the iterator object is at the top of the @@ -5230,27 +5284,96 @@ BytecodeEmitter::emitIteratorClose(bool return false; if (!emit1(JSOP_UNDEFINED)) // ... ITER RET RET UNDEFINED return false; if (!emit1(JSOP_NE)) // ... ITER RET ?NEQL return false; if (!ifReturnMethodIsDefined.emitIfElse()) return false; + if (completionKind == CompletionKind::Throw) { + // 7.4.6 IteratorClose ( iterator, completion ) + // ... + // 3. Let return be ? GetMethod(iterator, "return"). + // 4. If return is undefined, return Completion(completion). + // 5. Let innerResult be Call(return, iterator, « »). + // 6. If completion.[[Type]] is throw, return Completion(completion). + // 7. If innerResult.[[Type]] is throw, return + // Completion(innerResult). + // + // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET + // is callable, and throws if not. Since step 6 doesn't match and + // error handling in step 3 and step 7 can be merged. + // + // For CompletionKind::Throw case, an error thrown by JSOP_CALL for + // step 5 is ignored by try-catch. So we should check if RET is + // callable here, outside of try-catch, and the throw immediately if + // not. + CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; + if (!emitCheckIsCallable(kind)) // ... ITER RET + return false; + } + // Steps 5, 8. // // Call "return" if it is not undefined or null, and check that it returns // an Object. if (!emit1(JSOP_SWAP)) // ... RET ITER return false; - if (!emitCall(JSOP_CALL, 0)) // ... RESULT + + Maybe<TryEmitter> tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + + // Mutate stack to balance stack for try-catch. + if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF + return false; + if (!tryCatch->emitTry()) // ... RET ITER UNDEF + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER + return false; + } + + if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT return false; checkTypeSet(JSOP_CALL); - if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT - return false; + + if (completionKind == CompletionKind::Throw) { + if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitCatch()) // ... RET ITER RESULT + return false; + + // Just ignore the exception thrown by call. + if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; + + if (!tryCatch->emitEnd()) // ... RET ITER RESULT + return false; + + // Restore stack. + if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER + return false; + if (!emit1(JSOP_POP)) // ... RESULT RET + return false; + if (!emit1(JSOP_POP)) // ... RESULT + return false; + } else { + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; + } if (!ifReturnMethodIsDefined.emitElse()) return false; if (!emit1(JSOP_POP)) // ... ITER return false; if (!ifReturnMethodIsDefined.emitEnd()) return false; @@ -6797,60 +6920,73 @@ BytecodeEmitter::emitForOf(ParseNode* fo { MOZ_ASSERT(forOfLoop->isKind(PNK_FOR)); MOZ_ASSERT(forOfLoop->isArity(PN_BINARY)); ParseNode* forOfHead = forOfLoop->pn_left; MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); + ParseNode* forHeadExpr = forOfHead->pn_kid3; + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + bool allowSelfHostedIter = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(PNK_CALL) && + forHeadExpr->pn_head->name() == cx->names().allowContentIter) + { + allowSelfHostedIter = true; + } + // Evaluate the expression being iterated. - ParseNode* forHeadExpr = forOfHead->pn_kid3; if (!emitTree(forHeadExpr)) // ITERABLE return false; if (!emitIterator()) // ITER return false; int32_t iterDepth = stackDepth; - // For-of loops have both the iterator and the value on the stack. Push - // undefined to balance the stack. + // For-of loops have both the iterator, the result, and the result.value + // on the stack. Push undefineds to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; - - ForOfLoopControl loopInfo(this, iterDepth); + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false; JumpList initialJump; - if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT UNDEF return false; JumpTarget top{ -1 }; - if (!emitLoopHead(nullptr, &top)) // ITER RESULT + if (!emitLoopHead(nullptr, &top)) // ITER RESULT UNDEF return false; // If the loop had an escaping lexical declaration, replace the current // environment with an dead zoned one to implement TDZ semantics. if (headLexicalEmitterScope) { // The environment chain only includes an environment for the for-of // loop head *if* a scope binding is captured, thereby requiring // recreation each iteration. If a lexical scope exists for the head, // it must be the innermost one. If that scope has closed-over // bindings inducing an environment, recreate the current environment. DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1; MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST)); MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { - if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT UNDEF return false; } // For uncaptured bindings, put them back in TDZ. if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) return false; } @@ -6860,86 +6996,91 @@ BytecodeEmitter::emitForOf(ParseNode* fo #ifdef DEBUG auto loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. // // Note that ES 13.7.5.13, step 5.c says getting result.value does not // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; - loopInfo.startNewIterCloseTryNote(this); + if (!loopInfo.emitBeginCodeNeedingIteratorClose(this)) + return false; if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; - if (!emit1(JSOP_POP)) // ITER RESULT - return false; - MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); + // Remove VALUE from the stack to release it. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; - if (!emitTree(forBody)) // ITER RESULT - return false; - - if (!loopInfo.finishIterCloseTryNote(this)) + if (!emitTree(forBody)) // ITER RESULT UNDEF + return false; + + MOZ_ASSERT(stackDepth == loopDepth, + "the stack must be balanced around the for-of body"); + + if (!loopInfo.emitEndCodeNeedingIteratorClose(this)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; - if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT - return false; - - if (!emit1(JSOP_POP)) // ITER - return false; - if (!emit1(JSOP_DUP)) // ITER ITER - return false; - - // Certain builtins (e.g. Array.from) are implemented in self-hosting - // as for-of loops. - bool allowSelfHostedIter = false; - if (emitterMode == BytecodeEmitter::SelfHosting && - forHeadExpr->isKind(PNK_CALL) && - forHeadExpr->pn_head->name() == cx->names().allowContentIter) - { - allowSelfHostedIter = true; - } - - if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER RESULT - return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT - return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT UNDEF + return false; + + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_POP)) // ITER UNDEF + return false; + if (!emitDupAt(1)) // ITER UNDEF ITER + return false; + + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT + return false; + + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF + return false; + + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) - return false; // ITER RESULT + return false; // ITER RESULT UNDEF MOZ_ASSERT(this->stackDepth == loopDepth); } // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forInLoop->isKind(PNK_FOR)); MOZ_ASSERT(forInLoop->isArity(PN_BINARY)); MOZ_ASSERT(forInLoop->isOp(JSOP_ITER)); @@ -7339,16 +7480,18 @@ BytecodeEmitter::emitComprehensionForOf( if (!emitTree(forHeadExpr)) // EXPR return false; if (!emitIterator()) // ITER return false; // Push a dummy result so that we properly enter iteration midstream. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT VALUE + return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name // assigned to is a plain assignment. TDZCheckCache tdzCache(this); Maybe<EmitterScope> emitterScope; ParseNode* loopVariableName; if (lexicalScope) { @@ -7376,52 +7519,69 @@ BytecodeEmitter::emitComprehensionForOf( if (!emitLoopHead(nullptr, &top)) return false; #ifdef DEBUG int loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; + + // Notice: Comprehension for-of doesn't perform IteratorClose, since it's + // not in the spec. + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE return false; + + // Remove VALUE from the stack to release it. if (!emit1(JSOP_POP)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; // The stack should be balanced around the assignment opcode sequence. MOZ_ASSERT(this->stackDepth == loopDepth); // Emit code for the loop body. - if (!emitTree(forBody)) - return false; + if (!emitTree(forBody)) // ITER RESULT UNDEF + return false; + + // The stack should be balanced around the assignment opcode sequence. + MOZ_ASSERT(this->stackDepth == loopDepth); // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, jmp)) return false; - if (!emit1(JSOP_POP)) // ITER - return false; - if (!emit1(JSOP_DUP)) // ITER ITER - return false; - if (!emitIteratorNext(forHead)) // ITER RESULT - return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT - return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_POP)) // ITER UNDEF + return false; + if (!emitDupAt(1)) // ITER UNDEF ITER + return false; + if (!emitIteratorNext(forHead)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF + return false; + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; JumpList beq; JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT UNDEF return false; MOZ_ASSERT(this->stackDepth == loopDepth); // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) return false; @@ -7433,17 +7593,17 @@ BytecodeEmitter::emitComprehensionForOf( if (emitterScope) { if (!emitterScope->leave(this)) return false; emitterScope.reset(); } // Pop the result and the iter. - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool BytecodeEmitter::emitComprehensionForIn(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); ParseNode* forHead = pn->pn_left; @@ -10103,17 +10263,18 @@ BytecodeEmitter::emitClass(ParseNode* pn MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); return true; } bool BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; EmitLevelManager elm(this); /* Emit notes to tell the current bytecode's source line number. However, a couple trees require special treatment; see the relevant emitter functions for details. */ if (emitLineNote == EMIT_LINENOTE && !ParseNodeRequiresSpecialLineNumberNotes(pn)) { if (!updateLineNumberNotes(pn->pn_pos.begin))
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -468,16 +468,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Helper to emit JSOP_DUPAT. The argument is the value's depth on the // JS stack, as measured from the top. MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop); // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); + // Helper to emit JSOP_CHECKISCALLABLE. + MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind kind); + // Emit a bytecode followed by an uint16 immediate operand stored in // big-endian order. MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); // Emit a bytecode followed by an uint32 immediate operand. MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand); // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. @@ -676,17 +679,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter // emitIterator expects the iterable to already be on the stack. // It will replace that stack value with the corresponding iterator MOZ_MUST_USE bool emitIterator(); // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); - MOZ_MUST_USE bool emitIteratorClose(bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); template <typename InnerEmitter> MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. // |pattern| is a lhs node of the default expression. If it's an
--- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -51,17 +51,18 @@ ListContainsHoistedDeclaration(JSContext // // THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the // specific context of deciding that |node|, as one arm of a PNK_IF controlled // by a constant condition, contains a declaration that forbids |node| being // completely eliminated as dead. static bool ContainsHoistedDeclaration(JSContext* cx, ParseNode* node, bool* result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; restart: // With a better-typed AST, we would have distinct parse node classes for // expressions and for statements and would characterize expressions with // ExpressionKind and statements with StatementKind. Perhaps someday. In // the meantime we must characterize every ParseNodeKind, even the // expression/sub-expression ones that, if we handle all statement kinds @@ -1630,17 +1631,18 @@ FoldName(JSContext* cx, ParseNode* node, return true; return Fold(cx, &node->pn_expr, parser, inGenexpLambda); } bool Fold(JSContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; ParseNode* pn = *pnp; switch (pn->getKind()) { case PNK_NOP: case PNK_REGEXP: case PNK_STRING: case PNK_TRUE:
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -3929,17 +3929,18 @@ Parser<ParseHandler>::maybeParseDirectiv } return true; } template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::statementList(YieldHandling yieldHandling) { - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); Node pn = handler.newStatementList(pos()); if (!pn) return null(); bool canHaveDirectives = pc->atBodyLevel(); if (canHaveDirectives) tokenStream.clearSawOctalEscape(); @@ -7158,17 +7159,18 @@ Parser<ParseHandler>::variableStatement( } template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::statement(YieldHandling yieldHandling) { MOZ_ASSERT(checkOptionsCalled); - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); switch (tt) { // BlockStatement[?Yield, ?Return] case TOK_LC: @@ -7357,17 +7359,18 @@ Parser<ParseHandler>::statement(YieldHan template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling, bool canHaveDirectives /* = false */) { MOZ_ASSERT(checkOptionsCalled); - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); switch (tt) { // BlockStatement[?Yield, ?Return] case TOK_LC: @@ -7832,17 +7835,18 @@ class AutoClearInDestructuringDecl template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandling, TripledotHandling tripledotHandling, PossibleError* possibleError /* = nullptr */, InvokedPrediction invoked /* = PredictUninvoked */) { - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); // It's very common at this point to have a "detectably simple" expression, // i.e. a name/number/string token followed by one of the following tokens // that obviously isn't part of an expression: , ; : ) ] } // // (In Parsemark this happens 81.4% of the time; in code with large // numeric arrays, such as some Kraken benchmarks, it happens more often.) // @@ -8162,17 +8166,18 @@ Parser<ParseHandler>::unaryOpExpr(YieldH } template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, PossibleError* possibleError /* = nullptr */, InvokedPrediction invoked /* = PredictUninvoked */) { - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); uint32_t begin = pos().begin; switch (tt) { case TOK_VOID: return unaryOpExpr(yieldHandling, PNK_VOID, JSOP_VOID, begin); @@ -8475,17 +8480,18 @@ Parser<ParseHandler>::comprehensionIf(Ge return handler.newIfStatement(begin, cond, then, null()); } template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::comprehensionTail(GeneratorKind comprehensionKind) { - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); bool matched; if (!tokenStream.matchToken(&matched, TOK_FOR, TokenStream::Operand)) return null(); if (matched) return comprehensionFor(comprehensionKind); if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand)) @@ -8671,17 +8677,18 @@ Parser<ParseHandler>::memberExpr(YieldHa TokenKind tt, bool allowCallSyntax /* = true */, PossibleError* possibleError /* = nullptr */, InvokedPrediction invoked /* = PredictUninvoked */) { MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); Node lhs; - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); /* Check for new expression first. */ if (tt == TOK_NEW) { uint32_t newBegin = pos().begin; // Make sure this wasn't a |new.target| in disguise. Node newTarget; if (!tryNewTarget(newTarget)) return null(); @@ -9670,17 +9677,18 @@ Parser<ParseHandler>::tryNewTarget(Node template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, TokenKind tt, PossibleError* possibleError, InvokedPrediction invoked /* = PredictUninvoked */) { MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); - JS_CHECK_RECURSION(context, return null()); + if (!CheckRecursionLimit(context)) + return null(); switch (tt) { case TOK_FUNCTION: return functionExpr(invoked); case TOK_CLASS: return classDefinition(yieldHandling, ClassExpression, NameRequired);
--- a/js/src/irregexp/RegExpEngine.cpp +++ b/js/src/irregexp/RegExpEngine.cpp @@ -1215,17 +1215,20 @@ LoopChoiceNode::FilterASCII(int depth, b } // ------------------------------------------------------------------- // Analysis void Analysis::EnsureAnalyzed(RegExpNode* that) { - JS_CHECK_RECURSION(cx, failASCII("Stack overflow"); return); + if (!CheckRecursionLimit(cx)) { + failASCII("Stack overflow"); + return; + } if (that->info()->been_analyzed || that->info()->being_analyzed) return; that->info()->being_analyzed = true; that->Accept(this); that->info()->being_analyzed = false; that->info()->been_analyzed = true; } @@ -2491,17 +2494,21 @@ BoyerMooreLookahead::EmitSkipInstruction masm->Bind(&cont); return true; } bool BoyerMooreLookahead::CheckOverRecursed() { - JS_CHECK_RECURSION(compiler()->cx(), compiler()->SetRegExpTooBig(); return false); + if (!CheckRecursionLimit(compiler()->cx())) { + compiler()->SetRegExpTooBig(); + return false; + } + return true; } // ------------------------------------------------------------------- // Trace bool Trace::DeferredAction::Mentions(int that) {
--- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -512,23 +512,16 @@ HasLiveStackValueAtDepth(JSScript* scrip case JSTRY_FOR_OF: // For-of loops have both the iterator and the result object on // stack. The iterator is below the result object. if (stackDepth == tn->stackDepth - 1) return true; break; - case JSTRY_ITERCLOSE: - // Code that need to call IteratorClose have the iterator on the - // stack. - if (stackDepth == tn->stackDepth) - return true; - break; - case JSTRY_DESTRUCTURING_ITERCLOSE: // Destructuring code that need to call IteratorClose have both // the iterator and the "done" value on the stack. if (stackDepth == tn->stackDepth || stackDepth == tn->stackDepth - 1) return true; break; default: @@ -1662,17 +1655,18 @@ jit::BailoutIonToBaseline(JSContext* cx, // Do stack check. bool overRecursed = false; BaselineBailoutInfo *info = builder.info(); uint8_t* newsp = info->incomingStack - (info->copyStackTop - info->copyStackBottom); #ifdef JS_SIMULATOR if (Simulator::Current()->overRecursed(uintptr_t(newsp))) overRecursed = true; #else - JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, newsp, overRecursed = true); + if (!CheckRecursionLimitWithStackPointerDontReport(cx, newsp)) + overRecursed = true; #endif if (overRecursed) { JitSpew(JitSpew_BaselineBailouts, " Overrecursion check failed!"); return BAILOUT_RETURN_OVERRECURSED; } // Take the reconstructed baseline stack so it doesn't get freed when builder destructs. info = builder.takeBuffer();
--- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1383,16 +1383,36 @@ BaselineCompiler::emit_JSOP_CHECKISOBJ() pushArg(Imm32(GET_UINT8(pc))); if (!callVM(ThrowCheckIsObjectInfo)) return false; masm.bind(&ok); return true; } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable"); + +bool +BaselineCompiler::emit_JSOP_CHECKISCALLABLE() +{ + frame.syncStack(0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + prepareVMCall(); + + pushArg(Imm32(GET_UINT8(pc))); + pushArg(R0); + if (!callVM(CheckIsCallableInfo)) + return false; + + return true; +} + typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); static const VMFunction ThrowUninitializedThisInfo = FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis, "BaselineThrowUninitializedThis"); bool BaselineCompiler::emit_JSOP_CHECKTHIS() {
--- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -213,16 +213,17 @@ namespace jit { _(JSOP_CALLEE) \ _(JSOP_GETRVAL) \ _(JSOP_SETRVAL) \ _(JSOP_RETRVAL) \ _(JSOP_RETURN) \ _(JSOP_FUNCTIONTHIS) \ _(JSOP_GLOBALTHIS) \ _(JSOP_CHECKISOBJ) \ + _(JSOP_CHECKISCALLABLE) \ _(JSOP_CHECKTHIS) \ _(JSOP_CHECKRETURN) \ _(JSOP_NEWTARGET) \ _(JSOP_SUPERCALL) \ _(JSOP_SPREADSUPERCALL) \ _(JSOP_THROWSETCONST) \ _(JSOP_THROWSETALIASEDCONST) \ _(JSOP_THROWSETCALLEE) \
--- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -108,19 +108,21 @@ CheckFrame(InterpreterFrame* fp) static JitExecStatus EnterBaseline(JSContext* cx, EnterJitData& data) { if (data.osrFrame) { // Check for potential stack overflow before OSR-ing. uint8_t spDummy; uint32_t extra = BaselineFrame::Size() + (data.osrNumStackValues * sizeof(Value)); uint8_t* checkSp = (&spDummy) - extra; - JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return JitExec_Aborted); + if (!CheckRecursionLimitWithStackPointer(cx, checkSp)) + return JitExec_Aborted; } else { - JS_CHECK_RECURSION(cx, return JitExec_Aborted); + if (!CheckRecursionLimit(cx)) + return JitExec_Aborted; } #ifdef DEBUG // Assert we don't GC before entering JIT code. A GC could discard JIT code // or move the function stored in the CalleeToken (it won't be traced at // this point). We use Maybe<> here so we can call reset() to call the // AutoAssertNoGC destructor before we enter JIT code. mozilla::Maybe<JS::AutoAssertNoGC> nogc;
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11529,49 +11529,75 @@ class OutOfLineIsCallable : public OutOf void accept(CodeGenerator* codegen) { codegen->visitOutOfLineIsCallable(this); } LIsCallable* ins() const { return ins_; } }; -void -CodeGenerator::visitIsCallable(LIsCallable* ins) -{ - Register object = ToRegister(ins->object()); - Register output = ToRegister(ins->output()); - - OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); - addOutOfLineCode(ool, ins->mir()); - +template <CodeGenerator::CallableOrConstructor mode> +void +CodeGenerator::emitIsCallableOrConstructor(Register object, Register output, Label* failure) +{ Label notFunction, hasCOps, done; masm.loadObjClass(object, output); - // Just skim proxies off. Their notion of isCallable() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); + // Just skim proxies off. Their notion of isCallable()/isConstructor() is + // more complicated. + masm.branchTestClassIsProxy(true, output, failure); // An object is callable iff: // is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call). + // An object is constructor iff: + // ((is<JSFunction>() && as<JSFunction>().isConstructor) || + // (getClass()->cOps && getClass()->cOps->construct)). masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.move32(Imm32(1), output); + if (mode == Callable) { + masm.move32(Imm32(1), output); + } else { + Label notConstructor; + masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); + masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); + masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); + masm.move32(Imm32(1), output); + masm.jump(&done); + masm.bind(¬Constructor); + masm.move32(Imm32(0), output); + } masm.jump(&done); masm.bind(¬Function); masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)), ImmPtr(nullptr), &hasCOps); masm.move32(Imm32(0), output); masm.jump(&done); masm.bind(&hasCOps); masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, call)), + size_t opsOffset = mode == Callable + ? offsetof(js::ClassOps, call) + : offsetof(js::ClassOps, construct); + masm.cmpPtrSet(Assembler::NonZero, Address(output, opsOffset), ImmPtr(nullptr), output); masm.bind(&done); +} + +void +CodeGenerator::visitIsCallable(LIsCallable* ins) +{ + Register object = ToRegister(ins->object()); + Register output = ToRegister(ins->output()); + + OutOfLineIsCallable* ool = new(alloc()) OutOfLineIsCallable(ins); + addOutOfLineCode(ool, ins->mir()); + + emitIsCallableOrConstructor<Callable>(object, output, ool->entry()); + masm.bind(ool->rejoin()); } void CodeGenerator::visitOutOfLineIsCallable(OutOfLineIsCallable* ool) { LIsCallable* ins = ool->ins(); Register object = ToRegister(ins->object()); @@ -11581,16 +11607,46 @@ CodeGenerator::visitOutOfLineIsCallable( masm.setupUnalignedABICall(output); masm.passABIArg(object); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectIsCallable)); masm.storeCallBoolResult(output); restoreVolatile(output); masm.jump(ool->rejoin()); } +typedef bool (*CheckIsCallableFn)(JSContext*, HandleValue, CheckIsCallableKind); +static const VMFunction CheckIsCallableInfo = + FunctionInfo<CheckIsCallableFn>(CheckIsCallable, "CheckIsCallable"); + +void +CodeGenerator::visitCheckIsCallable(LCheckIsCallable* ins) +{ + ValueOperand checkValue = ToValue(ins, LCheckIsCallable::CheckValue); + Register temp = ToRegister(ins->temp()); + + // OOL code is used in the following 2 cases: + // * checkValue is not callable + // * checkValue is proxy and it's unknown whether it's callable or not + // CheckIsCallable checks if passed value is callable, regardless of the + // cases above. IsCallable operation is not observable and checking it + // again doesn't matter. + OutOfLineCode* ool = oolCallVM(CheckIsCallableInfo, ins, + ArgList(checkValue, Imm32(ins->mir()->checkKind())), + StoreNothing()); + + masm.branchTestObject(Assembler::NotEqual, checkValue, ool->entry()); + + Register object = masm.extractObject(checkValue, temp); + emitIsCallableOrConstructor<Callable>(object, temp, ool->entry()); + + masm.branchTest32(Assembler::Zero, temp, temp, ool->entry()); + + masm.bind(ool->rejoin()); +} + class OutOfLineIsConstructor : public OutOfLineCodeBase<CodeGenerator> { LIsConstructor* ins_; public: explicit OutOfLineIsConstructor(LIsConstructor* ins) : ins_(ins) { } @@ -11607,47 +11663,18 @@ void CodeGenerator::visitIsConstructor(LIsConstructor* ins) { Register object = ToRegister(ins->object()); Register output = ToRegister(ins->output()); OutOfLineIsConstructor* ool = new(alloc()) OutOfLineIsConstructor(ins); addOutOfLineCode(ool, ins->mir()); - Label notFunction, notConstructor, hasCOps, done; - masm.loadObjClass(object, output); - - // Just skim proxies off. Their notion of isConstructor() is more complicated. - masm.branchTestClassIsProxy(true, output, ool->entry()); - - // An object is constructor iff - // ((is<JSFunction>() && as<JSFunction>().isConstructor) || - // (getClass()->cOps && getClass()->cOps->construct)). - masm.branchPtr(Assembler::NotEqual, output, ImmPtr(&JSFunction::class_), ¬Function); - masm.load16ZeroExtend(Address(object, JSFunction::offsetOfFlags()), output); - masm.and32(Imm32(JSFunction::CONSTRUCTOR), output); - masm.branchTest32(Assembler::Zero, output, output, ¬Constructor); - masm.move32(Imm32(1), output); - masm.jump(&done); - masm.bind(¬Constructor); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(¬Function); - masm.branchPtr(Assembler::NonZero, Address(output, offsetof(js::Class, cOps)), - ImmPtr(nullptr), &hasCOps); - masm.move32(Imm32(0), output); - masm.jump(&done); - - masm.bind(&hasCOps); - masm.loadPtr(Address(output, offsetof(js::Class, cOps)), output); - masm.cmpPtrSet(Assembler::NonZero, Address(output, offsetof(js::ClassOps, construct)), - ImmPtr(nullptr), output); - - masm.bind(&done); + emitIsCallableOrConstructor<Constructor>(object, output, ool->entry()); + masm.bind(ool->rejoin()); } void CodeGenerator::visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool) { LIsConstructor* ins = ool->ins(); Register object = ToRegister(ins->object());
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -363,16 +363,22 @@ class CodeGenerator final : public CodeG void visitCallInstanceOf(LCallInstanceOf* ins); void visitGetDOMProperty(LGetDOMProperty* lir); void visitGetDOMMemberV(LGetDOMMemberV* lir); void visitGetDOMMemberT(LGetDOMMemberT* lir); void visitSetDOMProperty(LSetDOMProperty* lir); void visitCallDOMNative(LCallDOMNative* lir); void visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir); void visitCallBindVar(LCallBindVar* lir); + enum CallableOrConstructor { + Callable, + Constructor + }; + template <CallableOrConstructor mode> + void emitIsCallableOrConstructor(Register object, Register output, Label* failure); void visitIsCallable(LIsCallable* lir); void visitOutOfLineIsCallable(OutOfLineIsCallable* ool); void visitIsConstructor(LIsConstructor* lir); void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool); void visitIsObject(LIsObject* lir); void visitIsObjectAndBranch(LIsObjectAndBranch* lir); void visitHasClass(LHasClass* lir); void visitWasmParameter(LWasmParameter* lir); @@ -383,16 +389,17 @@ class CodeGenerator final : public CodeG void visitLexicalCheck(LLexicalCheck* ins); void visitThrowRuntimeLexicalError(LThrowRuntimeLexicalError* ins); void visitGlobalNameConflictsCheck(LGlobalNameConflictsCheck* ins); void visitDebugger(LDebugger* ins); void visitNewTarget(LNewTarget* ins); void visitArrowNewTarget(LArrowNewTarget* ins); void visitCheckReturn(LCheckReturn* ins); void visitCheckIsObj(LCheckIsObj* ins); + void visitCheckIsCallable(LCheckIsCallable* ins); void visitCheckObjCoercible(LCheckObjCoercible* ins); void visitDebugCheckSelfHosted(LDebugCheckSelfHosted* ins); void visitNaNToZero(LNaNToZero* ins); void visitOutOfLineNaNToZero(OutOfLineNaNToZero* ool); void visitCheckOverRecursed(LCheckOverRecursed* lir); void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool);
--- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2854,17 +2854,19 @@ jit::CanEnterUsingFastInvoke(JSContext* return Method_Skipped; return Method_Compiled; } static JitExecStatus EnterIon(JSContext* cx, EnterJitData& data) { - JS_CHECK_RECURSION(cx, return JitExec_Aborted); + if (!CheckRecursionLimit(cx)) + return JitExec_Aborted; + MOZ_ASSERT(jit::IsIonEnabled(cx)); MOZ_ASSERT(!data.osrFrame); #ifdef DEBUG // See comment in EnterBaseline. mozilla::Maybe<JS::AutoAssertNoGC> nogc; nogc.emplace(cx); #endif @@ -2988,17 +2990,18 @@ jit::IonCannon(JSContext* cx, RunState& state.setReturnValue(data.result); return status; } JitExecStatus jit::FastInvoke(JSContext* cx, HandleFunction fun, CallArgs& args) { - JS_CHECK_RECURSION(cx, return JitExec_Error); + if (!CheckRecursionLimit(cx)) + return JitExec_Error; RootedScript script(cx, fun->nonLazyScript()); if (!Debugger::checkNoExecute(cx, script)) return JitExec_Error; #ifdef DEBUG // See comment in EnterBaseline.
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2262,16 +2262,19 @@ IonBuilder::inspectOpcode(JSOp op) break; case JSOP_NEWTARGET: return jsop_newtarget(); case JSOP_CHECKISOBJ: return jsop_checkisobj(GET_UINT8(pc)); + case JSOP_CHECKISCALLABLE: + return jsop_checkiscallable(GET_UINT8(pc)); + case JSOP_CHECKOBJCOERCIBLE: return jsop_checkobjcoercible(); case JSOP_DEBUGCHECKSELFHOSTED: { #ifdef DEBUG MDebugCheckSelfHosted* check = MDebugCheckSelfHosted::New(alloc(), current->pop()); current->add(check); @@ -9415,16 +9418,25 @@ IonBuilder::jsop_checkisobj(uint8_t kind MCheckIsObj* check = MCheckIsObj::New(alloc(), current->pop(), kind); current->add(check); current->push(check); return Ok(); } AbortReasonOr<Ok> +IonBuilder::jsop_checkiscallable(uint8_t kind) +{ + MCheckIsCallable* check = MCheckIsCallable::New(alloc(), current->pop(), kind); + current->add(check); + current->push(check); + return Ok(); +} + +AbortReasonOr<Ok> IonBuilder::jsop_checkobjcoercible() { MDefinition* toCheck = current->peek(-1); if (!toCheck->mightBeType(MIRType::Undefined) && !toCheck->mightBeType(MIRType::Null)) { toCheck->setImplicitlyUsedUnchecked();
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -574,16 +574,17 @@ class IonBuilder AbortReasonOr<Ok> jsop_iterend(); AbortReasonOr<Ok> jsop_in(); AbortReasonOr<Ok> jsop_instanceof(); AbortReasonOr<Ok> jsop_getaliasedvar(EnvironmentCoordinate ec); AbortReasonOr<Ok> jsop_setaliasedvar(EnvironmentCoordinate ec); AbortReasonOr<Ok> jsop_debugger(); AbortReasonOr<Ok> jsop_newtarget(); AbortReasonOr<Ok> jsop_checkisobj(uint8_t kind); + AbortReasonOr<Ok> jsop_checkiscallable(uint8_t kind); AbortReasonOr<Ok> jsop_checkobjcoercible(); AbortReasonOr<Ok> jsop_pushcallobj(); /* Inlining. */ enum InliningStatus { InliningStatus_NotInlined,
--- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -326,17 +326,16 @@ NumArgAndLocalSlots(const InlineFrameIte JSScript* script = frame.script(); return CountArgSlots(script, frame.maybeCalleeTemplate()) + script->nfixed(); } static void CloseLiveIteratorIon(JSContext* cx, const InlineFrameIterator& frame, JSTryNote* tn) { MOZ_ASSERT(tn->kind == JSTRY_FOR_IN || - tn->kind == JSTRY_ITERCLOSE || tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE); bool isDestructuring = tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE; MOZ_ASSERT_IF(!isDestructuring, tn->stackDepth > 0); MOZ_ASSERT_IF(isDestructuring, tn->stackDepth > 1); SnapshotIterator si = frame.snapshotIterator(); @@ -437,17 +436,16 @@ HandleExceptionIon(JSContext* cx, const if (!script->hasTrynotes()) return; for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) { JSTryNote* tn = *tni; switch (tn->kind) { case JSTRY_FOR_IN: - case JSTRY_ITERCLOSE: case JSTRY_DESTRUCTURING_ITERCLOSE: MOZ_ASSERT_IF(tn->kind == JSTRY_FOR_IN, JSOp(*(script->main() + tn->start + tn->length)) == JSOP_ENDITER); CloseLiveIteratorIon(cx, frame, tn); break; case JSTRY_FOR_OF: case JSTRY_LOOP: @@ -638,29 +636,16 @@ ProcessTryNotesBaseline(JSContext* cx, c // ProcessTryNotes. SettleOnTryNote(cx, tn, frame, ei, rfe, pc); MOZ_ASSERT(**pc == JSOP_ENDITER); return false; } break; } - case JSTRY_ITERCLOSE: { - uint8_t* framePointer; - uint8_t* stackPointer; - BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); - Value iterValue(*reinterpret_cast<Value*>(stackPointer)); - RootedObject iterObject(cx, &iterValue.toObject()); - if (!IteratorCloseForException(cx, iterObject)) { - SettleOnTryNote(cx, tn, frame, ei, rfe, pc); - return false; - } - break; - } - case JSTRY_DESTRUCTURING_ITERCLOSE: { uint8_t* framePointer; uint8_t* stackPointer; BaselineFrameAndStackPointersFromTryNote(tn, frame, &framePointer, &stackPointer); RootedValue doneValue(cx, *(reinterpret_cast<Value*>(stackPointer))); bool done = ToBoolean(doneValue); if (!done) { Value iterValue(*(reinterpret_cast<Value*>(stackPointer) + 1));
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4782,16 +4782,29 @@ LIRGenerator::visitCheckIsObj(MCheckIsOb LCheckIsObj* lir = new(alloc()) LCheckIsObj(useBoxAtStart(checkVal)); redefine(ins, checkVal); add(lir, ins); assignSafepoint(lir, ins); } void +LIRGenerator::visitCheckIsCallable(MCheckIsCallable* ins) +{ + MDefinition* checkVal = ins->checkValue(); + MOZ_ASSERT(checkVal->type() == MIRType::Value); + + LCheckIsCallable* lir = new(alloc()) LCheckIsCallable(useBox(checkVal), + temp()); + redefine(ins, checkVal); + add(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitCheckObjCoercible(MCheckObjCoercible* ins) { MDefinition* checkVal = ins->checkValue(); MOZ_ASSERT(checkVal->type() == MIRType::Value); LCheckObjCoercible* lir = new(alloc()) LCheckObjCoercible(useBoxAtStart(checkVal)); redefine(ins, checkVal); add(lir, ins);
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -326,16 +326,17 @@ class LIRGenerator : public LIRGenerator void visitDebugger(MDebugger* ins); void visitNewTarget(MNewTarget* ins); void visitArrowNewTarget(MArrowNewTarget* ins); void visitNaNToZero(MNaNToZero *ins); void visitAtomicIsLockFree(MAtomicIsLockFree* ins); void visitGuardSharedTypedArray(MGuardSharedTypedArray* ins); void visitCheckReturn(MCheckReturn* ins); void visitCheckIsObj(MCheckIsObj* ins); + void visitCheckIsCallable(MCheckIsCallable* ins); void visitCheckObjCoercible(MCheckObjCoercible* ins); void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins); }; } // namespace jit } // namespace js #endif /* jit_Lowering_h */
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13511,18 +13511,19 @@ class MDebugger : public MNullaryInstruc }; class MCheckIsObj : public MUnaryInstruction, public BoxInputsPolicy::Data { uint8_t checkKind_; - explicit MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) - : MUnaryInstruction(toCheck), checkKind_(checkKind) + MCheckIsObj(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) { setResultType(MIRType::Value); setResultTypeSet(toCheck->resultTypeSet()); setGuard(); } public: INSTRUCTION_HEADER(CheckIsObj) @@ -13531,16 +13532,43 @@ class MCheckIsObj uint8_t checkKind() const { return checkKind_; } AliasSet getAliasSet() const override { return AliasSet::None(); } }; +class MCheckIsCallable + : public MUnaryInstruction, + public BoxInputsPolicy::Data +{ + uint8_t checkKind_; + + MCheckIsCallable(MDefinition* toCheck, uint8_t checkKind) + : MUnaryInstruction(toCheck), + checkKind_(checkKind) + { + setResultType(MIRType::Value); + setResultTypeSet(toCheck->resultTypeSet()); + setGuard(); + } + + public: + INSTRUCTION_HEADER(CheckIsCallable) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, checkValue)) + + uint8_t checkKind() const { return checkKind_; } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + class MCheckObjCoercible : public MUnaryInstruction, public BoxInputsPolicy::Data { explicit MCheckObjCoercible(MDefinition* toCheck) : MUnaryInstruction(toCheck) { setGuard();
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -282,16 +282,17 @@ namespace jit { _(LexicalCheck) \ _(ThrowRuntimeLexicalError) \ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSNeg) \ _(AsmJSLoadHeap) \ _(AsmJSStoreHeap) \ _(AsmJSCompareExchangeHeap) \ _(AsmJSAtomicExchangeHeap) \ _(AsmJSAtomicBinopHeap) \
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -118,27 +118,41 @@ bool InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs, uint32_t numFormalArgs, Value* argv, MutableHandleValue rval) { MOZ_ASSERT(numFormalArgs > numActualArgs); argv[1 + numActualArgs] = argv[1 + numFormalArgs]; return InvokeFunction(cx, obj, true, numActualArgs, argv, rval); } +#ifdef JS_SIMULATOR +static bool +CheckSimulatorRecursionLimitWithExtra(JSContext* cx, uint32_t extra) +{ + if (cx->simulator()->overRecursedWithExtra(extra)) { + ReportOverRecursed(cx); + return false; + } + return true; +} +#endif + bool CheckOverRecursed(JSContext* cx) { // We just failed the jitStackLimit check. There are two possible reasons: // - jitStackLimit was the real stack limit and we're over-recursed // - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt // and we need to call JSRuntime::handleInterrupt. #ifdef JS_SIMULATOR - JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false); + if (!CheckSimulatorRecursionLimitWithExtra(cx, 0)) + return false; #else - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; #endif gc::MaybeVerifyBarriers(cx); return cx->handleInterrupt(); } // This function can get called in two contexts. In the usual context, it's // called with earlyCheck=false, after the env chain has been initialized on // a baseline frame. In this case, it's ok to throw an exception, so a failed @@ -157,32 +171,36 @@ CheckOverRecursedWithExtra(JSContext* cx // See |CheckOverRecursed| above. This is a variant of that function which // accepts an argument holding the extra stack space needed for the Baseline // frame that's about to be pushed. uint8_t spDummy; uint8_t* checkSp = (&spDummy) - extra; if (earlyCheck) { #ifdef JS_SIMULATOR (void)checkSp; - JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, frame->setOverRecursed()); + if (!CheckSimulatorRecursionLimitWithExtra(cx, extra)) + frame->setOverRecursed(); #else - JS_CHECK_RECURSION_WITH_SP(cx, checkSp, frame->setOverRecursed()); + if (!CheckRecursionLimitWithStackPointer(cx, checkSp)) + frame->setOverRecursed(); #endif return true; } // The OVERRECURSED flag may have already been set on the frame by an // early over-recursed check. If so, throw immediately. if (frame->overRecursed()) return false; #ifdef JS_SIMULATOR - JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, return false); + if (!CheckSimulatorRecursionLimitWithExtra(cx, extra)) + return false; #else - JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false); + if (!CheckRecursionLimitWithStackPointer(cx, checkSp)) + return false; #endif gc::MaybeVerifyBarriers(cx); return cx->handleInterrupt(); } JSObject* BindVar(JSContext* cx, HandleObject envChain) @@ -1478,10 +1496,19 @@ EqualStringsHelper(JSString* str1, JSStr JSLinearString* str2Linear = str2->ensureLinear(nullptr); if (!str2Linear) return false; return EqualChars(&str1->asLinear(), str2Linear); } +bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind) +{ + if (!IsCallable(v)) + return ThrowCheckIsCallable(cx, kind); + + return true; +} + } // namespace jit } // namespace js
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -8,16 +8,17 @@ #define jit_VMFunctions_h #include "mozilla/Attributes.h" #include "jspubtd.h" #include "jit/CompileInfo.h" #include "jit/JitFrames.h" +#include "vm/Interpreter.h" namespace js { class NamedLambdaObject; class WithScope; class InlineTypedObject; class GeneratorObject; class TypedArrayObject; @@ -833,12 +834,15 @@ CallNativeGetter(JSContext* cx, HandleFu MOZ_MUST_USE bool CallNativeSetter(JSContext* cx, HandleFunction callee, HandleObject obj, HandleValue rhs); MOZ_MUST_USE bool EqualStringsHelper(JSString* str1, JSString* str2); +MOZ_MUST_USE bool +CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind); + } // namespace jit } // namespace js #endif /* jit_VMFunctions_h */
--- a/js/src/jit/arm/Simulator-arm.h +++ b/js/src/jit/arm/Simulator-arm.h @@ -526,22 +526,14 @@ class SimulatorProcess } static void setRedirection(js::jit::Redirection* redirection) { MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread()); singleton_->redirection_ = redirection; } }; -#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \ - JS_BEGIN_MACRO \ - if (cx->simulator()->overRecursedWithExtra(extra)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - } // namespace jit } // namespace js #endif /* JS_SIMULATOR_ARM */ #endif /* jit_arm_Simulator_arm_h */
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h +++ b/js/src/jit/arm64/vixl/Simulator-vixl.h @@ -41,24 +41,16 @@ #include "jit/arm64/vixl/Instructions-vixl.h" #include "jit/arm64/vixl/Instrument-vixl.h" #include "jit/arm64/vixl/Simulator-Constants-vixl.h" #include "jit/arm64/vixl/Utils-vixl.h" #include "jit/IonTypes.h" #include "vm/MutexIDs.h" #include "vm/PosixNSPR.h" -#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \ - JS_BEGIN_MACRO \ - if (cx->simulator()->overRecursedWithExtra(extra)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - namespace vixl { // Assemble the specified IEEE-754 components into the target type and apply // appropriate rounding. // sign: 0 = positive, 1 = negative // exponent: Unbiased IEEE-754 exponent. // mantissa: The mantissa of the input. The top bit (which is not encoded for // normal IEEE-754 values) must not be omitted. This bit has the
--- a/js/src/jit/mips32/Simulator-mips32.h +++ b/js/src/jit/mips32/Simulator-mips32.h @@ -433,22 +433,14 @@ class SimulatorProcess } static void setRedirection(js::jit::Redirection* redirection) { MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread()); singleton_->redirection_ = redirection; } }; -#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \ - JS_BEGIN_MACRO \ - if (cx->simulator()->overRecursedWithExtra(extra)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - } // namespace jit } // namespace js #endif /* JS_SIMULATOR_MIPS32 */ #endif /* jit_mips32_Simulator_mips32_h */
--- a/js/src/jit/mips64/Simulator-mips64.h +++ b/js/src/jit/mips64/Simulator-mips64.h @@ -447,22 +447,14 @@ class SimulatorProcess } static void setRedirection(js::jit::Redirection* redirection) { MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread()); singleton_->redirection_ = redirection; } }; -#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \ - JS_BEGIN_MACRO \ - if (cx->simulator()->overRecursedWithExtra(extra)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - } // namespace jit } // namespace js #endif /* JS_SIMULATOR_MIPS64 */ #endif /* jit_mips64_Simulator_mips64_h */
--- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -8987,16 +8987,37 @@ class LCheckIsObj : public LInstructionH setBoxOperand(CheckValue, value); } MCheckIsObj* mir() const { return mir_->toCheckIsObj(); } }; +class LCheckIsCallable : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 1> +{ + public: + LIR_HEADER(CheckIsCallable) + + static const size_t CheckValue = 0; + + LCheckIsCallable(const LBoxAllocation& value, const LDefinition& temp) { + setBoxOperand(CheckValue, value); + setTemp(0, temp); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MCheckIsCallable* mir() const { + return mir_->toCheckIsCallable(); + } +}; + class LCheckObjCoercible : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0> { public: LIR_HEADER(CheckObjCoercible) static const size_t CheckValue = 0; explicit LCheckObjCoercible(const LBoxAllocation& value) {
--- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -400,16 +400,17 @@ _(LexicalCheck) \ _(ThrowRuntimeLexicalError) \ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ _(ArrowNewTarget) \ _(CheckReturn) \ _(CheckIsObj) \ + _(CheckIsCallable) \ _(CheckObjCoercible) \ _(DebugCheckSelfHosted) \ _(AsmJSLoadHeap) \ _(AsmJSStoreHeap) \ _(AsmJSCompareExchangeHeap) \ _(AsmJSAtomicExchangeHeap) \ _(AsmJSAtomicBinopHeap) \ _(AsmJSAtomicBinopHeapForEffect)\
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -6824,10 +6824,19 @@ SetStopwatchCommitCallback(JSContext*, S typedef bool (*GetGroupsCallback)(JSContext*, PerformanceGroupVector&, void*); extern JS_PUBLIC_API(bool) SetGetPerformanceGroupsCallback(JSContext*, GetGroupsCallback, void*); } /* namespace js */ +namespace js { + +enum class CompletionKind { + Normal, + Return, + Throw +}; + +} /* namespace js */ #endif /* jsapi_h */
--- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -943,17 +943,19 @@ ArraySpeciesCreate(JSContext* cx, Handle return true; } #if JS_HAS_TOSOURCE static bool array_toSource(JSContext* cx, unsigned argc, Value* vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; + CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { ReportIncompatible(cx, args); return false; } Rooted<JSObject*> obj(cx, &args.thisv().toObject()); @@ -1156,17 +1158,18 @@ ArrayJoinKernel(JSContext* cx, Separator return true; } // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce // 22.1.3.13 Array.prototype.join ( separator ) bool js::array_join(JSContext* cx, unsigned argc, Value* vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; AutoGeckoProfilerEntry pseudoFrame(cx->runtime(), "Array.prototype.join"); CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; @@ -1259,17 +1262,18 @@ js::array_join(JSContext* cx, unsigned a // ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f // 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ]) // ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276 // 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]]) static bool array_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; CallArgs args = CallArgsFromVp(argc, vp); // Step 1 RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false;
--- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -267,17 +267,18 @@ assertSameCompartment(JSContext* cx, } #undef START_ASSERT_SAME_COMPARTMENT STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) MOZ_ALWAYS_INLINE bool CallJSNative(JSContext* cx, Native native, const CallArgs& args) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; #ifdef DEBUG bool alreadyThrowing = cx->isExceptionPending(); #endif assertSameCompartment(cx, args); bool ok = native(cx, args.length(), args.base()); if (ok) { assertSameCompartment(cx, args.rval()); @@ -341,50 +342,54 @@ CallJSNativeConstructor(JSContext* cx, N return true; } MOZ_ALWAYS_INLINE bool CallJSGetterOp(JSContext* cx, GetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; assertSameCompartment(cx, obj, id, vp); bool ok = op(cx, obj, id, vp); if (ok) assertSameCompartment(cx, vp); return ok; } MOZ_ALWAYS_INLINE bool CallJSSetterOp(JSContext* cx, SetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; assertSameCompartment(cx, obj, id, vp); return op(cx, obj, id, vp, result); } inline bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op, HandleObject obj, HandleId id, HandleValue v) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; assertSameCompartment(cx, obj, id, v); return op(cx, obj, id, v); } inline bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; assertSameCompartment(cx, receiver, id); if (op) return op(cx, receiver, id, result); return result.succeed(); } MOZ_ALWAYS_INLINE bool
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -396,17 +396,18 @@ JSCompartment::getNonWrapperObjectForCur // Invoke the prewrap callback. The prewrap callback is responsible for // doing similar reification as above, but can account for any additional // embedder requirements. // // We're a bit worried about infinite recursion here, so we do a check - // see bug 809295. auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap; - JS_CHECK_SYSTEM_RECURSION(cx, return false); + if (!CheckSystemRecursionLimit(cx)) + return false; if (preWrap) { preWrap(cx, cx->global(), obj, objectPassedToWrap, obj); if (!obj) return false; } MOZ_ASSERT(!IsWindow(obj)); return true;
--- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -489,17 +489,18 @@ Error(JSContext* cx, unsigned argc, Valu #if JS_HAS_TOSOURCE /* * Return a string that may eval to something similar to the original object. */ static bool exn_toSource(JSContext* cx, unsigned argc, Value* vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; RootedValue nameVal(cx); RootedString name(cx);
--- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -959,95 +959,117 @@ IsObjectInContextCompartment(JSObject* o #define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */ #define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */ #define JSITER_SYMBOLS 0x20 /* also include symbol property keys */ #define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */ JS_FRIEND_API(bool) RunningWithTrustedPrincipals(JSContext* cx); -inline uintptr_t +MOZ_ALWAYS_INLINE uintptr_t GetNativeStackLimit(JSContext* cx, JS::StackKind kind, int extraAllowance = 0) { uintptr_t limit = JS::RootingContext::get(cx)->nativeStackLimit[kind]; #if JS_STACK_GROWTH_DIRECTION > 0 limit += extraAllowance; #else limit -= extraAllowance; #endif return limit; } -inline uintptr_t +MOZ_ALWAYS_INLINE uintptr_t GetNativeStackLimit(JSContext* cx, int extraAllowance = 0) { JS::StackKind kind = RunningWithTrustedPrincipals(cx) ? JS::StackForTrustedScript : JS::StackForUntrustedScript; return GetNativeStackLimit(cx, kind, extraAllowance); } /* - * These macros report a stack overflow and run |onerror| if we are close to - * using up the C stack. The JS_CHECK_CHROME_RECURSION variant gives us a - * little extra space so that we can ensure that crucial code is able to run. - * JS_CHECK_RECURSION_CONSERVATIVE allows less space than any other check, - * including a safety buffer (as in, it uses the untrusted limit and subtracts - * a little more from it). + * These functions return |false| if we are close to using up the C++ stack. + * They also report an overrecursion error, except for the DontReport variants. + * The CheckSystemRecursionLimit variant gives us a little extra space so we + * can ensure that crucial code is able to run. CheckRecursionLimitConservative + * allows less space than any other check, including a safety buffer (as in, it + * uses the untrusted limit and subtracts a little more from it). */ -#define JS_CHECK_RECURSION_LIMIT(cx, limit, onerror) \ - JS_BEGIN_MACRO \ - int stackDummy_; \ - if (!JS_CHECK_STACK_SIZE(limit, &stackDummy_)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - -#define JS_CHECK_RECURSION(cx, onerror) \ - JS_CHECK_RECURSION_LIMIT(cx, js::GetNativeStackLimit(cx), onerror) - -#define JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx, limit, onerror) \ - JS_BEGIN_MACRO \ - int stackDummy_; \ - if (!JS_CHECK_STACK_SIZE(limit, &stackDummy_)) { \ - onerror; \ - } \ - JS_END_MACRO - -#define JS_CHECK_RECURSION_DONT_REPORT(cx, onerror) \ - JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx, js::GetNativeStackLimit(cx), onerror) - -#define JS_CHECK_RECURSION_WITH_SP_DONT_REPORT(cx, sp, onerror) \ - JS_BEGIN_MACRO \ - if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(cx), sp)) { \ - onerror; \ - } \ - JS_END_MACRO - -#define JS_CHECK_RECURSION_WITH_SP(cx, sp, onerror) \ - JS_BEGIN_MACRO \ - if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(cx), sp)) { \ - js::ReportOverRecursed(cx); \ - onerror; \ - } \ - JS_END_MACRO - -#define JS_CHECK_SYSTEM_RECURSION(cx, onerror) \ - JS_CHECK_RECURSION_LIMIT(cx, js::GetNativeStackLimit(cx, JS::StackForSystemCode), onerror) - -#define JS_CHECK_RECURSION_CONSERVATIVE(cx, onerror) \ - JS_CHECK_RECURSION_LIMIT(cx, \ - js::GetNativeStackLimit(cx, JS::StackForUntrustedScript, -1024 * int(sizeof(size_t))), \ - onerror) - -#define JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, onerror) \ - JS_CHECK_RECURSION_LIMIT_DONT_REPORT(cx, \ - js::GetNativeStackLimit(cx, JS::StackForUntrustedScript, -1024 * int(sizeof(size_t))), \ - onerror) +MOZ_ALWAYS_INLINE bool +CheckRecursionLimit(JSContext* cx, uintptr_t limit) +{ + int stackDummy; + if (!JS_CHECK_STACK_SIZE(limit, &stackDummy)) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitDontReport(JSContext* cx, uintptr_t limit) +{ + int stackDummy; + return JS_CHECK_STACK_SIZE(limit, &stackDummy); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimit(JSContext* cx) +{ + // GetNativeStackLimit(cx) is pretty slow because it has to do an uninlined + // call to RunningWithTrustedPrincipals to determine which stack limit to + // use. To work around this, check the untrusted limit first to avoid the + // overhead in most cases. + uintptr_t untrustedLimit = GetNativeStackLimit(cx, JS::StackForUntrustedScript); + if (MOZ_LIKELY(CheckRecursionLimitDontReport(cx, untrustedLimit))) + return true; + return CheckRecursionLimit(cx, GetNativeStackLimit(cx)); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitDontReport(JSContext* cx) +{ + return CheckRecursionLimitDontReport(cx, GetNativeStackLimit(cx)); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitWithStackPointerDontReport(JSContext* cx, void* sp) +{ + return JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), sp); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitWithStackPointer(JSContext* cx, void* sp) +{ + if (!JS_CHECK_STACK_SIZE(GetNativeStackLimit(cx), sp)) { + ReportOverRecursed(cx); + return false; + } + return true; +} + +MOZ_ALWAYS_INLINE bool +CheckSystemRecursionLimit(JSContext* cx) +{ + return CheckRecursionLimit(cx, GetNativeStackLimit(cx, JS::StackForSystemCode)); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitConservative(JSContext* cx) +{ + return CheckRecursionLimit(cx, GetNativeStackLimit(cx, JS::StackForUntrustedScript, + -1024 * int(sizeof(size_t)))); +} + +MOZ_ALWAYS_INLINE bool +CheckRecursionLimitConservativeDontReport(JSContext* cx) +{ + return CheckRecursionLimitDontReport(cx, GetNativeStackLimit(cx, JS::StackForUntrustedScript, + -1024 * int(sizeof(size_t)))); +} JS_FRIEND_API(void) StartPCCountProfiling(JSContext* cx); JS_FRIEND_API(void) StopPCCountProfiling(JSContext* cx); JS_FRIEND_API(void)
--- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -467,17 +467,18 @@ js::GetPropertyKeys(JSContext* cx, Handl props); } size_t sCustomIteratorCount = 0; static inline bool GetCustomIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; RootedValue rval(cx); /* Check whether we have a valid __iterator__ method. */ HandlePropertyName name = cx->names().iteratorIntrinsic; if (!GetProperty(cx, obj, obj, name, &rval)) return false; /* If there is no custom __iterator__ method, we are done here. */ @@ -1437,17 +1438,18 @@ js::IteratorMore(JSContext* cx, HandleOb return false; if (done) rval.setMagic(JS_NO_ITER_VALUE); return true; } // We're reentering below and can call anything. - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; // Call the iterator object's .next method. if (!GetProperty(cx, iterobj, iterobj, cx->names().next, rval)) return false; // Call the .next method. Fall through to the error-handling cases in the // unlikely event that either one of the fallible operations performed // during the call process fails.
--- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -526,17 +526,18 @@ JA(JSContext* cx, HandleObject obj, Stri } static bool Str(JSContext* cx, const Value& v, StringifyContext* scx) { /* Step 11 must be handled by the caller. */ MOZ_ASSERT(!IsFilteredValue(v)); - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; /* * This method implements the Str algorithm in ES5 15.12.3, but: * * * We move property retrieval (step 1) into callers to stream the * stringification process and avoid constantly copying strings. * * We move the preprocessing in steps 2-4 into a helper function to * allow both JO and JA to use this method. While JA could use it @@ -754,17 +755,18 @@ js::Stringify(JSContext* cx, MutableHand return Str(cx, vp, &scx); } /* ES5 15.12.2 Walk. */ static bool Walk(JSContext* cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; /* Step 1. */ RootedValue val(cx); if (!GetProperty(cx, holder, holder, name, &val)) return false; /* Step 2. */ if (val.isObject()) {
--- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -80,17 +80,16 @@ CopyScript(JSContext* cx, HandleScript s * heuristics that need to know whether a given op is inside a loop. */ enum JSTryNoteKind { JSTRY_CATCH, JSTRY_FINALLY, JSTRY_FOR_IN, JSTRY_FOR_OF, JSTRY_LOOP, - JSTRY_ITERCLOSE, JSTRY_DESTRUCTURING_ITERCLOSE }; /* * Exception handling record. */ struct JSTryNote { uint8_t kind; /* one of JSTryNoteKind */
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -449,17 +449,18 @@ const Class StringObject::class_ = { /* * Perform the initial |RequireObjectCoercible(thisv)| and |ToString(thisv)| * from nearly all String.prototype.* functions. */ static MOZ_ALWAYS_INLINE JSString* ToStringForStringFunction(JSContext* cx, HandleValue thisv) { - JS_CHECK_RECURSION(cx, return nullptr); + if (!CheckRecursionLimit(cx)) + return nullptr; if (thisv.isString()) return thisv.toString(); if (thisv.isObject()) { RootedObject obj(cx, &thisv.toObject()); if (obj->is<StringObject>()) { StringObject* nobj = &obj->as<StringObject>(); @@ -3140,17 +3141,18 @@ SymbolToSource(JSContext* cx, Symbol* sy if (!buf.append(')')) return nullptr; return buf.finishString(); } JSString* js::ValueToSource(JSContext* cx, HandleValue v) { - JS_CHECK_RECURSION(cx, return nullptr); + if (!CheckRecursionLimit(cx)) + return nullptr; assertSameCompartment(cx, v); if (v.isUndefined()) return cx->names().void0; if (v.isString()) return StringToSource(cx, v.toString()); if (v.isSymbol()) return SymbolToSource(cx, v.toSymbol());
--- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -87,17 +87,19 @@ js::assertEnteredPolicy(JSContext* cx, J MOZ_ASSERT(cx->enteredPolicy->enteredAction & act); } #endif bool Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle<PropertyDescriptor> desc) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; + const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); desc.object().set(nullptr); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); if (!policy.allowed()) return policy.returnValue(); // Special case. See the comment on BaseProxyHandler::mHasPrototype. if (handler->hasPrototype()) @@ -105,56 +107,59 @@ Proxy::getPropertyDescriptor(JSContext* return handler->getPropertyDescriptor(cx, proxy, id, desc); } bool Proxy::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle<PropertyDescriptor> desc) { - JS_CHECK_RECURSION(cx, return false); - + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); desc.object().set(nullptr); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); if (!policy.allowed()) return policy.returnValue(); return handler->getOwnPropertyDescriptor(cx, proxy, id, desc); } bool Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle<PropertyDescriptor> desc, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { if (!policy.returnValue()) return false; return result.succeed(); } return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc, result); } bool Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); if (!policy.allowed()) return policy.returnValue(); return proxy->as<ProxyObject>().handler()->ownPropertyKeys(cx, proxy, props); } bool Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { bool ok = policy.returnValue(); if (ok) result.succeed(); return ok; } @@ -182,64 +187,71 @@ js::AppendUnique(JSContext* cx, AutoIdVe } return base.appendAll(uniqueOthers); } /* static */ bool Proxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject proto) { MOZ_ASSERT(proxy->hasDynamicPrototype()); - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->getPrototype(cx, proxy, proto); } /* static */ bool Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) { MOZ_ASSERT(proxy->hasDynamicPrototype()); - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto, result); } /* static */ bool Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject proto) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(cx, proxy, isOrdinary, proto); } /* static */ bool Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); return handler->setImmutablePrototype(cx, proxy, succeeded); } /* static */ bool Proxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); return handler->preventExtensions(cx, proxy, result); } /* static */ bool Proxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy, extensible); } bool Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); if (handler->hasPrototype()) { if (!handler->hasOwn(cx, proxy, id, bp)) @@ -257,17 +269,18 @@ Proxy::has(JSContext* cx, HandleObject p } return handler->has(cx, proxy, id, bp); } bool Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); return handler->hasOwn(cx, proxy, id, bp); } @@ -278,17 +291,18 @@ ValueToWindowProxyIfWindow(const Value& return ObjectValue(*ToWindowProxyIfWindow(&v.toObject())); return v; } MOZ_ALWAYS_INLINE bool Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, HandleId id, MutableHandleValue vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); vp.setUndefined(); // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers // shouldn't have to know about the Window/WindowProxy distinction. @@ -329,17 +343,18 @@ js::ProxyGetPropertyByValue(JSContext* c RootedValue receiver(cx, ObjectValue(*proxy)); return Proxy::get(cx, proxy, receiver, id, vp); } bool Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_, ObjectOpResult& result) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); if (!policy.allowed()) { if (!policy.returnValue()) return false; return result.succeed(); } @@ -377,28 +392,30 @@ js::ProxySetPropertyByValue(JSContext* c if (!Proxy::set(cx, proxy, id, val, receiver, result)) return false; return result.checkStrictErrorOrWarning(cx, proxy, id, strict); } bool Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::ENUMERATE, true); if (!policy.allowed()) return policy.returnValue(); return handler->getOwnEnumerablePropertyKeys(cx, proxy, props); } bool Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); objp.set(nullptr); // default result if we refuse to perform this action if (handler->hasPrototype()) { AutoIdVector props(cx); if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) return false; @@ -425,17 +442,18 @@ Proxy::enumerate(JSContext* cx, HandleOb NewEmptyPropertyIterator(cx, 0, objp); } return handler->enumerate(cx, proxy, objp); } bool Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we // can only set our default value once we're sure that we're not calling the // trap. AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::CALL, true); if (!policy.allowed()) { @@ -444,17 +462,18 @@ Proxy::call(JSContext* cx, HandleObject } return handler->call(cx, proxy, args); } bool Proxy::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we // can only set our default value once we're sure that we're not calling the // trap. AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::CALL, true); if (!policy.allowed()) { @@ -463,47 +482,51 @@ Proxy::construct(JSContext* cx, HandleOb } return handler->construct(cx, proxy, args); } bool Proxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, const CallArgs& args) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; RootedObject proxy(cx, &args.thisv().toObject()); // Note - we don't enter a policy here because our security architecture // guards against nativeCall by overriding the trap itself in the right // circumstances. return proxy->as<ProxyObject>().handler()->nativeCall(cx, test, impl, args); } bool Proxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); *bp = false; // default result if we refuse to perform this action AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp); } bool Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls); } bool Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer); } const char* Proxy::className(JSContext* cx, HandleObject proxy) { // Check for unbounded recursion, but don't signal an error; className // needs to be infallible. @@ -519,61 +542,67 @@ Proxy::className(JSContext* cx, HandleOb return handler->BaseProxyHandler::className(cx, proxy); } return handler->className(cx, proxy); } JSString* Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) { - JS_CHECK_RECURSION(cx, return nullptr); + if (!CheckRecursionLimit(cx)) + return nullptr; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, /* mayThrow = */ false); // Do the safe thing if the policy rejects. if (!policy.allowed()) return handler->BaseProxyHandler::fun_toString(cx, proxy, indent); return handler->fun_toString(cx, proxy, indent); } bool Proxy::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy, g); } bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp); } JSObject * const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1); /* static */ bool Proxy::watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleObject callable) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->watch(cx, proxy, id, callable); } /* static */ bool Proxy::unwatch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; return proxy->as<ProxyObject>().handler()->unwatch(cx, proxy, id); } /* static */ bool Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, /* mayThrow = */ true); if (!policy.allowed()) { if (policy.returnValue()) { MOZ_ASSERT(!cx->isExceptionPending()); return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder); }
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2680,18 +2680,16 @@ TryNoteName(JSTryNoteKind kind) case JSTRY_FINALLY: return "finally"; case JSTRY_FOR_IN: return "for-in"; case JSTRY_FOR_OF: return "for-of"; case JSTRY_LOOP: return "loop"; - case JSTRY_ITERCLOSE: - return "iterclose"; case JSTRY_DESTRUCTURING_ITERCLOSE: return "dstr-iterclose"; } MOZ_CRASH("Bad JSTryNoteKind"); } static MOZ_MUST_USE bool
--- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -2747,17 +2747,18 @@ DebugEnvironments::onCompartmentUnsetIsD envs->missingEnvs.clear(); envs->liveEnvs.clear(); } } bool DebugEnvironments::updateLiveEnvironments(JSContext* cx) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; /* * Note that we must always update the top frame's environment objects' * entries in liveEnvs because we can't be sure code hasn't run in that * frame to change the environment chain since we were last called. The * fp->prevUpToDate() flag indicates whether the environments of frames * older than fp are already included in liveEnvs. It might seem simpler * to have fp instead carry a flag indicating whether fp itself is @@ -2988,17 +2989,18 @@ GetDebugEnvironmentForNonEnvironmentObje MOZ_ASSERT(!o->is<EnvironmentObject>()); #endif return &enclosing; } static JSObject* GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei) { - JS_CHECK_RECURSION(cx, return nullptr); + if (!CheckRecursionLimit(cx)) + return nullptr; if (ei.done()) return GetDebugEnvironmentForNonEnvironmentObject(ei); if (ei.hasAnyEnvironmentObject()) return GetDebugEnvironmentForEnvironmentObject(cx, ei); if (ei.scope().is<FunctionScope>() ||
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -338,17 +338,18 @@ ExecuteState::pushInterpreterFrame(JSCon // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to // avoid this. #ifdef _MSC_VER # pragma optimize("g", off) #endif bool js::RunScript(JSContext* cx, RunState& state) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; // Since any script can conceivably GC, make sure it's safe to do so. cx->verifyIsSafeToGC(); MOZ_DIAGNOSTIC_ASSERT(cx->compartment()->isSystem() || cx->runtime()->allowContentJS()); MOZ_ASSERT(!cx->enableAccessValidation || @@ -613,27 +614,29 @@ js::InternalConstructWithProvidedThis(JS return true; } bool js::CallGetter(JSContext* cx, HandleValue thisv, HandleValue getter, MutableHandleValue rval) { // Invoke could result in another try to get or set the same id again, see // bug 355497. - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; FixedInvokeArgs<0> args(cx); return Call(cx, getter, thisv, args, rval); } bool js::CallSetter(JSContext* cx, HandleValue thisv, HandleValue setter, HandleValue v) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; FixedInvokeArgs<1> args(cx); args[0].set(v); RootedValue ignored(cx); return Call(cx, setter, thisv, args, &ignored); } @@ -1177,27 +1180,16 @@ ProcessTryNotes(JSContext* cx, Environme // location of the throw (the iterator). Indeed, we must // settle to avoid infinitely handling the same exception. SettleOnTryNote(cx, tn, ei, regs); return ErrorReturnContinuation; } break; } - case JSTRY_ITERCLOSE: { - // The iterator object is at the top of the stack. - Value* sp = regs.spForStackDepth(tn->stackDepth); - RootedObject iterObject(cx, &sp[-1].toObject()); - if (!IteratorCloseForException(cx, iterObject)) { - SettleOnTryNote(cx, tn, ei, regs); - return ErrorReturnContinuation; - } - break; - } - case JSTRY_DESTRUCTURING_ITERCLOSE: { // Whether the destructuring iterator is done is at the top of the // stack. The iterator object is second from the top. MOZ_ASSERT(tn->stackDepth > 1); Value* sp = regs.spForStackDepth(tn->stackDepth); RootedValue doneValue(cx, sp[-1]); bool done = ToBoolean(doneValue); if (!done) { @@ -1876,17 +1868,16 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) -CASE(JSOP_UNUSED219) CASE(JSOP_UNUSED220) CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED222) CASE(JSOP_UNUSED223) CASE(JSOP_CONDSWITCH) { MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1); ADVANCE_AND_DISPATCH(1); @@ -2622,16 +2613,25 @@ CASE(JSOP_CHECKISOBJ) { if (!REGS.sp[-1].isObject()) { MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, CheckIsObjectKind(GET_UINT8(REGS.pc)))); goto error; } } END_CASE(JSOP_CHECKISOBJ) +CASE(JSOP_CHECKISCALLABLE) +{ + if (!IsCallable(REGS.sp[-1])) { + MOZ_ALWAYS_FALSE(ThrowCheckIsCallable(cx, CheckIsCallableKind(GET_UINT8(REGS.pc)))); + goto error; + } +} +END_CASE(JSOP_CHECKISCALLABLE) + CASE(JSOP_CHECKTHIS) { if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) { MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx, REGS.fp())); goto error; } } END_CASE(JSOP_CHECKTHIS) @@ -5036,16 +5036,29 @@ js::ThrowCheckIsObject(JSContext* cx, Ch break; default: MOZ_CRASH("Unknown kind"); } return false; } bool +js::ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind) +{ + switch (kind) { + case CheckIsCallableKind::IteratorReturn: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE); + break; + default: + MOZ_CRASH("Unknown kind"); + } + return false; +} + +bool js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) { RootedFunction fun(cx); if (frame.isFunctionFrame()) { fun = frame.callee(); } else { Scope* startingScope; if (frame.isDebuggerEvalFrame()) {
--- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -557,16 +557,23 @@ enum class CheckIsObjectKind : uint8_t { IteratorReturn, IteratorThrow, GetIterator }; bool ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind); +enum class CheckIsCallableKind : uint8_t { + IteratorReturn +}; + +bool +ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind); + bool ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame); bool DefaultClassConstructor(JSContext* cx, unsigned argc, Value* vp); bool Debug_CheckSelfHosted(JSContext* cx, HandleValue v);
--- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -2015,17 +2015,18 @@ GetNonexistentProperty(JSContext* cx, Na { return false; } static inline bool GeneralizedGetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue receiver, IsNameLookup nameLookup, MutableHandleValue vp) { - JS_CHECK_RECURSION(cx, return false); + if (!CheckRecursionLimit(cx)) + return false; if (nameLookup) { // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty) // and step 6 (the call to Get) fused so that only a single lookup is // needed. // // If we get here, we've reached a non-native object. Fall back on the // algorithm as specified, with two separate lookups. (Note that we @@ -2040,17 +2041,18 @@ GeneralizedGetProperty(JSContext* cx, Ha return GetProperty(cx, obj, receiver, id, vp); } static inline bool GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id, const Value& receiver, IsNameLookup nameLookup, FakeMutableHandle<Value> vp) { - JS_CHECK_RECURSION_DONT_REPORT(cx, return false); + if (!CheckRecursionLimitDontReport(cx)) + return false; if (nameLookup) return false; return GetPropertyNoGC(cx, obj, receiver, id, vp.address()); } template <AllowGC allowGC> static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline(JSContext* cx,
--- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2202,17 +2202,26 @@ 1234567890123456789012345678901234567890 * This opcode is used with the JSOP_NEWARRAY opcode. * Category: Literals * Type: Array * Operands: * Stack: => hole */ \ macro(JSOP_HOLE, 218, "hole", NULL, 1, 0, 1, JOF_BYTE) \ \ - macro(JSOP_UNUSED219, 219,"unused219", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Checks that the top value on the stack is callable, and throws a + * TypeError if not. The operand 'kind' is used only to generate an + * appropriate error message. + * Category: Statements + * Type: Function + * Operands: uint8_t kind + * Stack: result => result, callable + */ \ + macro(JSOP_CHECKISCALLABLE, 219, "checkiscallable", NULL, 2, 1, 1, JOF_UINT8) \ macro(JSOP_UNUSED220, 220,"unused220", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED221, 221,"unused221", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED222, 222,"unused222", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED223, 223,"unused223", NULL, 1, 0, 0, JOF_BYTE) \ \ /* * Creates rest parameter array for current function call, and pushes it * onto the stack.
--- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -5798,17 +5798,18 @@ CheckCoercedAtomicsBuiltinCall(FunctionV return CoerceResult(f, callNode, ret, actual, type); } static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); - JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); + if (!CheckRecursionLimitDontReport(f.cx())) + return f.m().failOverRecursed(); bool isSimd = false; if (IsNumericLiteral(f.m(), call, &isSimd)) { if (isSimd) f.setUsesSimd(); NumLit lit = ExtractNumericLiteral(f.m(), call); if (!f.writeConstExpr(lit)) return false; @@ -6107,17 +6108,18 @@ CheckMultiply(FunctionValidator& f, Pars } return f.fail(star, "multiply operands must be both int, both double? or both float?"); } static bool CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr) { - JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); + if (!CheckRecursionLimitDontReport(f.cx())) + return f.m().failOverRecursed(); MOZ_ASSERT(expr->isKind(PNK_ADD) || expr->isKind(PNK_SUB)); ParseNode* lhs = AddSubLeft(expr); ParseNode* rhs = AddSubRight(expr); Type lhsType, rhsType; unsigned lhsNumAddOrSub, rhsNumAddOrSub; @@ -6347,17 +6349,18 @@ CheckBitwise(FunctionValidator& f, Parse } return true; } static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type) { - JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); + if (!CheckRecursionLimitDontReport(f.cx())) + return f.m().failOverRecursed(); bool isSimd = false; if (IsNumericLiteral(f.m(), expr, &isSimd)) { if (isSimd) f.setUsesSimd(); return CheckNumericLiteral(f, expr, type); } @@ -7006,17 +7009,18 @@ CheckBreakOrContinue(FunctionValidator& if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt)) return f.writeLabeledBreakOrContinue(maybeLabel, isBreak); return f.writeUnlabeledBreakOrContinue(isBreak); } static bool CheckStatement(FunctionValidator& f, ParseNode* stmt) { - JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); + if (!CheckRecursionLimitDontReport(f.cx())) + return f.m().failOverRecursed(); switch (stmt->getKind()) { case PNK_SEMI: return CheckExprStatement(f, stmt); case PNK_WHILE: return CheckWhile(f, stmt); case PNK_FOR: return CheckFor(f, stmt); case PNK_DOWHILE: return CheckDoWhile(f, stmt); case PNK_LABEL: return CheckLabel(f, stmt); case PNK_IF: return CheckIf(f, stmt);
--- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -253,17 +253,18 @@ XPCArrayHomogenizer::GetTypeForArray(JSC NS_ERROR("bad state"); return false; } return true; } bool XPCVariant::InitializeData(JSContext* cx) { - JS_CHECK_RECURSION(cx, return false); + if (!js::CheckRecursionLimit(cx)) + return false; RootedValue val(cx, GetJSVal()); if (val.isInt32()) { mData.SetFromInt32(val.toInt32()); return true; } if (val.isDouble()) {
--- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -2908,21 +2908,21 @@ PresShell::RecreateFramesFor(nsIContent* nsAutoScriptBlocker scriptBlocker; nsStyleChangeList changeList; changeList.AppendChange(nullptr, aContent, nsChangeHint_ReconstructFrame); // Mark ourselves as not safe to flush while we're doing frame construction. ++mChangeNestCount; RestyleManager* restyleManager = mPresContext->RestyleManager(); - nsresult rv = restyleManager->ProcessRestyledFrames(changeList); + restyleManager->ProcessRestyledFrames(changeList); restyleManager->FlushOverflowChangedTracker(); --mChangeNestCount; - return rv; + return NS_OK; } void nsIPresShell::PostRecreateFramesFor(Element* aElement) { mPresContext->RestyleManager()->PostRestyleEvent(aElement, nsRestyleHint(0), nsChangeHint_ReconstructFrame); }
--- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -1355,23 +1355,23 @@ RestyleManager::GetNextContinuationWithS nextContinuation = nullptr; if (aHaveMoreContinuations) { *aHaveMoreContinuations = true; } } return nextContinuation; } -nsresult +void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) { NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "Someone forgot a script blocker"); if (aChangeList.IsEmpty()) - return NS_OK; + return; PROFILER_LABEL("RestyleManager", "ProcessRestyledFrames", js::ProfileEntry::Category::CSS); nsPresContext* presContext = PresContext(); FramePropertyTable* propTable = presContext->PropertyTable(); nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); @@ -1672,17 +1672,16 @@ RestyleManager::ProcessRestyledFrames(ns data.mFrame->GetType() != nsGkAtoms::viewportFrame) { NS_WARNING("Unable to test style tree integrity -- no content node " "(and not a viewport frame)"); } #endif } aChangeList.Clear(); - return NS_OK; } RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame( RestyleManager* aRestyleManager) : mRestyleManager(aRestyleManager) , mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) { MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
--- a/layout/base/RestyleManager.h +++ b/layout/base/RestyleManager.h @@ -73,17 +73,17 @@ public: mOverflowChangedTracker.RemoveFrame(aFrame); } // Note: It's the caller's responsibility to make sure to wrap a // ProcessRestyledFrames call in a view update batch and a script blocker. // This function does not call ProcessAttachedQueue() on the binding manager. // If the caller wants that to happen synchronously, it needs to handle that // itself. - nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList); + void ProcessRestyledFrames(nsStyleChangeList& aChangeList); bool IsInStyleRefresh() const { return mInStyleRefresh; } // AnimationsWithDestroyedFrame is used to stop animations and transitions // on elements that have no frame at the end of the restyling process. // It only lives during the restyling process. class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final { public:
--- a/layout/base/ServoRestyleManager.cpp +++ b/layout/base/ServoRestyleManager.cpp @@ -6,16 +6,17 @@ #include "mozilla/ServoRestyleManager.h" #include "mozilla/DocumentStyleRootIterator.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/dom/ChildIterator.h" #include "nsContentUtils.h" +#include "nsCSSFrameConstructor.h" #include "nsPrintfCString.h" #include "nsRefreshDriver.h" #include "nsStyleChangeList.h" using namespace mozilla::dom; namespace mozilla { @@ -191,34 +192,47 @@ ServoRestyleManager::RecreateStyleContex // // Hold the old style context alive, because it could become a dangling // pointer during the replacement. In practice it's not a huge deal (on // GetNextContinuationWithSameStyle the pointer is not dereferenced, only // compared), but better not playing with dangling pointers if not needed. RefPtr<nsStyleContext> oldStyleContext = styleFrame ? styleFrame->StyleContext() : nullptr; + UndisplayedNode* displayContentsNode = nullptr; + // FIXME(emilio, bug 1303605): This can be simpler for Servo. + // Note that we intentionally don't check for display: none content. + if (!oldStyleContext) { + displayContentsNode = + PresContext()->FrameConstructor()->GetDisplayContentsNodeFor(aElement); + if (displayContentsNode) { + oldStyleContext = displayContentsNode->mStyle; + } + } + RefPtr<ServoComputedValues> computedValues = aStyleSet->ResolveServoStyle(aElement); // Note that we rely in the fact that we don't cascade pseudo-element styles // separately right now (that is, if a pseudo style changes, the normal style // changes too). // // Otherwise we should probably encode that information somehow to avoid // expensive checks in the common case. // // Also, we're going to need to check for pseudos of display: contents // elements, though that is buggy right now even in non-stylo mode, see // bug 1251799. const bool recreateContext = oldStyleContext && oldStyleContext->StyleSource().AsServoComputedValues() != computedValues; + RefPtr<nsStyleContext> newContext = nullptr; if (recreateContext) { - RefPtr<nsStyleContext> newContext = + MOZ_ASSERT(styleFrame || displayContentsNode); + newContext = aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr, CSSPseudoElementType::NotPseudo, aElement); newContext->EnsureStructsForServo(oldStyleContext); // XXX This could not always work as expected: there are kinds of content // with the first split and the last sharing style, but others not. We // should handle those properly. @@ -230,16 +244,21 @@ ServoRestyleManager::RecreateStyleContex if (isTable) { nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); MOZ_ASSERT(primaryFrame->StyleContext()->GetPseudo() == nsCSSAnonBoxes::tableWrapper, "What sort of frame is this?"); UpdateStyleContextForTableWrapper(primaryFrame, newContext, aStyleSet); } + if (MOZ_UNLIKELY(displayContentsNode)) { + MOZ_ASSERT(!styleFrame); + displayContentsNode->mStyle = newContext; + } + // Update pseudo-elements state if appropriate. const static CSSPseudoElementType pseudosToRestyle[] = { CSSPseudoElementType::before, CSSPseudoElementType::after, }; for (CSSPseudoElementType pseudoType : pseudosToRestyle) { nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType); @@ -268,34 +287,26 @@ ServoRestyleManager::RecreateStyleContex } } } } bool traverseElementChildren = aElement->HasDirtyDescendantsForServo(); bool traverseTextChildren = recreateContext; if (traverseElementChildren || traverseTextChildren) { + nsStyleContext* upToDateContext = + recreateContext ? newContext : oldStyleContext; + StyleChildrenIterator it(aElement); for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) { if (traverseElementChildren && n->IsElement()) { - if (!styleFrame) { - // The frame constructor presumably decided to suppress frame - // construction on this subtree. Just clear the dirty descendants - // bit from the subtree, since there's no point in harvesting the - // change hints. - MOZ_ASSERT(!n->AsElement()->GetPrimaryFrame(), - "Only display:contents should do this, and we don't handle that yet"); - ClearDirtyDescendantsFromSubtree(n->AsElement()); - } else { - RecreateStyleContexts(n->AsElement(), styleFrame->StyleContext(), - aStyleSet, aChangeListToProcess); - } + RecreateStyleContexts(n->AsElement(), upToDateContext, + aStyleSet, aChangeListToProcess); } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) { - RecreateStyleContextsForText(n, styleFrame->StyleContext(), - aStyleSet); + RecreateStyleContextsForText(n, upToDateContext, aStyleSet); } } } aElement->UnsetHasDirtyDescendantsForServo(); } void @@ -322,16 +333,17 @@ ServoRestyleManager::FrameForPseudoEleme { MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement()); nsIFrame* primaryFrame = aContent->GetPrimaryFrame(); if (!aPseudoTagOrNull) { return primaryFrame; } + // FIXME(emilio): Need to take into account display: contents pseudos! if (!primaryFrame) { return nullptr; } // NOTE: we probably need to special-case display: contents here. Gecko's // RestyleManager passes the primary frame of the parent instead. if (aPseudoTagOrNull == nsCSSPseudoElements::before) { return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
--- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -201,33 +201,53 @@ nsFrameManager::ClearPlaceholderFrameMap auto entry = static_cast<PlaceholderMapEntry*>(iter.Get()); entry->placeholderFrame->SetOutOfFlowFrame(nullptr); } mPlaceholderMap.Clear(); } //---------------------------------------------------------------------- +static nsIContent* +ParentForUndisplayedMap(const nsIContent* aContent) +{ + MOZ_ASSERT(aContent); + + nsIContent* parent = aContent->GetParentElementCrossingShadowRoot(); + MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements"); + + return parent; +} + /* static */ nsStyleContext* -nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent) +nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, + const nsIContent* aContent) +{ + UndisplayedNode* node = GetUndisplayedNodeInMapFor(aMap, aContent); + return node ? node->mStyle.get() : nullptr; +} + +/* static */ UndisplayedNode* +nsFrameManager::GetUndisplayedNodeInMapFor(UndisplayedMap* aMap, + const nsIContent* aContent) { if (!aContent) { return nullptr; } - nsIContent* parent = aContent->GetParentElementCrossingShadowRoot(); - MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements"); + nsIContent* parent = ParentForUndisplayedMap(aContent); for (UndisplayedNode* node = aMap->GetFirstNode(parent); node; node = node->mNext) { if (node->mContent == aContent) - return node->mStyle; + return node; } return nullptr; } + /* static */ UndisplayedNode* nsFrameManager::GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap, nsIContent* aParentContent) { return aMap ? aMap->GetFirstNode(aParentContent) : nullptr; } UndisplayedNode* @@ -247,18 +267,17 @@ nsFrameManager::SetStyleContextInMap(Und #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP) static int i = 0; printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent); #endif NS_ASSERTION(!GetStyleContextInMap(aMap, aContent), "Already have an entry for aContent"); - nsIContent* parent = aContent->GetParentElementCrossingShadowRoot(); - MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements"); + nsIContent* parent = ParentForUndisplayedMap(aContent); #ifdef DEBUG nsIPresShell* shell = aStyleContext->PresContext()->PresShell(); NS_ASSERTION(parent || (shell && shell->GetDocument() && shell->GetDocument()->GetRootElement() == aContent), "undisplayed content must have a parent, unless it's the root " "element"); #endif aMap->AddNodeFor(parent, aContent, aStyleContext);
--- a/layout/base/nsFrameManager.h +++ b/layout/base/nsFrameManager.h @@ -97,17 +97,17 @@ public: // Placeholder frame functions nsPlaceholderFrame* GetPlaceholderFrameFor(const nsIFrame* aFrame); void RegisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame); void UnregisterPlaceholderFrame(nsPlaceholderFrame* aPlaceholderFrame); void ClearPlaceholderFrameMap(); // Mapping undisplayed content - nsStyleContext* GetUndisplayedContent(nsIContent* aContent) + nsStyleContext* GetUndisplayedContent(const nsIContent* aContent) { if (!mUndisplayedMap) { return nullptr; } return GetStyleContextInMap(mUndisplayedMap, aContent); } mozilla::UndisplayedNode* GetAllUndisplayedContentIn(nsIContent* aParentContent); @@ -122,29 +122,42 @@ public: void ClearUndisplayedContentIn(nsIContent* aContent, nsIContent* aParentContent); void ClearAllUndisplayedContentIn(nsIContent* aParentContent); // display:contents related methods: /** * Return the registered display:contents style context for aContent, if any. */ - nsStyleContext* GetDisplayContentsStyleFor(nsIContent* aContent) + nsStyleContext* GetDisplayContentsStyleFor(const nsIContent* aContent) { if (!mDisplayContentsMap) { return nullptr; } return GetStyleContextInMap(mDisplayContentsMap, aContent); } /** * Return the linked list of UndisplayedNodes containing the registered * display:contents children of aParentContent, if any. */ mozilla::UndisplayedNode* GetAllDisplayContentsIn(nsIContent* aParentContent); + + /** + * Return the relevant undisplayed node for a given content with display: + * contents style. + */ + mozilla::UndisplayedNode* GetDisplayContentsNodeFor( + const nsIContent* aContent) { + if (!mDisplayContentsMap) { + return nullptr; + } + return GetUndisplayedNodeInMapFor(mDisplayContentsMap, aContent); + } + /** * Register aContent having a display:contents style context. */ void SetDisplayContents(nsIContent* aContent, nsStyleContext* aStyleContext); /** * Change the registered style context for aContent to aStyleContext. */ @@ -202,17 +215,20 @@ public: */ void CaptureFrameStateFor(nsIFrame* aFrame, nsILayoutHistoryState* aState); void RestoreFrameStateFor(nsIFrame* aFrame, nsILayoutHistoryState* aState); protected: static nsStyleContext* GetStyleContextInMap(UndisplayedMap* aMap, - nsIContent* aContent); + const nsIContent* aContent); + static mozilla::UndisplayedNode* + GetUndisplayedNodeInMapFor(UndisplayedMap* aMap, + const nsIContent* aContent); static mozilla::UndisplayedNode* GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap, nsIContent* aParentContent); static void SetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent, nsStyleContext* aStyleContext); static void ChangeStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent,
--- a/layout/reftests/css-grid/reftest-stylo.list +++ b/layout/reftests/css-grid/reftest-stylo.list @@ -169,18 +169,18 @@ fails == grid-repeat-auto-fill-fit-002.h fails == grid-repeat-auto-fill-fit-003.html grid-repeat-auto-fill-fit-003.html fails == grid-repeat-auto-fill-fit-004.html grid-repeat-auto-fill-fit-004.html fails == grid-repeat-auto-fill-fit-005.html grid-repeat-auto-fill-fit-005.html fails == grid-repeat-auto-fill-fit-006.html grid-repeat-auto-fill-fit-006.html fails == grid-repeat-auto-fill-fit-007.html grid-repeat-auto-fill-fit-007.html fails == grid-repeat-auto-fill-fit-008.html grid-repeat-auto-fill-fit-008.html fails == grid-repeat-auto-fill-fit-009.html grid-repeat-auto-fill-fit-009.html fails == grid-repeat-auto-fill-fit-010.html grid-repeat-auto-fill-fit-010.html -# == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-011.html # bug 1342710 -fails == grid-item-blockifying-001.html grid-item-blockifying-001.html # bug 1335339 +fails == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-011.html +fails == grid-item-blockifying-001.html grid-item-blockifying-001.html fails == grid-fragmentation-001.html grid-fragmentation-001.html fails == grid-fragmentation-002.html grid-fragmentation-002.html fails == grid-fragmentation-003.html grid-fragmentation-003.html fails == grid-fragmentation-004.html grid-fragmentation-004.html fails == grid-fragmentation-005.html grid-fragmentation-005.html fails == grid-fragmentation-006.html grid-fragmentation-006.html fails == grid-fragmentation-007.html grid-fragmentation-007.html fails == grid-fragmentation-008.html grid-fragmentation-008.html
--- a/layout/style/ServoBindings.cpp +++ b/layout/style/ServoBindings.cpp @@ -4,16 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ServoBindings.h" #include "ChildIterator.h" #include "gfxFontFamilyList.h" #include "nsAttrValueInlines.h" +#include "nsCSSFrameConstructor.h" #include "nsCSSProps.h" #include "nsCSSParser.h" #include "nsCSSPseudoElements.h" #include "nsCSSRuleProcessor.h" #include "nsContentUtils.h" #include "nsDOMTokenList.h" #include "nsIContentInlines.h" #include "nsIDOMNode.h" @@ -274,21 +275,31 @@ Gecko_SetOwnerDocumentNeedsStyleFlush(Ra nsStyleContext* Gecko_GetStyleContext(RawGeckoNodeBorrowed aNode, nsIAtom* aPseudoTagOrNull) { MOZ_ASSERT(aNode->IsContent()); nsIFrame* relevantFrame = ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(), aPseudoTagOrNull); - if (!relevantFrame) { + if (relevantFrame) { + return relevantFrame->StyleContext(); + } + + if (aPseudoTagOrNull) { return nullptr; } - return relevantFrame->StyleContext(); + // FIXME(emilio): Is there a shorter path? + nsCSSFrameConstructor* fc = + aNode->OwnerDoc()->GetShell()->GetPresContext()->FrameConstructor(); + + // NB: This is only called for CalcStyleDifference, and we handle correctly + // the display: none case since Servo still has the older style. + return fc->GetDisplayContentsStyleFor(aNode->AsContent()); } nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext, ServoComputedValuesBorrowed aComputedValues) { MOZ_ASSERT(aOldStyleContext); MOZ_ASSERT(aComputedValues);