diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp index 9820c489a00..1cba6fa2085 100644 --- a/accessible/base/AccEvent.cpp +++ b/accessible/base/AccEvent.cpp @@ -186,8 +186,9 @@ AccVCChangeEvent:: AccVCChangeEvent(Accessible* aAccessible, nsIAccessible* aOldAccessible, int32_t aOldStart, int32_t aOldEnd, - int16_t aReason) : - AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible), + int16_t aReason, EIsFromUserInput aIsFromUserInput) : + AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible, + aIsFromUserInput), mOldAccessible(aOldAccessible), mOldStart(aOldStart), mOldEnd(aOldEnd), mReason(aReason) { diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h index 0147982e81e..cdc64ef6b50 100644 --- a/accessible/base/AccEvent.h +++ b/accessible/base/AccEvent.h @@ -469,7 +469,8 @@ public: AccVCChangeEvent(Accessible* aAccessible, nsIAccessible* aOldAccessible, int32_t aOldStart, int32_t aOldEnd, - int16_t aReason); + int16_t aReason, + EIsFromUserInput aIsFromUserInput = eFromUserInput); virtual ~AccVCChangeEvent() { } diff --git a/accessible/base/nsAccessiblePivot.cpp b/accessible/base/nsAccessiblePivot.cpp index 30323c38607..dcf9fcb3f78 100644 --- a/accessible/base/nsAccessiblePivot.cpp +++ b/accessible/base/nsAccessiblePivot.cpp @@ -101,7 +101,7 @@ nsAccessiblePivot::SetPosition(nsIAccessible* aPosition) int32_t oldStart = mStartOffset, oldEnd = mEndOffset; mStartOffset = mEndOffset = -1; NotifyOfPivotChange(secondPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_NONE); + nsIAccessiblePivot::REASON_NONE, false); return NS_OK; } @@ -154,7 +154,8 @@ nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset) NS_IMETHODIMP nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible, - int32_t aStartOffset, int32_t aEndOffset) + int32_t aStartOffset, int32_t aEndOffset, + bool aIsFromUserInput, uint8_t aArgc) { NS_ENSURE_ARG(aTextAccessible); @@ -186,7 +187,8 @@ nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible, mPosition = newPosition; NotifyOfPivotChange(oldPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_TEXT); + nsIAccessiblePivot::REASON_TEXT, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } @@ -196,7 +198,7 @@ nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible, NS_IMETHODIMP nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, nsIAccessible* aAnchor, bool aIncludeStart, - uint8_t aArgc, bool* aResult) + bool aIsFromUserInput, uint8_t aArgc, bool* aResult) { NS_ENSURE_ARG(aResult); NS_ENSURE_ARG(aRule); @@ -215,7 +217,8 @@ nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, NS_ENSURE_SUCCESS(rv, rv); if (accessible) - *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT); + *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT, + (aArgc > 2) ? aIsFromUserInput : true); return NS_OK; } @@ -223,7 +226,7 @@ nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, NS_IMETHODIMP nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, nsIAccessible* aAnchor, - bool aIncludeStart, + bool aIncludeStart, bool aIsFromUserInput, uint8_t aArgc, bool* aResult) { NS_ENSURE_ARG(aResult); @@ -243,13 +246,16 @@ nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, NS_ENSURE_SUCCESS(rv, rv); if (accessible) - *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV); + *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV, + (aArgc > 2) ? aIsFromUserInput : true); return NS_OK; } NS_IMETHODIMP -nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult) +nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, + bool aIsFromUserInput, + uint8_t aArgc, bool* aResult) { NS_ENSURE_ARG(aResult); NS_ENSURE_ARG(aRule); @@ -262,14 +268,16 @@ nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult) NS_ENSURE_SUCCESS(rv, rv); if (accessible) - *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST); + *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } NS_IMETHODIMP nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, - bool* aResult) + bool aIsFromUserInput, + uint8_t aArgc, bool* aResult) { NS_ENSURE_ARG(aResult); NS_ENSURE_ARG(aRule); @@ -291,13 +299,16 @@ nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, NS_ENSURE_SUCCESS(rv, rv); if (accessible) - *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST); + *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } NS_IMETHODIMP -nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult) +nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, + bool aIsFromUserInput, uint8_t aArgc, + bool* aResult) { NS_ENSURE_ARG(aResult); @@ -407,13 +418,16 @@ nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult) mStartOffset = tempStart; mEndOffset = tempEnd; NotifyOfPivotChange(startPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_TEXT); + nsIAccessiblePivot::REASON_TEXT, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } } NS_IMETHODIMP -nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) +nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, + bool aIsFromUserInput, uint8_t aArgc, + bool* aResult) { NS_ENSURE_ARG(aResult); @@ -536,7 +550,8 @@ nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) mEndOffset = tempEnd; NotifyOfPivotChange(startPosition, oldStart, oldEnd, - nsIAccessiblePivot::REASON_TEXT); + nsIAccessiblePivot::REASON_TEXT, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } } @@ -544,6 +559,7 @@ nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) NS_IMETHODIMP nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule, int32_t aX, int32_t aY, bool aIgnoreNoMatch, + bool aIsFromUserInput, uint8_t aArgc, bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); @@ -581,7 +597,8 @@ nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule, } if (match || !aIgnoreNoMatch) - *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT); + *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT, + (aArgc > 0) ? aIsFromUserInput : true); return NS_OK; } @@ -626,14 +643,16 @@ nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor bool nsAccessiblePivot::MovePivotInternal(Accessible* aPosition, - PivotMoveReason aReason) + PivotMoveReason aReason, + bool aIsFromUserInput) { nsRefPtr oldPosition = mPosition.forget(); mPosition = aPosition; int32_t oldStart = mStartOffset, oldEnd = mEndOffset; mStartOffset = mEndOffset = -1; - return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason); + return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason, + aIsFromUserInput); } Accessible* @@ -824,7 +843,7 @@ nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward) bool nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition, int32_t aOldStart, int32_t aOldEnd, - int16_t aReason) + int16_t aReason, bool aIsFromUserInput) { if (aOldPosition == mPosition && aOldStart == mStartOffset && aOldEnd == mEndOffset) @@ -833,7 +852,8 @@ nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition, nsTObserverArray >::ForwardIterator iter(mObservers); while (iter.HasMore()) { nsIAccessiblePivotObserver* obs = iter.GetNext(); - obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd, aReason); + obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd, aReason, + aIsFromUserInput); } return true; diff --git a/accessible/base/nsAccessiblePivot.h b/accessible/base/nsAccessiblePivot.h index 2e9ba256803..5bdabca0eab 100644 --- a/accessible/base/nsAccessiblePivot.h +++ b/accessible/base/nsAccessiblePivot.h @@ -49,7 +49,8 @@ private: */ bool NotifyOfPivotChange(Accessible* aOldAccessible, int32_t aOldStart, int32_t aOldEnd, - PivotMoveReason aReason); + PivotMoveReason aReason, + bool aIsFromUserInput); /* * Check to see that the given accessible is a descendant of given ancestor @@ -95,7 +96,8 @@ private: /* * Update the pivot, and notify observers. Return true if it moved. */ - bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason); + bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason, + bool aIsFromUserInput); /* * Get initial node we should start a search from with a given rule. diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 671cef026cd..d726c680fcd 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -816,11 +816,12 @@ NS_IMETHODIMP DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot, nsIAccessible* aOldAccessible, int32_t aOldStart, int32_t aOldEnd, - PivotMoveReason aReason) + PivotMoveReason aReason, + bool aIsFromUserInput) { - nsRefPtr event = new AccVCChangeEvent(this, aOldAccessible, - aOldStart, aOldEnd, - aReason); + nsRefPtr event = new AccVCChangeEvent( + this, aOldAccessible, aOldStart, aOldEnd, aReason, + aIsFromUserInput ? eFromUserInput : eNoUserInput); nsEventShell::FireEvent(event); return NS_OK; diff --git a/accessible/interfaces/nsIAccessiblePivot.idl b/accessible/interfaces/nsIAccessiblePivot.idl index a4daaed05f0..72c9e48f9fe 100644 --- a/accessible/interfaces/nsIAccessiblePivot.idl +++ b/accessible/interfaces/nsIAccessiblePivot.idl @@ -20,7 +20,7 @@ interface nsIAccessiblePivotObserver; * provides traversal methods to move the pivot to next/prev state that complies * to a given rule. */ -[scriptable, uuid(c2938033-e240-4fe5-9cb6-e7ad649ccd10)] +[scriptable, uuid(81fe5144-059b-42db-bd3a-f6ce3158d5e9)] interface nsIAccessiblePivot : nsISupports { const TextBoundaryType CHAR_BOUNDARY = 0; @@ -64,91 +64,114 @@ interface nsIAccessiblePivot : nsISupports /** * Set the pivot's text range in a text accessible. * - * @param aTextAccessible [in] the text accessible that contains the desired - * range. - * @param aStartOffset [in] the start offset to set. - * @param aEndOffset [in] the end offset to set. + * @param aTextAccessible [in] the text accessible that contains the desired + * range. + * @param aStartOffset [in] the start offset to set. + * @param aEndOffset [in] the end offset to set. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @throws NS_ERROR_INVALID_ARG when the offset exceeds the accessible's * character count. */ - void setTextRange(in nsIAccessibleText aTextAccessible, - in long aStartOffset, in long aEndOffset); + [optional_argc] void setTextRange(in nsIAccessibleText aTextAccessible, + in long aStartOffset, in long aEndOffset, + [optional] in boolean aIsFromUserInput); /** * Move pivot to next object, from current position or given anchor, * complying to given traversal rule. * - * @param aRule [in] traversal rule to use. - * @param aAnchor [in] accessible to start search from, if not provided, + * @param aRule [in] traversal rule to use. + * @param aAnchor [in] accessible to start search from, if not provided, * current position will be used. - * @param aIncludeStart [in] include anchor accessible in search. + * @param aIncludeStart [in] include anchor accessible in search. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if there are no new nodes to traverse to. */ [optional_argc] boolean moveNext(in nsIAccessibleTraversalRule aRule, [optional] in nsIAccessible aAnchor, - [optional] in boolean aIncludeStart); + [optional] in boolean aIncludeStart, + [optional] in boolean aIsFromUserInput); /** * Move pivot to previous object, from current position or given anchor, * complying to given traversal rule. * - * @param aRule [in] traversal rule to use. - * @param aAnchor [in] accessible to start search from, if not provided, + * @param aRule [in] traversal rule to use. + * @param aAnchor [in] accessible to start search from, if not provided, * current position will be used. - * @param aIncludeStart [in] include anchor accessible in search. + * @param aIncludeStart [in] include anchor accessible in search. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if there are no new nodes to traverse to. */ [optional_argc] boolean movePrevious(in nsIAccessibleTraversalRule aRule, [optional] in nsIAccessible aAnchor, - [optional] in boolean aIncludeStart); + [optional] in boolean aIncludeStart, + [optional] in boolean aIsFromUserInput); /** * Move pivot to first object in subtree complying to given traversal rule. * - * @param aRule [in] traversal rule to use. + * @param aRule [in] traversal rule to use. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if there are no new nodes to traverse to. */ - boolean moveFirst(in nsIAccessibleTraversalRule aRule); + [optional_argc] boolean moveFirst(in nsIAccessibleTraversalRule aRule, + [optional] in boolean aIsFromUserInput); /** * Move pivot to last object in subtree complying to given traversal rule. * - * @param aRule [in] traversal rule to use. - * @return true on success, false if there are no new nodes to traverse to. + * @param aRule [in] traversal rule to use. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). */ - boolean moveLast(in nsIAccessibleTraversalRule aRule); + [optional_argc] boolean moveLast(in nsIAccessibleTraversalRule aRule, + [optional] in boolean aIsFromUserInput); /** * Move pivot to next text range. * - * @param aBoundary [in] type of boundary for next text range, character, word, - * etc. + * @param aBoundary [in] type of boundary for next text range, + * character, word, etc. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if there are is no more text. */ - boolean moveNextByText(in TextBoundaryType aBoundary); - + [optional_argc] boolean moveNextByText(in TextBoundaryType aBoundary, + [optional] in boolean aIsFromUserInput); + /** * Move pivot to previous text range. * - * @param aBoundary [in] type of boundary for previous text range, character, - * word, etc. + * @param aBoundary [in] type of boundary for next text range, + * character, word, etc. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if there are is no more text. */ - boolean movePreviousByText(in TextBoundaryType aBoundary); + [optional_argc] boolean movePreviousByText(in TextBoundaryType aBoundary, + [optional] in boolean aIsFromUserInput); /** * Move pivot to given coordinate in screen pixels. * - * @param aRule [in] raversal rule to use. - * @param aX [in] screen's x coordinate - * @param aY [in] screen's y coordinate - * @param aIgnoreNoMatch [in] don't unset position if no object was found at - * point. + * @param aRule [in] raversal rule to use. + * @param aX [in] screen's x coordinate + * @param aY [in] screen's y coordinate + * @param aIgnoreNoMatch [in] don't unset position if no object was found + * at point. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). * @return true on success, false if the pivot has not been moved. */ - boolean moveToPoint(in nsIAccessibleTraversalRule aRule, - in long aX, in long aY, - in boolean aIgnoreNoMatch); + [optional_argc] boolean moveToPoint(in nsIAccessibleTraversalRule aRule, + in long aX, in long aY, + in boolean aIgnoreNoMatch, + [optional] in boolean aIsFromUserInput); /** * Add an observer for pivot changes. @@ -174,19 +197,23 @@ interface nsIAccessiblePivotObserver : nsISupports /** * Called when the pivot changes. * - * @param aPivot [in] the pivot that has changed. - * @param aOldAccessible [in] the old pivot position before the change, or null. - * @param aOldStart [in] the old start offset, or -1. - * @param aOldEnd [in] the old end offset, or -1. - * @param aReason [in] the reason for the pivot change. + * @param aPivot [in] the pivot that has changed. + * @param aOldAccessible [in] the old pivot position before the change, + * or null. + * @param aOldStart [in] the old start offset, or -1. + * @param aOldEnd [in] the old end offset, or -1. + * @param aReason [in] the reason for the pivot change. + * @param aIsFromUserInput [in] the pivot changed because of direct user input + * (default is true). */ void onPivotChanged(in nsIAccessiblePivot aPivot, in nsIAccessible aOldAccessible, in long aOldStart, in long aOldEnd, - in PivotMoveReason aReason); + in PivotMoveReason aReason, + in boolean aIsFromUserInput); }; -[scriptable, uuid(4d9c4352-20f5-4c54-9580-0c77bb6b1115)] +[scriptable, uuid(e197460d-1eff-4247-b4bb-a43be1840dae)] interface nsIAccessibleTraversalRule : nsISupports { /* Ignore this accessible object */ diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js index 82bfc829ea7..ebb5eb03257 100644 --- a/accessible/tests/mochitest/pivot.js +++ b/accessible/tests/mochitest/pivot.js @@ -75,11 +75,12 @@ var ObjectTraversalRule = /** * A checker for virtual cursor changed events. */ -function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod) +function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod, + aIsFromUserInput) { this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc); - this.match = function VCChangedChecker_check(aEvent) + this.match = function VCChangedChecker_match(aEvent) { var event = null; try { @@ -114,6 +115,9 @@ function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMetho "expecting " + aIdOrNameOrAcc + ", got '" + prettyName(position)); + SimpleTest.is(aEvent.isFromUserInput, aIsFromUserInput, + "Expected user input is " + aIsFromUserInput + '\n'); + if (aTextOffsets) { SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0], "wrong start offset"); @@ -190,7 +194,7 @@ function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) }; this.eventSeq = [ - new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange") + new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange", true) ]; } @@ -203,8 +207,11 @@ function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect * virtual cursor to land on after performing move method. * false if no move is expected. + * @param aIsFromUserInput [in] set user input flag when invoking method, and + * expect it in the event. */ -function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc) +function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc, + aIsFromUserInput) { var expectMove = (aIdOrNameOrAcc != false); this.invoke = function virtualCursorChangedInvoker_invoke() @@ -212,7 +219,20 @@ function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc) VCChangedChecker. storePreviousPosAndOffset(aDocAcc.virtualCursor); if (aPivotMoveMethod && aRule) { - var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule); + var moved = false; + switch (aPivotMoveMethod) { + case 'moveFirst': + case 'moveLast': + moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule, + aIsFromUserInput === undefined ? true : aIsFromUserInput); + break; + case 'moveNext': + case 'movePrevious': + moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule, + aDocAcc.virtualCursor.position, false, + aIsFromUserInput === undefined ? true : aIsFromUserInput); + break; + } SimpleTest.is(!!moved, !!expectMove, "moved pivot with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc); @@ -228,7 +248,8 @@ function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc) if (expectMove) { this.eventSeq = [ - new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod) + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod, + aIsFromUserInput === undefined ? !!aPivotMoveMethod : aIsFromUserInput) ]; } else { this.eventSeq = []; @@ -249,15 +270,19 @@ function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc) * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect * virtual cursor to land on after performing move method. * false if no move is expected. + * @param aIsFromUserInput [in] set user input flag when invoking method, and + * expect it in the event. */ -function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, aIdOrNameOrAcc) +function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, + aIdOrNameOrAcc, aIsFromUserInput) { var expectMove = (aIdOrNameOrAcc != false); this.invoke = function virtualCursorChangedInvoker_invoke() { VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); SimpleTest.info(aDocAcc.virtualCursor.position); - var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary); + var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary, + aIsFromUserInput === undefined ? true : false); SimpleTest.is(!!moved, !!expectMove, "moved pivot by text with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc); @@ -272,7 +297,8 @@ function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, aI if (expectMove) { this.eventSeq = [ - new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod) + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod, + aIsFromUserInput === undefined ? true : aIsFromUserInput) ]; } else { this.eventSeq = []; @@ -317,7 +343,7 @@ function moveVCCoordInvoker(aDocAcc, aX, aY, aIgnoreNoMatch, if (expectMove) { this.eventSeq = [ - new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint') + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint', true) ]; } else { this.eventSeq = []; @@ -407,7 +433,8 @@ function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence) // No further more matches for given rule, expect no virtual cursor changes. aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false)); - aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0])); + // set isFromUserInput to false, just to test.. + aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0], false)); // No previous more matches for given rule, expect no virtual cursor changes. aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false)); diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html index 4f6af438e26..2fb339964af 100644 --- a/accessible/tests/mochitest/pivot/test_virtualcursor.html +++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html @@ -96,8 +96,10 @@ NS_ERROR_INVALID_ARG)); // Put cursor in an ignored subtree + // set isFromUserInput to false, just to test.. gQueue.push(new setVCPosInvoker(docAcc, null, null, - getAccessible(doc.getElementById("hidden-link")))); + getAccessible(doc.getElementById("hidden-link")), + false)); // Next item shoud be outside of that subtree gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "An ")); diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html index 36a8194ec2c..1c11094fc3d 100644 --- a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html +++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html @@ -53,8 +53,10 @@ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,3], getAccessible(doc.getElementById('p1-link-1'), nsIAccessibleText))); + // set user input to false, and see if it works gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [5,7], - getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText))); + getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)), + false); gQueue.push(new setVCPosInvoker(docAcc, null, null, getAccessible(doc.getElementById('section-1')))); @@ -62,8 +64,10 @@ getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,9], getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + // set user input to false, and see if it works gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14], - getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText))); + getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText), + false)); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [4,6], getAccessible(doc.getElementById('section-1'), nsIAccessibleText))); gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [7,12], diff --git a/build/docs/mozbuild-files.rst b/build/docs/mozbuild-files.rst index b143c01fa24..a1cba010607 100644 --- a/build/docs/mozbuild-files.rst +++ b/build/docs/mozbuild-files.rst @@ -53,16 +53,15 @@ side-effects. Previously, when the build configuration was defined in unnoticed. ``moz.build`` files fix this problem by eliminating the potential for false promises. -In the sandbox, all ``UPPERCASE`` variables are globals and all -non-``UPPERCASE`` variables are locals. After a ``moz.build`` file has -completed execution, only the globals are used to retrieve state. +After a ``moz.build`` file has completed execution, only the +``UPPERCASE`` variables are used to retrieve state. The set of variables and functions available to the Python sandbox is -defined by the :py:mod:`mozbuild.frontend.sandbox_symbols` module. The +defined by the :py:mod:`mozbuild.frontend.context` module. The data structures in this module are consumed by the :py:class:`mozbuild.frontend.reader.MozbuildSandbox` class to construct the sandbox. There are tests to ensure that the set of symbols exposed -to an empty sandbox are all defined in the ``sandbox_symbols`` module. +to an empty sandbox are all defined in the ``context`` module. This module also contains documentation for each symbol, so nothing can sneak into the sandbox without being explicitly defined and documented. @@ -81,13 +80,14 @@ of all the special ``UPPERCASE`` variables populated during its execution. The code for reading ``moz.build`` files lives in -:py:mod:`mozbuild.frontend.reader`. The evaluated Python sandboxes are -passed into :py:mod:`mozbuild.frontend.emitter`, which converts them to -classes defined in :py:mod:`mozbuild.frontend.data`. Each class in this -module define a domain-specific component of tree metdata. e.g. there -will be separate classes that represent a JavaScript file vs a compiled -C++ file or test manifests. This means downstream consumers of this data -can filter on class types to only consume what they are interested in. +:py:mod:`mozbuild.frontend.reader`. The Python sandboxes evaluation results +(:py:class:`mozbuild.frontend.context.Context`) are passed into +:py:mod:`mozbuild.frontend.emitter`, which converts them to classes defined +in :py:mod:`mozbuild.frontend.data`. Each class in this module defines a +domain-specific component of tree metdata. e.g. there will be separate +classes that represent a JavaScript file vs a compiled C++ file or test +manifests. This means downstream consumers of this data can filter on class +types to only consume what they are interested in. There is no well-defined mapping between ``moz.build`` file instances and the number of :py:mod:`mozbuild.frontend.data` classes derived from @@ -98,7 +98,7 @@ The purpose of the ``emitter`` layer between low-level sandbox execution and metadata representation is to facilitate a unified normalization and verification step. There are multiple downstream consumers of the ``moz.build``-derived data and many will perform the same actions. This -logic can be complicated, so we a component dedicated to it. +logic can be complicated, so we have a component dedicated to it. Other Notes =========== diff --git a/build/docs/mozbuild-symbols.rst b/build/docs/mozbuild-symbols.rst index 0a99d64ce73..4e9a8853a09 100644 --- a/build/docs/mozbuild-symbols.rst +++ b/build/docs/mozbuild-symbols.rst @@ -4,4 +4,4 @@ mozbuild Sandbox Symbols ======================== -.. mozbuildsymbols:: mozbuild.frontend.sandbox_symbols +.. mozbuildsymbols:: mozbuild.frontend.context diff --git a/caps/nsPrincipal.cpp b/caps/nsPrincipal.cpp index 2fcad5ed504..661f7b96bb0 100644 --- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -19,9 +19,9 @@ #include "nsIClassInfoImpl.h" #include "nsError.h" #include "nsIContentSecurityPolicy.h" -#include "nsCxPusher.h" #include "jswrapper.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/HashFunctions.h" diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 2c15f4c2e3d..0c3d7bd3e4f 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -61,10 +61,10 @@ #include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsILoadInfo.h" diff --git a/configure.in b/configure.in index cd0e29e469e..852f7f3c2ff 100644 --- a/configure.in +++ b/configure.in @@ -7154,6 +7154,17 @@ if test -n "$JSGC_GENERATIONAL"; then AC_DEFINE(JSGC_GENERATIONAL) fi +dnl ======================================================== +dnl = Use compacting GC +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(gccompacting, +[ --enable-gccompacting Compact the JS heap by moving GC things], + JS_GCCOMPACTING=1, + JS_GCCOMPACTING= ) +if test -n "$JS_GCCOMPACTING"; then + AC_DEFINE(JS_GCCOMPACTING) +fi + dnl ======================================================== dnl = Use a smaller chunk size for GC chunks dnl ======================================================== @@ -9115,6 +9126,9 @@ fi if test -z "$JSGC_GENERATIONAL" ; then ac_configure_args="$ac_configure_args --disable-gcgenerational" fi +if test -n "$JS_GCCOMPACTING" ; then + ac_configure_args="$ac_configure_args --enable-gccompacting" +fi if test -n "$JS_GC_SMALL_CHUNK_SIZE" ; then ac_configure_args="$ac_configure_args --enable-small-chunk-size" fi diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 2a6e5d9bf30..e67be4a1081 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -28,6 +28,7 @@ #include "nsTArrayForwardDeclare.h" #include "Units.h" #include "mozilla/dom/AutocompleteInfoBinding.h" +#include "mozilla/dom/ScriptSettings.h" #if defined(XP_WIN) // Undefine LoadImage to prevent naming conflict with Windows. diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 4837b9fa934..3fe31b825d9 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -12,7 +12,6 @@ #include #include "prprf.h" -#include "nsCxPusher.h" #include "DecoderTraits.h" #include "harfbuzz/hb.h" #include "imgICache.h" diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index a06b0c2e404..eca503a7817 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -39,7 +39,6 @@ #include "nsIDocShellTreeItem.h" #include "nsCOMArray.h" #include "nsDOMClassInfo.h" -#include "nsCxPusher.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/BasicEvents.h" diff --git a/content/base/src/nsFrameMessageManager.cpp b/content/base/src/nsFrameMessageManager.cpp index bcf47bbbbfc..6fb3b596fa3 100644 --- a/content/base/src/nsFrameMessageManager.cpp +++ b/content/base/src/nsFrameMessageManager.cpp @@ -10,7 +10,6 @@ #include "AppProcessChecker.h" #include "ContentChild.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsError.h" #include "nsIXPConnect.h" #include "jsapi.h" @@ -33,6 +32,7 @@ #include "mozilla/Preferences.h" #include "mozilla/dom/nsIContentParent.h" #include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StructuredCloneUtils.h" #include "mozilla/dom/PBlobChild.h" #include "mozilla/dom/PBlobParent.h" diff --git a/content/base/src/nsNodeUtils.cpp b/content/base/src/nsNodeUtils.cpp index 8ab312b511d..fb5441f7055 100644 --- a/content/base/src/nsNodeUtils.cpp +++ b/content/base/src/nsNodeUtils.cpp @@ -6,7 +6,6 @@ #include "nsNodeUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsINode.h" #include "nsIContent.h" #include "mozilla/dom/Element.h" diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index fdcbfb59d28..c7a14d260c7 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -48,7 +48,6 @@ #include "nsCURILoader.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsDocShellCID.h" #include "nsGkAtoms.h" #include "nsThreadUtils.h" diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index 03c6ad87d08..f82cbf0cd2f 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -33,7 +33,6 @@ #include "nsIDOMHTMLScriptElement.h" #include "nsIDocShell.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsUnicharUtils.h" #include "nsAutoPtr.h" #include "nsIXPConnect.h" diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index 877342fd4d3..554eb7279c1 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -41,7 +41,6 @@ #include "nsIStreamConverterService.h" #include "nsICachingChannel.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsCycleCollectionParticipant.h" #include "nsIContentPolicy.h" #include "nsContentPolicyUtils.h" diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index 99ab9d6c782..286cc3e8742 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -23,7 +23,6 @@ #ifdef MOZ_WIDGET_GONK #include "nsJSUtils.h" -#include "nsCxPusher.h" #include "nsIAudioManager.h" #include "SpeakerManagerService.h" #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp index ae1391ada65..a6c21cb4770 100644 --- a/dom/base/DOMRequest.cpp +++ b/dom/base/DOMRequest.cpp @@ -7,7 +7,6 @@ #include "DOMRequest.h" #include "DOMError.h" -#include "nsCxPusher.h" #include "nsThreadUtils.h" #include "DOMCursor.h" #include "nsIDOMEvent.h" diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index 9674ada989d..d3d2fa0737e 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -9,6 +9,7 @@ #include "mozilla/Assertions.h" #include "jsapi.h" +#include "xpcprivate.h" // For AutoCxPusher guts #include "xpcpublic.h" #include "nsIGlobalObject.h" #include "nsIScriptGlobalObject.h" @@ -18,6 +19,8 @@ #include "nsPIDOMWindow.h" #include "nsTArray.h" #include "nsJSUtils.h" +#include "nsDOMJSUtils.h" +#include "WorkerPrivate.h" namespace mozilla { namespace dom { @@ -373,5 +376,153 @@ AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread) } } +danger::AutoCxPusher::AutoCxPusher(JSContext* cx, bool allowNull) +{ + MOZ_ASSERT_IF(!allowNull, cx); + + // Hold a strong ref to the nsIScriptContext, if any. This ensures that we + // only destroy the mContext of an nsJSContext when it is not on the cx stack + // (and therefore not in use). See nsJSContext::DestroyJSContext(). + if (cx) + mScx = GetScriptContextFromJSContext(cx); + + XPCJSContextStack *stack = XPCJSRuntime::Get()->GetJSContextStack(); + if (!stack->Push(cx)) { + MOZ_CRASH(); + } + mStackDepthAfterPush = stack->Count(); + +#ifdef DEBUG + mPushedContext = cx; + mCompartmentDepthOnEntry = cx ? js::GetEnterCompartmentDepth(cx) : 0; +#endif + + // Enter a request and a compartment for the duration that the cx is on the + // stack if non-null. + if (cx) { + mAutoRequest.emplace(cx); + + // DOM JSContexts don't store their default compartment object on the cx. + JSObject *compartmentObject = mScx ? mScx->GetWindowProxy() + : js::DefaultObjectForContextOrNull(cx); + if (compartmentObject) + mAutoCompartment.emplace(cx, compartmentObject); + } +} + +danger::AutoCxPusher::~AutoCxPusher() +{ + // Leave the compartment and request before popping. + mAutoCompartment.reset(); + mAutoRequest.reset(); + + // When we push a context, we may save the frame chain and pretend like we + // haven't entered any compartment. This gets restored on Pop(), but we can + // run into trouble if a Push/Pop are interleaved with a + // JSAutoEnterCompartment. Make sure the compartment depth right before we + // pop is the same as it was right after we pushed. + MOZ_ASSERT_IF(mPushedContext, mCompartmentDepthOnEntry == + js::GetEnterCompartmentDepth(mPushedContext)); + DebugOnly stackTop; + MOZ_ASSERT(mPushedContext == nsXPConnect::XPConnect()->GetCurrentJSContext()); + XPCJSRuntime::Get()->GetJSContextStack()->Pop(); + mScx = nullptr; +} + +bool +danger::AutoCxPusher::IsStackTop() const +{ + uint32_t currentDepth = XPCJSRuntime::Get()->GetJSContextStack()->Count(); + MOZ_ASSERT(currentDepth >= mStackDepthAfterPush); + return currentDepth == mStackDepthAfterPush; +} + } // namespace dom + +AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : mCx(nullptr) +{ + Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); +} + +AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mCx(nullptr) +{ + Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); +} + +void +AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +{ + JS::AutoSuppressGCAnalysis nogc; + MOZ_ASSERT(!mCx, "mCx should not be initialized!"); + + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + nsXPConnect *xpc = nsXPConnect::XPConnect(); + if (!aSafe) { + mCx = xpc->GetCurrentJSContext(); + } + + if (!mCx) { + mJSAPI.Init(); + mCx = mJSAPI.cx(); + } +} + +AutoJSContext::operator JSContext*() const +{ + return mCx; +} + +ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + if (NS_IsMainThread()) { + mCx = nullptr; + mAutoJSContext.emplace(); + } else { + mCx = mozilla::dom::workers::GetCurrentThreadJSContext(); + mRequest.emplace(mCx); + } +} + +ThreadsafeAutoJSContext::operator JSContext*() const +{ + if (mCx) { + return mCx; + } else { + return *mAutoJSContext; + } +} + +AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + , mAc(mCx, XPCJSRuntime::Get()->GetJSContextStack()->GetSafeJSContextGlobal()) +{ +} + +ThreadsafeAutoSafeJSContext::ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + if (NS_IsMainThread()) { + mCx = nullptr; + mAutoSafeJSContext.emplace(); + } else { + mCx = mozilla::dom::workers::GetCurrentThreadJSContext(); + mRequest.emplace(mCx); + } +} + +ThreadsafeAutoSafeJSContext::operator JSContext*() const +{ + if (mCx) { + return mCx; + } else { + return *mAutoSafeJSContext; + } +} + } // namespace mozilla diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index dc2ad441c81..8e5dc63630d 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -9,19 +9,53 @@ #ifndef mozilla_dom_ScriptSettings_h #define mozilla_dom_ScriptSettings_h -#include "nsCxPusher.h" #include "MainThreadUtils.h" #include "nsIGlobalObject.h" #include "nsIPrincipal.h" #include "mozilla/Maybe.h" +#include "jsapi.h" + class nsPIDOMWindow; class nsGlobalWindow; +class nsIScriptContext; namespace mozilla { namespace dom { +// For internal use only - use AutoJSAPI instead. +namespace danger { + +/** + * Fundamental cx pushing class. All other cx pushing classes are implemented + * in terms of this class. + */ +class MOZ_STACK_CLASS AutoCxPusher +{ +public: + explicit AutoCxPusher(JSContext *aCx, bool aAllowNull = false); + ~AutoCxPusher(); + + nsIScriptContext* GetScriptContext() { return mScx; } + + // Returns true if this AutoCxPusher performed the push that is currently at + // the top of the cx stack. + bool IsStackTop() const; + +private: + mozilla::Maybe mAutoRequest; + mozilla::Maybe mAutoCompartment; + nsCOMPtr mScx; + uint32_t mStackDepthAfterPush; +#ifdef DEBUG + JSContext* mPushedContext; + unsigned mCompartmentDepthOnEntry; +#endif +}; + +} /* namespace danger */ + /* * System-wide setup/teardown routines. Init and Destroy should be invoked * once each, at startup and shutdown (respectively). @@ -184,7 +218,7 @@ protected: AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, JSContext* aCx); private: - mozilla::Maybe mCxPusher; + mozilla::Maybe mCxPusher; mozilla::Maybe mAutoNullableCompartment; JSContext *mCx; @@ -242,10 +276,79 @@ class AutoNoJSAPI : protected ScriptSettingsStackEntry { public: explicit AutoNoJSAPI(bool aIsMainThread = NS_IsMainThread()); private: - mozilla::Maybe mCxPusher; + mozilla::Maybe mCxPusher; }; } // namespace dom + +/** + * Use AutoJSContext when you need a JS context on the stack but don't have one + * passed as a parameter. AutoJSContext will take care of finding the most + * appropriate JS context and release it when leaving the stack. + */ +class MOZ_STACK_CLASS AutoJSContext { +public: + explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*() const; + +protected: + explicit AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + + // We need this Init() method because we can't use delegating constructor for + // the moment. It is a C++11 feature and we do not require C++11 to be + // supported to be able to compile Gecko. + void Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + + JSContext* mCx; + dom::AutoJSAPI mJSAPI; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/** + * Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be + * running on a worker thread. + */ +class MOZ_STACK_CLASS ThreadsafeAutoJSContext { +public: + explicit ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*() const; + +private: + JSContext* mCx; // Used on workers. Null means mainthread. + Maybe mRequest; // Used on workers. + Maybe mAutoJSContext; // Used on main thread. + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/** + * AutoSafeJSContext is similar to AutoJSContext but will only return the safe + * JS context. That means it will never call nsContentUtils::GetCurrentJSContext(). + * + * Note - This is deprecated. Please use AutoJSAPI instead. + */ +class MOZ_STACK_CLASS AutoSafeJSContext : public AutoJSContext { +public: + explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); +private: + JSAutoCompartment mAc; +}; + +/** + * Like AutoSafeJSContext but can be used safely on worker threads. + */ +class MOZ_STACK_CLASS ThreadsafeAutoSafeJSContext { +public: + explicit ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*() const; + +private: + JSContext* mCx; // Used on workers. Null means mainthread. + Maybe mRequest; // Used on workers. + Maybe mAutoSafeJSContext; // Used on main thread. + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + + } // namespace mozilla #endif // mozilla_dom_ScriptSettings_h diff --git a/dom/base/nsContentPermissionHelper.cpp b/dom/base/nsContentPermissionHelper.cpp index 98e6ee2fba5..4d955b6dadf 100644 --- a/dom/base/nsContentPermissionHelper.cpp +++ b/dom/base/nsContentPermissionHelper.cpp @@ -21,7 +21,6 @@ #include "nsArrayUtils.h" #include "nsIMutableArray.h" #include "nsContentPermissionHelper.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsISupportsPrimitives.h" #include "nsServiceManagerUtils.h" diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index d851b683ace..ac70ca637e6 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -48,7 +48,6 @@ #include "nsIDOMEvent.h" #include "nsIDOMEventListener.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIDOMGlobalPropertyInitializer.h" #include "mozilla/Attributes.h" #include "mozilla/Telemetry.h" diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index d3f6cd9ccea..62cfef6397a 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -119,7 +119,6 @@ #include "nsGlobalWindowCommands.h" #include "nsAutoPtr.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsCSSProps.h" #include "nsIDOMFile.h" #include "nsIDOMFileList.h" diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index bc710cd4aa7..7ae1c32bb6d 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -31,7 +31,6 @@ #include "nsITimer.h" #include "nsIAtom.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "mozilla/EventDispatcher.h" #include "nsIContent.h" #include "nsCycleCollector.h" diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index b2681a3e81a..a2c92d5305c 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -14,7 +14,6 @@ #include "nsIScriptContext.h" #include "nsPIDOMWindow.h" #include "nsJSUtils.h" -#include "nsCxPusher.h" #include "nsIScriptSecurityManager.h" #include "xpcprivate.h" #include "WorkerPrivate.h" diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 1374de7ca79..6ae351b4aff 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -11831,7 +11831,6 @@ class CGBindingRoot(CGThing): bindingHeaders["nsThreadUtils.h"] = hasWorkerStuff dictionaries = config.getDictionaries(webIDLFile=webIDLFile) - bindingHeaders["nsCxPusher.h"] = dictionaries hasNonEmptyDictionaries = any( len(dict.members) > 0 for dict in dictionaries) mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile, diff --git a/dom/bluetooth/BluetoothReplyRunnable.cpp b/dom/bluetooth/BluetoothReplyRunnable.cpp index a8fe77632a9..d2e2f3771b9 100644 --- a/dom/bluetooth/BluetoothReplyRunnable.cpp +++ b/dom/bluetooth/BluetoothReplyRunnable.cpp @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "BluetoothReplyRunnable.h" #include "DOMRequest.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsServiceManagerUtils.h" diff --git a/dom/bluetooth/BluetoothService.cpp b/dom/bluetooth/BluetoothService.cpp index 4b95ffd965a..4784ae3608b 100644 --- a/dom/bluetooth/BluetoothService.cpp +++ b/dom/bluetooth/BluetoothService.cpp @@ -28,7 +28,6 @@ #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/ipc/UnixSocket.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIObserverService.h" #include "nsISettingsService.h" #include "nsISystemMessagesInternal.h" diff --git a/dom/bluetooth/bluedroid/BluetoothUtils.cpp b/dom/bluetooth/bluedroid/BluetoothUtils.cpp index c89efef54f8..7853ebf098e 100644 --- a/dom/bluetooth/bluedroid/BluetoothUtils.cpp +++ b/dom/bluetooth/bluedroid/BluetoothUtils.cpp @@ -14,7 +14,6 @@ #include "mozilla/Scoped.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptContext.h" #include "nsISystemMessagesInternal.h" #include "nsString.h" diff --git a/dom/bluetooth/bluez/BluetoothUtils.cpp b/dom/bluetooth/bluez/BluetoothUtils.cpp index d9bd896fb2b..d4120082d9e 100644 --- a/dom/bluetooth/bluez/BluetoothUtils.cpp +++ b/dom/bluetooth/bluez/BluetoothUtils.cpp @@ -13,7 +13,6 @@ #include "mozilla/Scoped.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptContext.h" #include "nsISystemMessagesInternal.h" #include "nsString.h" diff --git a/dom/bluetooth2/BluetoothReplyRunnable.cpp b/dom/bluetooth2/BluetoothReplyRunnable.cpp index d1ede83d464..a381aaa8935 100644 --- a/dom/bluetooth2/BluetoothReplyRunnable.cpp +++ b/dom/bluetooth2/BluetoothReplyRunnable.cpp @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "BluetoothReplyRunnable.h" #include "DOMRequest.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/dom/Promise.h" #include "nsServiceManagerUtils.h" diff --git a/dom/bluetooth2/BluetoothService.cpp b/dom/bluetooth2/BluetoothService.cpp index 5cafeb0a430..428c7c1f25c 100644 --- a/dom/bluetooth2/BluetoothService.cpp +++ b/dom/bluetooth2/BluetoothService.cpp @@ -27,7 +27,6 @@ #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIObserverService.h" #include "nsISettingsService.h" #include "nsISystemMessagesInternal.h" diff --git a/dom/bluetooth2/bluedroid/BluetoothUtils.cpp b/dom/bluetooth2/bluedroid/BluetoothUtils.cpp index d4432660ca7..063ca036550 100644 --- a/dom/bluetooth2/bluedroid/BluetoothUtils.cpp +++ b/dom/bluetooth2/bluedroid/BluetoothUtils.cpp @@ -12,9 +12,9 @@ #include "BluetoothUtils.h" #include "jsapi.h" #include "mozilla/Scoped.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptContext.h" #include "nsISystemMessagesInternal.h" #include "nsString.h" diff --git a/dom/bluetooth2/bluez/BluetoothUtils.cpp b/dom/bluetooth2/bluez/BluetoothUtils.cpp index d9bd896fb2b..d4bc173db6e 100644 --- a/dom/bluetooth2/bluez/BluetoothUtils.cpp +++ b/dom/bluetooth2/bluez/BluetoothUtils.cpp @@ -11,9 +11,9 @@ #include "BluetoothUtils.h" #include "jsapi.h" #include "mozilla/Scoped.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptContext.h" #include "nsISystemMessagesInternal.h" #include "nsString.h" diff --git a/dom/browser-element/BrowserElementParent.cpp b/dom/browser-element/BrowserElementParent.cpp index d2a123a4cea..586d481ade9 100644 --- a/dom/browser-element/BrowserElementParent.cpp +++ b/dom/browser-element/BrowserElementParent.cpp @@ -19,7 +19,6 @@ #include "nsIInterfaceRequestorUtils.h" #include "nsVariant.h" #include "mozilla/dom/BrowserElementDictionariesBinding.h" -#include "nsCxPusher.h" #include "mozilla/dom/CustomEvent.h" using namespace mozilla; diff --git a/dom/canvas/WebGLContextUtils.cpp b/dom/canvas/WebGLContextUtils.cpp index 545a6220046..657230d422d 100644 --- a/dom/canvas/WebGLContextUtils.cpp +++ b/dom/canvas/WebGLContextUtils.cpp @@ -10,7 +10,6 @@ #include "GLContext.h" #include "jsapi.h" #include "mozilla/Preferences.h" -#include "nsCxPusher.h" #include "nsIDOMDataContainerEvent.h" #include "nsIDOMEvent.h" #include "nsIScriptSecurityManager.h" @@ -24,6 +23,8 @@ #include "WebGLTexture.h" #include "WebGLVertexArray.h" +#include "mozilla/dom/ScriptSettings.h" + namespace mozilla { using namespace gl; diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp index f3e66cbeb77..1147ebd3f8b 100644 --- a/dom/devicestorage/nsDeviceStorage.cpp +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -43,7 +43,6 @@ #include "nsIPrincipal.h" #include "nsJSUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsXULAppAPI.h" #include "DeviceStorageFileDescriptor.h" #include "DeviceStorageRequestChild.h" diff --git a/dom/events/EventListenerService.cpp b/dom/events/EventListenerService.cpp index 4a4ded4017b..a0cc99ca5c5 100644 --- a/dom/events/EventListenerService.cpp +++ b/dom/events/EventListenerService.cpp @@ -10,7 +10,6 @@ #include "mozilla/JSEventHandler.h" #include "mozilla/Maybe.h" #include "nsCOMArray.h" -#include "nsCxPusher.h" #include "nsDOMClassInfoID.h" #include "nsIXPConnect.h" #include "nsJSUtils.h" diff --git a/dom/fmradio/FMRadioService.cpp b/dom/fmradio/FMRadioService.cpp index f37bbd7bf70..d6885eebd6d 100644 --- a/dom/fmradio/FMRadioService.cpp +++ b/dom/fmradio/FMRadioService.cpp @@ -12,10 +12,10 @@ #include "nsDOMClassInfo.h" #include "mozilla/Preferences.h" #include "mozilla/dom/FMRadioChild.h" +#include "mozilla/dom/ScriptSettings.h" #include "nsIObserverService.h" #include "nsISettingsService.h" #include "nsJSUtils.h" -#include "nsCxPusher.h" #define BAND_87500_108000_kHz 1 #define BAND_76000_108000_kHz 2 diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index 1f890665adb..a17ce5d1f86 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -26,7 +26,6 @@ #include "nsComponentManagerUtils.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsDOMClassInfoID.h" #include "nsGlobalWindow.h" #include "nsHashKeys.h" diff --git a/dom/indexedDB/IDBFileRequest.cpp b/dom/indexedDB/IDBFileRequest.cpp index d470b2830b5..d774df24aaa 100644 --- a/dom/indexedDB/IDBFileRequest.cpp +++ b/dom/indexedDB/IDBFileRequest.cpp @@ -16,7 +16,6 @@ #include "mozilla/dom/ProgressEvent.h" #include "mozilla/EventDispatcher.h" #include "nsCOMPtr.h" -#include "nsCxPusher.h" #include "nsDebug.h" #include "nsError.h" #include "nsIDOMEvent.h" diff --git a/dom/indexedDB/IDBRequest.cpp b/dom/indexedDB/IDBRequest.cpp index 2a813a4fb76..e778a4bfdee 100644 --- a/dom/indexedDB/IDBRequest.cpp +++ b/dom/indexedDB/IDBRequest.cpp @@ -18,7 +18,6 @@ #include "nsDOMClassInfoID.h" #include "nsDOMJSUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsPIDOMWindow.h" #include "nsString.h" diff --git a/dom/indexedDB/ipc/IndexedDBParent.cpp b/dom/indexedDB/ipc/IndexedDBParent.cpp index 20ff78ec0d6..c5f4f265886 100644 --- a/dom/indexedDB/ipc/IndexedDBParent.cpp +++ b/dom/indexedDB/ipc/IndexedDBParent.cpp @@ -17,7 +17,6 @@ #include "mozilla/dom/ipc/Blob.h" #include "mozilla/dom/TabParent.h" #include "mozilla/unused.h" -#include "nsCxPusher.h" #include "AsyncConnectionHelper.h" #include "DatabaseInfo.h" diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp index c74d1c6fe35..ea0ab9bbee6 100644 --- a/dom/ipc/PreallocatedProcessManager.cpp +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -8,10 +8,10 @@ #include "mozilla/ClearOnShutdown.h" #include "mozilla/Preferences.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" #include "nsIPropertyBag2.h" #include "ProcessPriorityManager.h" #include "nsServiceManagerUtils.h" -#include "nsCxPusher.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 9ebf11d0d68..512209f3a68 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -34,7 +34,6 @@ #include "mozilla/unused.h" #include "mozIApplication.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsEmbedCID.h" #include #ifdef MOZ_CRASHREPORTER diff --git a/dom/media/GetUserMediaRequest.cpp b/dom/media/GetUserMediaRequest.cpp index f0e8b9ca58a..d884ac828d6 100644 --- a/dom/media/GetUserMediaRequest.cpp +++ b/dom/media/GetUserMediaRequest.cpp @@ -8,7 +8,6 @@ #include "mozilla/dom/GetUserMediaRequestBinding.h" #include "nsIScriptGlobalObject.h" #include "nsPIDOMWindow.h" -#include "nsCxPusher.h" namespace mozilla { namespace dom { diff --git a/dom/media/MediaPermissionGonk.cpp b/dom/media/MediaPermissionGonk.cpp index 2f4a78ff1de..83d35e641fa 100644 --- a/dom/media/MediaPermissionGonk.cpp +++ b/dom/media/MediaPermissionGonk.cpp @@ -6,7 +6,6 @@ #include "MediaPermissionGonk.h" #include "nsCOMPtr.h" -#include "nsCxPusher.h" #include "nsIContentPermissionPrompt.h" #include "nsIDocument.h" #include "nsIDOMNavigatorUserMedia.h" diff --git a/dom/mobileconnection/src/MobileConnectionInfo.cpp b/dom/mobileconnection/src/MobileConnectionInfo.cpp index b9ad84f25cc..1b8803d26b1 100644 --- a/dom/mobileconnection/src/MobileConnectionInfo.cpp +++ b/dom/mobileconnection/src/MobileConnectionInfo.cpp @@ -6,8 +6,9 @@ #include "MobileConnectionInfo.h" +#include "mozilla/dom/ScriptSettings.h" + #include "jsapi.h" -#include "nsCxPusher.h" #define CONVERT_STRING_TO_NULLABLE_ENUM(_string, _enumType, _enum) \ { \ diff --git a/dom/mobilemessage/src/MmsMessage.cpp b/dom/mobilemessage/src/MmsMessage.cpp index ab8c588245b..c62d017d325 100644 --- a/dom/mobilemessage/src/MmsMessage.cpp +++ b/dom/mobilemessage/src/MmsMessage.cpp @@ -14,9 +14,9 @@ #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType #include "mozilla/dom/mobilemessage/SmsTypes.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ToJSValue.h" #include "nsDOMFile.h" -#include "nsCxPusher.h" using namespace mozilla::dom::mobilemessage; diff --git a/dom/mobilemessage/src/MobileMessageCallback.cpp b/dom/mobilemessage/src/MobileMessageCallback.cpp index 9d9f9122779..981e80641ad 100644 --- a/dom/mobilemessage/src/MobileMessageCallback.cpp +++ b/dom/mobilemessage/src/MobileMessageCallback.cpp @@ -6,7 +6,6 @@ #include "MobileMessageCallback.h" #include "mozilla/dom/ToJSValue.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIDOMMozSmsMessage.h" #include "nsIDOMMozMmsMessage.h" #include "nsIScriptGlobalObject.h" diff --git a/dom/mobilemessage/src/ipc/SmsIPCService.cpp b/dom/mobilemessage/src/ipc/SmsIPCService.cpp index cdca5b39245..83f2931947c 100644 --- a/dom/mobilemessage/src/ipc/SmsIPCService.cpp +++ b/dom/mobilemessage/src/ipc/SmsIPCService.cpp @@ -10,7 +10,6 @@ #include "SmsMessage.h" #include "SmsFilter.h" #include "nsJSUtils.h" -#include "nsCxPusher.h" #include "mozilla/dom/MozMobileMessageManagerBinding.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/Preferences.h" diff --git a/dom/mobilemessage/src/ipc/SmsParent.cpp b/dom/mobilemessage/src/ipc/SmsParent.cpp index 20d136797c7..987d74ea0fd 100644 --- a/dom/mobilemessage/src/ipc/SmsParent.cpp +++ b/dom/mobilemessage/src/ipc/SmsParent.cpp @@ -22,7 +22,6 @@ #include "mozilla/dom/mobilemessage/Constants.h" // For MessageType #include "nsContentUtils.h" #include "nsTArrayHelpers.h" -#include "nsCxPusher.h" #include "xpcpublic.h" #include "nsServiceManagerUtils.h" #include "DeletedMessageInfo.h" diff --git a/dom/network/src/TCPSocketParent.cpp b/dom/network/src/TCPSocketParent.cpp index af306d776ab..e9de185ce13 100644 --- a/dom/network/src/TCPSocketParent.cpp +++ b/dom/network/src/TCPSocketParent.cpp @@ -7,12 +7,12 @@ #include "jsfriendapi.h" #include "nsJSUtils.h" #include "nsIDOMTCPSocket.h" -#include "nsCxPusher.h" #include "mozilla/unused.h" #include "mozilla/AppProcessChecker.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/PNeckoParent.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/TabParent.h" #include "nsIScriptSecurityManager.h" diff --git a/dom/nfc/gonk/NfcService.cpp b/dom/nfc/gonk/NfcService.cpp index 02ed8d611b5..17bef26571f 100644 --- a/dom/nfc/gonk/NfcService.cpp +++ b/dom/nfc/gonk/NfcService.cpp @@ -9,7 +9,6 @@ #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/RootedDictionary.h" #include "nsAutoPtr.h" -#include "nsCxPusher.h" #include "nsString.h" #include "nsXULAppAPI.h" #include "NfcOptions.h" diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 375f9364b2d..0833c4cd69c 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -17,7 +17,6 @@ #include "nsIScriptContext.h" #include "nsDOMJSUtils.h" #include "nsJSUtils.h" -#include "nsCxPusher.h" #include "nsIDocument.h" #include "nsIJSRuntimeService.h" #include "nsIXPConnect.h" diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index d955254d280..0455b86d619 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -40,7 +40,6 @@ #include "nsIPrincipal.h" #include "nsWildCard.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "mozilla/dom/ScriptSettings.h" #include "nsIXPConnect.h" diff --git a/dom/plugins/base/nsNPAPIPlugin.h b/dom/plugins/base/nsNPAPIPlugin.h index c5ba6d9c814..29edc92b0db 100644 --- a/dom/plugins/base/nsNPAPIPlugin.h +++ b/dom/plugins/base/nsNPAPIPlugin.h @@ -10,8 +10,7 @@ #include "npfunctions.h" #include "nsPluginHost.h" -#include "nsCxPusher.h" - +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/PluginLibrary.h" #if defined(XP_WIN) diff --git a/dom/plugins/ipc/PluginIdentifierParent.cpp b/dom/plugins/ipc/PluginIdentifierParent.cpp index afdeb81aaa9..137162c59a9 100644 --- a/dom/plugins/ipc/PluginIdentifierParent.cpp +++ b/dom/plugins/ipc/PluginIdentifierParent.cpp @@ -9,7 +9,6 @@ #include "nsNPAPIPlugin.h" #include "nsServiceManagerUtils.h" #include "PluginScriptableObjectUtils.h" -#include "nsCxPusher.h" #include "mozilla/unused.h" using namespace mozilla::plugins::parent; diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp index 5d55b1df53f..baea05c1715 100644 --- a/dom/plugins/ipc/PluginScriptableObjectParent.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -9,7 +9,6 @@ #include "mozilla/DebugOnly.h" #include "mozilla/plugins/PluginIdentifierParent.h" #include "mozilla/unused.h" -#include "nsCxPusher.h" #include "nsNPAPIPlugin.h" #include "PluginScriptableObjectUtils.h" diff --git a/dom/src/jsurl/nsJSProtocolHandler.cpp b/dom/src/jsurl/nsJSProtocolHandler.cpp index 0ce5193cb4d..06ad830f7a6 100644 --- a/dom/src/jsurl/nsJSProtocolHandler.cpp +++ b/dom/src/jsurl/nsJSProtocolHandler.cpp @@ -36,7 +36,6 @@ #include "nsIContentViewer.h" #include "nsIXPConnect.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "nsIScriptChannel.h" diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp index a4a2ceead67..82517df7a7e 100644 --- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -30,13 +30,13 @@ #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/ScriptSettings.h" #include "base/message_loop.h" #include "BluetoothCommon.h" #include "BluetoothHfpManagerBase.h" #include "nsJSUtils.h" -#include "nsCxPusher.h" #include "nsThreadUtils.h" #include "nsServiceManagerUtils.h" #include "nsComponentManagerUtils.h" diff --git a/dom/system/gonk/AutoMounterSetting.cpp b/dom/system/gonk/AutoMounterSetting.cpp index d6e4637a1b7..876a756b02b 100644 --- a/dom/system/gonk/AutoMounterSetting.cpp +++ b/dom/system/gonk/AutoMounterSetting.cpp @@ -11,7 +11,6 @@ #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsIObserverService.h" -#include "nsCxPusher.h" #include "nsISettingsService.h" #include "nsJSUtils.h" #include "nsPrintfCString.h" @@ -19,6 +18,7 @@ #include "nsString.h" #include "nsThreadUtils.h" #include "xpcpublic.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/Attributes.h" #undef LOG diff --git a/dom/system/gonk/NetworkWorker.cpp b/dom/system/gonk/NetworkWorker.cpp index fa4769ef4c6..b848c406736 100644 --- a/dom/system/gonk/NetworkWorker.cpp +++ b/dom/system/gonk/NetworkWorker.cpp @@ -7,9 +7,9 @@ #include #include "mozilla/ModuleUtils.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ToJSValue.h" #include "nsXULAppAPI.h" -#include "nsCxPusher.h" #define NS_NETWORKWORKER_CID \ { 0x6df093e1, 0x8127, 0x4fa7, {0x90, 0x13, 0xa3, 0xaa, 0xa7, 0x79, 0xbb, 0xdd} } diff --git a/dom/system/gonk/SystemWorkerManager.cpp b/dom/system/gonk/SystemWorkerManager.cpp index 9d157370292..7332817662d 100644 --- a/dom/system/gonk/SystemWorkerManager.cpp +++ b/dom/system/gonk/SystemWorkerManager.cpp @@ -27,12 +27,12 @@ #include "AutoMounter.h" #include "TimeZoneSettingObserver.h" #include "AudioManager.h" +#include "mozilla/dom/ScriptSettings.h" #ifdef MOZ_B2G_RIL #include "mozilla/ipc/Ril.h" #endif #include "mozilla/ipc/KeyStore.h" #include "nsIObserverService.h" -#include "nsCxPusher.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsRadioInterfaceLayer.h" diff --git a/dom/system/gonk/TimeZoneSettingObserver.cpp b/dom/system/gonk/TimeZoneSettingObserver.cpp index 3db33a9e343..18306e7e0d2 100644 --- a/dom/system/gonk/TimeZoneSettingObserver.cpp +++ b/dom/system/gonk/TimeZoneSettingObserver.cpp @@ -4,6 +4,7 @@ #include "base/message_loop.h" #include "jsapi.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/Attributes.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Hal.h" @@ -20,7 +21,6 @@ #include "TimeZoneSettingObserver.h" #include "xpcpublic.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsPrintfCString.h" #undef LOG diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp index d947b88dbbf..2a63cb4e697 100644 --- a/dom/telephony/Telephony.cpp +++ b/dom/telephony/Telephony.cpp @@ -17,7 +17,6 @@ #include "mozilla/Preferences.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" diff --git a/dom/time/DateCacheCleaner.cpp b/dom/time/DateCacheCleaner.cpp index 5be69faf88a..a976ef4ea3b 100644 --- a/dom/time/DateCacheCleaner.cpp +++ b/dom/time/DateCacheCleaner.cpp @@ -6,10 +6,10 @@ #include "DateCacheCleaner.h" #include "jsapi.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Hal.h" #include "mozilla/StaticPtr.h" -#include "nsCxPusher.h" using namespace mozilla::hal; diff --git a/dom/wifi/WifiCertService.cpp b/dom/wifi/WifiCertService.cpp index cfc0910dc2a..23bca42f24e 100644 --- a/dom/wifi/WifiCertService.cpp +++ b/dom/wifi/WifiCertService.cpp @@ -16,7 +16,6 @@ #include "cert.h" #include "certdb.h" #include "CryptoTask.h" -#include "nsCxPusher.h" #include "nsIDOMFile.h" #include "nsIWifiService.h" #include "nsNetUtil.h" diff --git a/dom/wifi/WifiProxyService.cpp b/dom/wifi/WifiProxyService.cpp index 78bc7076a20..113c6fcb2b7 100644 --- a/dom/wifi/WifiProxyService.cpp +++ b/dom/wifi/WifiProxyService.cpp @@ -9,7 +9,6 @@ #include "mozilla/dom/ToJSValue.h" #include "nsXULAppAPI.h" #include "WifiUtils.h" -#include "nsCxPusher.h" #ifdef MOZ_TASK_TRACER #include "GeckoTaskTracer.h" diff --git a/dom/wifi/WifiUtils.h b/dom/wifi/WifiUtils.h index 41e20f1b0a1..7aacf6935ed 100644 --- a/dom/wifi/WifiUtils.h +++ b/dom/wifi/WifiUtils.h @@ -15,7 +15,6 @@ #include "mozilla/dom/WifiOptionsBinding.h" #include "mozilla/dom/network/NetUtils.h" #include "WifiHotspotUtils.h" -#include "nsCxPusher.h" // Needed to add a copy constructor to WifiCommandOptions. struct CommandOptions diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 6b9a5454cb7..97282f4982a 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -18,7 +18,6 @@ #include "mozilla/dom/PromiseNativeHandler.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsNetUtil.h" #include "nsProxyRelease.h" #include "nsTArray.h" diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 26cd35fe473..c5ca440f22c 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -53,7 +53,6 @@ #include "mozilla/Preferences.h" #include "nsAlgorithm.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsError.h" #include "nsDOMJSUtils.h" #include "nsHostObjectProtocolHandler.h" diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 9db5f6908b8..111d3046b09 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -18,7 +18,6 @@ #include "mozilla/dom/ProgressEvent.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" diff --git a/dom/xbl/nsXBLDocumentInfo.cpp b/dom/xbl/nsXBLDocumentInfo.cpp index 851dfb70f28..3931f8666ac 100644 --- a/dom/xbl/nsXBLDocumentInfo.cpp +++ b/dom/xbl/nsXBLDocumentInfo.cpp @@ -23,7 +23,6 @@ #include "nsJSPrincipals.h" #include "nsIScriptSecurityManager.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsDOMJSUtils.h" #include "mozilla/Services.h" #include "xpcpublic.h" diff --git a/dom/xbl/nsXBLProtoImpl.cpp b/dom/xbl/nsXBLProtoImpl.cpp index e18fbd2ce14..56020202600 100644 --- a/dom/xbl/nsXBLProtoImpl.cpp +++ b/dom/xbl/nsXBLProtoImpl.cpp @@ -9,7 +9,6 @@ #include "nsIContent.h" #include "nsIDocument.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIXPConnect.h" #include "nsIServiceManager.h" #include "nsIDOMNode.h" diff --git a/dom/xbl/nsXBLProtoImplMethod.cpp b/dom/xbl/nsXBLProtoImplMethod.cpp index 437d844fe7c..798d07eeb6e 100644 --- a/dom/xbl/nsXBLProtoImplMethod.cpp +++ b/dom/xbl/nsXBLProtoImplMethod.cpp @@ -14,7 +14,6 @@ #include "nsXBLProtoImplMethod.h" #include "nsJSUtils.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" #include "xpcpublic.h" diff --git a/dom/xbl/nsXBLProtoImplProperty.cpp b/dom/xbl/nsXBLProtoImplProperty.cpp index eaa8e672dca..295f63db05c 100644 --- a/dom/xbl/nsXBLProtoImplProperty.cpp +++ b/dom/xbl/nsXBLProtoImplProperty.cpp @@ -9,7 +9,6 @@ #include "nsIContent.h" #include "nsXBLProtoImplProperty.h" #include "nsUnicharUtils.h" -#include "nsCxPusher.h" #include "nsReadableUtils.h" #include "nsJSUtils.h" #include "nsXBLPrototypeBinding.h" diff --git a/dom/xbl/nsXBLPrototypeHandler.cpp b/dom/xbl/nsXBLPrototypeHandler.cpp index 1663738cfff..01df4b48c79 100644 --- a/dom/xbl/nsXBLPrototypeHandler.cpp +++ b/dom/xbl/nsXBLPrototypeHandler.cpp @@ -9,7 +9,6 @@ #include "nsXBLPrototypeHandler.h" #include "nsXBLPrototypeBinding.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsGlobalWindow.h" #include "nsIContent.h" #include "nsIAtom.h" diff --git a/dom/xbl/nsXBLSerialize.cpp b/dom/xbl/nsXBLSerialize.cpp index d57e13573b0..d29f987cfed 100644 --- a/dom/xbl/nsXBLSerialize.cpp +++ b/dom/xbl/nsXBLSerialize.cpp @@ -10,7 +10,6 @@ #include "nsXBLPrototypeBinding.h" #include "nsIXPConnect.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" using namespace mozilla; diff --git a/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp index 99287dad61e..904cf531482 100644 --- a/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp +++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp @@ -19,7 +19,6 @@ #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsIScriptSecurityManager.h" #include "nsJSPrincipals.h" #include "jswrapper.h" diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index ee3ef016b90..210b84dbfdc 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -68,7 +68,14 @@ public: MOZ_ASSERT(cairo_status(mCtx) || dt->GetTransform() == GetTransform()); } - ~AutoPrepareForDrawing() { cairo_restore(mCtx); } + ~AutoPrepareForDrawing() + { + cairo_restore(mCtx); + cairo_status_t status = cairo_status(mCtx); + if (status) { + gfxWarning() << "DrawTargetCairo context in error state: " << cairo_status_to_string(status) << "(" << status << ")"; + } + } private: #ifdef DEBUG @@ -1290,6 +1297,13 @@ DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& mSize = aSize; mFormat = aFormat ? *aFormat : CairoContentToGfxFormat(cairo_surface_get_content(aSurface)); + // Cairo image surface have a bug where they will allocate a mask surface (for clipping) + // the size of the clip extents, and don't take the surface extents into account. + // Add a manual clip to the surface extents to prevent this. + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height); + cairo_clip(mContext); + if (mFormat == SurfaceFormat::B8G8R8A8 || mFormat == SurfaceFormat::R8G8B8A8) { SetPermitSubpixelAA(false); diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp index 5074a783c1b..36173d2ae92 100644 --- a/gfx/thebes/gfxPattern.cpp +++ b/gfx/thebes/gfxPattern.cpp @@ -58,6 +58,7 @@ gfxPattern::gfxPattern(SourceSurface *aSurface, const Matrix &aTransform) , mSourceSurface(aSurface) , mTransform(aTransform) , mExtend(EXTEND_NONE) + , mFilter(mozilla::gfx::Filter::GOOD) { } diff --git a/ipc/testshell/XPCShellEnvironment.cpp b/ipc/testshell/XPCShellEnvironment.cpp index 238707ff3e4..921e120e6d5 100644 --- a/ipc/testshell/XPCShellEnvironment.cpp +++ b/ipc/testshell/XPCShellEnvironment.cpp @@ -33,7 +33,6 @@ #include "nsIXPConnect.h" #include "nsIXPCScriptable.h" -#include "nsCxPusher.h" #include "nsJSUtils.h" #include "nsJSPrincipals.h" #include "nsThreadUtils.h" diff --git a/js/ipc/JavaScriptChild.cpp b/js/ipc/JavaScriptChild.cpp index a8d65997637..02c93647184 100644 --- a/js/ipc/JavaScriptChild.cpp +++ b/js/ipc/JavaScriptChild.cpp @@ -12,7 +12,6 @@ #include "nsContentUtils.h" #include "xpcprivate.h" #include "jsfriendapi.h" -#include "nsCxPusher.h" #include "AccessCheck.h" using namespace JS; diff --git a/js/ipc/WrapperAnswer.cpp b/js/ipc/WrapperAnswer.cpp index db8c7b11e5e..7ad31f7607d 100644 --- a/js/ipc/WrapperAnswer.cpp +++ b/js/ipc/WrapperAnswer.cpp @@ -12,7 +12,6 @@ #include "nsContentUtils.h" #include "xpcprivate.h" #include "jsfriendapi.h" -#include "nsCxPusher.h" using namespace JS; using namespace mozilla; diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 7d1ef07960f..8cdcfe99e38 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -90,7 +90,7 @@ namespace JS { D(REFRESH_FRAME) \ D(FULL_GC_TIMER) \ D(SHUTDOWN_CC) \ - D(FINISH_LARGE_EVALUTE) + D(FINISH_LARGE_EVALUATE) namespace gcreason { diff --git a/js/public/HashTable.h b/js/public/HashTable.h index f8d91ef092e..b958c8e9cc4 100644 --- a/js/public/HashTable.h +++ b/js/public/HashTable.h @@ -959,6 +959,7 @@ class HashTable : private AllocPolicy // a new key at the new Lookup position. |front()| is invalid after // this operation until the next call to |popFront()|. void rekeyFront(const Lookup &l, const Key &k) { + JS_ASSERT(&k != &HashPolicy::getKey(this->cur->get())); Ptr p(*this->cur, table_); table_.rekeyWithoutRehash(p, l, k); rekeyed = true; diff --git a/js/public/Utility.h b/js/public/Utility.h index 89c495e4111..f6714f9d783 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -43,6 +43,7 @@ namespace js {} #define JS_SWEPT_NURSERY_PATTERN 0x2B #define JS_ALLOCATED_NURSERY_PATTERN 0x2D #define JS_FRESH_TENURED_PATTERN 0x4F +#define JS_MOVED_TENURED_PATTERN 0x49 #define JS_SWEPT_TENURED_PATTERN 0x4B #define JS_ALLOCATED_TENURED_PATTERN 0x4D #define JS_SWEPT_CODE_PATTERN 0x3b diff --git a/js/src/assembler/assembler/MacroAssembler.h b/js/src/assembler/assembler/MacroAssembler.h index 0d86a42ad2e..d2e41e45618 100644 --- a/js/src/assembler/assembler/MacroAssembler.h +++ b/js/src/assembler/assembler/MacroAssembler.h @@ -42,15 +42,15 @@ namespace JSC { typedef MacroAssemblerNone MacroAssembler; } #elif JS_CODEGEN_ARM // Merged with the jit backend support. -#elif WTF_CPU_MIPS +#elif JS_CODEGEN_MIPS #include "assembler/assembler/MacroAssemblerMIPS.h" namespace JSC { typedef MacroAssemblerMIPS MacroAssembler; } -#elif WTF_CPU_X86 +#elif JS_CODEGEN_X86 #include "assembler/assembler/MacroAssemblerX86.h" namespace JSC { typedef MacroAssemblerX86 MacroAssembler; } -#elif WTF_CPU_X86_64 +#elif JS_CODEGEN_X64 #include "assembler/assembler/MacroAssemblerX86_64.h" namespace JSC { typedef MacroAssemblerX86_64 MacroAssembler; } diff --git a/js/src/assembler/assembler/MacroAssemblerMIPS.h b/js/src/assembler/assembler/MacroAssemblerMIPS.h index aa368c91bfa..bac462580c3 100644 --- a/js/src/assembler/assembler/MacroAssemblerMIPS.h +++ b/js/src/assembler/assembler/MacroAssemblerMIPS.h @@ -37,7 +37,7 @@ class MacroAssemblerMIPS { public: static bool supportsFloatingPoint() { -#if WTF_MIPS_DOUBLE_FLOAT +#if (defined(__mips_hard_float) && !defined(__mips_single_float)) || defined(JS_MIPS_SIMULATOR) return true; #else return false; diff --git a/js/src/assembler/assembler/MacroAssemblerX86.h b/js/src/assembler/assembler/MacroAssemblerX86.h index 14df66716ec..e51fc3e865a 100644 --- a/js/src/assembler/assembler/MacroAssemblerX86.h +++ b/js/src/assembler/assembler/MacroAssemblerX86.h @@ -30,10 +30,6 @@ #ifndef assembler_assembler_MacroAssemblerX86_h #define assembler_assembler_MacroAssemblerX86_h -#include "assembler/wtf/Platform.h" - -#if ENABLE_ASSEMBLER && WTF_CPU_X86 - #include "assembler/assembler/MacroAssemblerX86Common.h" namespace JSC { @@ -45,6 +41,4 @@ public: } // namespace JSC -#endif // ENABLE(ASSEMBLER) - #endif /* assembler_assembler_MacroAssemblerX86_h */ diff --git a/js/src/assembler/assembler/MacroAssemblerX86Common.cpp b/js/src/assembler/assembler/MacroAssemblerX86Common.cpp index d48f20a0624..58d9e4298c7 100644 --- a/js/src/assembler/assembler/MacroAssemblerX86Common.cpp +++ b/js/src/assembler/assembler/MacroAssemblerX86Common.cpp @@ -4,15 +4,12 @@ * 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 "assembler/wtf/Platform.h" - -/* SSE checks only make sense on Intel platforms. */ -#if WTF_CPU_X86 || WTF_CPU_X86_64 - #include "assembler/assembler/MacroAssemblerX86Common.h" -#if WTF_COMPILER_MSVC -#if WTF_CPU_X86_64 +#include "assembler/wtf/Platform.h" + +#ifdef _MSC_VER +#ifdef JS_CODEGEN_X64 /* for __cpuid */ #include #endif @@ -33,8 +30,8 @@ void MacroAssemblerX86Common::setSSECheckState() // not MSVC or GCC we will read this as SSE2 not present. int flags_edx = 0; int flags_ecx = 0; -#if WTF_COMPILER_MSVC -#if WTF_CPU_X86_64 +#ifdef _MSC_VER +#ifdef JS_CODEGEN_X64 int cpuinfo[4]; __cpuid(cpuinfo, 1); @@ -48,8 +45,8 @@ void MacroAssemblerX86Common::setSSECheckState() mov flags_edx, edx; } #endif -#elif WTF_COMPILER_GCC -#if WTF_CPU_X86_64 +#elif defined(__GNUC__) +#ifdef JS_CODEGEN_X64 asm ( "movl $0x1, %%eax;" "cpuid;" @@ -69,32 +66,6 @@ void MacroAssemblerX86Common::setSSECheckState() : "%eax" ); #endif -#elif WTF_COMPILER_SUNCC -#if WTF_CPU_X86_64 - asm ( - "movl $0x1, %%eax;" - "pushq %%rbx;" - "cpuid;" - "popq %%rbx;" - "movl %%ecx, (%rsi);" - "movl %%edx, (%rdi);" - : - : "S" (&flags_ecx), "D" (&flags_edx) - : "%eax", "%ecx", "%edx" - ); -#else - asm ( - "movl $0x1, %eax;" - "pushl %ebx;" - "cpuid;" - "popl %ebx;" - "movl %ecx, (%esi);" - "movl %edx, (%edi);" - : - : "S" (&flags_ecx), "D" (&flags_edx) - : "%eax", "%ecx", "%edx" - ); -#endif #endif #ifdef DEBUG @@ -133,6 +104,3 @@ void MacroAssemblerX86Common::setSSECheckState() s_sseCheckState = HasSSE2; #endif } - -#endif /* WTF_CPU_X86 || WTF_CPU_X86_64 */ - diff --git a/js/src/assembler/assembler/MacroAssemblerX86Common.h b/js/src/assembler/assembler/MacroAssemblerX86Common.h index eb9c385d683..736eadf085d 100644 --- a/js/src/assembler/assembler/MacroAssemblerX86Common.h +++ b/js/src/assembler/assembler/MacroAssemblerX86Common.h @@ -70,7 +70,7 @@ private: static void setSSECheckState(); public: -#if WTF_CPU_X86 +#ifdef JS_CODEGEN_X86 static bool isSSEPresent() { #if defined(__SSE__) && !defined(DEBUG) diff --git a/js/src/assembler/assembler/MacroAssemblerX86_64.h b/js/src/assembler/assembler/MacroAssemblerX86_64.h index 3f4352c73bf..c9b8a827637 100644 --- a/js/src/assembler/assembler/MacroAssemblerX86_64.h +++ b/js/src/assembler/assembler/MacroAssemblerX86_64.h @@ -30,10 +30,6 @@ #ifndef assembler_assembler_MacroAssemblerX86_64_h #define assembler_assembler_MacroAssemblerX86_64_h -#include "assembler/wtf/Platform.h" - -#if ENABLE_ASSEMBLER && WTF_CPU_X86_64 - #include "assembler/assembler/MacroAssemblerX86Common.h" namespace JSC { @@ -45,6 +41,4 @@ public: } // namespace JSC -#endif // ENABLE(ASSEMBLER) - #endif /* assembler_assembler_MacroAssemblerX86_64_h */ diff --git a/js/src/assembler/assembler/X86Assembler.h b/js/src/assembler/assembler/X86Assembler.h index 032b99c64e3..b5ce80e31f6 100644 --- a/js/src/assembler/assembler/X86Assembler.h +++ b/js/src/assembler/assembler/X86Assembler.h @@ -34,8 +34,6 @@ #include "assembler/wtf/Platform.h" -#if ENABLE_ASSEMBLER && (WTF_CPU_X86 || WTF_CPU_X86_64) - #include "assembler/assembler/AssemblerBuffer.h" #include "js/Vector.h" @@ -57,7 +55,7 @@ namespace X86Registers { esi, edi -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 ,r8, r9, r10, @@ -79,7 +77,7 @@ namespace X86Registers { xmm5, xmm6, xmm7 -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 ,xmm8, xmm9, xmm10, @@ -129,7 +127,7 @@ namespace X86Registers { static const char* nameIReg(RegisterID reg) { -# if WTF_CPU_X86_64 +# ifdef JS_CODEGEN_X64 return nameIReg(8, reg); # else return nameIReg(4, reg); @@ -138,7 +136,7 @@ namespace X86Registers { inline bool hasSubregL(RegisterID reg) { -# if WTF_CPU_X86_64 +# ifdef JS_CODEGEN_X64 // In 64-bit mode, all registers have an 8-bit lo subreg. return true; # else @@ -226,16 +224,16 @@ private: OP_CMP_EvGv = 0x39, OP_CMP_GvEv = 0x3B, OP_CMP_EAXIv = 0x3D, -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 PRE_REX = 0x40, #endif OP_PUSH_EAX = 0x50, OP_POP_EAX = 0x58, -#if WTF_CPU_X86 +#ifdef JS_CODEGEN_X86 OP_PUSHA = 0x60, OP_POPA = 0x61, #endif -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 OP_MOVSXD_GvEv = 0x63, #endif PRE_OPERAND_SIZE = 0x66, @@ -520,7 +518,7 @@ public: // Arithmetic operations: -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void adcl_im(int imm, const void* addr) { FIXME_INSN_PRINTING; @@ -580,7 +578,7 @@ public: } } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void addq_rr(RegisterID src, RegisterID dst) { spew("addq %s, %s", @@ -832,7 +830,7 @@ public: } } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void andq_rr(RegisterID src, RegisterID dst) { spew("andq %s, %s", @@ -997,7 +995,7 @@ public: } } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void negq_r(RegisterID dst) { spew("negq %s", nameIReg(8,dst)); @@ -1088,7 +1086,7 @@ public: } } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void subq_rr(RegisterID src, RegisterID dst) { spew("subq %s, %s", @@ -1189,7 +1187,7 @@ public: } } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void xorq_rr(RegisterID src, RegisterID dst) { spew("xorq %s, %s", @@ -1262,7 +1260,7 @@ public: m_formatter.oneByteOp(OP_GROUP2_EvCL, GROUP2_OP_SHL, dst); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void sarq_CLr(RegisterID dst) { FIXME_INSN_PRINTING; @@ -1467,7 +1465,7 @@ public: m_formatter.immediate32(imm); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void cmpq_rr(RegisterID src, RegisterID dst) { spew("cmpq %s, %s", @@ -1674,7 +1672,7 @@ public: m_formatter.immediate32(imm); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void testq_rr(RegisterID src, RegisterID dst) { spew("testq %s, %s", @@ -1779,7 +1777,7 @@ public: m_formatter.oneByteOp(OP_XCHG_EvGv, src, dst); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void xchgq_rr(RegisterID src, RegisterID dst) { spew("xchgq %s, %s", @@ -1819,7 +1817,7 @@ public: m_formatter.oneByteOp(OP_MOV_EvGv, src, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movw_rm(RegisterID src, const void* addr) { spew("movw %s, %p", @@ -1854,7 +1852,7 @@ public: { spew("movl %p, %%eax", addr); m_formatter.oneByteOp(OP_MOV_EAXOv); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 m_formatter.immediate64(reinterpret_cast(addr)); #else m_formatter.immediate32(reinterpret_cast(addr)); @@ -1965,14 +1963,14 @@ public: { spew("movl %%eax, %p", addr); m_formatter.oneByteOp(OP_MOV_OvEAX); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 m_formatter.immediate64(reinterpret_cast(addr)); #else m_formatter.immediate32(reinterpret_cast(addr)); #endif } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void movq_rr(RegisterID src, RegisterID dst) { spew("movq %s, %s", @@ -2179,7 +2177,7 @@ public: m_formatter.oneByteOp8(OP_MOV_EbGv, src, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movb_rm(RegisterID src, const void* addr) { spew("movb %s, %p", @@ -2209,7 +2207,7 @@ public: m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movzbl_mr(const void* addr, RegisterID dst) { spew("movzbl %p, %s", @@ -2239,7 +2237,7 @@ public: m_formatter.twoByteOp(OP2_MOVSX_GvEb, dst, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movsbl_mr(const void* addr, RegisterID dst) { spew("movsbl %p, %s", @@ -2276,7 +2274,7 @@ public: m_formatter.twoByteOp(OP2_MOVZX_GvEw, dst, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movzwl_mr(const void* addr, RegisterID dst) { spew("movzwl %p, %s", @@ -2306,7 +2304,7 @@ public: m_formatter.twoByteOp(OP2_MOVSX_GvEw, dst, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movswl_mr(const void* addr, RegisterID dst) { spew("movswl %p, %s", @@ -2335,7 +2333,7 @@ public: PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4,dst)); m_formatter.oneByteOp(OP_LEA, dst, base, offset); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void leaq_mr(int offset, RegisterID base, RegisterID dst) { spew("leaq %s0x%x(%s), %s", @@ -2420,7 +2418,7 @@ public: m_formatter.oneByteOp(OP_GROUP5_Ev, GROUP5_OP_JMPN, base, index, scale, offset); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void jmp_rip(int ripOffset) { // rip-relative addressing. spew("jmp *%d(%%rip)", ripOffset); @@ -2607,7 +2605,7 @@ public: m_formatter.twoByteOp(OP2_CVTSI2SD_VsdEd, (RegisterID)dst, src); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void cvtsq2sd_rr(RegisterID src, XMMRegisterID dst) { spew("cvtsq2sd %s, %s", @@ -2656,7 +2654,7 @@ public: m_formatter.twoByteOp(OP2_CVTSI2SD_VsdEd, (RegisterID)dst, base, index, scale, offset); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void cvtsi2sd_mr(const void* address, XMMRegisterID dst) { spew("cvtsi2sd %p, %s", @@ -2682,7 +2680,7 @@ public: m_formatter.twoByteOp(OP2_CVTTSD2SI_GdWsd, dst, (RegisterID)src); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void cvttsd2sq_rr(XMMRegisterID src, RegisterID dst) { spew("cvttsd2si %s, %s", @@ -2806,7 +2804,7 @@ public: m_formatter.twoByteOp(OP2_MOVD_EdVd, (RegisterID)src, dst); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void movq_rr(XMMRegisterID src, RegisterID dst) { spew("movq %s, %s", @@ -2941,7 +2939,7 @@ public: m_formatter.twoByteOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, (RegisterID)src); } -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 void movsd_mr(const void* address, XMMRegisterID dst) { spew("movsd %p, %s", @@ -3065,7 +3063,7 @@ public: m_formatter.twoByteOp(OP2_MOVAPD_VsdWsd, (RegisterID)dst, (RegisterID)src); } -#ifdef WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 JmpSrc movaps_ripr(XMMRegisterID dst) { spew("movaps ?(%%rip), %s", @@ -3097,7 +3095,7 @@ public: m_formatter.prefix(PRE_SSE_66); m_formatter.twoByteOp(OP2_MOVDQ_VdqWdq, (RegisterID)dst, address); } -#endif // WTF_CPU_X86_64 +#endif // JS_CODEGEN_X64 void movdqu_rm(XMMRegisterID src, int offset, RegisterID base) { @@ -3455,7 +3453,7 @@ public: m_formatter.prefix(PRE_PREDICT_BRANCH_NOT_TAKEN); } -#if WTF_CPU_X86 +#ifdef JS_CODEGEN_X86 void pusha() { spew("pusha"); @@ -3648,7 +3646,7 @@ public: staticSpew("##repatchLoadPtrToLEA ((where=%p))", where); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 // On x86-64 pointer memory accesses require a 64-bit operand, and as such a REX prefix. // Skip over the prefix byte. where = reinterpret_cast(where) + 1; @@ -3660,7 +3658,7 @@ public: { staticSpew("##repatchLEAToLoadPtr ((where=%p))", where); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 // On x86-64 pointer memory accesses require a 64-bit operand, and as such a REX prefix. // Skip over the prefix byte. where = reinterpret_cast(where) + 1; @@ -3762,7 +3760,7 @@ public: // a no-op on x86, but on x64 it asserts that the address is actually // a valid address immediate. static int32_t addressImmediate(const void *address) { -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 // x64's 64-bit addresses don't all fit in the 32-bit immediate. MOZ_ASSERT(isAddressImmediate(address)); #endif @@ -3876,7 +3874,7 @@ private: m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(reg, address); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 void oneByteRipOp(OneByteOpcodeID opcode, int reg, int ripOffset) { m_buffer.ensureSpace(maxInstructionSize); @@ -3977,7 +3975,7 @@ private: memoryModRM(reg, base, offset); } -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 // Quad-word-sized operands: // // Used to format 64-bit operantions, planting a REX.w prefix. @@ -4063,7 +4061,7 @@ private: void oneByteOp8(OneByteOpcodeID opcode, GroupOpcodeID groupOp, RegisterID rm) { -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 MOZ_ASSERT(!byteRegRequiresRex(rm)); #endif m_buffer.ensureSpace(maxInstructionSize); @@ -4083,7 +4081,7 @@ private: void oneByteOp8(OneByteOpcodeID opcode, int reg, RegisterID base, int offset) { -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 MOZ_ASSERT(!byteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); @@ -4094,7 +4092,7 @@ private: void oneByteOp8_disp32(OneByteOpcodeID opcode, int reg, RegisterID base, int offset) { -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 MOZ_ASSERT(!byteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); @@ -4105,7 +4103,7 @@ private: void oneByteOp8(OneByteOpcodeID opcode, int reg, RegisterID base, RegisterID index, int scale, int offset) { -#if !WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X86 MOZ_ASSERT(!byteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); @@ -4181,7 +4179,7 @@ private: void jumpTablePointer(uintptr_t ptr) { m_buffer.ensureSpace(sizeof(uintptr_t)); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 m_buffer.putInt64Unchecked(ptr); #else m_buffer.putIntUnchecked(ptr); @@ -4260,7 +4258,7 @@ private: static const RegisterID noBase = X86Registers::ebp; static const RegisterID hasSib = X86Registers::esp; static const RegisterID noIndex = X86Registers::esp; -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 static const RegisterID noBase2 = X86Registers::r13; static const RegisterID hasSib2 = X86Registers::r12; @@ -4334,7 +4332,7 @@ private: void memoryModRM(int reg, RegisterID base, int offset) { // A base of esp or r12 would be interpreted as a sib, so force a sib with no index & put the base in there. -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 if ((base == hasSib) || (base == hasSib2)) #else if (base == hasSib) @@ -4350,7 +4348,7 @@ private: m_buffer.putIntUnchecked(offset); } } else { -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 if (!offset && (base != noBase) && (base != noBase2)) #else if (!offset && (base != noBase)) @@ -4369,7 +4367,7 @@ private: void memoryModRM_disp32(int reg, RegisterID base, int offset) { // A base of esp or r12 would be interpreted as a sib, so force a sib with no index & put the base in there. -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 if ((base == hasSib) || (base == hasSib2)) #else if (base == hasSib) @@ -4387,7 +4385,7 @@ private: { MOZ_ASSERT(index != noIndex); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 if (!offset && (base != noBase) && (base != noBase2)) #else if (!offset && (base != noBase)) @@ -4428,7 +4426,7 @@ private: { int32_t disp = addressImmediate(address); -#if WTF_CPU_X86_64 +#ifdef JS_CODEGEN_X64 // On x64-64, non-RIP-relative absolute mode requires a SIB. putModRmSib(ModRmMemoryNoDisp, reg, noBase, noIndex, 0); #else @@ -4449,6 +4447,4 @@ private: } // namespace JSC -#endif // ENABLE(ASSEMBLER) && CPU(X86) - #endif /* assembler_assembler_X86Assembler_h */ diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index b70d744d3d7..acb17c74cac 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -268,7 +268,7 @@ MinorGC(JSContext *cx, unsigned argc, jsval *vp) if (args.get(0) == BooleanValue(true)) cx->runtime()->gc.storeBuffer.setAboutToOverflow(); - MinorGC(cx, gcreason::API); + cx->minorGC(gcreason::API); #endif args.rval().setUndefined(); return true; @@ -519,7 +519,7 @@ SelectForGC(JSContext *cx, unsigned argc, Value *vp) * to be in the set, so evict the nursery before adding items. */ JSRuntime *rt = cx->runtime(); - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + rt->gc.evictNursery(); for (unsigned i = 0; i < args.length(); i++) { if (args[i].isObject()) { @@ -628,7 +628,7 @@ GCSlice(JSContext *cx, unsigned argc, Value *vp) limit = false; } - GCDebugSlice(cx->runtime(), limit, budget); + cx->runtime()->gc.gcDebugSlice(limit, budget); args.rval().setUndefined(); return true; } @@ -2062,6 +2062,7 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { " 11: Verify post write barriers between instructions\n" " 12: Verify post write barriers between paints\n" " 13: Check internal hashtables on minor GC\n" +" 14: Always compact arenas after GC\n" " Period specifies that collection happens every n allocations.\n"), JS_FN_HELP("schedulegc", ScheduleGC, 1, 0, diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index b52f6d83551..412cbebf99f 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1126,6 +1126,14 @@ StructTypeDescr::fieldCount() const return getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).toObject().getDenseInitializedLength(); } +size_t +StructTypeDescr::maybeForwardedFieldCount() const +{ + JSObject *fieldNames = + MaybeForwarded(&getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).toObject()); + return fieldNames->getDenseInitializedLength(); +} + bool StructTypeDescr::fieldIndex(jsid id, size_t *out) const { @@ -1157,6 +1165,15 @@ StructTypeDescr::fieldOffset(size_t index) const return SafeCast(fieldOffsets.getDenseElement(index).toInt32()); } +size_t +StructTypeDescr::maybeForwardedFieldOffset(size_t index) const +{ + JSObject &fieldOffsets = + *MaybeForwarded(&getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS).toObject()); + JS_ASSERT(index < fieldOffsets.getDenseInitializedLength()); + return SafeCast(fieldOffsets.getDenseElement(index).toInt32()); +} + SizedTypeDescr& StructTypeDescr::fieldDescr(size_t index) const { @@ -1166,6 +1183,15 @@ StructTypeDescr::fieldDescr(size_t index) const return fieldDescrs.getDenseElement(index).toObject().as(); } +SizedTypeDescr& +StructTypeDescr::maybeForwardedFieldDescr(size_t index) const +{ + JSObject &fieldDescrs = + *MaybeForwarded(&getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_TYPES).toObject()); + JS_ASSERT(index < fieldDescrs.getDenseInitializedLength()); + return fieldDescrs.getDenseElement(index).toObject().as(); +} + /****************************************************************************** * Creating the TypedObject "module" * @@ -1630,7 +1656,11 @@ TypedObject::obj_trace(JSTracer *trace, JSObject *object) JS_ASSERT(object->is()); TypedObject &typedObj = object->as(); - TypeDescr &descr = typedObj.typeDescr(); + + // When this is called for compacting GC, the related objects we touch here + // may not have had their slots updated yet. + TypeDescr &descr = typedObj.maybeForwardedTypeDescr(); + if (descr.opaque()) { uint8_t *mem = typedObj.typedMem(); if (!mem) @@ -3092,7 +3122,7 @@ visitReferences(SizedTypeDescr &descr, case type::SizedArray: { SizedArrayTypeDescr &arrayDescr = descr.as(); - SizedTypeDescr &elementDescr = arrayDescr.elementType(); + SizedTypeDescr &elementDescr = arrayDescr.maybeForwardedElementType(); for (int32_t i = 0; i < arrayDescr.length(); i++) { visitReferences(elementDescr, mem, visitor); mem += elementDescr.size(); @@ -3108,9 +3138,9 @@ visitReferences(SizedTypeDescr &descr, case type::Struct: { StructTypeDescr &structDescr = descr.as(); - for (size_t i = 0; i < structDescr.fieldCount(); i++) { - SizedTypeDescr &descr = structDescr.fieldDescr(i); - size_t offset = structDescr.fieldOffset(i); + for (size_t i = 0; i < structDescr.maybeForwardedFieldCount(); i++) { + SizedTypeDescr &descr = structDescr.maybeForwardedFieldDescr(i); + size_t offset = structDescr.maybeForwardedFieldOffset(i); visitReferences(descr, mem + offset, visitor); } return; diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index bfc59e0d4f0..78cbcfbbcf1 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -169,6 +169,10 @@ class TypedProto : public JSObject return getReservedSlot(JS_TYPROTO_SLOT_DESCR).toObject().as(); } + TypeDescr &maybeForwardedTypeDescr() const { + return MaybeForwarded(&getReservedSlot(JS_TYPROTO_SLOT_DESCR).toObject())->as(); + } + inline type::Kind kind() const; }; @@ -453,6 +457,11 @@ class SizedArrayTypeDescr : public ComplexTypeDescr return getReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE).toObject().as(); } + SizedTypeDescr &maybeForwardedElementType() const { + JSObject *elemType = &getReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE).toObject(); + return MaybeForwarded(elemType)->as(); + } + int32_t length() const { return getReservedSlot(JS_DESCR_SLOT_SIZED_ARRAY_LENGTH).toInt32(); } @@ -492,6 +501,7 @@ class StructTypeDescr : public ComplexTypeDescr // Returns the number of fields defined in this struct. size_t fieldCount() const; + size_t maybeForwardedFieldCount() const; // Set `*out` to the index of the field named `id` and returns true, // or return false if no such field exists. @@ -502,9 +512,11 @@ class StructTypeDescr : public ComplexTypeDescr // Return the type descr of the field at index `index`. SizedTypeDescr &fieldDescr(size_t index) const; + SizedTypeDescr &maybeForwardedFieldDescr(size_t index) const; // Return the offset of the field at index `index`. size_t fieldOffset(size_t index) const; + size_t maybeForwardedFieldOffset(size_t index) const; }; typedef Handle HandleStructTypeDescr; @@ -678,10 +690,18 @@ class TypedObject : public ArrayBufferViewObject return getProto()->as(); } + TypedProto &maybeForwardedTypedProto() const { + return MaybeForwarded(getProto())->as(); + } + TypeDescr &typeDescr() const { return typedProto().typeDescr(); } + TypeDescr &maybeForwardedTypeDescr() const { + return maybeForwardedTypedProto().maybeForwardedTypeDescr(); + } + uint8_t *typedMem() const { return (uint8_t*) getPrivate(); } diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index a8103385c3c..492ca547671 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -153,7 +153,7 @@ function ToLength(v) { return 0; // Math.pow(2, 53) - 1 = 0x1fffffffffffff - return std_Math_min(v, 0x1fffffffffffff); + return v < 0x1fffffffffffff ? v : 0x1fffffffffffff; } /********** Testing code **********/ diff --git a/js/src/configure.in b/js/src/configure.in index 9399ec1836a..1044b76336c 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -3120,6 +3120,18 @@ if test -n "$JSGC_USE_EXACT_ROOTING"; then AC_DEFINE(JSGC_USE_EXACT_ROOTING) fi +dnl ======================================================== +dnl = Use compacting GC +dnl ======================================================== +dnl Compact the heap by moving GC things when doing a shrinking colletion. +MOZ_ARG_ENABLE_BOOL(gccompacting, +[ --enable-gccompacting Compact the heap by moving GC things], + JSGC_COMPACTING=1, + JSGC_COMPACTING= ) +if test -n "$JSGC_COMPACTING"; then + AC_DEFINE(JSGC_COMPACTING) +fi + dnl ======================================================== dnl = Use a smaller chunk size for GC chunks dnl ======================================================== diff --git a/js/src/devtools/rootAnalysis/loadCallgraph.js b/js/src/devtools/rootAnalysis/loadCallgraph.js index 0afe02e7fe9..08aec6c1fca 100644 --- a/js/src/devtools/rootAnalysis/loadCallgraph.js +++ b/js/src/devtools/rootAnalysis/loadCallgraph.js @@ -161,9 +161,10 @@ function loadCallgraph(file) } for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, int64, uint32, uint32)', - 'void js::MinorGC(JSRuntime*, uint32)' ]) + 'void js::gc::GCRuntime::minorGC(uint32)', + 'void js::gc::GCRuntime::minorGC(uint32)' ]) { - assert(gcName in mangledName); + assert(gcName in mangledName, "GC function not found: " + gcName); addGCFunction(mangledName[gcName], "GC"); } diff --git a/js/src/gc/Barrier.h b/js/src/gc/Barrier.h index 34468f74b29..21bce82f051 100644 --- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -169,6 +169,7 @@ class BaseShape; class DebugScopeObject; class GlobalObject; class LazyScript; +class NestedScopeObject; class Nursery; class ObjectImpl; class PropertyName; @@ -217,6 +218,7 @@ template <> struct MapTypeToTraceKind { static const JSGCTrace template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_SCRIPT; }; template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_STRING; }; template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_LAZY_SCRIPT; }; +template <> struct MapTypeToTraceKind{ static const JSGCTraceKind kind = JSTRACE_OBJECT; }; template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_OBJECT; }; template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_STRING; }; template <> struct MapTypeToTraceKind { static const JSGCTraceKind kind = JSTRACE_OBJECT; }; diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 368f27e4a06..3712af87288 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -131,6 +131,12 @@ struct AutoStopVerifyingBarriers }; #endif /* JS_GC_ZEAL */ +#ifdef JSGC_HASH_TABLE_CHECKS +void +CheckHashTablesAfterMovingGC(JSRuntime *rt); +#endif + + } /* namespace gc */ } /* namespace js */ diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 5379ceb4f4d..91daac090be 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -262,6 +262,11 @@ class GCRuntime bool isHeapMajorCollecting() { return heapState == js::MajorCollecting; } bool isHeapMinorCollecting() { return heapState == js::MinorCollecting; } bool isHeapCollecting() { return isHeapMajorCollecting() || isHeapMinorCollecting(); } +#ifdef JSGC_COMPACTING + bool isHeapCompacting() { return isHeapMajorCollecting() && state() == COMPACT; } +#else + bool isHeapCompacting() { return false; } +#endif // Performance note: if isFJMinorCollecting turns out to be slow because // reading the counter is slow then we may be able to augment the counter @@ -278,14 +283,27 @@ class GCRuntime void maybePeriodicFullGC(); void minorGC(JS::gcreason::Reason reason); void minorGC(JSContext *cx, JS::gcreason::Reason reason); + void evictNursery(JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY) { minorGC(reason); } void gcIfNeeded(JSContext *cx); - void collect(bool incremental, int64_t budget, JSGCInvocationKind gckind, - JS::gcreason::Reason reason); + void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason); void gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0); + void gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason); + void gcDebugSlice(bool limit, int64_t objCount); + void runDebugGC(); inline void poke(); - void markRuntime(JSTracer *trc, bool useSavedRoots = false); + enum TraceOrMarkRuntime { + TraceRuntime, + MarkRuntime + }; + enum TraceRootsOrUsedSaved { + TraceRoots, + UseSavedRoots + }; + void markRuntime(JSTracer *trc, + TraceOrMarkRuntime traceOrMark = TraceRuntime, + TraceRootsOrUsedSaved rootsSource = TraceRoots); void notifyDidPaint(); void shrinkBuffers(); @@ -378,6 +396,11 @@ class GCRuntime void disableGenerationalGC(); void enableGenerationalGC(); +#ifdef JSGC_COMPACTING + void disableCompactingGC(); + void enableCompactingGC(); +#endif + void setGrayRootsTracer(JSTraceDataOp traceOp, void *data); bool addBlackRootsTracer(JSTraceDataOp traceOp, void *data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void *data); @@ -421,7 +444,7 @@ class GCRuntime bool isGcNeeded() { return isNeeded; } double computeHeapGrowthFactor(size_t lastBytes); - size_t computeTriggerBytes(double growthFactor, size_t lastBytes, JSGCInvocationKind gckind); + size_t computeTriggerBytes(double growthFactor, size_t lastBytes); JSGCMode gcMode() const { return mode; } void setGCMode(JSGCMode m) { @@ -465,13 +488,14 @@ class GCRuntime bool initZeal(); void requestInterrupt(JS::gcreason::Reason reason); + void collect(bool incremental, int64_t budget, JSGCInvocationKind gckind, + JS::gcreason::Reason reason); bool gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind, JS::gcreason::Reason reason); gcstats::ZoneGCStats scanZonesBeforeGC(); void budgetIncrementalGC(int64_t *budget); void resetIncrementalGC(const char *reason); - void incrementalCollectSlice(int64_t budget, JS::gcreason::Reason reason, - JSGCInvocationKind gckind); + void incrementalCollectSlice(int64_t budget, JS::gcreason::Reason reason); void pushZealSelectedObjects(); bool beginMarkPhase(JS::gcreason::Reason reason); bool shouldPreserveJITCode(JSCompartment *comp, int64_t currentTime, @@ -480,8 +504,11 @@ class GCRuntime bool drainMarkStack(SliceBudget &sliceBudget, gcstats::Phase phase); template void markWeakReferences(gcstats::Phase phase); void markWeakReferencesInCurrentGroup(gcstats::Phase phase); - template void markGrayReferences(); - void markGrayReferencesInCurrentGroup(); + template void markGrayReferences(gcstats::Phase phase); + void markGrayReferencesInCurrentGroup(gcstats::Phase phase); + void markAllWeakReferences(gcstats::Phase phase); + void markAllGrayReferences(gcstats::Phase phase); + void beginSweepPhase(bool lastGC); void findZoneGroups(); bool findZoneEdgesForWeakMaps(); @@ -491,13 +518,21 @@ class GCRuntime bool shouldReleaseObservedTypes(); void endSweepingZoneGroup(); bool sweepPhase(SliceBudget &sliceBudget); - void endSweepPhase(JSGCInvocationKind gckind, bool lastGC); + void endSweepPhase(bool lastGC); void sweepZones(FreeOp *fop, bool lastGC); void decommitArenasFromAvailableList(Chunk **availableListHeadp); void decommitArenas(); void expireChunksAndArenas(bool shouldShrink); void sweepBackgroundThings(bool onBackgroundThread); void assertBackgroundSweepingFinished(); + bool shouldCompact(); +#ifdef JSGC_COMPACTING + void compactPhase(); + ArenaHeader *relocateArenas(); + void updatePointersToRelocatedCells(); + void releaseRelocatedArenas(ArenaHeader *relocatedList); +#endif + void finishCollection(); void computeNonIncrementalMarkingForValidation(); void validateIncrementalMarking(); @@ -507,8 +542,6 @@ class GCRuntime #ifdef DEBUG void checkForCompartmentMismatches(); - void markAllWeakReferences(gcstats::Phase phase); - void markAllGrayReferences(); #endif public: @@ -607,6 +640,9 @@ class GCRuntime /* Whether all compartments are being collected in first GC slice. */ bool isFull; + /* The invocation kind of the current GC, taken from the first slice. */ + JSGCInvocationKind invocationKind; + /* The reason that an interrupt-triggered GC should be called. */ JS::gcreason::Reason triggerReason; @@ -682,6 +718,15 @@ class GCRuntime */ unsigned generationalDisabled; +#ifdef JSGC_COMPACTING + /* + * Some code cannot tolerate compacting GC so it can be disabled with this + * counter. This can happen from code executing in a ThreadSafeContext so + * we make it atomic. + */ + mozilla::Atomic compactingDisabled; +#endif + /* * This is true if we are in the middle of a brain transplant (e.g., * JS_TransplantObject) or some other operation that can manipulate @@ -827,7 +872,8 @@ GCRuntime::needZealousGC() { if (zealMode == ZealAllocValue || zealMode == ZealGenerationalGCValue || (zealMode >= ZealIncrementalRootsThenFinish && - zealMode <= ZealIncrementalMultipleSlices)) + zealMode <= ZealIncrementalMultipleSlices) || + zealMode == ZealCompactValue) { nextScheduled = zealFrequency; } diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index d3444b1f9bd..0758d12b7d9 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -102,6 +102,7 @@ struct Cell MOZ_ALWAYS_INLINE bool isMarked(uint32_t color = BLACK) const; MOZ_ALWAYS_INLINE bool markIfUnmarked(uint32_t color = BLACK) const; MOZ_ALWAYS_INLINE void unmark(uint32_t color) const; + MOZ_ALWAYS_INLINE void copyMarkBitsFrom(const Cell *src); inline JSRuntime *runtimeFromMainThread() const; inline JS::shadow::Runtime *shadowRuntimeFromMainThread() const; @@ -761,6 +762,12 @@ struct ChunkBitmap *word &= ~mask; } + MOZ_ALWAYS_INLINE void copyMarkBit(Cell *dst, const Cell *src, uint32_t color) { + uintptr_t *word, mask; + getMarkWordAndMask(dst, color, &word, &mask); + *word = (*word & ~mask) | (src->isMarked(color) ? mask : 0); + } + void clear() { memset((void *)bitmap, 0, sizeof(bitmap)); } @@ -1112,6 +1119,16 @@ Cell::unmark(uint32_t color) const chunk()->bitmap.unmark(this, color); } +void +Cell::copyMarkBitsFrom(const Cell *src) +{ + JS_ASSERT(isTenured()); + JS_ASSERT(src->isTenured()); + ChunkBitmap &bitmap = chunk()->bitmap; + bitmap.copyMarkBit(this, src, BLACK); + bitmap.copyMarkBit(this, src, GRAY); +} + JS::Zone * Cell::tenuredZone() const { diff --git a/js/src/gc/Iteration.cpp b/js/src/gc/Iteration.cpp index c6e799580e9..0c2e31fce44 100644 --- a/js/src/gc/Iteration.cpp +++ b/js/src/gc/Iteration.cpp @@ -23,7 +23,7 @@ js::TraceRuntime(JSTracer *trc) JS_ASSERT(!IS_GC_MARKING_TRACER(trc)); JSRuntime *rt = trc->runtime(); - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + rt->gc.evictNursery(); AutoPrepareForTracing prep(rt, WithAtoms); rt->gc.markRuntime(trc); } @@ -93,7 +93,7 @@ void js::IterateScripts(JSRuntime *rt, JSCompartment *compartment, void *data, IterateScriptCallback scriptCallback) { - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + rt->gc.evictNursery(); AutoPrepareForTracing prep(rt, SkipAtoms); if (compartment) { @@ -113,7 +113,7 @@ js::IterateScripts(JSRuntime *rt, JSCompartment *compartment, void js::IterateGrayObjects(Zone *zone, GCThingCallback cellCallback, void *data) { - MinorGC(zone->runtimeFromMainThread(), JS::gcreason::EVICT_NURSERY); + zone->runtimeFromMainThread()->gc.evictNursery(); AutoPrepareForTracing prep(zone->runtimeFromMainThread(), SkipAtoms); for (size_t finalizeKind = 0; finalizeKind <= FINALIZE_OBJECT_LAST; finalizeKind++) { diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 073704606d6..583361534b4 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -164,6 +164,10 @@ CheckMarkedThing(JSTracer *trc, T **thingp) T *thing = *thingp; JS_ASSERT(*thingp); +#ifdef JSGC_COMPACTING + thing = MaybeForwarded(thing); +#endif + # ifdef JSGC_FJGENERATIONAL /* * The code below (runtimeFromMainThread(), etc) makes assumptions @@ -442,6 +446,10 @@ IsMarked(T **thingp) Zone *zone = (*thingp)->tenuredZone(); if (!zone->isCollecting() || zone->isGCFinished()) return true; +#ifdef JSGC_COMPACTING + if (zone->isGCCompacting() && IsForwarded(*thingp)) + *thingp = Forwarded(*thingp); +#endif return (*thingp)->isMarked(); } @@ -480,19 +488,27 @@ IsAboutToBeFinalized(T **thingp) } #endif // JSGC_GENERATIONAL - if (!thing->tenuredZone()->isGCSweeping()) + Zone *zone = thing->tenuredZone(); + if (zone->isGCSweeping()) { + /* + * We should return false for things that have been allocated during + * incremental sweeping, but this possibility doesn't occur at the moment + * because this function is only called at the very start of the sweeping a + * compartment group and during minor gc. Rather than do the extra check, + * we just assert that it's not necessary. + */ + JS_ASSERT_IF(!rt->isHeapMinorCollecting(), !thing->arenaHeader()->allocatedDuringIncremental); + + return !thing->isMarked(); + } +#ifdef JSGC_COMPACTING + else if (zone->isGCCompacting() && IsForwarded(thing)) { + *thingp = Forwarded(thing); return false; + } +#endif - /* - * We should return false for things that have been allocated during - * incremental sweeping, but this possibility doesn't occur at the moment - * because this function is only called at the very start of the sweeping a - * compartment group and during minor gc. Rather than do the extra check, - * we just assert that it's not necessary. - */ - JS_ASSERT_IF(!rt->isHeapMinorCollecting(), !thing->arenaHeader()->allocatedDuringIncremental); - - return !thing->isMarked(); + return false; } template @@ -500,21 +516,32 @@ T * UpdateIfRelocated(JSRuntime *rt, T **thingp) { JS_ASSERT(thingp); + if (!*thingp) + return nullptr; + #ifdef JSGC_GENERATIONAL + #ifdef JSGC_FJGENERATIONAL - if (*thingp && rt->isFJMinorCollecting()) { + if (rt->isFJMinorCollecting()) { ForkJoinContext *ctx = ForkJoinContext::current(); ForkJoinNursery &nursery = ctx->nursery(); if (nursery.isInsideFromspace(*thingp)) nursery.getForwardedPointer(thingp); + return *thingp; } - else #endif - { - if (*thingp && rt->isHeapMinorCollecting() && IsInsideNursery(*thingp)) - rt->gc.nursery.getForwardedPointer(thingp); + + if (rt->isHeapMinorCollecting() && IsInsideNursery(*thingp)) { + rt->gc.nursery.getForwardedPointer(thingp); + return *thingp; } #endif // JSGC_GENERATIONAL + +#ifdef JSGC_COMPACTING + Zone *zone = (*thingp)->tenuredZone(); + if (zone->isGCCompacting() && IsForwarded(*thingp)) + *thingp = Forwarded(*thingp); +#endif return *thingp; } @@ -602,6 +629,7 @@ DeclMarkerImpl(Object, DebugScopeObject) DeclMarkerImpl(Object, GlobalObject) DeclMarkerImpl(Object, JSObject) DeclMarkerImpl(Object, JSFunction) +DeclMarkerImpl(Object, NestedScopeObject) DeclMarkerImpl(Object, ObjectImpl) DeclMarkerImpl(Object, SavedFrame) DeclMarkerImpl(Object, ScopeObject) diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 440b33261a0..ade9c5df0ee 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -23,6 +23,7 @@ class DebugScopeObject; class GCMarker; class GlobalObject; class LazyScript; +class NestedScopeObject; class SavedFrame; class ScopeObject; class Shape; @@ -112,6 +113,7 @@ DeclMarker(Object, DebugScopeObject) DeclMarker(Object, GlobalObject) DeclMarker(Object, JSObject) DeclMarker(Object, JSFunction) +DeclMarker(Object, NestedScopeObject) DeclMarker(Object, SavedFrame) DeclMarker(Object, ScopeObject) DeclMarker(Script, JSScript) diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index ff927c98542..64faf27c198 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -722,23 +722,6 @@ js::Nursery::MinorGCCallback(JSTracer *jstrc, void **thingp, JSGCTraceKind kind) *thingp = trc->nursery->moveToTenured(trc, static_cast(*thingp)); } -static void -CheckHashTablesAfterMovingGC(JSRuntime *rt) -{ -#ifdef JS_GC_ZEAL - if (rt->gcZeal() == ZealCheckHashTablesOnMinorGC) { - /* Check that internal hash tables no longer have any pointers into the nursery. */ - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - c->checkNewTypeObjectTableAfterMovingGC(); - c->checkInitialShapesTableAfterMovingGC(); - c->checkWrapperMapAfterMovingGC(); - if (c->debugScopes) - c->debugScopes->checkHashTablesAfterMovingGC(rt); - } - } -#endif -} - #ifdef PROFILE_NURSERY #define TIME_START(name) int64_t timstampStart_##name = PRMJ_Now() #define TIME_END(name) int64_t timstampEnd_##name = PRMJ_Now() @@ -810,7 +793,10 @@ js::Nursery::collect(JSRuntime *rt, JS::gcreason::Reason reason, TypeObjectList TIME_END(markGenericEntries); TIME_START(checkHashTables); - CheckHashTablesAfterMovingGC(rt); +#ifdef JS_GC_ZEAL + if (rt->gcZeal() == ZealCheckHashTablesOnMinorGC) + CheckHashTablesAfterMovingGC(rt); +#endif TIME_END(checkHashTables); TIME_START(markRuntime); diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 62df4ff4cb4..5949fe798ef 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -707,13 +707,17 @@ js::gc::MarkForkJoinStack(ForkJoinNurseryCollectionTracer *trc) #endif // JSGC_FJGENERATIONAL void -js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) +js::gc::GCRuntime::markRuntime(JSTracer *trc, + TraceOrMarkRuntime traceOrMark, + TraceRootsOrUsedSaved rootsSource) { JS_ASSERT(trc->callback != GCMarker::GrayCallback); + JS_ASSERT(traceOrMark == TraceRuntime || traceOrMark == MarkRuntime); + JS_ASSERT(rootsSource == TraceRoots || rootsSource == UseSavedRoots); JS_ASSERT(!rt->mainThread.suppressGC); - if (IS_GC_MARKING_TRACER(trc)) { + if (traceOrMark == MarkRuntime) { for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { if (!c->zone()->isCollecting()) c->markCrossCompartmentWrappers(trc); @@ -727,7 +731,7 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) #ifdef JSGC_USE_EXACT_ROOTING MarkExactStackRoots(rt, trc); #else - markConservativeStackRoots(trc, useSavedRoots); + markConservativeStackRoots(trc, rootsSource == UseSavedRoots); #endif rt->markSelfHostingGlobal(trc); } @@ -760,7 +764,7 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) } if (!rt->isBeingDestroyed() && !trc->runtime()->isHeapMinorCollecting()) { - if (!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting()) { + if (traceOrMark == TraceRuntime || rt->atomsCompartment()->zone()->isCollecting()) { MarkPermanentAtoms(trc); MarkAtoms(trc); MarkWellKnownSymbols(trc); @@ -772,7 +776,7 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) acx->mark(trc); for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { - if (IS_GC_MARKING_TRACER(trc) && !zone->isCollecting()) + if (traceOrMark == MarkRuntime && !zone->isCollecting()) continue; /* Do not discard scripts with counts while profiling. */ @@ -792,11 +796,11 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) if (trc->runtime()->isHeapMinorCollecting()) c->globalWriteBarriered = false; - if (IS_GC_MARKING_TRACER(trc) && !c->zone()->isCollecting()) + if (traceOrMark == MarkRuntime && !c->zone()->isCollecting()) continue; /* During a GC, these are treated as weak pointers. */ - if (!IS_GC_MARKING_TRACER(trc)) { + if (traceOrMark == TraceRuntime) { if (c->watchpointMap) c->watchpointMap->markAll(trc); } @@ -812,9 +816,9 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) if (!isHeapMinorCollecting()) { /* - * All JSCompartment::mark does is mark the globals for compartments - * which have been entered. Globals aren't nursery allocated so there's - * no need to do this for minor GCs. + * All JSCompartment::markRoots() does is mark the globals for + * compartments which have been entered. Globals aren't nursery + * allocated so there's no need to do this for minor GCs. */ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) c->markRoots(trc); @@ -833,7 +837,7 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) /* During GC, we don't mark gray roots at this stage. */ if (JSTraceDataOp op = grayRootTracer.op) { - if (!IS_GC_MARKING_TRACER(trc)) + if (traceOrMark == TraceRuntime) (*op)(trc, grayRootTracer.data); } } diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index c8789f6ca5c..8bd0896a2db 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -307,6 +307,10 @@ static const PhaseInfo phases[] = { { PHASE_SWEEP_SCRIPT, "Sweep Script", PHASE_SWEEP }, { PHASE_SWEEP_SHAPE, "Sweep Shape", PHASE_SWEEP }, { PHASE_SWEEP_JITCODE, "Sweep JIT code", PHASE_SWEEP }, + { PHASE_COMPACT, "Compact", PHASE_NO_PARENT }, + { PHASE_COMPACT_MOVE, "Compact Move", PHASE_COMPACT }, + { PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT, }, + { PHASE_COMPACT_UPDATE_GRAY, "Compact Update Gray", PHASE_COMPACT_UPDATE, }, { PHASE_FINALIZE_END, "Finalize End Callback", PHASE_SWEEP }, { PHASE_DESTROY, "Deallocate", PHASE_SWEEP }, { PHASE_GC_END, "End Callback", PHASE_NO_PARENT }, diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index f211004b7c7..258119064d0 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -58,6 +58,10 @@ enum Phase { PHASE_SWEEP_SCRIPT, PHASE_SWEEP_SHAPE, PHASE_SWEEP_JITCODE, + PHASE_COMPACT, + PHASE_COMPACT_MOVE, + PHASE_COMPACT_UPDATE, + PHASE_COMPACT_UPDATE_GRAY, PHASE_FINALIZE_END, PHASE_DESTROY, PHASE_GC_END, @@ -227,17 +231,16 @@ struct AutoPhase struct MaybeAutoPhase { - explicit MaybeAutoPhase(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + explicit MaybeAutoPhase(Statistics &statsArg, bool condition, Phase phaseArg + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : stats(nullptr) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } - void construct(Statistics &statsArg, Phase phaseArg) - { - JS_ASSERT(!stats); - stats = &statsArg; - phase = phaseArg; - stats->beginPhase(phase); + if (condition) { + stats = &statsArg; + phase = phaseArg; + stats->beginPhase(phase); + } } ~MaybeAutoPhase() { if (stats) diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index f5c9d6761da..93e53176a8f 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -554,9 +554,8 @@ GCMarker::markDelayedChildren(ArenaHeader *aheader) bool GCMarker::markDelayedChildren(SliceBudget &budget) { - gcstats::MaybeAutoPhase ap; - if (runtime()->gc.state() == MARK) - ap.construct(runtime()->gc.stats, gcstats::PHASE_MARK_DELAYED); + GCRuntime &gc = runtime()->gc; + gcstats::MaybeAutoPhase ap(gc.stats, gc.state() == MARK, gcstats::PHASE_MARK_DELAYED); JS_ASSERT(unmarkedArenaStackTop); do { @@ -632,16 +631,13 @@ void GCMarker::markBufferedGrayRoots(JS::Zone *zone) { JS_ASSERT(grayBufferState == GRAY_BUFFER_OK); - JS_ASSERT(zone->isGCMarkingGray()); + JS_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting()); for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { #ifdef DEBUG setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); #endif - void *tmp = elem->thing; - setTracingLocation((void *)&elem->thing); - MarkKind(this, &tmp, elem->kind); - JS_ASSERT(tmp == elem->thing); + MarkKind(this, &elem->thing, elem->kind); } } diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index 5499a53b6f7..9bbe39a25f4 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -182,7 +182,7 @@ gc::GCRuntime::startVerifyPreBarriers() if (verifyPostData) return; - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + evictNursery(); AutoPrepareForTracing prep(rt, WithAtoms); @@ -410,7 +410,7 @@ gc::GCRuntime::startVerifyPostBarriers() return; } - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + evictNursery(); number++; diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index ad261345639..3e949df80ca 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -97,8 +97,10 @@ Zone::setGCMaxMallocBytes(size_t value) void Zone::onTooMuchMalloc() { - if (!gcMallocGCTriggered) - gcMallocGCTriggered = TriggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC); + if (!gcMallocGCTriggered) { + GCRuntime &gc = runtimeFromAnyThread()->gc; + gcMallocGCTriggered = gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC); + } } void @@ -111,15 +113,21 @@ Zone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) if (active) releaseTypes = false; + GCRuntime &gc = fop->runtime()->gc; + { - gcstats::AutoPhase ap(fop->runtime()->gc.stats, gcstats::PHASE_DISCARD_ANALYSIS); + gcstats::MaybeAutoPhase ap(gc.stats, !gc.isHeapCompacting(), + gcstats::PHASE_DISCARD_ANALYSIS); types.sweep(fop, releaseTypes, oom); } - if (!fop->runtime()->debuggerList.isEmpty()) + if (!fop->runtime()->debuggerList.isEmpty()) { + gcstats::MaybeAutoPhase ap1(gc.stats, !gc.isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES); + gcstats::MaybeAutoPhase ap2(gc.stats, !gc.isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_BREAKPOINT); sweepBreakpoints(fop); - - active = false; + } } void @@ -130,13 +138,10 @@ Zone::sweepBreakpoints(FreeOp *fop) * to iterate over the scripts belonging to a single compartment in a zone. */ - gcstats::AutoPhase ap1(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_TABLES); - gcstats::AutoPhase ap2(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_TABLES_BREAKPOINT); - - JS_ASSERT(isGCSweeping()); + JS_ASSERT(isGCSweepingOrCompacting()); for (ZoneCellIterUnderGC i(this, FINALIZE_SCRIPT); !i.done(); i.next()) { JSScript *script = i.get(); - JS_ASSERT(script->zone()->isGCSweeping()); + JS_ASSERT_IF(isGCSweeping(), script->zone()->isGCSweeping()); if (!script->hasAnyBreakpointsOrStepMode()) continue; @@ -151,7 +156,8 @@ Zone::sweepBreakpoints(FreeOp *fop) for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) { nextbp = bp->nextInSite(); HeapPtrObject &dbgobj = bp->debugger->toJSObjectRef(); - JS_ASSERT_IF(dbgobj->zone()->isCollecting(), dbgobj->zone()->isGCSweeping()); + JS_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(), + dbgobj->zone()->isGCSweeping()); bool dying = scriptGone || IsObjectAboutToBeFinalized(&dbgobj); JS_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef())); if (dying) diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 0f3318da4cd..1749052518d 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -175,7 +175,8 @@ struct Zone : public JS::shadow::Zone, Mark, MarkGray, Sweep, - Finished + Finished, + Compact }; void setGCState(GCState state) { JS_ASSERT(runtimeFromMainThread()->isHeapBusy()); @@ -193,7 +194,8 @@ struct Zone : public JS::shadow::Zone, // If this returns true, all object tracing must be done with a GC marking // tracer. bool requireGCTracer() const { - return runtimeFromMainThread()->isHeapMajorCollecting() && gcState_ != NoGC; + JSRuntime *rt = runtimeFromMainThread(); + return rt->isHeapMajorCollecting() && !rt->isHeapCompacting() && gcState_ != NoGC; } bool isGCMarking() { @@ -208,6 +210,8 @@ struct Zone : public JS::shadow::Zone, bool isGCMarkingGray() { return gcState_ == MarkGray; } bool isGCSweeping() { return gcState_ == Sweep; } bool isGCFinished() { return gcState_ == Finished; } + bool isGCCompacting() { return gcState_ == Compact; } + bool isGCSweepingOrCompacting() { return gcState_ == Sweep || gcState_ == Compact; } // Get a number that is incremented whenever this zone is collected, and // possibly at other times too. diff --git a/js/src/jit-test/tests/ion/bug1053074.js b/js/src/jit-test/tests/ion/bug1053074.js new file mode 100644 index 00000000000..8a9cf78b418 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1053074.js @@ -0,0 +1,12 @@ +function f(y) { + return 2147483648 < y >>> 0 +} +assertEq(f(0), false); +assertEq(f(-8), true); + +function g(y) { + var t = Math.floor(y); + return 2147483648 < t+2; +} +assertEq(g(0), false) +assertEq(g(2147483647), true) diff --git a/js/src/jit/BaselineDebugModeOSR.cpp b/js/src/jit/BaselineDebugModeOSR.cpp index c118ce94ef8..6472f8f1bf6 100644 --- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -658,7 +658,7 @@ jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx, JSCompartment *c #ifdef JSGC_GENERATIONAL // Scripts can entrain nursery things. See note in js::ReleaseAllJITCode. if (!entries.empty()) - MinorGC(cx->runtime(), JS::gcreason::EVICT_NURSERY); + cx->runtime()->gc.evictNursery(); #endif // When the profiler is enabled, we need to suppress sampling from here until diff --git a/js/src/jit/ExecutableAllocator.h b/js/src/jit/ExecutableAllocator.h index 2a30cfe5134..a8aabf9abd8 100644 --- a/js/src/jit/ExecutableAllocator.h +++ b/js/src/jit/ExecutableAllocator.h @@ -402,7 +402,7 @@ public: #elif defined(JS_CODEGEN_MIPS) static void cacheFlush(void* code, size_t size) { -#if WTF_COMPILER_GCC && (GCC_VERSION >= 40300) +#if defined(__GNUC__) && (GCC_VERSION >= 40300) #if WTF_MIPS_ISA_REV(2) && (GCC_VERSION < 40403) int lineSize; asm("rdhwr %0, $1" : "=r" (lineSize)); @@ -425,9 +425,7 @@ public: _flush_cache(reinterpret_cast(code), size, BCACHE); #endif } -#elif WTF_CPU_ARM_TRADITIONAL && WTF_OS_LINUX && WTF_COMPILER_RVCT - static __asm void cacheFlush(void* code, size_t size); -#elif WTF_CPU_ARM_TRADITIONAL && (WTF_OS_LINUX || WTF_OS_ANDROID) && WTF_COMPILER_GCC +#elif WTF_CPU_ARM_TRADITIONAL && (defined(__linux__) || defined(ANDROID)) && defined(__GNUC__) static void cacheFlush(void* code, size_t size) { asm volatile ( diff --git a/js/src/jit/ExecutableAllocatorPosix.cpp b/js/src/jit/ExecutableAllocatorPosix.cpp index b2ede9e3c49..1f37b34307e 100644 --- a/js/src/jit/ExecutableAllocatorPosix.cpp +++ b/js/src/jit/ExecutableAllocatorPosix.cpp @@ -75,21 +75,6 @@ void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSe } #endif -#if WTF_CPU_ARM_TRADITIONAL && WTF_OS_LINUX && WTF_COMPILER_RVCT -__asm void ExecutableAllocator::cacheFlush(void* code, size_t size) -{ - ARM - push {r7} - add r1, r1, r0 - mov r7, #0xf0000 - add r7, r7, #0x2 - mov r2, #0x0 - svc #0x0 - pop {r7} - bx lr -} -#endif - void ExecutablePool::toggleAllCodeAsAccessible(bool accessible) { diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 2cffdbaaac8..0c7b897b922 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -1093,64 +1093,31 @@ IonBuilder::inlineMathFRound(CallInfo &callInfo) IonBuilder::InliningStatus IonBuilder::inlineMathMinMax(CallInfo &callInfo, bool max) { - if (callInfo.argc() < 1 || callInfo.constructing()) + if (callInfo.argc() < 2 || callInfo.constructing()) return InliningStatus_NotInlined; MIRType returnType = getInlineReturnType(); if (!IsNumberType(returnType)) return InliningStatus_NotInlined; - MDefinitionVector int32Cases(alloc()); for (unsigned i = 0; i < callInfo.argc(); i++) { - MDefinition *arg = callInfo.getArg(i); - - switch (arg->type()) { - case MIRType_Int32: - if (!int32Cases.append(arg)) - return InliningStatus_Error; - break; - case MIRType_Double: - case MIRType_Float32: - // Don't force a double MMinMax for arguments that would be a NOP - // when doing an integer MMinMax. - // If the argument is NaN, the conditions below will be false and - // a double comparison is forced. - if (arg->isConstant()) { - double d = arg->toConstant()->value().toDouble(); - // min(int32, d >= INT32_MAX) = int32 - if (d >= INT32_MAX && !max) - break; - // max(int32, d <= INT32_MIN) = int32 - if (d <= INT32_MIN && max) - break; - } - - // Force double MMinMax if result would be different. - returnType = MIRType_Double; - break; - default: + MIRType argType = callInfo.getArg(i)->type(); + if (!IsNumberType(argType)) return InliningStatus_NotInlined; - } - } - if (int32Cases.length() == 0) - returnType = MIRType_Double; + // When one of the arguments is double, do a double MMinMax. + if (returnType == MIRType_Int32 && IsFloatingPointType(argType)) + returnType = MIRType_Double; + } callInfo.setImplicitlyUsedUnchecked(); - MDefinitionVector &cases = (returnType == MIRType_Int32) ? int32Cases : callInfo.argv(); - - if (cases.length() == 1) { - current->push(cases[0]); - return InliningStatus_Inlined; - } - // Chain N-1 MMinMax instructions to compute the MinMax. - MMinMax *last = MMinMax::New(alloc(), cases[0], cases[1], returnType, max); + MMinMax *last = MMinMax::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), returnType, max); current->add(last); - for (unsigned i = 2; i < cases.length(); i++) { - MMinMax *ins = MMinMax::New(alloc(), last, cases[2], returnType, max); + for (unsigned i = 2; i < callInfo.argc(); i++) { + MMinMax *ins = MMinMax::New(alloc(), last, callInfo.getArg(i), returnType, max); current->add(ins); last = ins; } diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 39874ca4054..b013bd32f8e 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -1559,29 +1559,6 @@ MBinaryArithInstruction::trySpecializeFloat32(TempAllocator &alloc) setResultType(MIRType_Float32); } -MDefinition * -MMinMax::foldsTo(TempAllocator &alloc) -{ - if (!lhs()->isConstant() && !rhs()->isConstant()) - return this; - - MDefinition *operand = lhs()->isConstant() ? rhs() : lhs(); - MConstant *constant = lhs()->isConstant() ? lhs()->toConstant() : rhs()->toConstant(); - - if (operand->isToDouble() && operand->getOperand(0)->type() == MIRType_Int32) { - const js::Value &val = constant->value(); - - // min(int32, d >= INT32_MAX) = int32 - if (val.isDouble() && val.toDouble() >= INT32_MAX && !isMax()) - return operand; - - // max(int32, d <= INT32_MIN) = int32 - if (val.isDouble() && val.toDouble() <= INT32_MIN && isMax()) - return operand; - } - return this; -} - bool MAbs::fallible() const { @@ -2675,65 +2652,6 @@ MCompare::evaluateConstantOperands(bool *result) MDefinition *left = getOperand(0); MDefinition *right = getOperand(1); - if (compareType() == Compare_Double) { - // Optimize "MCompare MConstant (MToDouble SomethingInInt32Range). - // In most cases the MToDouble was added, because the constant is - // a double. e.g. v < 9007199254740991, - // where v is an int32 so the result is always true. - if (!lhs()->isConstant() && !rhs()->isConstant()) - return false; - - MDefinition *operand = left->isConstant() ? right : left; - MConstant *constant = left->isConstant() ? left->toConstant() : right->toConstant(); - JS_ASSERT(constant->value().isDouble()); - double d = constant->value().toDouble(); - - if (operand->isToDouble() && operand->getOperand(0)->type() == MIRType_Int32) { - switch (jsop_) { - case JSOP_LT: - if (d > INT32_MAX || d < INT32_MIN) { - *result = !((constant == lhs()) ^ (d < INT32_MIN)); - return true; - } - break; - case JSOP_LE: - if (d >= INT32_MAX || d <= INT32_MIN) { - *result = !((constant == lhs()) ^ (d <= INT32_MIN)); - return true; - } - break; - case JSOP_GT: - if (d > INT32_MAX || d < INT32_MIN) { - *result = !((constant == rhs()) ^ (d < INT32_MIN)); - return true; - } - break; - case JSOP_GE: - if (d >= INT32_MAX || d <= INT32_MIN) { - *result = !((constant == rhs()) ^ (d <= INT32_MIN)); - return true; - } - break; - case JSOP_STRICTEQ: // Fall through. - case JSOP_EQ: - if (d > INT32_MAX || d < INT32_MIN) { - *result = false; - return true; - } - break; - case JSOP_STRICTNE: // Fall through. - case JSOP_NE: - if (d > INT32_MAX || d < INT32_MIN) { - *result = true; - return true; - } - break; - default: - MOZ_ASSUME_UNREACHABLE("Unexpected op."); - } - } - } - if (!left->isConstant() || !right->isConstant()) return false; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 66b1adc6b0b..2ff672bf9e6 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4410,7 +4410,6 @@ class MMinMax AliasSet getAliasSet() const { return AliasSet::None(); } - MDefinition *foldsTo(TempAllocator &alloc); void computeRange(TempAllocator &alloc); bool writeRecoverData(CompactBufferWriter &writer) const; bool canRecoverOnBailout() const { diff --git a/js/src/jit/arm/Architecture-arm.cpp b/js/src/jit/arm/Architecture-arm.cpp index f70eed5bf1e..13c8fc06b76 100644 --- a/js/src/jit/arm/Architecture-arm.cpp +++ b/js/src/jit/arm/Architecture-arm.cpp @@ -167,7 +167,7 @@ uint32_t GetARMFlags() flags = HWCAP_ARMv7 | HWCAP_VFP | HWCAP_VFPv3 | HWCAP_VFPv4 | HWCAP_NEON; #else -#if defined(WTF_OS_LINUX) || defined(WTF_OS_ANDROID) || defined(MOZ_B2G) +#if defined(__linux__) || defined(ANDROID) || defined(MOZ_B2G) bool readAuxv = false; int fd = open("/proc/self/auxv", O_RDONLY); if (fd > 0) { diff --git a/js/src/jit/arm/Assembler-arm.cpp b/js/src/jit/arm/Assembler-arm.cpp index 220486b56ee..b519dacf3b9 100644 --- a/js/src/jit/arm/Assembler-arm.cpp +++ b/js/src/jit/arm/Assembler-arm.cpp @@ -795,36 +795,49 @@ Assembler::TraceJumpRelocations(JSTracer *trc, JitCode *code, CompactBufferReade } } +template static void -TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader) +TraceOneDataRelocation(JSTracer *trc, Iter *iter, MacroAssemblerARM *masm) +{ + Instruction *ins = iter->cur(); + Register dest; + Assembler::RelocStyle rs; + const void *prior = Assembler::GetPtr32Target(iter, &dest, &rs); + void *ptr = const_cast(prior); + + // No barrier needed since these are constants. + gc::MarkGCThingUnbarriered(trc, &ptr, "ion-masm-ptr"); + + if (ptr != prior) + masm->ma_movPatchable(Imm32(int32_t(ptr)), dest, Assembler::Always, rs, ins); +} + +static void +TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader, + MacroAssemblerARM *masm) { while (reader.more()) { size_t offset = reader.readUnsigned(); InstructionIterator iter((Instruction*)(buffer + offset)); - void *ptr = const_cast(Assembler::GetPtr32Target(&iter)); - // No barrier needed since these are constants. - gc::MarkGCThingUnbarriered(trc, reinterpret_cast(&ptr), "ion-masm-ptr"); + TraceOneDataRelocation(trc, &iter, masm); } - } + static void TraceDataRelocations(JSTracer *trc, ARMBuffer *buffer, - Vector *locs) + Vector *locs, MacroAssemblerARM *masm) { for (unsigned int idx = 0; idx < locs->length(); idx++) { BufferOffset bo = (*locs)[idx]; ARMBuffer::AssemblerBufferInstIterator iter(bo, buffer); - void *ptr = const_cast(Assembler::GetPtr32Target(&iter)); - - // No barrier needed since these are constants. - gc::MarkGCThingUnbarriered(trc, reinterpret_cast(&ptr), "ion-masm-ptr"); + TraceOneDataRelocation(trc, &iter, masm); } - } + void Assembler::TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader) { - ::TraceDataRelocations(trc, code->raw(), reader); + ::TraceDataRelocations(trc, code->raw(), reader, static_cast(Dummy)); } void @@ -860,8 +873,10 @@ Assembler::trace(JSTracer *trc) } } - if (tmpDataRelocations_.length()) - ::TraceDataRelocations(trc, &m_buffer, &tmpDataRelocations_); + if (tmpDataRelocations_.length()) { + ::TraceDataRelocations(trc, &m_buffer, &tmpDataRelocations_, + static_cast(this)); + } } void diff --git a/js/src/jit/mips/Architecture-mips.cpp b/js/src/jit/mips/Architecture-mips.cpp index d722c5b3188..a3215e9ace3 100644 --- a/js/src/jit/mips/Architecture-mips.cpp +++ b/js/src/jit/mips/Architecture-mips.cpp @@ -29,7 +29,7 @@ uint32_t GetMIPSFlags() return flags; #else -#if WTF_OS_LINUX +#ifdef __linux__ FILE *fp = fopen("/proc/cpuinfo", "r"); if (!fp) return false; diff --git a/js/src/jit/mips/Assembler-mips.cpp b/js/src/jit/mips/Assembler-mips.cpp index 58a077255fc..0ba04c834be 100644 --- a/js/src/jit/mips/Assembler-mips.cpp +++ b/js/src/jit/mips/Assembler-mips.cpp @@ -262,6 +262,7 @@ TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader // No barrier needed since these are constants. gc::MarkGCThingUnbarriered(trc, reinterpret_cast(&ptr), "ion-masm-ptr"); + Assembler::UpdateLuiOriValue(inst, inst->next(), uint32_t(ptr)); } } @@ -276,6 +277,7 @@ TraceDataRelocations(JSTracer *trc, MIPSBuffer *buffer, CompactBufferReader &rea // No barrier needed since these are constants. gc::MarkGCThingUnbarriered(trc, reinterpret_cast(&ptr), "ion-masm-ptr"); + Assembler::UpdateLuiOriValue(iter.cur(), iter.next(), uint32_t(ptr)); } } diff --git a/js/src/jit/shared/Assembler-x86-shared.cpp b/js/src/jit/shared/Assembler-x86-shared.cpp index 53fd2101856..665d8083541 100644 --- a/js/src/jit/shared/Assembler-x86-shared.cpp +++ b/js/src/jit/shared/Assembler-x86-shared.cpp @@ -54,7 +54,7 @@ TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader layout.asBits = *word; Value v = IMPL_TO_JSVAL(layout); gc::MarkValueUnbarriered(trc, &v, "ion-masm-value"); - JS_ASSERT(*word == JSVAL_TO_IMPL(v).asBits); + *word = JSVAL_TO_IMPL(v).asBits; continue; } #endif diff --git a/js/src/jsapi-tests/testGCFinalizeCallback.cpp b/js/src/jsapi-tests/testGCFinalizeCallback.cpp index fc4dc708464..4c38c45407f 100644 --- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp +++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp @@ -88,12 +88,12 @@ BEGIN_TEST(testGCFinalizeCallback) FinalizeCalls = 0; JS_SetGCZeal(cx, 9, 1000000); JS::PrepareForFullGC(rt); - js::GCDebugSlice(rt, true, 1); + rt->gc.gcDebugSlice(true, 1); CHECK(rt->gc.state() == js::gc::MARK); CHECK(rt->gc.isFullGc()); JS::RootedObject global4(cx, createGlobal()); - js::GCDebugSlice(rt, true, 1); + rt->gc.gcDebugSlice(true, 1); CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL); CHECK(!rt->gc.isFullGc()); CHECK(checkMultipleGroups()); diff --git a/js/src/jsapi-tests/testGCHeapPostBarriers.cpp b/js/src/jsapi-tests/testGCHeapPostBarriers.cpp index 6aa4dced04a..2fc66907921 100644 --- a/js/src/jsapi-tests/testGCHeapPostBarriers.cpp +++ b/js/src/jsapi-tests/testGCHeapPostBarriers.cpp @@ -51,7 +51,7 @@ TestHeapPostBarriers(T initialObj) uintptr_t initialObjAsInt = uintptr_t(initialObj); /* Perform minor GC and check heap wrapper is udated with new pointer. */ - js::MinorGC(cx, JS::gcreason::API); + cx->minorGC(JS::gcreason::API); CHECK(uintptr_t(heapData->get()) != initialObjAsInt); CHECK(!js::gc::IsInsideNursery(heapData->get())); diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index 7bfacf11801..ef583c299bd 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -87,7 +87,7 @@ BEGIN_TEST(testWeakMap_keyDelegates) * zone to finish marking before the delegate zone. */ CHECK(newCCW(map, delegate)); - GCDebugSlice(rt, true, 1000000); + rt->gc.gcDebugSlice(true, 1000000); #ifdef DEBUG CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex()); #endif @@ -100,7 +100,7 @@ BEGIN_TEST(testWeakMap_keyDelegates) /* Check the delegate keeps the entry alive even if the key is not reachable. */ key = nullptr; CHECK(newCCW(map, delegate)); - GCDebugSlice(rt, true, 100000); + rt->gc.gcDebugSlice(true, 100000); CHECK(checkSize(map, 1)); /* @@ -111,7 +111,7 @@ BEGIN_TEST(testWeakMap_keyDelegates) CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex()); #endif - /* Check that when the delegate becomes unreacable the entry is removed. */ + /* Check that when the delegate becomes unreachable the entry is removed. */ delegate = nullptr; JS_GC(rt); CHECK(checkSize(map, 0)); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c2d15765045..89bfa8e876a 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1873,7 +1873,7 @@ JS_GC(JSRuntime *rt) { AssertHeapIsIdle(rt); JS::PrepareForFullGC(rt); - GC(rt, GC_NORMAL, JS::gcreason::API); + rt->gc.gc(GC_NORMAL, JS::gcreason::API); } JS_PUBLIC_API(void) @@ -4807,7 +4807,7 @@ Evaluate(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &optionsA if (script->length() > LARGE_SCRIPT_LENGTH) { script = nullptr; PrepareZoneForGC(cx->zone()); - GC(cx->runtime(), GC_NORMAL, JS::gcreason::FINISH_LARGE_EVALUTE); + cx->runtime()->gc.gc(GC_NORMAL, JS::gcreason::FINISH_LARGE_EVALUATE); } return result; diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index f5effb93166..eb4cf1c3875 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -87,10 +87,11 @@ void js::TraceCycleDetectionSet(JSTracer *trc, js::ObjectSet &set) { for (js::ObjectSet::Enum e(set); !e.empty(); e.popFront()) { - JSObject *prior = e.front(); - MarkObjectRoot(trc, const_cast(&e.front()), "cycle detector table entry"); - if (prior != e.front()) - e.rekeyFront(e.front()); + JSObject *key = e.front(); + trc->setTracingLocation((void *)&e.front()); + MarkObjectRoot(trc, &key, "cycle detector table entry"); + if (key != e.front()) + e.rekeyFront(key); } } @@ -100,9 +101,13 @@ JSCompartment::sweepCallsiteClones() if (callsiteClones.initialized()) { for (CallsiteCloneTable::Enum e(callsiteClones); !e.empty(); e.popFront()) { CallsiteCloneKey key = e.front().key(); - JSFunction *fun = e.front().value(); - if (!IsScriptMarked(&key.script) || !IsObjectMarked(&fun)) + if (IsObjectAboutToBeFinalized(&key.original) || IsScriptAboutToBeFinalized(&key.script) || + IsObjectAboutToBeFinalized(e.front().value().unsafeGet())) + { e.removeFront(); + } else if (key != e.front().key()) { + e.rekeyFront(key); + } } } } @@ -251,7 +256,7 @@ js::DestroyContext(JSContext *cx, DestroyContextMode mode) if (mode == DCM_FORCE_GC) { JS_ASSERT(!rt->isHeapBusy()); JS::PrepareForFullGC(rt); - GC(rt, GC_NORMAL, JS::gcreason::DESTROY_CONTEXT); + rt->gc.gc(GC_NORMAL, JS::gcreason::DESTROY_CONTEXT); } js_delete_poison(cx); } @@ -986,7 +991,7 @@ js::InvokeInterruptCallback(JSContext *cx) // callbacks. rt->resetJitStackLimit(); - js::gc::GCIfNeeded(cx); + cx->gcIfNeeded(); rt->interruptPar = false; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 7f7a8add373..93bb160c8e3 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -49,6 +49,14 @@ struct CallsiteCloneKey { CallsiteCloneKey(JSFunction *f, JSScript *s, uint32_t o) : original(f), script(s), offset(o) {} + bool operator==(const CallsiteCloneKey& other) { + return original == other.original && script == other.script && offset == other.offset; + } + + bool operator!=(const CallsiteCloneKey& other) { + return !(*this == other); + } + typedef CallsiteCloneKey Lookup; static inline uint32_t hash(CallsiteCloneKey key) { @@ -58,6 +66,12 @@ struct CallsiteCloneKey { static inline bool match(const CallsiteCloneKey &a, const CallsiteCloneKey &b) { return a.script == b.script && a.offset == b.offset && a.original == b.original; } + + static void rekey(CallsiteCloneKey &k, const CallsiteCloneKey &newKey) { + k.original = newKey.original; + k.script = newKey.script; + k.offset = newKey.offset; + } }; typedef HashMapgc.minorGC(this, reason); + } + + void gcIfNeeded() { + runtime_->gc.gcIfNeeded(this); + } + private: /* Innermost-executing generator or null if no generator are executing. */ JSGenerator *innermostGenerator_; diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 6dbe8279dac..46ee7e5ad95 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -20,6 +20,7 @@ #include "gc/Marking.h" #include "jit/JitCompartment.h" #include "js/RootingAPI.h" +#include "vm/Debugger.h" #include "vm/StopIterationObject.h" #include "vm/WrapperObject.h" @@ -210,7 +211,7 @@ class WrapperMapRef : public BufferableRef } }; -#ifdef JS_GC_ZEAL +#ifdef JSGC_HASH_TABLE_CHECKS void JSCompartment::checkWrapperMapAfterMovingGC() { @@ -221,9 +222,9 @@ JSCompartment::checkWrapperMapAfterMovingGC() */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { CrossCompartmentKey key = e.front().key(); - JS_ASSERT(!IsInsideNursery(key.debugger)); - JS_ASSERT(!IsInsideNursery(key.wrapped)); - JS_ASSERT(!IsInsideNursery(static_cast(e.front().value().get().toGCThing()))); + CheckGCThingAfterMovingGC(key.debugger); + CheckGCThingAfterMovingGC(key.wrapped); + CheckGCThingAfterMovingGC(static_cast(e.front().value().get().toGCThing())); WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key); JS_ASSERT(ptr.found() && &*ptr == &e.front()); @@ -572,50 +573,53 @@ void JSCompartment::sweep(FreeOp *fop, bool releaseTypes) { JS_ASSERT(!activeAnalysis); - - /* This function includes itself in PHASE_SWEEP_TABLES. */ - sweepCrossCompartmentWrappers(); - JSRuntime *rt = runtimeFromMainThread(); { - gcstats::AutoPhase ap(rt->gc.stats, gcstats::PHASE_SWEEP_TABLES); - - /* Remove dead references held weakly by the compartment. */ - - sweepBaseShapeTable(); - sweepInitialShapeTable(); - sweepNewTypeObjectTable(newTypeObjects); - sweepNewTypeObjectTable(lazyTypeObjects); - sweepCallsiteClones(); - savedStacks_.sweep(rt); - - if (global_ && IsObjectAboutToBeFinalized(global_.unsafeGet())) - global_.set(nullptr); - - if (selfHostingScriptSource && - IsObjectAboutToBeFinalized((JSObject **) selfHostingScriptSource.unsafeGet())) - { - selfHostingScriptSource.set(nullptr); - } - - if (jitCompartment_) - jitCompartment_->sweep(fop, this); - - /* - * JIT code increments activeUseCount for any RegExpShared used by jit - * code for the lifetime of the JIT script. Thus, we must perform - * sweeping after clearing jit code. - */ - regExps.sweep(rt); - - if (debugScopes) - debugScopes->sweep(rt); - - /* Finalize unreachable (key,value) pairs in all weak maps. */ - WeakMapBase::sweepCompartment(this); + gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_WRAPPER); + sweepCrossCompartmentWrappers(); } + /* Remove dead references held weakly by the compartment. */ + + sweepBaseShapeTable(); + sweepInitialShapeTable(); + { + gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); + sweepNewTypeObjectTable(newTypeObjects); + sweepNewTypeObjectTable(lazyTypeObjects); + } + sweepCallsiteClones(); + savedStacks_.sweep(rt); + + if (global_ && IsObjectAboutToBeFinalized(global_.unsafeGet())) + global_.set(nullptr); + + if (selfHostingScriptSource && + IsObjectAboutToBeFinalized((JSObject **) selfHostingScriptSource.unsafeGet())) + { + selfHostingScriptSource.set(nullptr); + } + + if (jitCompartment_) + jitCompartment_->sweep(fop, this); + + /* + * JIT code increments activeUseCount for any RegExpShared used by jit + * code for the lifetime of the JIT script. Thus, we must perform + * sweeping after clearing jit code. + */ + regExps.sweep(rt); + + if (debugScopes) + debugScopes->sweep(rt); + + /* Finalize unreachable (key,value) pairs in all weak maps. */ + WeakMapBase::sweepCompartment(this); + + /* Sweep list of native iterators. */ NativeIterator *ni = enumerators->next(); while (ni != enumerators) { JSObject *iterObj = ni->iterObj(); @@ -624,6 +628,17 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) ni->unlink(); ni = next; } + + /* For each debuggee being GC'd, detach it from all its debuggers. */ + for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { + GlobalObject *global = e.front(); + if (IsObjectAboutToBeFinalized(&global)) { + // See infallibility note above. + Debugger::detachAllDebuggersFromGlobal(fop, global, &e); + } else if (global != e.front()) { + e.rekeyFront(global); + } + } } /* @@ -634,11 +649,6 @@ JSCompartment::sweep(FreeOp *fop, bool releaseTypes) void JSCompartment::sweepCrossCompartmentWrappers() { - JSRuntime *rt = runtimeFromMainThread(); - - gcstats::AutoPhase ap1(rt->gc.stats, gcstats::PHASE_SWEEP_TABLES); - gcstats::AutoPhase ap2(rt->gc.stats, gcstats::PHASE_SWEEP_TABLES_WRAPPER); - /* Remove dead wrappers from the table. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { CrossCompartmentKey key = e.front().key(); @@ -656,6 +666,59 @@ JSCompartment::sweepCrossCompartmentWrappers() } } +#ifdef JSGC_COMPACTING + +/* + * Fixup wrappers with moved keys or values. + */ +void +JSCompartment::fixupCrossCompartmentWrappers(JSTracer *trc) +{ + for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { + Value val = e.front().value(); + if (IsForwarded(val)) { + val = Forwarded(val); + e.front().value().set(val); + } + + // CrossCompartmentKey's hash does not depend on the debugger object, + // so update it but do not rekey if it changes + CrossCompartmentKey key = e.front().key(); + if (key.debugger) + key.debugger = MaybeForwarded(key.debugger); + if (key.wrapped && IsForwarded(key.wrapped)) { + key.wrapped = Forwarded(key.wrapped); + e.rekeyFront(key, key); + } + + if (!zone()->isCollecting() && val.isObject()) { + // Call the trace hook to update any pointers to relocated things. + JSObject *obj = &val.toObject(); + const Class *clasp = obj->getClass(); + if (clasp->trace) + clasp->trace(trc, obj); + } + } +} + +void JSCompartment::fixupAfterMovingGC() +{ + fixupGlobal(); + fixupNewTypeObjectTable(newTypeObjects); + fixupNewTypeObjectTable(lazyTypeObjects); + fixupInitialShapeTable(); +} + +void +JSCompartment::fixupGlobal() +{ + GlobalObject *global = *global_.unsafeGet(); + if (global) + global_.set(MaybeForwarded(global)); +} + +#endif // JSGC_COMPACTING + void JSCompartment::purge() { @@ -830,7 +893,7 @@ JSCompartment::updateJITForDebugMode(JSContext *maybecx, AutoDebugModeInvalidati } bool -JSCompartment::addDebuggee(JSContext *cx, js::GlobalObject *global) +JSCompartment::addDebuggee(JSContext *cx, JS::Handle global) { AutoDebugModeInvalidation invalidate(this); return addDebuggee(cx, global, invalidate); @@ -838,11 +901,9 @@ JSCompartment::addDebuggee(JSContext *cx, js::GlobalObject *global) bool JSCompartment::addDebuggee(JSContext *cx, - GlobalObject *globalArg, + JS::Handle global, AutoDebugModeInvalidation &invalidate) { - Rooted global(cx, globalArg); - bool wasEnabled = debugMode(); if (!debuggees.put(global)) { js_ReportOutOfMemory(cx); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index ffdc97eb6e5..0c1a9675029 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -254,8 +254,9 @@ struct JSCompartment js::types::TypeObjectWithNewScriptSet newTypeObjects; js::types::TypeObjectWithNewScriptSet lazyTypeObjects; void sweepNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table); -#if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) - void checkNewTypeObjectTableAfterMovingGC(); +#ifdef JSGC_HASH_TABLE_CHECKS + void checkNewTypeObjectTablesAfterMovingGC(); + void checkNewTypeObjectTableAfterMovingGC(js::types::TypeObjectWithNewScriptSet &table); void checkInitialShapesTableAfterMovingGC(); void checkWrapperMapAfterMovingGC(); #endif @@ -346,6 +347,14 @@ struct JSCompartment void purge(); void clearTables(); +#ifdef JSGC_COMPACTING + void fixupInitialShapeTable(); + void fixupNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table); + void fixupCrossCompartmentWrappers(JSTracer *trc); + void fixupAfterMovingGC(); + void fixupGlobal(); +#endif + bool hasObjectMetadataCallback() const { return objectMetadataCallback; } void setObjectMetadataCallback(js::ObjectMetadataCallback callback); void forgetObjectMetadataCallback() { @@ -414,8 +423,8 @@ struct JSCompartment public: js::GlobalObjectSet &getDebuggees() { return debuggees; } - bool addDebuggee(JSContext *cx, js::GlobalObject *global); - bool addDebuggee(JSContext *cx, js::GlobalObject *global, + bool addDebuggee(JSContext *cx, JS::Handle global); + bool addDebuggee(JSContext *cx, JS::Handle global, js::AutoDebugModeInvalidation &invalidate); bool removeDebuggee(JSContext *cx, js::GlobalObject *global, js::GlobalObjectSet::Enum *debuggeesEnum = nullptr); diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index d46d33db6c2..520ba1b18cf 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -197,25 +197,25 @@ JS::SkipZoneForGC(Zone *zone) JS_FRIEND_API(void) JS::GCForReason(JSRuntime *rt, gcreason::Reason reason) { - GC(rt, GC_NORMAL, reason); + rt->gc.gc(GC_NORMAL, reason); } JS_FRIEND_API(void) JS::ShrinkingGC(JSRuntime *rt, gcreason::Reason reason) { - GC(rt, GC_SHRINK, reason); + rt->gc.gc(GC_SHRINK, reason); } JS_FRIEND_API(void) JS::IncrementalGC(JSRuntime *rt, gcreason::Reason reason, int64_t millis) { - GCSlice(rt, GC_NORMAL, reason, millis); + rt->gc.gcSlice(GC_NORMAL, reason, millis); } JS_FRIEND_API(void) JS::FinishIncrementalGC(JSRuntime *rt, gcreason::Reason reason) { - GCFinalSlice(rt, GC_NORMAL, reason); + rt->gc.gcFinalSlice(GC_NORMAL, reason); } JS_FRIEND_API(JSPrincipals *) @@ -810,7 +810,7 @@ js::DumpHeapComplete(JSRuntime *rt, FILE *fp, js::DumpHeapNurseryBehaviour nurse { #ifdef JSGC_GENERATIONAL if (nurseryBehaviour == js::CollectNurseryBeforeDump) - MinorGC(rt, JS::gcreason::API); + rt->gc.evictNursery(JS::gcreason::API); #endif DumpHeapTracer dtrc(fp, rt, DumpHeapVisitRoot, TraceWeakMapKeysValues); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 07d3a4ffbc3..6fba654c6a8 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -169,6 +169,16 @@ * the mark state, this just stops marking, but if we have started sweeping * already, we continue until we have swept the current zone group. Following a * reset, a new non-incremental collection is started. + * + * Compacting GC + * ------------- + * + * Compacting GC happens at the end of a major GC as part of the last slice. + * There are three parts: + * + * - Arenas are selected for compaction. + * - The contents of those arenas are moved to new arenas. + * - All references to moved things are updated. */ #include "jsgcinlines.h" @@ -916,7 +926,10 @@ Chunk::allocateArena(Zone *zone, AllocKind thingKind) JS_ASSERT(hasAvailableArenas()); JSRuntime *rt = zone->runtimeFromAnyThread(); - if (!rt->isHeapMinorCollecting() && rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes()) { + if (!rt->isHeapMinorCollecting() && + !rt->isHeapCompacting() && + rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes()) + { #ifdef JSGC_FJGENERATIONAL // This is an approximation to the best test, which would check that // this thread is currently promoting into the tenured area. I doubt @@ -937,9 +950,9 @@ Chunk::allocateArena(Zone *zone, AllocKind thingKind) zone->usage.addGCArena(); - if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) { + if (!rt->isHeapCompacting() && zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) { AutoUnlockGC unlock(rt); - TriggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER); + rt->gc.triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER); } return aheader; @@ -1145,6 +1158,9 @@ GCRuntime::GCRuntime(JSRuntime *rt) : sliceBudget(SliceBudget::Unlimited), incrementalAllowed(true), generationalDisabled(0), +#ifdef JSGC_COMPACTING + compactingDisabled(0), +#endif manipulatingDeadZones(false), objectsMarkedInDeadZones(0), poked(false), @@ -1186,7 +1202,7 @@ GCRuntime::setZeal(uint8_t zeal, uint32_t frequency) #ifdef JSGC_GENERATIONAL if (zealMode == ZealGenerationalGCValue) { - minorGC(JS::gcreason::DEBUG_GC); + evictNursery(JS::gcreason::DEBUG_GC); nursery.leaveZealMode(); } @@ -1985,6 +2001,479 @@ ArenaLists::wipeDuringParallelExecution(JSRuntime *rt) } } +/* Compacting GC */ + +bool +GCRuntime::shouldCompact() +{ +#ifdef JSGC_COMPACTING + return invocationKind == GC_SHRINK && !compactingDisabled; +#else + return false; +#endif +} + +#ifdef JSGC_COMPACTING + +void +GCRuntime::disableCompactingGC() +{ + ++rt->gc.compactingDisabled; +} + +void +GCRuntime::enableCompactingGC() +{ + JS_ASSERT(compactingDisabled > 0); + --compactingDisabled; +} + +AutoDisableCompactingGC::AutoDisableCompactingGC(JSRuntime *rt) + : gc(rt->gc) +{ + gc.disableCompactingGC(); +} + +AutoDisableCompactingGC::~AutoDisableCompactingGC() +{ + gc.enableCompactingGC(); +} + +static void +ForwardCell(Cell *dest, Cell *src) +{ + // Mark a cell has having been relocated and astore forwarding pointer to + // the new cell. + MOZ_ASSERT(src->tenuredZone() == dest->tenuredZone()); + + // Putting the values this way round is a terrible hack to make + // ObjectImpl::zone() work on forwarded objects. + MOZ_ASSERT(ObjectImpl::offsetOfShape() == 0); + uintptr_t *ptr = reinterpret_cast(src); + ptr[0] = reinterpret_cast(dest); // Forwarding address + ptr[1] = ForwardedCellMagicValue; // Moved! +} + +static bool +ArenaContainsGlobal(ArenaHeader *arena) +{ + if (arena->getAllocKind() > FINALIZE_OBJECT_LAST) + return false; + + for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { + JSObject *obj = static_cast(i.getCell()); + if (obj->is()) + return true; + } + + return false; +} + +static bool +CanRelocateArena(ArenaHeader *arena) +{ + /* + * We can't currently move global objects because their address is baked + * into compiled code. We therefore skip moving the contents of any arena + * containing a global if ion or baseline are enabled. + */ + JSRuntime *rt = arena->zone->runtimeFromMainThread(); + return arena->getAllocKind() <= FINALIZE_OBJECT_LAST && + ((!rt->options().baseline() && !rt->options().ion()) || !ArenaContainsGlobal(arena)); +} + +static bool +ShouldRelocateArena(ArenaHeader *arena) +{ +#ifdef JS_GC_ZEAL + if (arena->zone->runtimeFromMainThread()->gc.zeal() == ZealCompactValue) + return true; +#endif + + /* + * Eventually, this will be based on brilliant heuristics that look at fill + * percentage and fragmentation and... stuff. + */ + return arena->hasFreeThings(); +} + +/* + * Choose some arenas to relocate all cells out of and remove them from the + * arena list. Return the head of the list of arenas to relocate. + */ +ArenaHeader * +ArenaList::pickArenasToRelocate() +{ + check(); + ArenaHeader *head = nullptr; + ArenaHeader **tailp = &head; + + // TODO: Only scan through the arenas with space available. + ArenaHeader **arenap = &head_; + while (*arenap) { + ArenaHeader *arena = *arenap; + JS_ASSERT(arena); + if (CanRelocateArena(arena) && ShouldRelocateArena(arena)) { + // Remove from arena list + if (cursorp_ == &arena->next) + cursorp_ = arenap; + *arenap = arena->next; + arena->next = nullptr; + + // Append to relocation list + *tailp = arena; + tailp = &arena->next; + } else { + arenap = &arena->next; + } + } + + check(); + return head; +} + +#ifdef DEBUG +inline bool +PtrIsInRange(void *ptr, void *start, size_t length) +{ + return uintptr_t(ptr) - uintptr_t(start) < length; +} +#endif + +static bool +RelocateCell(Zone *zone, Cell *src, AllocKind thingKind, size_t thingSize) +{ + // Allocate a new cell. + void *dst = zone->allocator.arenas.allocateFromFreeList(thingKind, thingSize); + if (!dst) + dst = js::gc::ArenaLists::refillFreeListInGC(zone, thingKind); + if (!dst) + return false; + + // Copy source cell contents to destination. + memcpy(dst, src, thingSize); + + // Fixup the pointer to inline object elements if necessary. + if (thingKind <= FINALIZE_OBJECT_LAST) { + JSObject *srcObj = static_cast(src); + JSObject *dstObj = static_cast(dst); + if (srcObj->hasFixedElements()) + dstObj->setFixedElements(); + + if (srcObj->is()) { + // We must fix up any inline data pointers while we know the source + // object and before we mark any of the views. + ArrayBufferObject::fixupDataPointerAfterMovingGC( + srcObj->as(), dstObj->as()); + } else if (srcObj->is()) { + TypedArrayObject &typedArray = srcObj->as(); + if (!typedArray.hasBuffer()) { + JS_ASSERT(srcObj->getPrivate() == + srcObj->fixedData(TypedArrayObject::FIXED_DATA_START)); + dstObj->setPrivate(dstObj->fixedData(TypedArrayObject::FIXED_DATA_START)); + } + } + + + JS_ASSERT_IF(dstObj->isNative(), + !PtrIsInRange((HeapSlot*)dstObj->getDenseElements(), src, thingSize)); + } + + // Copy the mark bits. + static_cast(dst)->copyMarkBitsFrom(src); + + // Mark source cell as forwarded and leave a pointer to the destination. + ForwardCell(static_cast(dst), src); + + return true; +} + +static bool +RelocateArena(ArenaHeader *aheader) +{ + JS_ASSERT(aheader->allocated()); + JS_ASSERT(!aheader->hasDelayedMarking); + JS_ASSERT(!aheader->markOverflow); + JS_ASSERT(!aheader->allocatedDuringIncremental); + + Zone *zone = aheader->zone; + + AllocKind thingKind = aheader->getAllocKind(); + size_t thingSize = aheader->getThingSize(); + + for (ArenaCellIterUnderFinalize i(aheader); !i.done(); i.next()) { + if (!RelocateCell(zone, i.getCell(), thingKind, thingSize)) { + MOZ_CRASH(); // TODO: Handle failure here. + return false; + } + } + + return true; +} + +/* + * Relocate all arenas identified by pickArenasToRelocate: for each arena, + * relocate each cell within it, then tack it onto a list of relocated arenas. + * Currently, we allow the relocation to fail, in which case the arena will be + * moved back onto the list of arenas with space available. (I did this + * originally to test my list manipulation before implementing the actual + * moving, with half a thought to allowing pinning (moving only a portion of + * the cells in an arena), but now it's probably just dead weight. FIXME) + */ +ArenaHeader * +ArenaList::relocateArenas(ArenaHeader *toRelocate, ArenaHeader *relocated) +{ + check(); + + while (ArenaHeader *arena = toRelocate) { + toRelocate = arena->next; + + if (RelocateArena(arena)) { + // Prepend to list of relocated arenas + arena->next = relocated; + relocated = arena; + } else { + // For some reason, the arena did not end up empty. Prepend it to + // the portion of the list that the cursor is pointing to (the + // arenas with space available) so that it will be used for future + // allocations. + JS_ASSERT(arena->hasFreeThings()); + insertAtCursor(arena); + } + } + + check(); + + return relocated; +} + +ArenaHeader * +ArenaLists::relocateArenas(ArenaHeader *relocatedList) +{ + // Flush all the freeLists back into the arena headers + purge(); + checkEmptyFreeLists(); + + for (size_t i = 0; i < FINALIZE_LIMIT; i++) { + ArenaList &al = arenaLists[i]; + ArenaHeader *toRelocate = al.pickArenasToRelocate(); + if (toRelocate) + relocatedList = al.relocateArenas(toRelocate, relocatedList); + } + + /* + * When we allocate new locations for cells, we use + * allocateFromFreeList(). Reset the free list again so that + * AutoCopyFreeListToArenasForGC doesn't complain that the free lists + * are different now. + */ + purge(); + checkEmptyFreeLists(); + + return relocatedList; +} + +ArenaHeader * +GCRuntime::relocateArenas() +{ + gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT_MOVE); + + ArenaHeader *relocatedList = nullptr; + for (GCZonesIter zone(rt); !zone.done(); zone.next()) { + JS_ASSERT(zone->isGCFinished()); + JS_ASSERT(!zone->isPreservingCode()); + + // We cannot move atoms as we depend on their addresses being constant. + if (!rt->isAtomsZone(zone)) { + zone->setGCState(Zone::Compact); + relocatedList = zone->allocator.arenas.relocateArenas(relocatedList); + } + } + + return relocatedList; +} + +struct MovingTracer : JSTracer { + MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapValues) {} + + static void Visit(JSTracer *jstrc, void **thingp, JSGCTraceKind kind); + static void Sweep(JSTracer *jstrc); +}; + +void +MovingTracer::Visit(JSTracer *jstrc, void **thingp, JSGCTraceKind kind) +{ + Cell *thing = static_cast(*thingp); + Zone *zone = thing->tenuredZoneFromAnyThread(); + if (!zone->isGCCompacting()) { + JS_ASSERT(!IsForwarded(thing)); + return; + } + JS_ASSERT(CurrentThreadCanAccessZone(zone)); + + if (IsForwarded(thing)) { + Cell *dst = Forwarded(thing); + *thingp = dst; + } +} + +void +MovingTracer::Sweep(JSTracer *jstrc) +{ + JSRuntime *rt = jstrc->runtime(); + FreeOp *fop = rt->defaultFreeOp(); + + WatchpointMap::sweepAll(rt); + + Debugger::sweepAll(fop); + + for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { + if (zone->isCollecting()) { + bool oom = false; + zone->sweep(fop, false, &oom); + JS_ASSERT(!oom); + + for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { + c->sweep(fop, false); + } + } else { + /* Update cross compartment wrappers into moved zones. */ + for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) + c->sweepCrossCompartmentWrappers(); + } + } + + /* Type inference may put more blocks here to free. */ + rt->freeLifoAlloc.freeAll(); + + /* Clear the new object cache as this can contain cell pointers. */ + rt->newObjectCache.purge(); +} + +/* + * Update the interal pointers in a single cell. + */ +static void +UpdateCellPointers(MovingTracer *trc, Cell *cell, JSGCTraceKind traceKind) { + TraceChildren(trc, cell, traceKind); + + if (traceKind == JSTRACE_SHAPE) { + Shape *shape = static_cast(cell); + shape->fixupAfterMovingGC(); + } else if (traceKind == JSTRACE_BASE_SHAPE) { + BaseShape *base = static_cast(cell); + base->fixupAfterMovingGC(); + } +} + +/* + * Update pointers to relocated cells by doing a full heap traversal and sweep. + * + * The latter is necessary to update weak references which are not marked as + * part of the traversal. + */ +void +GCRuntime::updatePointersToRelocatedCells() +{ + JS_ASSERT(rt->currentThreadHasExclusiveAccess()); + + gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT_UPDATE); + MovingTracer trc(rt); + + // TODO: We may need to fix up other weak pointers here. + + // Fixup compartment global pointers as these get accessed during marking. + for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) + comp->fixupAfterMovingGC(); + + // Fixup cross compartment wrappers as we assert the existence of wrappers in the map. + for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) + comp->fixupCrossCompartmentWrappers(&trc); + + // Fixup generators as these are not normally traced. + for (ContextIter i(rt); !i.done(); i.next()) { + for (JSGenerator *gen = i.get()->innermostGenerator(); gen; gen = gen->prevGenerator) + gen->obj = MaybeForwarded(gen->obj.get()); + } + + // Iterate through all allocated cells to update internal pointers. + for (GCZonesIter zone(rt); !zone.done(); zone.next()) { + ArenaLists &al = zone->allocator.arenas; + for (unsigned i = 0; i < FINALIZE_LIMIT; ++i) { + AllocKind thingKind = static_cast(i); + JSGCTraceKind traceKind = MapAllocToTraceKind(thingKind); + for (ArenaHeader *arena = al.getFirstArena(thingKind); arena; arena = arena->next) { + for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { + UpdateCellPointers(&trc, i.getCell(), traceKind); + } + } + } + } + + // Mark roots to update them. + markRuntime(&trc, MarkRuntime); + Debugger::markAll(&trc); + Debugger::markCrossCompartmentDebuggerObjectReferents(&trc); + + for (GCCompartmentsIter c(rt); !c.done(); c.next()) { + WeakMapBase::markAll(c, &trc); + if (c->watchpointMap) + c->watchpointMap->markAll(&trc); + } + + // Mark all gray roots, making sure we call the trace callback to get the + // current set. + marker.resetBufferedGrayRoots(); + markAllGrayReferences(gcstats::PHASE_COMPACT_UPDATE_GRAY); + + MovingTracer::Sweep(&trc); +} + +void +GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList) +{ + // Release the relocated arenas, now containing only forwarding pointers + +#ifdef DEBUG + for (ArenaHeader *arena = relocatedList; arena; arena = arena->next) { + for (ArenaCellIterUnderFinalize i(arena); !i.done(); i.next()) { + Cell *src = i.getCell(); + JS_ASSERT(IsForwarded(src)); + Cell *dest = Forwarded(src); + JS_ASSERT(src->isMarked(BLACK) == dest->isMarked(BLACK)); + JS_ASSERT(src->isMarked(GRAY) == dest->isMarked(GRAY)); + } + } +#endif + + unsigned count = 0; + while (relocatedList) { + ArenaHeader *aheader = relocatedList; + relocatedList = relocatedList->next; + + // Mark arena as empty + AllocKind thingKind = aheader->getAllocKind(); + size_t thingSize = aheader->getThingSize(); + Arena *arena = aheader->getArena(); + FreeSpan fullSpan; + fullSpan.initFinal(arena->thingsStart(thingKind), arena->thingsEnd() - thingSize, thingSize); + aheader->setFirstFreeSpan(&fullSpan); + +#if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL) + JS_POISON(reinterpret_cast(arena->thingsStart(thingKind)), + JS_MOVED_TENURED_PATTERN, Arena::thingsSpan(thingSize)); +#endif + + aheader->chunk()->releaseArena(aheader); + ++count; + } + + AutoLockGC lock(rt); + expireChunksAndArenas(true); +} + +#endif // JSGC_COMPACTING + void ArenaLists::finalizeNow(FreeOp *fop, AllocKind thingKind) { @@ -2169,7 +2658,7 @@ RunLastDitchGC(JSContext *cx, JS::Zone *zone, AllocKind thingKind) /* The last ditch GC preserves all atoms. */ AutoKeepAtoms keepAtoms(cx->perThreadData); - GC(rt, GC_NORMAL, JS::gcreason::LAST_DITCH); + rt->gc.gc(GC_NORMAL, JS::gcreason::LAST_DITCH); /* * The JSGC_END callback can legitimately allocate new GC @@ -2271,10 +2760,20 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind); template void * ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind); -JSGCTraceKind -js_GetGCThingTraceKind(void *thing) +/* static */ void * +ArenaLists::refillFreeListInGC(Zone *zone, AllocKind thingKind) { - return GetGCThingTraceKind(thing); + /* + * Called by compacting GC to refill a free list while we are in a GC. + */ + + Allocator &allocator = zone->allocator; + JS_ASSERT(allocator.arenas.freeLists[thingKind].isEmpty()); + JSRuntime *rt = zone->runtimeFromMainThread(); + JS_ASSERT(rt->isHeapMajorCollecting()); + JS_ASSERT(!rt->gc.isBackgroundSweeping()); + + return allocator.arenas.allocateFromArena(zone, thingKind); } /* static */ int64_t @@ -2334,12 +2833,6 @@ GCRuntime::requestInterrupt(JS::gcreason::Reason reason) rt->requestInterrupt(JSRuntime::RequestInterruptMainThread); } -bool -js::TriggerGC(JSRuntime *rt, JS::gcreason::Reason reason) -{ - return rt->gc.triggerGC(reason); -} - bool GCRuntime::triggerGC(JS::gcreason::Reason reason) { @@ -2369,12 +2862,6 @@ GCRuntime::triggerGC(JS::gcreason::Reason reason) return true; } -bool -js::TriggerZoneGC(Zone *zone, JS::gcreason::Reason reason) -{ - return zone->runtimeFromAnyThread()->gc.triggerZoneGC(zone, reason); -} - bool GCRuntime::triggerZoneGC(Zone *zone, JS::gcreason::Reason reason) { @@ -2401,14 +2888,14 @@ GCRuntime::triggerZoneGC(Zone *zone, JS::gcreason::Reason reason) #ifdef JS_GC_ZEAL if (zealMode == ZealAllocValue) { - TriggerGC(rt, reason); + triggerGC(reason); return true; } #endif if (rt->isAtomsZone(zone)) { /* We can't do a zone GC of the atoms compartment. */ - TriggerGC(rt, reason); + triggerGC(reason); return true; } @@ -2425,13 +2912,13 @@ GCRuntime::maybeGC(Zone *zone) #ifdef JS_GC_ZEAL if (zealMode == ZealAllocValue || zealMode == ZealPokeValue) { JS::PrepareForFullGC(rt); - GC(rt, GC_NORMAL, JS::gcreason::MAYBEGC); + gc(GC_NORMAL, JS::gcreason::MAYBEGC); return true; } #endif if (isNeeded) { - GCSlice(rt, GC_NORMAL, JS::gcreason::MAYBEGC); + gcSlice(GC_NORMAL, JS::gcreason::MAYBEGC); return true; } @@ -2442,7 +2929,7 @@ GCRuntime::maybeGC(Zone *zone) !isBackgroundSweeping()) { PrepareZoneForGC(zone); - GCSlice(rt, GC_NORMAL, JS::gcreason::MAYBEGC); + gcSlice(GC_NORMAL, JS::gcreason::MAYBEGC); return true; } @@ -2468,7 +2955,7 @@ GCRuntime::maybePeriodicFullGC() numArenasFreeCommitted > decommitThreshold) { JS::PrepareForFullGC(rt); - GCSlice(rt, GC_SHRINK, JS::gcreason::MAYBEGC); + gcSlice(GC_SHRINK, JS::gcreason::MAYBEGC); } else { nextFullGCTime = now + GC_IDLE_FULL_SPAN; } @@ -3255,7 +3742,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason) if (isFull) UnmarkScriptData(rt); - markRuntime(gcmarker); + markRuntime(gcmarker, MarkRuntime); if (isIncremental) bufferGrayRoots(); @@ -3327,7 +3814,6 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) { JS_ASSERT(marker.isDrained()); - gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MARK); gcstats::AutoPhase ap1(stats, phase); for (;;) { @@ -3355,39 +3841,27 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase) template void -GCRuntime::markGrayReferences() +GCRuntime::markGrayReferences(gcstats::Phase phase) { - { - gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MARK); - gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP_MARK_GRAY); - marker.setMarkColorGray(); - if (marker.hasBufferedGrayRoots()) { - for (ZoneIterT zone(rt); !zone.done(); zone.next()) - marker.markBufferedGrayRoots(zone); - } else { - JS_ASSERT(!isIncremental); - if (JSTraceDataOp op = grayRootTracer.op) - (*op)(&marker, grayRootTracer.data); - } - SliceBudget budget; - marker.drainMarkStack(budget); + gcstats::AutoPhase ap(stats, phase); + if (marker.hasBufferedGrayRoots()) { + for (ZoneIterT zone(rt); !zone.done(); zone.next()) + marker.markBufferedGrayRoots(zone); + } else { + JS_ASSERT(!isIncremental); + if (JSTraceDataOp op = grayRootTracer.op) + (*op)(&marker, grayRootTracer.data); } - - markWeakReferences(gcstats::PHASE_SWEEP_MARK_GRAY_WEAK); - - JS_ASSERT(marker.isDrained()); - - marker.setMarkColorBlack(); + SliceBudget budget; + marker.drainMarkStack(budget); } void -GCRuntime::markGrayReferencesInCurrentGroup() +GCRuntime::markGrayReferencesInCurrentGroup(gcstats::Phase phase) { - markGrayReferences(); + markGrayReferences(phase); } -#ifdef DEBUG - void GCRuntime::markAllWeakReferences(gcstats::Phase phase) { @@ -3395,11 +3869,13 @@ GCRuntime::markAllWeakReferences(gcstats::Phase phase) } void -GCRuntime::markAllGrayReferences() +GCRuntime::markAllGrayReferences(gcstats::Phase phase) { - markGrayReferences(); + markGrayReferences(phase); } +#ifdef DEBUG + class js::gc::MarkingValidator { public: @@ -3504,7 +3980,7 @@ js::gc::MarkingValidator::nonIncrementalMark() { gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_MARK); gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_MARK_ROOTS); - gc->markRuntime(gcmarker, true); + gc->markRuntime(gcmarker, GCRuntime::MarkRuntime, GCRuntime::UseSavedRoots); } { @@ -3516,7 +3992,8 @@ js::gc::MarkingValidator::nonIncrementalMark() gc->incrementalState = SWEEP; { - gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_SWEEP); + gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP); + gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_MARK); gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_WEAK); /* Update zone state for gray marking. */ @@ -3524,14 +4001,18 @@ js::gc::MarkingValidator::nonIncrementalMark() JS_ASSERT(zone->isGCMarkingBlack()); zone->setGCState(Zone::MarkGray); } + gc->marker.setMarkColorGray(); - gc->markAllGrayReferences(); + gc->markAllGrayReferences(gcstats::PHASE_SWEEP_MARK_GRAY); + gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_GRAY_WEAK); /* Restore zone state. */ for (GCZonesIter zone(runtime); !zone.done(); zone.next()) { JS_ASSERT(zone->isGCMarkingGray()); zone->setGCState(Zone::Mark); } + JS_ASSERT(gc->marker.isDrained()); + gc->marker.setMarkColorBlack(); } /* Take a copy of the non-incremental mark state and restore the original. */ @@ -3932,7 +4413,6 @@ MarkIncomingCrossCompartmentPointers(JSRuntime *rt, const uint32_t color) { JS_ASSERT(color == BLACK || color == GRAY); - gcstats::AutoPhase ap(rt->gc.stats, gcstats::PHASE_SWEEP_MARK); static const gcstats::Phase statsPhases[] = { gcstats::PHASE_SWEEP_MARK_INCOMING_BLACK, gcstats::PHASE_SWEEP_MARK_INCOMING_GRAY @@ -4057,6 +4537,8 @@ js::NotifyGCPostSwap(JSObject *a, JSObject *b, unsigned removedFlags) void GCRuntime::endMarkingZoneGroup() { + gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MARK); + /* * Mark any incoming black pointers from previously swept compartments * whose referents are not marked. This can occur when gray cells become @@ -4076,22 +4558,22 @@ GCRuntime::endMarkingZoneGroup() JS_ASSERT(zone->isGCMarkingBlack()); zone->setGCState(Zone::MarkGray); } + marker.setMarkColorGray(); /* Mark incoming gray pointers from previously swept compartments. */ - marker.setMarkColorGray(); MarkIncomingCrossCompartmentPointers(rt, GRAY); - marker.setMarkColorBlack(); /* Mark gray roots and mark transitively inside the current compartment group. */ - markGrayReferencesInCurrentGroup(); + markGrayReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_GRAY); + markWeakReferencesInCurrentGroup(gcstats::PHASE_SWEEP_MARK_GRAY_WEAK); /* Restore marking state. */ for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) { JS_ASSERT(zone->isGCMarkingGray()); zone->setGCState(Zone::Mark); } - - JS_ASSERT(marker.isDrained()); + MOZ_ASSERT(marker.isDrained()); + marker.setMarkColorBlack(); } void @@ -4164,6 +4646,8 @@ GCRuntime::beginSweepingZoneGroup() for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { gcstats::AutoSCC scc(stats, zoneGroupIndex); + gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_TABLES); + c->sweep(&fop, releaseObservedTypes && !c->zone()->isPreservingCode()); } @@ -4266,7 +4750,8 @@ GCRuntime::beginSweepPhase(bool lastGC) gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP); - sweepOnBackgroundThread = !lastGC && !TraceEnabled() && CanUseExtraThreads(); + sweepOnBackgroundThread = + !lastGC && !TraceEnabled() && CanUseExtraThreads() && !shouldCompact(); releaseObservedTypes = shouldReleaseObservedTypes(); @@ -4387,16 +4872,13 @@ GCRuntime::sweepPhase(SliceBudget &sliceBudget) } void -GCRuntime::endSweepPhase(JSGCInvocationKind gckind, bool lastGC) +GCRuntime::endSweepPhase(bool lastGC) { gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP); FreeOp fop(rt); JS_ASSERT_IF(lastGC, !sweepOnBackgroundThread); - JS_ASSERT(marker.isDrained()); - marker.stop(); - /* * Recalculate whether GC was full or not as this may have changed due to * newly created zones. Can only change from full to not full. @@ -4459,7 +4941,7 @@ GCRuntime::endSweepPhase(JSGCInvocationKind gckind, bool lastGC) * Expire needs to unlock it for other callers. */ AutoLockGC lock(rt); - expireChunksAndArenas(gckind == GC_SHRINK); + expireChunksAndArenas(invocationKind == GC_SHRINK); } } @@ -4497,29 +4979,17 @@ GCRuntime::endSweepPhase(JSGCInvocationKind gckind, bool lastGC) sweepZones(&fop, lastGC); } - uint64_t currentTime = PRMJ_Now(); - schedulingState.updateHighFrequencyMode(lastGCTime, currentTime, tunables); - - for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { - zone->threshold.updateAfterGC(zone->usage.gcBytes(), gckind, tunables, schedulingState); - if (zone->isCollecting()) { - JS_ASSERT(zone->isGCFinished()); - zone->setGCState(Zone::NoGC); - } + finishMarkingValidation(); #ifdef DEBUG - JS_ASSERT(!zone->isCollecting()); - JS_ASSERT(!zone->wasGCStarted()); - + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { for (unsigned i = 0 ; i < FINALIZE_LIMIT ; ++i) { JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) || !sweepOnBackgroundThread, !zone->allocator.arenas.arenaListsToSweep[i]); } -#endif } -#ifdef DEBUG for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { JS_ASSERT(!c->gcIncomingGrayPointers); JS_ASSERT(c->gcLiveArrayBuffers.empty()); @@ -4530,8 +5000,52 @@ GCRuntime::endSweepPhase(JSGCInvocationKind gckind, bool lastGC) } } #endif +} - finishMarkingValidation(); +#ifdef JSGC_COMPACTING +void +GCRuntime::compactPhase() +{ + JS_ASSERT(rt->gc.nursery.isEmpty()); + JS_ASSERT(!sweepOnBackgroundThread); + + gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT); + + ArenaHeader *relocatedList = relocateArenas(); + updatePointersToRelocatedCells(); + releaseRelocatedArenas(relocatedList); + +#ifdef DEBUG + CheckHashTablesAfterMovingGC(rt); + for (GCZonesIter zone(rt); !zone.done(); zone.next()) { + if (!rt->isAtomsZone(zone) && !zone->isPreservingCode()) + zone->allocator.arenas.checkEmptyFreeLists(); + } +#endif +} +#endif // JSGC_COMPACTING + +void +GCRuntime::finishCollection() +{ + JS_ASSERT(marker.isDrained()); + marker.stop(); + + uint64_t currentTime = PRMJ_Now(); + schedulingState.updateHighFrequencyMode(lastGCTime, currentTime, tunables); + + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { + zone->threshold.updateAfterGC(zone->usage.gcBytes(), invocationKind, tunables, + schedulingState); + if (zone->isCollecting()) { + JS_ASSERT(zone->isGCFinished() || zone->isGCCompacting()); + zone->setGCState(Zone::NoGC); + zone->active = false; + } + + JS_ASSERT(!zone->isCollecting()); + JS_ASSERT(!zone->wasGCStarted()); + } lastGCTime = currentTime; } @@ -4652,7 +5166,7 @@ GCRuntime::resetIncrementalGC(const char *reason) /* Finish sweeping the current zone group, then abort. */ abortSweepAfterCurrentGroup = true; - incrementalCollectSlice(SliceBudget::Unlimited, JS::gcreason::RESET, GC_NORMAL); + incrementalCollectSlice(SliceBudget::Unlimited, JS::gcreason::RESET); { gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD); @@ -4750,8 +5264,7 @@ GCRuntime::pushZealSelectedObjects() void GCRuntime::incrementalCollectSlice(int64_t budget, - JS::gcreason::Reason reason, - JSGCInvocationKind gckind) + JS::gcreason::Reason reason) { JS_ASSERT(rt->currentThreadHasExclusiveAccess()); @@ -4767,7 +5280,7 @@ GCRuntime::incrementalCollectSlice(int64_t budget, if (reason == JS::gcreason::DEBUG_GC && budget != SliceBudget::Unlimited) { /* * Do the incremental collection type specified by zeal mode if the - * collection was triggered by RunDebugGC() and incremental GC has not + * collection was triggered by runDebugGC() and incremental GC has not * been cancelled by resetIncrementalGC(). */ zeal = zealMode; @@ -4864,11 +5377,19 @@ GCRuntime::incrementalCollectSlice(int64_t budget, if (!finished) break; - endSweepPhase(gckind, lastGC); + endSweepPhase(lastGC); if (sweepOnBackgroundThread) - helperState.startBackgroundSweep(gckind == GC_SHRINK); + helperState.startBackgroundSweep(invocationKind == GC_SHRINK); +#ifdef JSGC_COMPACTING + if (shouldCompact()) { + incrementalState = COMPACT; + compactPhase(); + } +#endif + + finishCollection(); incrementalState = NO_INCREMENTAL; break; } @@ -5030,7 +5551,11 @@ GCRuntime::gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind, TraceMajorGCStart(); - incrementalCollectSlice(budget, reason, gckind); + /* Set the invocation kind in the first slice. */ + if (incrementalState == NO_INCREMENTAL) + invocationKind = gckind; + + incrementalCollectSlice(budget, reason); #ifndef JS_MORE_DETERMINISTIC nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN; @@ -5200,15 +5725,9 @@ GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind, } void -js::GC(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason) +GCRuntime::gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason) { - rt->gc.collect(false, SliceBudget::Unlimited, gckind, reason); -} - -void -js::GCSlice(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis) -{ - rt->gc.gcSlice(gckind, reason, millis); + collect(false, SliceBudget::Unlimited, gckind, reason); } void @@ -5226,9 +5745,9 @@ GCRuntime::gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64 } void -js::GCFinalSlice(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason) +GCRuntime::gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason) { - rt->gc.collect(true, SliceBudget::Unlimited, gckind, reason); + collect(true, SliceBudget::Unlimited, gckind, reason); } void @@ -5271,7 +5790,7 @@ ZonesSelected(JSRuntime *rt) } void -js::GCDebugSlice(JSRuntime *rt, bool limit, int64_t objCount) +GCRuntime::gcDebugSlice(bool limit, int64_t objCount) { int64_t budget = limit ? SliceBudget::WorkBudget(objCount) : SliceBudget::Unlimited; if (!ZonesSelected(rt)) { @@ -5280,7 +5799,7 @@ js::GCDebugSlice(JSRuntime *rt, bool limit, int64_t objCount) else JS::PrepareForFullGC(rt); } - rt->gc.collect(true, budget, GC_NORMAL, JS::gcreason::DEBUG_GC); + collect(true, budget, GC_NORMAL, JS::gcreason::DEBUG_GC); } /* Schedule a full GC unless a zone will already be collected. */ @@ -5310,12 +5829,6 @@ GCRuntime::shrinkBuffers() expireChunksAndArenas(true); } -void -js::MinorGC(JSRuntime *rt, JS::gcreason::Reason reason) -{ - rt->gc.minorGC(reason); -} - void GCRuntime::minorGC(JS::gcreason::Reason reason) { @@ -5328,16 +5841,10 @@ GCRuntime::minorGC(JS::gcreason::Reason reason) } void -js::MinorGC(JSContext *cx, JS::gcreason::Reason reason) +GCRuntime::minorGC(JSContext *cx, JS::gcreason::Reason reason) { // Alternate to the runtime-taking form above which allows marking type // objects as needing pretenuring. - cx->runtime()->gc.minorGC(cx, reason); -} - -void -GCRuntime::minorGC(JSContext *cx, JS::gcreason::Reason reason) -{ #ifdef JSGC_GENERATIONAL TraceLogger *logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); @@ -5377,12 +5884,6 @@ GCRuntime::enableGenerationalGC() #endif } -void -js::gc::GCIfNeeded(JSContext *cx) -{ - cx->runtime()->gc.gcIfNeeded(cx); -} - void GCRuntime::gcIfNeeded(JSContext *cx) { @@ -5399,12 +5900,6 @@ GCRuntime::gcIfNeeded(JSContext *cx) gcSlice(GC_NORMAL, rt->gc.triggerReason, 0); } -void -js::gc::FinishBackgroundFinalize(JSRuntime *rt) -{ - rt->gc.waitBackgroundSweepEnd(); -} - AutoFinishGC::AutoFinishGC(JSRuntime *rt) { if (JS::IsIncrementalGCInProgress(rt)) { @@ -5412,7 +5907,7 @@ AutoFinishGC::AutoFinishGC(JSRuntime *rt) JS::FinishIncrementalGC(rt, JS::gcreason::API); } - gc::FinishBackgroundFinalize(rt); + rt->gc.waitBackgroundSweepEnd(); } AutoPrepareForTracing::AutoPrepareForTracing(JSRuntime *rt, ZoneSelector selector) @@ -5520,12 +6015,6 @@ gc::MergeCompartments(JSCompartment *source, JSCompartment *target) target->zone()->types.typeLifoAlloc.transferFrom(&source->zone()->types.typeLifoAlloc); } -void -gc::RunDebugGC(JSContext *cx) -{ - cx->runtime()->gc.runDebugGC(); -} - void GCRuntime::runDebugGC() { @@ -5536,7 +6025,7 @@ GCRuntime::runDebugGC() return; if (type == js::gc::ZealGenerationalGCValue) - return MinorGC(rt, JS::gcreason::DEBUG_GC); + return minorGC(JS::gcreason::DEBUG_GC); PrepareForDebugGC(rt); @@ -5573,6 +6062,8 @@ GCRuntime::runDebugGC() { incrementalLimit = zealFrequency / 2; } + } else if (type == ZealCompactValue) { + collect(false, SliceBudget::Unlimited, GC_SHRINK, JS::gcreason::DEBUG_GC); } else { collect(false, SliceBudget::Unlimited, GC_NORMAL, JS::gcreason::DEBUG_GC); } @@ -5634,7 +6125,7 @@ js::ReleaseAllJITCode(FreeOp *fop) * Scripts can entrain nursery things, inserting references to the script * into the store buffer. Clear the store buffer before discarding scripts. */ - MinorGC(fop->runtime(), JS::gcreason::EVICT_NURSERY); + fop->runtime()->gc.evictNursery(); #endif for (ZonesIter zone(fop->runtime(), SkipAtoms); !zone.done(); zone.next()) { @@ -5864,3 +6355,21 @@ JS::AutoAssertOnGC::VerifyIsSafeToGC(JSRuntime *rt) MOZ_CRASH("[AutoAssertOnGC] possible GC in GC-unsafe region"); } #endif + +#ifdef JSGC_HASH_TABLE_CHECKS +void +js::gc::CheckHashTablesAfterMovingGC(JSRuntime *rt) +{ + /* + * Check that internal hash tables no longer have any pointers to things + * that have been moved. + */ + for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { + c->checkNewTypeObjectTablesAfterMovingGC(); + c->checkInitialShapesTableAfterMovingGC(); + c->checkWrapperMapAfterMovingGC(); + if (c->debugScopes) + c->debugScopes->checkHashTablesAfterMovingGC(rt); + } +} +#endif diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 8cdd447a3e9..624480e1298 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -12,6 +12,7 @@ #include "mozilla/Atomics.h" #include "mozilla/DebugOnly.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/TypeTraits.h" #include "jslock.h" #include "jsobj.h" @@ -46,7 +47,9 @@ enum State { MARK_ROOTS, MARK, SWEEP, - INVALID +#ifdef JSGC_COMPACTING + COMPACT +#endif }; static inline JSGCTraceKind @@ -520,6 +523,11 @@ class ArenaList { check(); return *this; } + +#ifdef JSGC_COMPACTING + ArenaHeader *pickArenasToRelocate(); + ArenaHeader *relocateArenas(ArenaHeader *toRelocate, ArenaHeader *relocated); +#endif }; /* @@ -796,7 +804,6 @@ class ArenaLists clearFreeListInArena(AllocKind(i)); } - void clearFreeListInArena(AllocKind kind) { FreeList *freeList = &freeLists[kind]; if (!freeList->isEmpty()) { @@ -842,6 +849,8 @@ class ArenaLists template static void *refillFreeList(ThreadSafeContext *cx, AllocKind thingKind); + static void *refillFreeListInGC(Zone *zone, AllocKind thingKind); + /* * Moves all arenas from |fromArenaLists| into |this|. In * parallel blocks, we temporarily create one ArenaLists per @@ -865,6 +874,10 @@ class ArenaLists JS_ASSERT(freeLists[kind].isEmpty()); } +#ifdef JSGC_COMPACTING + ArenaHeader *relocateArenas(ArenaHeader *relocatedList); +#endif + void queueObjectsForSweep(FreeOp *fop); void queueStringsAndSymbolsForSweep(FreeOp *fop); void queueShapesForSweep(FreeOp *fop); @@ -963,14 +976,6 @@ MarkCompartmentActive(js::InterpreterFrame *fp); extern void TraceRuntime(JSTracer *trc); -/* Must be called with GC lock taken. */ -extern bool -TriggerGC(JSRuntime *rt, JS::gcreason::Reason reason); - -/* Must be called with GC lock taken. */ -extern bool -TriggerZoneGC(Zone *zone, JS::gcreason::Reason reason); - extern void ReleaseAllJITCode(FreeOp *op); @@ -985,27 +990,9 @@ typedef enum JSGCInvocationKind { GC_SHRINK = 1 } JSGCInvocationKind; -extern void -GC(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason); - -extern void -GCSlice(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0); - -extern void -GCFinalSlice(JSRuntime *rt, JSGCInvocationKind gckind, JS::gcreason::Reason reason); - -extern void -GCDebugSlice(JSRuntime *rt, bool limit, int64_t objCount); - extern void PrepareForDebugGC(JSRuntime *rt); -extern void -MinorGC(JSRuntime *rt, JS::gcreason::Reason reason); - -extern void -MinorGC(JSContext *cx, JS::gcreason::Reason reason); - /* Functions for managing cross compartment gray pointers. */ extern void @@ -1225,17 +1212,6 @@ NewCompartment(JSContext *cx, JS::Zone *zone, JSPrincipals *principals, namespace gc { -extern void -GCIfNeeded(JSContext *cx); - -/* Tries to run a GC no matter what (used for GC zeal). */ -void -RunDebugGC(JSContext *cx); - -/* Wait for the background thread to finish sweeping if it is running. */ -void -FinishBackgroundFinalize(JSRuntime *rt); - /* * Merge all contents of source into target. This can only be used if source is * the only compartment in its zone. @@ -1243,6 +1219,104 @@ FinishBackgroundFinalize(JSRuntime *rt); void MergeCompartments(JSCompartment *source, JSCompartment *target); +#ifdef JSGC_COMPACTING + +/* Functions for checking and updating things that might be moved by compacting GC. */ + +#ifdef JS_PUNBOX64 +const uintptr_t ForwardedCellMagicValue = 0xf1f1f1f1f1f1f1f1; +#else +const uintptr_t ForwardedCellMagicValue = 0xf1f1f1f1; +#endif + +template +inline bool +IsForwarded(T *t) +{ + static_assert(mozilla::IsBaseOf::value, "T must be a subclass of Cell"); + uintptr_t *ptr = reinterpret_cast(t); + return ptr[1] == ForwardedCellMagicValue; +} + +inline bool +IsForwarded(const JS::Value &value) +{ + if (value.isObject()) + return IsForwarded(&value.toObject()); + + if (value.isString()) + return IsForwarded(value.toString()); + + if (value.isSymbol()) + return IsForwarded(value.toSymbol()); + + JS_ASSERT(!value.isGCThing()); + return false; +} + +template +inline T * +Forwarded(T *t) +{ + JS_ASSERT(IsForwarded(t)); + uintptr_t *ptr = reinterpret_cast(t); + return reinterpret_cast(ptr[0]); +} + +inline Value +Forwarded(const JS::Value &value) +{ + if (value.isObject()) + return ObjectValue(*Forwarded(&value.toObject())); + else if (value.isString()) + return StringValue(Forwarded(value.toString())); + else if (value.isSymbol()) + return SymbolValue(Forwarded(value.toSymbol())); + + JS_ASSERT(!value.isGCThing()); + return value; +} + +template +inline T +MaybeForwarded(T t) +{ + return IsForwarded(t) ? Forwarded(t) : t; +} + +#else + +template inline bool IsForwarded(T t) { return false; } +template inline T Forwarded(T t) { return t; } +template inline T MaybeForwarded(T t) { return t; } + +#endif // JSGC_COMPACTING + +#ifdef JSGC_HASH_TABLE_CHECKS + +template +inline void +CheckGCThingAfterMovingGC(T *t) +{ + JS_ASSERT_IF(t, !IsInsideNursery(t)); +#ifdef JSGC_COMPACTING + JS_ASSERT_IF(t, !IsForwarded(t)); +#endif +} + +inline void +CheckValueAfterMovingGC(const JS::Value& value) +{ + if (value.isObject()) + return CheckGCThingAfterMovingGC(&value.toObject()); + else if (value.isString()) + return CheckGCThingAfterMovingGC(value.toString()); + else if (value.isSymbol()) + return CheckGCThingAfterMovingGC(value.toSymbol()); +} + +#endif // JSGC_HASH_TABLE_CHECKS + const int ZealPokeValue = 1; const int ZealAllocValue = 2; const int ZealFrameGCValue = 3; @@ -1256,7 +1330,8 @@ const int ZealIncrementalMultipleSlices = 10; const int ZealVerifierPostValue = 11; const int ZealFrameVerifierPostValue = 12; const int ZealCheckHashTablesOnMinorGC = 13; -const int ZealLimit = 13; +const int ZealCompactValue = 14; +const int ZealLimit = 14; enum VerifierType { PreBarrierVerifier, @@ -1352,6 +1427,20 @@ struct AutoDisableProxyCheck }; #endif +struct AutoDisableCompactingGC +{ +#ifdef JSGC_COMPACTING + explicit AutoDisableCompactingGC(JSRuntime *rt); + ~AutoDisableCompactingGC(); + + private: + gc::GCRuntime &gc; +#else + explicit AutoDisableCompactingGC(JSRuntime *rt) {} + ~AutoDisableCompactingGC() {} +#endif +}; + void PurgeJITCaches(JS::Zone *zone); diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 8e764d08d0d..384b2a0af4a 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -368,14 +368,13 @@ class ZoneCellIter : public ZoneCellIterImpl if (IsBackgroundFinalized(kind) && zone->allocator.arenas.needBackgroundFinalizeWait(kind)) { - gc::FinishBackgroundFinalize(zone->runtimeFromMainThread()); + zone->runtimeFromMainThread()->gc.waitBackgroundSweepEnd(); } #ifdef JSGC_GENERATIONAL /* Evict the nursery before iterating so we can see all things. */ JSRuntime *rt = zone->runtimeFromMainThread(); - if (!rt->gc.nursery.isEmpty()) - MinorGC(rt, JS::gcreason::EVICT_NURSERY); + rt->gc.evictNursery(); #endif if (lists->isSynchronizedFreeList(kind)) { @@ -473,7 +472,7 @@ TryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots) if (obj) return obj; if (allowGC && !rt->mainThread.suppressGC) { - MinorGC(cx, JS::gcreason::OUT_OF_NURSERY); + cx->minorGC(JS::gcreason::OUT_OF_NURSERY); /* Exceeding gcMaxBytes while tenuring can disable the Nursery. */ if (nursery.isEnabled()) { @@ -547,13 +546,13 @@ CheckAllocatorState(ThreadSafeContext *cx, AllocKind kind) if (allowGC) { #ifdef JS_GC_ZEAL if (rt->gc.needZealousGC()) - js::gc::RunDebugGC(ncx); + rt->gc.runDebugGC(); #endif if (rt->interrupt) { // Invoking the interrupt callback can fail and we can't usefully // handle that here. Just check in case we need to collect instead. - js::gc::GCIfNeeded(ncx); + ncx->gcIfNeeded(); } } @@ -682,7 +681,7 @@ AllocateObjectForCacheHit(JSContext *cx, AllocKind kind, InitialHeap heap) JSObject *obj = TryNewNurseryObject(cx, thingSize, 0); if (!obj && allowGC) { - MinorGC(cx, JS::gcreason::OUT_OF_NURSERY); + cx->minorGC(JS::gcreason::OUT_OF_NURSERY); return nullptr; } return obj; diff --git a/js/src/jshashutil.h b/js/src/jshashutil.h index aebb15bcfc3..d95cbd41a9b 100644 --- a/js/src/jshashutil.h +++ b/js/src/jshashutil.h @@ -13,7 +13,7 @@ namespace js { /* * Used to add entries to a js::HashMap or HashSet where the key depends on a GC - * thing that may be moved by generational collection between the call to + * thing that may be moved by generational or compacting GC between the call to * lookupForAdd() and relookupOrAdd(). */ template diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 5d2bf9b2613..a0a7eb49a22 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -3440,12 +3440,13 @@ types::TypeMonitorCallSlow(JSContext *cx, JSObject *callee, const CallArgs &args } static inline bool -IsAboutToBeFinalized(TypeObjectKey *key) +IsAboutToBeFinalized(TypeObjectKey **keyp) { /* Mask out the low bit indicating whether this is a type or JS object. */ - gc::Cell *tmp = reinterpret_cast(uintptr_t(key) & ~1); + uintptr_t flagBit = uintptr_t(*keyp) & 1; + gc::Cell *tmp = reinterpret_cast(uintptr_t(*keyp) & ~1); bool isAboutToBeFinalized = IsCellAboutToBeFinalized(&tmp); - JS_ASSERT(tmp == reinterpret_cast(uintptr_t(key) & ~1)); + *keyp = reinterpret_cast(uintptr_t(tmp) | flagBit); return isAboutToBeFinalized; } @@ -3887,28 +3888,6 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction * return type; } -#if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) -void -JSCompartment::checkNewTypeObjectTableAfterMovingGC() -{ - /* - * Assert that the postbarriers have worked and that nothing is left in - * newTypeObjects that points into the nursery, and that the hash table - * entries are discoverable. - */ - for (TypeObjectWithNewScriptSet::Enum e(newTypeObjects); !e.empty(); e.popFront()) { - TypeObjectWithNewScriptEntry entry = e.front(); - JS_ASSERT(!IsInsideNursery(entry.newFunction)); - TaggedProto proto = entry.object->proto(); - JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(proto.toObject())); - TypeObjectWithNewScriptEntry::Lookup - lookup(entry.object->clasp(), proto, entry.newFunction); - TypeObjectWithNewScriptSet::Ptr ptr = newTypeObjects.lookup(lookup); - JS_ASSERT(ptr.found() && &*ptr == &e.front()); - } -} -#endif - TypeObject * ExclusiveContext::getSingletonType(const Class *clasp, TaggedProto proto) { @@ -3965,7 +3944,7 @@ ConstraintTypeSet::sweep(Zone *zone, bool *oom) objectCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { TypeObjectKey *object = oldArray[i]; - if (object && !IsAboutToBeFinalized(object)) { + if (object && !IsAboutToBeFinalized(&object)) { TypeObjectKey **pentry = HashSetInsert (zone->types.typeLifoAlloc, objectSet, objectCount, object); @@ -3983,9 +3962,11 @@ ConstraintTypeSet::sweep(Zone *zone, bool *oom) setBaseObjectCount(objectCount); } else if (objectCount == 1) { TypeObjectKey *object = (TypeObjectKey *) objectSet; - if (IsAboutToBeFinalized(object)) { + if (IsAboutToBeFinalized(&object)) { objectSet = nullptr; setBaseObjectCount(0); + } else { + objectSet = reinterpret_cast(object); } } @@ -4199,26 +4180,95 @@ TypeCompartment::sweep(FreeOp *fop) void JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) { - gcstats::AutoPhase ap(runtimeFromMainThread()->gc.stats, - gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); - - JS_ASSERT(zone()->isGCSweeping()); + JS_ASSERT(zone()->isCollecting()); if (table.initialized()) { for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { TypeObjectWithNewScriptEntry entry = e.front(); - if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) { + if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet()) || + (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction))) + { e.removeFront(); - } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) { - e.removeFront(); - } else if (entry.object.unbarrieredGet() != e.front().object.unbarrieredGet()) { + } else { + /* Any rekeying necessary is handled by fixupNewTypeObjectTable() below. */ + JS_ASSERT(entry.object == e.front().object); + JS_ASSERT(entry.newFunction == e.front().newFunction); + } + } + } +} + +#ifdef JSGC_COMPACTING +void +JSCompartment::fixupNewTypeObjectTable(TypeObjectWithNewScriptSet &table) +{ + /* + * Each entry's hash depends on the object's prototype and we can't tell + * whether that has been moved or not in sweepNewTypeObjectTable(). + */ + JS_ASSERT(zone()->isCollecting()); + if (table.initialized()) { + for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { + TypeObjectWithNewScriptEntry entry = e.front(); + bool needRekey = false; + if (IsForwarded(entry.object.get())) { + entry.object.set(Forwarded(entry.object.get())); + needRekey = true; + } + TaggedProto proto = entry.object->proto(); + if (proto.isObject() && IsForwarded(proto.toObject())) { + proto = TaggedProto(Forwarded(proto.toObject())); + needRekey = true; + } + if (entry.newFunction && IsForwarded(entry.newFunction)) { + entry.newFunction = Forwarded(entry.newFunction); + needRekey = true; + } + if (needRekey) { TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp(), - entry.object->proto(), + proto, entry.newFunction); e.rekeyFront(lookup, entry); } } } } +#endif + +#ifdef JSGC_HASH_TABLE_CHECKS + +void +JSCompartment::checkNewTypeObjectTablesAfterMovingGC() +{ + checkNewTypeObjectTableAfterMovingGC(newTypeObjects); + checkNewTypeObjectTableAfterMovingGC(lazyTypeObjects); +} + +void +JSCompartment::checkNewTypeObjectTableAfterMovingGC(TypeObjectWithNewScriptSet &table) +{ + /* + * Assert that nothing points into the nursery or needs to be relocated, and + * that the hash table entries are discoverable. + */ + if (!table.initialized()) + return; + + for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { + TypeObjectWithNewScriptEntry entry = e.front(); + CheckGCThingAfterMovingGC(entry.object.get()); + TaggedProto proto = entry.object->proto(); + if (proto.isObject()) + CheckGCThingAfterMovingGC(proto.toObject()); + CheckGCThingAfterMovingGC(entry.newFunction); + + TypeObjectWithNewScriptEntry::Lookup + lookup(entry.object->clasp(), proto, entry.newFunction); + TypeObjectWithNewScriptSet::Ptr ptr = table.lookup(lookup); + JS_ASSERT(ptr.found() && &*ptr == &e.front()); + } +} + +#endif // JSGC_HASH_TABLE_CHECKS TypeCompartment::~TypeCompartment() { @@ -4231,7 +4281,7 @@ TypeCompartment::~TypeCompartment() TypeScript::Sweep(FreeOp *fop, JSScript *script, bool *oom) { JSCompartment *compartment = script->compartment(); - JS_ASSERT(compartment->zone()->isGCSweeping()); + JS_ASSERT(compartment->zone()->isGCSweepingOrCompacting()); unsigned num = NumTypeSets(script); StackTypeSet *typeArray = script->types->typeArray(); @@ -4310,7 +4360,7 @@ TypeZone::~TypeZone() void TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) { - JS_ASSERT(zone()->isGCSweeping()); + JS_ASSERT(zone()->isGCSweepingOrCompacting()); JSRuntime *rt = fop->runtime(); @@ -4340,7 +4390,8 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) } { - gcstats::AutoPhase ap2(rt->gc.stats, gcstats::PHASE_DISCARD_TI); + gcstats::MaybeAutoPhase ap2(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_DISCARD_TI); for (ZoneCellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) { JSScript *script = i.get(); @@ -4371,7 +4422,8 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) } { - gcstats::AutoPhase ap2(rt->gc.stats, gcstats::PHASE_SWEEP_TYPES); + gcstats::MaybeAutoPhase ap2(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_SWEEP_TYPES); for (gc::ZoneCellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); !iter.done(); iter.next()) @@ -4399,7 +4451,8 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) } { - gcstats::AutoPhase ap2(rt->gc.stats, gcstats::PHASE_FREE_TI_ARENA); + gcstats::MaybeAutoPhase ap2(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_FREE_TI_ARENA); rt->freeLifoAlloc.transferFrom(&oldAlloc); } } diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 17dba45ac10..d9688403f95 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -1571,6 +1571,8 @@ FinalizeGenerator(FreeOp *fop, JSObject *obj) static void MarkGeneratorFrame(JSTracer *trc, JSGenerator *gen) { + gen->obj = MaybeForwarded(gen->obj.get()); + MarkObject(trc, &gen->obj, "Generator Object"); MarkValueRange(trc, HeapValueify(gen->fp->generatorArgsSnapshotBegin()), HeapValueify(gen->fp->generatorArgsSnapshotEnd()), diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index c6656f40c87..8c338e22b3c 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -17,6 +17,7 @@ #include "vm/Shape-inl.h" using namespace js; +using namespace js::gc; inline HashNumber ShapeHasher::hash(const Lookup &l) @@ -268,6 +269,76 @@ Shape::finalize(FreeOp *fop) fop->delete_(kids.toHash()); } +#ifdef JSGC_COMPACTING + +void +Shape::fixupDictionaryShapeAfterMovingGC() +{ + if (!listp) + return; + + JS_ASSERT(!IsInsideNursery(reinterpret_cast(listp))); + AllocKind kind = reinterpret_cast(listp)->tenuredGetAllocKind(); + JS_ASSERT(kind == FINALIZE_SHAPE || kind <= FINALIZE_OBJECT_LAST); + if (kind == FINALIZE_SHAPE) { + // listp points to the parent field of the next shape. + Shape *next = reinterpret_cast(uintptr_t(listp) - + offsetof(Shape, parent)); + listp = &gc::MaybeForwarded(next)->parent; + } else { + // listp points to the shape_ field of an object. + JSObject *last = reinterpret_cast(uintptr_t(listp) - + offsetof(JSObject, shape_)); + listp = &gc::MaybeForwarded(last)->shape_; + } +} + +void +Shape::fixupShapeTreeAfterMovingGC() +{ + if (kids.isNull()) + return; + + if (kids.isShape()) { + if (gc::IsForwarded(kids.toShape())) + kids.setShape(gc::Forwarded(kids.toShape())); + return; + } + + JS_ASSERT(kids.isHash()); + KidsHash *kh = kids.toHash(); + for (KidsHash::Enum e(*kh); !e.empty(); e.popFront()) { + Shape *key = e.front(); + if (!IsForwarded(key)) + continue; + + key = Forwarded(key); + BaseShape *base = key->base(); + if (IsForwarded(base)) + base = Forwarded(base); + UnownedBaseShape *unowned = base->unowned(); + if (IsForwarded(unowned)) + unowned = Forwarded(unowned); + StackShape lookup(unowned, + const_cast(key)->propidRef(), + key->slotInfo & Shape::SLOT_MASK, + key->attrs, + key->flags); + e.rekeyFront(lookup, key); + } +} + +void +Shape::fixupAfterMovingGC() +{ + if (inDictionary()) + fixupDictionaryShapeAfterMovingGC(); + else + fixupShapeTreeAfterMovingGC(); +} + +#endif // JSGC_COMPACTING + #ifdef DEBUG void diff --git a/js/src/jspropertytree.h b/js/src/jspropertytree.h index 40b509be9ab..f0c526cdc83 100644 --- a/js/src/jspropertytree.h +++ b/js/src/jspropertytree.h @@ -17,7 +17,7 @@ namespace js { class Shape; struct StackShape; -struct ShapeHasher { +struct ShapeHasher : public DefaultHasher { typedef Shape *Key; typedef StackShape Lookup; diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 1fdfdc4f013..ae41836dc40 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -2834,7 +2834,7 @@ ProxyObject::trace(JSTracer *trc, JSObject *obj) #ifdef DEBUG if (trc->runtime()->gc.isStrictProxyCheckingEnabled() && proxy->is()) { - JSObject *referent = &proxy->private_().toObject(); + JSObject *referent = MaybeForwarded(&proxy->private_().toObject()); if (referent->compartment() != proxy->compartment()) { /* * Assert that this proxy is tracked in the wrapper map. We maintain @@ -2842,6 +2842,7 @@ ProxyObject::trace(JSTracer *trc, JSObject *obj) */ Value key = ObjectValue(*referent); WrapperMap::Ptr p = proxy->compartment()->lookupWrapper(key); + JS_ASSERT(p); JS_ASSERT(*p->value().unsafeGet() == ObjectValue(*proxy)); } } diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 576fa4134cf..86145d0afe7 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -25,6 +25,11 @@ # define JSGC_TRACK_EXACT_ROOTS #endif +#if (defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL)) || \ + (defined(JSGC_COMPACTING) && defined(DEBUG)) +# define JSGC_HASH_TABLE_CHECKS +#endif + namespace JS { class AutoIdVector; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index ff6f3eb1b00..d7978e07345 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -3339,7 +3339,7 @@ JSScript::markChildren(JSTracer *trc) } if (sourceObject()) { - JS_ASSERT(sourceObject()->compartment() == compartment()); + JS_ASSERT(MaybeForwarded(sourceObject())->compartment() == compartment()); MarkObject(trc, &sourceObject_, "sourceObject"); } diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index 2783658a0f9..c25a0dc2a14 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -68,6 +68,13 @@ WeakMapBase::unmarkCompartment(JSCompartment *c) m->marked = false; } +void +WeakMapBase::markAll(JSCompartment *c, JSTracer *tracer) +{ + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) + m->markIteratively(tracer); +} + bool WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer) { diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index 690278be3a7..07fa008fc61 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -49,6 +49,9 @@ class WeakMapBase { // Unmark all weak maps in a compartment. static void unmarkCompartment(JSCompartment *c); + // Mark all the weakmaps in a compartment. + static void markAll(JSCompartment *c, JSTracer *tracer); + // Check all weak maps in a compartment that have been marked as live in this garbage // collection, and mark the values of all entries that have become strong references // to them. Return true if we marked any new values, indicating that we need to make diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 1a629cba7dd..59fc41680da 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -88,6 +88,11 @@ js::UncheckedUnwrap(JSObject *wrapped, bool stopAtOuter, unsigned *flagsp) } flags |= Wrapper::wrapperHandler(wrapped)->flags(); wrapped = wrapped->as().private_().toObjectOrNull(); + + // This can be called from DirectProxyHandler::weakmapKeyDelegate() on a + // wrapper whose referent has been moved while it is still unmarked. + if (wrapped) + wrapped = MaybeForwarded(wrapped); } if (flagsp) *flagsp = flags; diff --git a/js/src/shell/jsheaptools.cpp b/js/src/shell/jsheaptools.cpp index 93cc36cbdd2..f172b1710e0 100644 --- a/js/src/shell/jsheaptools.cpp +++ b/js/src/shell/jsheaptools.cpp @@ -157,6 +157,7 @@ class HeapReverser : public JSTracer, public JS::CustomAutoRooter : JSTracer(cx->runtime(), traverseEdgeWithThis), JS::CustomAutoRooter(cx), noggc(JS_GetRuntime(cx)), + nocgc(JS_GetRuntime(cx)), runtime(JS_GetRuntime(cx)), parent(nullptr) { @@ -169,6 +170,7 @@ class HeapReverser : public JSTracer, public JS::CustomAutoRooter private: JS::AutoDisableGenerationalGC noggc; + js::AutoDisableCompactingGC nocgc; /* A runtime pointer for use by the destructor. */ JSRuntime *runtime; diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 6e0d35d3880..2aaf5b34f24 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -826,9 +826,10 @@ ArrayBufferObject::finalize(FreeOp *fop, JSObject *obj) /* static */ void ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) { - if (!IS_GC_MARKING_TRACER(trc) && !trc->runtime()->isHeapMinorCollecting() + JSRuntime *rt = trc->runtime(); + if (!IS_GC_MARKING_TRACER(trc) && !rt->isHeapMinorCollecting() && !rt->isHeapCompacting() #ifdef JSGC_FJGENERATIONAL - && !trc->runtime()->isFJMinorCollecting() + && !rt->isFJMinorCollecting() #endif ) { @@ -852,15 +853,16 @@ ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) if (!viewsHead) return; - buffer.setViewList(UpdateObjectIfRelocated(trc->runtime(), &viewsHead)); + ArrayBufferViewObject *tmp = viewsHead; + buffer.setViewList(UpdateObjectIfRelocated(rt, &tmp)); - if (viewsHead->nextView() == nullptr) { + if (tmp->nextView() == nullptr) { // Single view: mark it, but only if we're actually doing a GC pass // right now. Otherwise, the tracing pass for barrier verification will // fail if we add another view and the pointer becomes weak. MarkObjectUnbarriered(trc, &viewsHead, "arraybuffer.singleview"); buffer.setViewListNoBarrier(viewsHead); - } else { + } else if (!rt->isHeapCompacting()) { // Multiple views: do not mark, but append buffer to list. ArrayBufferVector &gcLiveArrayBuffers = buffer.compartment()->gcLiveArrayBuffers; @@ -878,6 +880,19 @@ ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) } else { CrashAtUnhandlableOOM("OOM while updating live array buffers"); } + } else { + // If we're fixing up pointers after compacting then trace everything. + ArrayBufferViewObject *prev = nullptr; + ArrayBufferViewObject *view = viewsHead; + while (view) { + JS_ASSERT(buffer.compartment() == MaybeForwarded(view)->compartment()); + MarkObjectUnbarriered(trc, &view, "arraybuffer.singleview"); + if (prev) + prev->setNextView(view); + else + buffer.setViewListNoBarrier(view); + view = view->nextView(); + } } } @@ -917,6 +932,15 @@ ArrayBufferObject::sweep(JSCompartment *compartment) gcLiveArrayBuffers.clear(); } +/* static */ void +ArrayBufferObject::fixupDataPointerAfterMovingGC(const ArrayBufferObject &src, ArrayBufferObject &dst) +{ + // Fix up possible inline data pointer. + const size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&ArrayBufferObject::class_); + if (src.dataPointer() == src.fixedData(reservedSlots)) + dst.setSlot(DATA_SLOT, PrivateValue(dst.fixedData(reservedSlots))); +} + void ArrayBufferObject::resetArrayBufferList(JSCompartment *comp) { @@ -977,7 +1001,7 @@ ArrayBufferViewObject::trace(JSTracer *trc, JSObject *obj) // Update obj's data pointer if the array buffer moved. Note that during // initialization, bufSlot may still contain |undefined|. if (bufSlot.isObject()) { - ArrayBufferObject &buf = AsArrayBuffer(&bufSlot.toObject()); + ArrayBufferObject &buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject())); int32_t offset = obj->getReservedSlot(BYTEOFFSET_SLOT).toInt32(); MOZ_ASSERT(buf.dataPointer() != nullptr); obj->initPrivate(buf.dataPointer() + offset); diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 22b84081320..8bf20bc7658 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -95,6 +95,8 @@ class ArrayBufferObject : public JSObject static void sweep(JSCompartment *rt); + static void fixupDataPointerAfterMovingGC(const ArrayBufferObject &src, ArrayBufferObject &dst); + static void resetArrayBufferList(JSCompartment *rt); static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector); static void restoreArrayBufferLists(ArrayBufferVector &vector); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 452ac41585a..1d9bb69a6cf 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1709,7 +1709,7 @@ Debugger::trace(JSTracer *trc) */ for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) { RelocatablePtrObject &frameobj = r.front().value(); - JS_ASSERT(frameobj->getPrivate()); + JS_ASSERT(MaybeForwarded(frameobj.get())->getPrivate()); MarkObject(trc, &frameobj, "live Debugger.Frame"); } @@ -1752,20 +1752,6 @@ Debugger::sweepAll(FreeOp *fop) } } } - - for (gc::GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) { - /* For each debuggee being GC'd, detach it from all its debuggers. */ - GlobalObjectSet &debuggees = comp->getDebuggees(); - for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { - GlobalObject *global = e.front(); - if (IsObjectAboutToBeFinalized(&global)) { - // See infallibility note above. - detachAllDebuggersFromGlobal(fop, global, &e); - } - else if (global != e.front()) - e.rekeyFront(global); - } - } } void diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 02d66156b21..784e7f944f3 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -127,6 +127,9 @@ class DebuggerWeakMap : private WeakMap > if (gc::IsAboutToBeFinalized(&k)) { e.removeFront(); decZoneCount(k->zone()); + } else { + // markKeys() should have done any necessary relocation. + JS_ASSERT(k == e.front().key()); } } Base::assertEntriesNotAboutToBeFinalized(); diff --git a/js/src/vm/ForkJoin.cpp b/js/src/vm/ForkJoin.cpp index a7f9952b6b1..fe89743475b 100644 --- a/js/src/vm/ForkJoin.cpp +++ b/js/src/vm/ForkJoin.cpp @@ -355,7 +355,7 @@ ForkJoinActivation::ForkJoinActivation(JSContext *cx) JS::FinishIncrementalGC(cx->runtime(), JS::gcreason::API); } - MinorGC(cx->runtime(), JS::gcreason::API); + cx->runtime()->gc.evictNursery(); cx->runtime()->gc.waitBackgroundSweepEnd(); @@ -1492,10 +1492,11 @@ ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() if (gcRequested_) { Spew(SpewGC, "Triggering garbage collection in SpiderMonkey heap"); + gc::GCRuntime &gc = cx_->runtime()->gc; if (!gcZone_) - TriggerGC(cx_->runtime(), gcReason_); + gc.triggerGC(gcReason_); else - TriggerZoneGC(gcZone_, gcReason_); + gc.triggerZoneGC(gcZone_, gcReason_); gcRequested_ = false; gcZone_ = nullptr; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9967804c1de..e5fc625344b 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -415,7 +415,7 @@ struct AutoGCIfNeeded { JSContext *cx_; explicit AutoGCIfNeeded(JSContext *cx) : cx_(cx) {} - ~AutoGCIfNeeded() { js::gc::GCIfNeeded(cx_); } + ~AutoGCIfNeeded() { cx_->gcIfNeeded(); } }; /* diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 9c1729d7c65..994cdfd3f9d 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -376,7 +376,7 @@ JSRuntime::~JSRuntime() profilingScripts = false; JS::PrepareForFullGC(this); - GC(this, GC_NORMAL, JS::gcreason::DESTROY_RUNTIME); + gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME); } /* diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 348e2a1be9c..a6441186694 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -937,13 +937,14 @@ struct JSRuntime : public JS::shadow::Runtime, /* Garbage collector state, used by jsgc.c. */ js::gc::GCRuntime gc; - /* Garbase collector state has been sucessfully initialized. */ + /* Garbage collector state has been sucessfully initialized. */ bool gcInitialized; bool isHeapBusy() { return gc.isHeapBusy(); } bool isHeapMajorCollecting() { return gc.isHeapMajorCollecting(); } bool isHeapMinorCollecting() { return gc.isHeapMinorCollecting(); } bool isHeapCollecting() { return gc.isHeapCollecting(); } + bool isHeapCompacting() { return gc.isHeapCompacting(); } bool isFJMinorCollecting() { return gc.isFJMinorCollecting(); } diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 815b7e5d5d1..3ecaff67ba6 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -10,6 +10,7 @@ #include "jsapi.h" #include "jscompartment.h" #include "jsfriendapi.h" +#include "jshashutil.h" #include "jsnum.h" #include "gc/Marking.h" @@ -562,7 +563,7 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram SavedFrame * SavedStacks::getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup) { - SavedFrame::Set::AddPtr p = frames.lookupForAdd(lookup); + DependentAddPtr p(cx, frames, lookup); if (p) return *p; @@ -570,7 +571,7 @@ SavedStacks::getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup looku if (!frame) return nullptr; - if (!frames.relookupOrAdd(p, lookup, frame)) + if (!p.add(cx, frames, lookup, frame)) return nullptr; return frame; diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 0d3ae2b4f0f..5fe614acb05 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -24,6 +24,7 @@ #include "vm/Stack-inl.h" using namespace js; +using namespace js::gc; using namespace js::types; using mozilla::PodZero; @@ -1069,6 +1070,15 @@ ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2) si1.type_ == si2.type_)); } +void +ScopeIterVal::sweep() +{ + /* We need to update possibly moved pointers on sweep. */ + MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalized(cur_.unsafeGet())); + if (staticScope_) + MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalized(staticScope_.unsafeGet())); +} + // Live ScopeIter values may be added to DebugScopes::liveScopes, as // ScopeIterVal instances. They need to have write barriers when they are added // to the hash table, but no barriers when rehashing inside GC. It's a nasty @@ -1784,24 +1794,39 @@ DebugScopes::sweep(JSRuntime *rt) */ liveScopes.remove(&(*debugScope)->scope()); e.removeFront(); + } else { + ScopeIterKey key = e.front().key(); + bool needsUpdate = false; + if (IsForwarded(key.cur())) { + key.updateCur(js::gc::Forwarded(key.cur())); + needsUpdate = true; + } + if (key.staticScope() && IsForwarded(key.staticScope())) { + key.updateStaticScope(Forwarded(key.staticScope())); + needsUpdate = true; + } + if (needsUpdate) + e.rekeyFront(key); } } for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { ScopeObject *scope = e.front().key(); + e.front().value().sweep(); + /* * Scopes can be finalized when a debugger-synthesized ScopeObject is * no longer reachable via its DebugScopeObject. */ - if (IsObjectAboutToBeFinalized(&scope)) { + if (IsObjectAboutToBeFinalized(&scope)) e.removeFront(); - continue; - } + else if (scope != e.front().key()) + e.rekeyFront(scope); } } -#if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) +#ifdef JSGC_HASH_TABLE_CHECKS void DebugScopes::checkHashTablesAfterMovingGC(JSRuntime *runtime) { @@ -1811,18 +1836,18 @@ DebugScopes::checkHashTablesAfterMovingGC(JSRuntime *runtime) * pointing into the nursery. */ for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) { - JS_ASSERT(!IsInsideNursery(r.front().key().get())); - JS_ASSERT(!IsInsideNursery(r.front().value().get())); + CheckGCThingAfterMovingGC(r.front().key().get()); + CheckGCThingAfterMovingGC(r.front().value().get()); } for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) { - JS_ASSERT(!IsInsideNursery(r.front().key().cur())); - JS_ASSERT(!IsInsideNursery(r.front().key().staticScope())); - JS_ASSERT(!IsInsideNursery(r.front().value().get())); + CheckGCThingAfterMovingGC(r.front().key().cur()); + CheckGCThingAfterMovingGC(r.front().key().staticScope()); + CheckGCThingAfterMovingGC(r.front().value().get()); } for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) { - JS_ASSERT(!IsInsideNursery(r.front().key())); - JS_ASSERT(!IsInsideNursery(r.front().value().cur_.get())); - JS_ASSERT(!IsInsideNursery(r.front().value().staticScope_.get())); + CheckGCThingAfterMovingGC(r.front().key()); + CheckGCThingAfterMovingGC(r.front().value().cur_.get()); + CheckGCThingAfterMovingGC(r.front().value().staticScope_.get()); } } #endif diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 5bed48fb20f..30e86d50ef0 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -704,6 +704,9 @@ class ScopeIterKey JSObject *enclosingScope() const { return cur_; } JSObject *&enclosingScope() { return cur_; } + void updateCur(JSObject *obj) { cur_ = obj; } + void updateStaticScope(NestedScopeObject *obj) { staticScope_ = obj; } + /* For use as hash policy */ typedef ScopeIterKey Lookup; static HashNumber hash(ScopeIterKey si); @@ -730,6 +733,8 @@ class ScopeIterVal ScopeIter::Type type_; bool hasScopeObject_; + void sweep(); + static void staticAsserts(); public: diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h index 7f91e365852..6cd5241bdb5 100644 --- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -220,6 +220,15 @@ GetShapeAttributes(JSObject *obj, Shape *shape) return shape->attributes(); } +#ifdef JSGC_COMPACTING +inline void +BaseShape::fixupAfterMovingGC() +{ + if (hasTable()) + table().fixupAfterMovingGC(); +} +#endif + } /* namespace js */ #endif /* vm_Shape_inl_h */ diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index e683548c25b..267a6ffc41e 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -248,6 +248,20 @@ ShapeTable::search(jsid id, bool adding) return nullptr; } +#ifdef JSGC_COMPACTING +void +ShapeTable::fixupAfterMovingGC() +{ + int log2 = HASH_BITS - hashShift; + uint32_t size = JS_BIT(log2); + for (HashNumber i = 0; i < size; i++) { + Shape *shape = SHAPE_FETCH(&entries[i]); + if (shape && IsForwarded(shape)) + SHAPE_STORE_PRESERVING_COLLISION(&entries[i], Forwarded(shape)); + } +} +#endif + bool ShapeTable::change(int log2Delta, ThreadSafeContext *cx) { @@ -429,6 +443,11 @@ js::ObjectImpl::toDictionaryMode(ThreadSafeContext *cx) { JS_ASSERT(!inDictionaryMode()); +#ifdef JSGC_COMPACTING + // TODO: This crashes if we run a compacting GC here. + js::AutoDisableCompactingGC nogc(zone()->runtimeFromAnyThread()); +#endif + /* We allocate the shapes from cx->compartment(), so make sure it's right. */ JS_ASSERT(cx->isInsideCurrentCompartment(this)); @@ -1523,14 +1542,20 @@ BaseShape::assertConsistency() void JSCompartment::sweepBaseShapeTable() { - gcstats::AutoPhase ap(runtimeFromMainThread()->gc.stats, - gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE); + GCRuntime &gc = runtimeFromMainThread()->gc; + gcstats::MaybeAutoPhase ap(gc.stats, !gc.isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE); if (baseShapes.initialized()) { for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { UnownedBaseShape *base = e.front().unbarrieredGet(); - if (IsBaseShapeAboutToBeFinalized(&base)) + if (IsBaseShapeAboutToBeFinalized(&base)) { e.removeFront(); + } else if (base != e.front()) { + StackBaseShape sbase(base); + ReadBarriered b(base); + e.rekeyFront(&sbase, b); + } } } } @@ -1652,7 +1677,10 @@ class InitialShapeSetRef : public BufferableRef } }; -#ifdef JS_GC_ZEAL +#endif // JSGC_GENERATIONAL + +#ifdef JSGC_HASH_TABLE_CHECKS + void JSCompartment::checkInitialShapesTableAfterMovingGC() { @@ -1669,9 +1697,12 @@ JSCompartment::checkInitialShapesTableAfterMovingGC() TaggedProto proto = entry.proto; Shape *shape = entry.shape.get(); - JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(proto.toObject())); - JS_ASSERT_IF(shape->getObjectParent(), !IsInsideNursery(shape->getObjectParent())); - JS_ASSERT_IF(shape->getObjectMetadata(), !IsInsideNursery(shape->getObjectMetadata())); + if (proto.isObject()) + CheckGCThingAfterMovingGC(proto.toObject()); + if (shape->getObjectParent()) + CheckGCThingAfterMovingGC(shape->getObjectParent()); + if (shape->getObjectMetadata()) + CheckGCThingAfterMovingGC(shape->getObjectMetadata()); InitialShapeEntry::Lookup lookup(shape->getObjectClass(), proto, @@ -1683,9 +1714,8 @@ JSCompartment::checkInitialShapesTableAfterMovingGC() JS_ASSERT(ptr.found() && &*ptr == &e.front()); } } -#endif -#endif +#endif // JSGC_HASH_TABLE_CHECKS /* static */ Shape * EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, @@ -1810,15 +1840,18 @@ EmptyShape::insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleOb void JSCompartment::sweepInitialShapeTable() { - gcstats::AutoPhase ap(runtimeFromMainThread()->gc.stats, - gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE); + GCRuntime &gc = runtimeFromMainThread()->gc; + gcstats::MaybeAutoPhase ap(gc.stats, !gc.isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE); if (initialShapes.initialized()) { for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { const InitialShapeEntry &entry = e.front(); Shape *shape = entry.shape.unbarrieredGet(); JSObject *proto = entry.proto.raw(); - if (IsShapeAboutToBeFinalized(&shape) || (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) { + if (IsShapeAboutToBeFinalized(&shape) || + (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) + { e.removeFront(); } else { #ifdef DEBUG @@ -1836,6 +1869,47 @@ JSCompartment::sweepInitialShapeTable() } } +#ifdef JSGC_COMPACTING +void +JSCompartment::fixupInitialShapeTable() +{ + if (!initialShapes.initialized()) + return; + + for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) { + InitialShapeEntry entry = e.front(); + bool needRekey = false; + if (IsForwarded(entry.shape.get())) { + entry.shape.set(Forwarded(entry.shape.get())); + needRekey = true; + } + if (entry.proto.isObject() && IsForwarded(entry.proto.toObject())) { + entry.proto = TaggedProto(Forwarded(entry.proto.toObject())); + needRekey = true; + } + JSObject *parent = entry.shape->getObjectParent(); + if (parent) { + parent = MaybeForwarded(parent); + needRekey = true; + } + JSObject *metadata = entry.shape->getObjectMetadata(); + if (metadata) { + metadata = MaybeForwarded(metadata); + needRekey = true; + } + if (needRekey) { + InitialShapeEntry::Lookup relookup(entry.shape->getObjectClass(), + entry.proto, + parent, + metadata, + entry.shape->numFixedSlots(), + entry.shape->getObjectFlags()); + e.rekeyFront(relookup, entry); + } + } +} +#endif // JSGC_COMPACTING + void AutoRooterGetterSetter::Inner::trace(JSTracer *trc) { diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index e334c466cfd..90f35873ef8 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -190,6 +190,11 @@ struct ShapeTable { bool init(ThreadSafeContext *cx, Shape *lastProp); bool change(int log2Delta, ThreadSafeContext *cx); Shape **search(jsid id, bool adding); + +#ifdef JSGC_COMPACTING + /* Update entries whose shapes have been moved */ + void fixupAfterMovingGC(); +#endif }; /* @@ -505,6 +510,10 @@ class BaseShape : public gc::BarrieredCell gc::MarkObject(trc, &metadata, "metadata"); } +#ifdef JSGC_COMPACTING + void fixupAfterMovingGC(); +#endif + private: static void staticAsserts() { JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_)); @@ -550,7 +559,7 @@ BaseShape::baseUnowned() } /* Entries for the per-compartment baseShapes set of unowned base shapes. */ -struct StackBaseShape +struct StackBaseShape : public DefaultHasher { typedef const StackBaseShape *Lookup; @@ -1028,10 +1037,19 @@ class Shape : public gc::BarrieredCell inline Shape *search(ExclusiveContext *cx, jsid id); inline Shape *searchLinear(jsid id); +#ifdef JSGC_COMPACTING + void fixupAfterMovingGC(); +#endif + /* For JIT usage */ static inline size_t offsetOfBase() { return offsetof(Shape, base_); } private: +#ifdef JSGC_COMPACTING + void fixupDictionaryShapeAfterMovingGC(); + void fixupShapeTreeAfterMovingGC(); +#endif + static void staticAsserts() { JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base)); JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo)); diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 75916593f1f..0fdbbba4a6d 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -80,6 +80,9 @@ class TypedArrayObject : public ArrayBufferViewObject ensureHasBuffer(JSContext *cx, Handle tarray); ArrayBufferObject *sharedBuffer() const; + bool hasBuffer() const { + return bufferValue(const_cast(this)).isObject(); + } ArrayBufferObject *buffer() const { JSObject *obj = bufferValue(const_cast(this)).toObjectOrNull(); if (!obj) diff --git a/js/xpconnect/loader/mozJSComponentLoader.h b/js/xpconnect/loader/mozJSComponentLoader.h index 67f55352e60..fcd02fb6339 100644 --- a/js/xpconnect/loader/mozJSComponentLoader.h +++ b/js/xpconnect/loader/mozJSComponentLoader.h @@ -7,6 +7,7 @@ #ifndef mozJSComponentLoader_h #define mozJSComponentLoader_h +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/MemoryReporting.h" #include "mozilla/ModuleLoader.h" #include "nsISupports.h" @@ -14,7 +15,6 @@ #include "nsIURI.h" #include "xpcIJSModuleLoader.h" #include "nsClassHashtable.h" -#include "nsCxPusher.h" #include "nsDataHashtable.h" #include "jsapi.h" diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index 513c284d2e9..9eaa4d53038 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -14,7 +14,6 @@ #include "js/OldDebugAPI.h" #include "js/StructuredClone.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsGlobalWindow.h" #include "nsIScriptContext.h" #include "nsIScriptObjectPrincipal.h" diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 45d1beeaf85..bf8f372fc0a 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -29,7 +29,6 @@ #include "mozilla/Services.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsCCUncollectableMarker.h" #include "nsCycleCollectionNoteRootCallback.h" #include "nsScriptLoader.h" diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp index 245c56225ad..89e679f9740 100644 --- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -9,7 +9,6 @@ #include "mozilla/Range.h" #include "xpcprivate.h" -#include "nsCxPusher.h" #include "jsfriendapi.h" #include "jsprf.h" diff --git a/js/xpconnect/src/XPCWrappedJS.cpp b/js/xpconnect/src/XPCWrappedJS.cpp index 20b4d7579ae..1835bbfed17 100644 --- a/js/xpconnect/src/XPCWrappedJS.cpp +++ b/js/xpconnect/src/XPCWrappedJS.cpp @@ -9,7 +9,6 @@ #include "xpcprivate.h" #include "jsprf.h" #include "nsCCUncollectableMarker.h" -#include "nsCxPusher.h" #include "nsContentUtils.h" #include "nsThreadUtils.h" #include "JavaScriptParent.h" diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 75da46970b4..e77f13cf55d 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -15,7 +15,6 @@ #include "XrayWrapper.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include #include "mozilla/Likely.h" diff --git a/js/xpconnect/src/XPCWrappedNativeInfo.cpp b/js/xpconnect/src/XPCWrappedNativeInfo.cpp index 304b85f0426..a5682fc78a1 100644 --- a/js/xpconnect/src/XPCWrappedNativeInfo.cpp +++ b/js/xpconnect/src/XPCWrappedNativeInfo.cpp @@ -8,7 +8,6 @@ #include "xpcprivate.h" #include "jswrapper.h" -#include "nsCxPusher.h" #include "mozilla/MemoryReporting.h" #include "mozilla/XPTInterfaceInfoManager.h" diff --git a/js/xpconnect/src/XPCWrappedNativeProto.cpp b/js/xpconnect/src/XPCWrappedNativeProto.cpp index 617cd8a320e..1fa9a370c48 100644 --- a/js/xpconnect/src/XPCWrappedNativeProto.cpp +++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp @@ -7,7 +7,6 @@ /* Shared proto object for XPCWrappedNative. */ #include "xpcprivate.h" -#include "nsCxPusher.h" #include "pratom.h" using namespace mozilla; diff --git a/js/xpconnect/src/moz.build b/js/xpconnect/src/moz.build index 755f9caafcd..bdc7cd37802 100644 --- a/js/xpconnect/src/moz.build +++ b/js/xpconnect/src/moz.build @@ -6,7 +6,6 @@ EXPORTS += [ 'BackstagePass.h', - 'nsCxPusher.h', 'qsObjectHelper.h', 'XPCJSMemoryReporter.h', 'xpcObjectHelper.h', @@ -15,7 +14,6 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'ExportHelpers.cpp', - 'nsCxPusher.cpp', 'nsScriptError.cpp', 'nsXPConnect.cpp', 'Sandbox.cpp', diff --git a/js/xpconnect/src/nsCxPusher.cpp b/js/xpconnect/src/nsCxPusher.cpp deleted file mode 100644 index 7242c4a176a..00000000000 --- a/js/xpconnect/src/nsCxPusher.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* 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/. */ - -#include "nsCxPusher.h" - -#include "nsIScriptContext.h" -#include "nsDOMJSUtils.h" -#include "xpcprivate.h" -#include "WorkerPrivate.h" - -using mozilla::DebugOnly; - -namespace mozilla { - -AutoCxPusher::AutoCxPusher(JSContext* cx, bool allowNull) -{ - MOZ_ASSERT_IF(!allowNull, cx); - - // Hold a strong ref to the nsIScriptContext, if any. This ensures that we - // only destroy the mContext of an nsJSContext when it is not on the cx stack - // (and therefore not in use). See nsJSContext::DestroyJSContext(). - if (cx) - mScx = GetScriptContextFromJSContext(cx); - - XPCJSContextStack *stack = XPCJSRuntime::Get()->GetJSContextStack(); - if (!stack->Push(cx)) { - MOZ_CRASH(); - } - mStackDepthAfterPush = stack->Count(); - -#ifdef DEBUG - mPushedContext = cx; - mCompartmentDepthOnEntry = cx ? js::GetEnterCompartmentDepth(cx) : 0; -#endif - - // Enter a request and a compartment for the duration that the cx is on the - // stack if non-null. - if (cx) { - mAutoRequest.emplace(cx); - - // DOM JSContexts don't store their default compartment object on the cx. - JSObject *compartmentObject = mScx ? mScx->GetWindowProxy() - : js::DefaultObjectForContextOrNull(cx); - if (compartmentObject) - mAutoCompartment.emplace(cx, compartmentObject); - } -} - -AutoCxPusher::~AutoCxPusher() -{ - // Leave the compartment and request before popping. - mAutoCompartment.reset(); - mAutoRequest.reset(); - - // When we push a context, we may save the frame chain and pretend like we - // haven't entered any compartment. This gets restored on Pop(), but we can - // run into trouble if a Push/Pop are interleaved with a - // JSAutoEnterCompartment. Make sure the compartment depth right before we - // pop is the same as it was right after we pushed. - MOZ_ASSERT_IF(mPushedContext, mCompartmentDepthOnEntry == - js::GetEnterCompartmentDepth(mPushedContext)); - DebugOnly stackTop; - MOZ_ASSERT(mPushedContext == nsXPConnect::XPConnect()->GetCurrentJSContext()); - XPCJSRuntime::Get()->GetJSContextStack()->Pop(); - mScx = nullptr; -} - -bool -AutoCxPusher::IsStackTop() const -{ - uint32_t currentDepth = XPCJSRuntime::Get()->GetJSContextStack()->Count(); - MOZ_ASSERT(currentDepth >= mStackDepthAfterPush); - return currentDepth == mStackDepthAfterPush; -} - -AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : mCx(nullptr) -{ - Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); -} - -AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : mCx(nullptr) -{ - Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); -} - -void -AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) -{ - JS::AutoSuppressGCAnalysis nogc; - MOZ_ASSERT(!mCx, "mCx should not be initialized!"); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - nsXPConnect *xpc = nsXPConnect::XPConnect(); - if (!aSafe) { - mCx = xpc->GetCurrentJSContext(); - } - - if (!mCx) { - mCx = xpc->GetSafeJSContext(); - mPusher.emplace(mCx); - } -} - -AutoJSContext::operator JSContext*() const -{ - return mCx; -} - -ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) -{ - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - if (NS_IsMainThread()) { - mCx = nullptr; - mAutoJSContext.emplace(); - } else { - mCx = mozilla::dom::workers::GetCurrentThreadJSContext(); - mRequest.emplace(mCx); - } -} - -ThreadsafeAutoJSContext::operator JSContext*() const -{ - if (mCx) { - return mCx; - } else { - return *mAutoJSContext; - } -} - -AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) - , mAc(mCx, XPCJSRuntime::Get()->GetJSContextStack()->GetSafeJSContextGlobal()) -{ -} - -ThreadsafeAutoSafeJSContext::ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) -{ - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - if (NS_IsMainThread()) { - mCx = nullptr; - mAutoSafeJSContext.emplace(); - } else { - mCx = mozilla::dom::workers::GetCurrentThreadJSContext(); - mRequest.emplace(mCx); - } -} - -ThreadsafeAutoSafeJSContext::operator JSContext*() const -{ - if (mCx) { - return mCx; - } else { - return *mAutoSafeJSContext; - } -} - -} // namespace mozilla diff --git a/js/xpconnect/src/nsCxPusher.h b/js/xpconnect/src/nsCxPusher.h deleted file mode 100644 index cbf3de8b46f..00000000000 --- a/js/xpconnect/src/nsCxPusher.h +++ /dev/null @@ -1,112 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* 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/. */ - -#ifndef nsCxPusher_h -#define nsCxPusher_h - -#include "jsapi.h" -#include "mozilla/Maybe.h" -#include "nsCOMPtr.h" - -class nsIScriptContext; - -namespace mozilla { - -/** - * Fundamental cx pushing class. All other cx pushing classes are implemented - * in terms of this class. - */ -class MOZ_STACK_CLASS AutoCxPusher -{ -public: - explicit AutoCxPusher(JSContext *aCx, bool aAllowNull = false); - ~AutoCxPusher(); - - nsIScriptContext* GetScriptContext() { return mScx; } - - // Returns true if this AutoCxPusher performed the push that is currently at - // the top of the cx stack. - bool IsStackTop() const; - -private: - mozilla::Maybe mAutoRequest; - mozilla::Maybe mAutoCompartment; - nsCOMPtr mScx; - uint32_t mStackDepthAfterPush; -#ifdef DEBUG - JSContext* mPushedContext; - unsigned mCompartmentDepthOnEntry; -#endif -}; - -/** - * Use AutoJSContext when you need a JS context on the stack but don't have one - * passed as a parameter. AutoJSContext will take care of finding the most - * appropriate JS context and release it when leaving the stack. - */ -class MOZ_STACK_CLASS AutoJSContext { -public: - explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*() const; - -protected: - explicit AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - - // We need this Init() method because we can't use delegating constructor for - // the moment. It is a C++11 feature and we do not require C++11 to be - // supported to be able to compile Gecko. - void Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - - JSContext* mCx; - Maybe mPusher; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -/** - * Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be - * running on a worker thread. - */ -class MOZ_STACK_CLASS ThreadsafeAutoJSContext { -public: - explicit ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*() const; - -private: - JSContext* mCx; // Used on workers. Null means mainthread. - Maybe mRequest; // Used on workers. - Maybe mAutoJSContext; // Used on main thread. - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -/** - * AutoSafeJSContext is similar to AutoJSContext but will only return the safe - * JS context. That means it will never call ::GetCurrentJSContext(). - */ -class MOZ_STACK_CLASS AutoSafeJSContext : public AutoJSContext { -public: - explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); -private: - JSAutoCompartment mAc; -}; - -/** - * Like AutoSafeJSContext but can be used safely on worker threads. - */ -class MOZ_STACK_CLASS ThreadsafeAutoSafeJSContext { -public: - explicit ThreadsafeAutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*() const; - -private: - JSContext* mCx; // Used on workers. Null means mainthread. - Maybe mRequest; // Used on workers. - Maybe mAutoSafeJSContext; // Used on main thread. - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -} // namespace mozilla - -#endif /* nsCxPusher_h */ diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 93dee6ca694..a17623f6696 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -85,6 +85,8 @@ #include "mozilla/Preferences.h" #include "mozilla/TimeStamp.h" +#include "mozilla/dom/ScriptSettings.h" + #include #include #include @@ -157,7 +159,6 @@ #include "SandboxPrivate.h" #include "BackstagePass.h" -#include "nsCxPusher.h" #include "nsAXPCNativeCallContext.h" #ifdef XP_WIN @@ -2833,6 +2834,14 @@ void PopJSContextNoScriptContext(); } /* namespace xpc */ +namespace mozilla { +namespace dom { +namespace danger { +class AutoCxPusher; +} +} +} + class XPCJSContextStack { public: @@ -2863,7 +2872,7 @@ public: { return &mStack; } private: - friend class mozilla::AutoCxPusher; + friend class mozilla::dom::danger::AutoCxPusher; friend bool xpc::PushJSContextNoScriptContext(JSContext *aCx);; friend void xpc::PopJSContextNoScriptContext(); diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 9cf0b9f4462..39b3df4fa5a 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -696,10 +696,11 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) mRequestedHighPrecision(false), mInRefresh(false), mWaitingForTransaction(false), - mSkippedPaints(0) + mSkippedPaints(false) { mMostRecentRefreshEpochTime = JS_Now(); mMostRecentRefresh = TimeStamp::Now(); + mMostRecentTick = mMostRecentRefresh; } nsRefreshDriver::~nsRefreshDriver() @@ -737,7 +738,7 @@ nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) if (mWaitingForTransaction) { // Disable any refresh driver throttling when entering test mode mWaitingForTransaction = false; - mSkippedPaints = 0; + mSkippedPaints = false; } } @@ -1078,17 +1079,18 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) mMostRecentRefresh = aNowTime; mMostRecentRefreshEpochTime = aNowEpoch; - if (IsWaitingForPaint()) { + if (IsWaitingForPaint(aNowTime)) { // We're currently suspended waiting for earlier Tick's to // be completed (on the Compositor). Mark that we missed the paint // and keep waiting. return; } + mMostRecentTick = aNowTime; if (mRootRefresh) { mRootRefresh->RemoveRefreshObserver(this, Flush_Style); mRootRefresh = nullptr; } - mSkippedPaints = 0; + mSkippedPaints = false; nsCOMPtr presShell = mPresContext->GetPresShell(); if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { @@ -1400,7 +1402,7 @@ nsRefreshDriver::FinishedWaitingForTransaction() DoRefresh(); profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); } - mSkippedPaints = 0; + mSkippedPaints = false; } uint64_t @@ -1408,11 +1410,11 @@ nsRefreshDriver::GetTransactionId() { ++mPendingTransaction; - if (mPendingTransaction == mCompletedTransaction + 2 && + if (mPendingTransaction >= mCompletedTransaction + 2 && !mWaitingForTransaction && !mTestControllingRefreshes) { mWaitingForTransaction = true; - mSkippedPaints = 0; + mSkippedPaints = false; } return mPendingTransaction; @@ -1455,7 +1457,7 @@ nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime) } bool -nsRefreshDriver::IsWaitingForPaint() +nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) { if (mTestControllingRefreshes) { return false; @@ -1463,9 +1465,8 @@ nsRefreshDriver::IsWaitingForPaint() // If we've skipped too many ticks then it's possible // that something went wrong and we're waiting on // a notification that will never arrive. - static const uint32_t kMaxSkippedPaints = 10; - if (mSkippedPaints > kMaxSkippedPaints) { - mSkippedPaints = 0; + if (aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(200))) { + mSkippedPaints = false; mWaitingForTransaction = false; if (mRootRefresh) { mRootRefresh->RemoveRefreshObserver(this, Flush_Style); @@ -1473,7 +1474,7 @@ nsRefreshDriver::IsWaitingForPaint() return false; } if (mWaitingForTransaction) { - mSkippedPaints++; + mSkippedPaints = true; return true; } @@ -1483,7 +1484,7 @@ nsRefreshDriver::IsWaitingForPaint() if (displayRoot) { nsRefreshDriver *rootRefresh = displayRoot->GetRootPresContext()->RefreshDriver(); if (rootRefresh && rootRefresh != this) { - if (rootRefresh->IsWaitingForPaint()) { + if (rootRefresh->IsWaitingForPaint(aTime)) { if (mRootRefresh != rootRefresh) { if (mRootRefresh) { mRootRefresh->RemoveRefreshObserver(this, Flush_Style); @@ -1491,7 +1492,7 @@ nsRefreshDriver::IsWaitingForPaint() rootRefresh->AddRefreshObserver(this, Flush_Style); mRootRefresh = rootRefresh; } - mSkippedPaints++; + mSkippedPaints = true; return true; } } diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 8de27d5133e..7967ebeb2bf 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -276,7 +276,7 @@ public: void NotifyTransactionCompleted(uint64_t aTransactionId) MOZ_OVERRIDE; void RevokeTransactionId(uint64_t aTransactionId) MOZ_OVERRIDE; - bool IsWaitingForPaint(); + bool IsWaitingForPaint(mozilla::TimeStamp aTime); // nsARefreshObserver NS_IMETHOD_(MozExternalRefCountType) AddRef(void) { return TransactionIdAllocator::AddRef(); } @@ -355,10 +355,11 @@ private: // True if Tick() was skipped because of mWaitingForTransaction and // we should schedule a new Tick immediately when resumed instead // of waiting until the next interval. - uint32_t mSkippedPaints; + bool mSkippedPaints; int64_t mMostRecentRefreshEpochTime; mozilla::TimeStamp mMostRecentRefresh; + mozilla::TimeStamp mMostRecentTick; // separate arrays for each flush type we support ObserverArray mObservers[3]; diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in index 648f3a4b694..a3f6abed8ef 100644 --- a/layout/media/symbols.def.in +++ b/layout/media/symbols.def.in @@ -439,6 +439,7 @@ _moz_cairo_set_source_rgba _moz_cairo_set_source_surface _moz_cairo_show_glyphs _moz_cairo_status +_moz_cairo_status_to_string _moz_cairo_stroke _moz_cairo_stroke_extents _moz_cairo_stroke_preserve diff --git a/netwerk/base/src/Dashboard.cpp b/netwerk/base/src/Dashboard.cpp index fee88a5e6ce..7e758a2e53a 100644 --- a/netwerk/base/src/Dashboard.cpp +++ b/netwerk/base/src/Dashboard.cpp @@ -6,7 +6,6 @@ #include "mozilla/dom/ToJSValue.h" #include "mozilla/net/Dashboard.h" #include "mozilla/net/HttpInfo.h" -#include "nsCxPusher.h" #include "nsHttp.h" #include "nsICancelable.h" #include "nsIDNSService.h" diff --git a/netwerk/protocol/app/AppProtocolHandler.cpp b/netwerk/protocol/app/AppProtocolHandler.cpp index ac19890063c..4f13047c496 100644 --- a/netwerk/protocol/app/AppProtocolHandler.cpp +++ b/netwerk/protocol/app/AppProtocolHandler.cpp @@ -10,9 +10,10 @@ #include "nsNetCID.h" #include "nsIAppsService.h" #include "nsILoadInfo.h" -#include "nsCxPusher.h" #include "nsXULAppAPI.h" +#include "mozilla/dom/ScriptSettings.h" + /** * This dummy channel implementation only provides enough functionality * to return a fake 404 error when the caller asks for an app:// URL diff --git a/python/moz.build b/python/moz.build index 9fa86161f46..1dfae867cf8 100644 --- a/python/moz.build +++ b/python/moz.build @@ -36,11 +36,11 @@ PYTHON_UNIT_TESTS += [ 'mozbuild/mozbuild/test/controller/test_ccachestats.py', 'mozbuild/mozbuild/test/controller/test_clobber.py', 'mozbuild/mozbuild/test/frontend/__init__.py', + 'mozbuild/mozbuild/test/frontend/test_context.py', 'mozbuild/mozbuild/test/frontend/test_emitter.py', 'mozbuild/mozbuild/test/frontend/test_namespaces.py', 'mozbuild/mozbuild/test/frontend/test_reader.py', 'mozbuild/mozbuild/test/frontend/test_sandbox.py', - 'mozbuild/mozbuild/test/frontend/test_sandbox_symbols.py', 'mozbuild/mozbuild/test/test_base.py', 'mozbuild/mozbuild/test/test_containers.py', 'mozbuild/mozbuild/test/test_expression.py', diff --git a/python/mozbuild/mozbuild/backend/android_eclipse.py b/python/mozbuild/mozbuild/backend/android_eclipse.py index 41ed63ddb4e..43f76e35e64 100644 --- a/python/mozbuild/mozbuild/backend/android_eclipse.py +++ b/python/mozbuild/mozbuild/backend/android_eclipse.py @@ -19,8 +19,8 @@ import mozpack.path as mozpath from .common import CommonBackend from ..frontend.data import ( AndroidEclipseProjectData, - SandboxDerived, - SandboxWrapped, + ContextDerived, + ContextWrapped, ) from ..makeutil import Makefile from ..util import ensureParentDir @@ -58,7 +58,7 @@ class AndroidEclipseBackend(CommonBackend): def consume_object(self, obj): """Write out Android Eclipse project files.""" - if not isinstance(obj, SandboxDerived): + if not isinstance(obj, ContextDerived): return CommonBackend.consume_object(self, obj) @@ -71,7 +71,7 @@ class AndroidEclipseBackend(CommonBackend): obj.ack() # ... and handle the one case we care about specially. - if isinstance(obj, SandboxWrapped) and isinstance(obj.wrapped, AndroidEclipseProjectData): + if isinstance(obj, ContextWrapped) and isinstance(obj.wrapped, AndroidEclipseProjectData): self._process_android_eclipse_project_data(obj.wrapped, obj.srcdir, obj.objdir) def consume_finished(self): diff --git a/python/mozbuild/mozbuild/backend/base.py b/python/mozbuild/mozbuild/backend/base.py index 117db3c5f26..e2baa2025ed 100644 --- a/python/mozbuild/mozbuild/backend/base.py +++ b/python/mozbuild/mozbuild/backend/base.py @@ -22,8 +22,8 @@ from ..preprocessor import Preprocessor from ..pythonutil import iter_modules_in_path from ..util import FileAvoidWrite from ..frontend.data import ( + ContextDerived, ReaderSummary, - SandboxDerived, ) from .configenvironment import ConfigEnvironment import mozpack.path as mozpath @@ -153,11 +153,6 @@ class BuildBackend(LoggingMixin): l = open(self._backend_output_list_file).read().split('\n') self._backend_output_list.update(mozpath.normsep(p) for p in l) - # Pull in all loaded Python as dependencies so any Python changes that - # could influence our output result in a rescan. - self.backend_input_files |= set(iter_modules_in_path(environment.topsrcdir)) - self.backend_input_files |= set(iter_modules_in_path(environment.topobjdir)) - self._environments = {} self._environments[environment.topobjdir] = environment @@ -189,14 +184,19 @@ class BuildBackend(LoggingMixin): self.consume_object(obj) backend_time += time.time() - obj_start - if isinstance(obj, SandboxDerived): - self.backend_input_files |= obj.sandbox_all_paths + if isinstance(obj, ContextDerived): + self.backend_input_files |= obj.context_all_paths if isinstance(obj, ReaderSummary): self.summary.mozbuild_count = obj.total_file_count self.summary.mozbuild_execution_time = obj.total_sandbox_execution_time self.summary.emitter_execution_time = obj.total_emitter_execution_time + # Pull in all loaded Python as dependencies so any Python changes that + # could influence our output result in a rescan. + self.backend_input_files |= set(iter_modules_in_path( + self.environment.topsrcdir, self.environment.topobjdir)) + finished_start = time.time() self.consume_finished() backend_time += time.time() - finished_start diff --git a/python/mozbuild/mozbuild/backend/common.py b/python/mozbuild/mozbuild/backend/common.py index 2b303b4e84a..e2be49b0b7e 100644 --- a/python/mozbuild/mozbuild/backend/common.py +++ b/python/mozbuild/mozbuild/backend/common.py @@ -154,7 +154,7 @@ class TestManager(object): topsrcdir = mozpath.normpath(topsrcdir) path = mozpath.normpath(t['path']) - assert path.startswith(topsrcdir) + assert mozpath.basedir(path, [topsrcdir]) key = path[len(topsrcdir)+1:] t['file_relpath'] = key diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index 4797868524b..b5cb5ddf252 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -30,6 +30,8 @@ from .common import CommonBackend from ..frontend.data import ( AndroidEclipseProjectData, ConfigFileSubstitution, + ContextDerived, + ContextWrapped, Defines, DirectoryTraversal, Exports, @@ -48,8 +50,6 @@ from ..frontend.data import ( PerSourceFlag, Program, Resources, - SandboxDerived, - SandboxWrapped, SharedLibrary, SimpleProgram, StaticLibrary, @@ -324,7 +324,7 @@ class RecursiveMakeBackend(CommonBackend): def consume_object(self, obj): """Write out build files necessary to build with recursive make.""" - if not isinstance(obj, SandboxDerived): + if not isinstance(obj, ContextDerived): return if obj.objdir not in self._backend_files: @@ -454,7 +454,7 @@ class RecursiveMakeBackend(CommonBackend): elif isinstance(obj, JavaScriptModules): self._process_javascript_modules(obj, backend_file) - elif isinstance(obj, SandboxWrapped): + elif isinstance(obj, ContextWrapped): # Process a rich build system object from the front-end # as-is. Please follow precedent and handle CamelCaseData # in a function named _process_camel_case_data. At some diff --git a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py b/python/mozbuild/mozbuild/frontend/context.py similarity index 83% rename from python/mozbuild/mozbuild/frontend/sandbox_symbols.py rename to python/mozbuild/mozbuild/frontend/context.py index 7c7a44bae2e..212820a5189 100644 --- a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py +++ b/python/mozbuild/mozbuild/frontend/context.py @@ -6,39 +6,192 @@ # DO NOT UPDATE THIS FILE WITHOUT SIGN-OFF FROM A BUILD MODULE PEER. # ###################################################################### -r"""Defines the global config variables. +r"""This module contains the data structure (context) holding the configuration +from a moz.build. The data emitted by the frontend derives from those contexts. -This module contains data structures defining the global symbols that have -special meaning in the frontend files for the build system. - -If you are looking for the absolute authority on what the global namespace in -the Sandbox consists of, you've come to the right place. +It also defines the set of variables and functions available in moz.build. +If you are looking for the absolute authority on what moz.build files can +contain, you've come to the right place. """ from __future__ import unicode_literals +import os + from collections import OrderedDict +from contextlib import contextmanager from mozbuild.util import ( HierarchicalStringList, HierarchicalStringListWithFlagsFactory, + KeyedDefaultDict, List, + memoized_property, + ReadOnlyKeyedDefaultDict, StrictOrderingOnAppendList, StrictOrderingOnAppendListWithFlagsFactory, ) -from .sandbox import SandboxDerivedValue +import mozpack.path as mozpath from types import StringTypes +import itertools -class FinalTargetValue(SandboxDerivedValue, unicode): - def __new__(cls, sandbox, value=""): + +class ContextDerivedValue(object): + """Classes deriving from this one receive a special treatment in a + Context. See Context documentation. + """ + + +class Context(KeyedDefaultDict): + """Represents a moz.build configuration context. + + Instances of this class are filled by the execution of sandboxes. + At the core, a Context is a dict, with a defined set of possible keys we'll + call variables. Each variable is associated with a type. + + When reading a value for a given key, we first try to read the existing + value. If a value is not found and it is defined in the allowed variables + set, we return a new instance of the class for that variable. We don't + assign default instances until they are accessed because this makes + debugging the end-result much simpler. Instead of a data structure with + lots of empty/default values, you have a data structure with only the + values that were read or touched. + + Instances of variables classes are created by invoking class_name(), + except when class_name derives from ContextDerivedValue, in which + case class_name(instance_of_the_context) is invoked. + A value is added to those calls when instances are created during + assignment (setitem). + + allowed_variables is a dict of the variables that can be set and read in + this context instance. Keys in this dict are the strings representing keys + in this context which are valid. Values are tuples of stored type, + assigned type, default value, a docstring describing the purpose of the + variable, and a tier indicator (see comment above the VARIABLES declaration + in this module). + + config is the ConfigEnvironment for this context. + """ + def __init__(self, allowed_variables={}, config=None): + self._allowed_variables = allowed_variables + self.main_path = None + self.all_paths = set() + self.config = config + self.executed_time = 0 + KeyedDefaultDict.__init__(self, self._factory) + + def add_source(self, path): + """Adds the given path as source of the data from this context.""" + assert os.path.isabs(path) + + if not self.main_path: + self.main_path = path + self.all_paths.add(path) + + @memoized_property + def objdir(self): + return mozpath.join(self.config.topobjdir, self.relobjdir).rstrip('/') + + @memoized_property + def srcdir(self): + return mozpath.join(self.config.topsrcdir, self.relsrcdir).rstrip('/') + + @memoized_property + def relsrcdir(self): + assert self.main_path + return mozpath.relpath(mozpath.dirname(self.main_path), + self.config.topsrcdir) + + @memoized_property + def relobjdir(self): + return self.relsrcdir + + def _factory(self, key): + """Function called when requesting a missing key.""" + + defaults = self._allowed_variables.get(key) + if not defaults: + raise KeyError('global_ns', 'get_unknown', key) + + # If the default is specifically a lambda (or, rather, any function + # --but not a class that can be called), then it is actually a rule to + # generate the default that should be used. + default = defaults[0] + if issubclass(default, ContextDerivedValue): + return default(self) + else: + return default() + + def _validate(self, key, value): + """Validates whether the key is allowed and if the value's type + matches. + """ + stored_type, input_type, docs, tier = \ + self._allowed_variables.get(key, (None, None, None, None)) + + if stored_type is None: + raise KeyError('global_ns', 'set_unknown', key, value) + + # If the incoming value is not the type we store, we try to convert + # it to that type. This relies on proper coercion rules existing. This + # is the responsibility of whoever defined the symbols: a type should + # not be in the allowed set if the constructor function for the stored + # type does not accept an instance of that type. + if not isinstance(value, (stored_type, input_type)): + raise ValueError('global_ns', 'set_type', key, value, input_type) + + return stored_type + + def __setitem__(self, key, value): + stored_type = self._validate(key, value) + + if not isinstance(value, stored_type): + if issubclass(stored_type, ContextDerivedValue): + value = stored_type(self, value) + else: + value = stored_type(value) + + return KeyedDefaultDict.__setitem__(self, key, value) + + def update(self, iterable={}, **kwargs): + """Like dict.update(), but using the context's setitem. + + This function is transactional: if setitem fails for one of the values, + the context is not updated at all.""" + if isinstance(iterable, dict): + iterable = iterable.items() + + update = {} + for key, value in itertools.chain(iterable, kwargs.items()): + stored_type = self._validate(key, value) + # Don't create an instance of stored_type if coercion is needed, + # until all values are validated. + update[key] = (value, stored_type) + for key, (value, stored_type) in update.items(): + if not isinstance(value, stored_type): + update[key] = stored_type(value) + else: + update[key] = value + KeyedDefaultDict.update(self, update) + + def get_affected_tiers(self): + """Returns the list of tiers affected by the variables set in the + context. + """ + tiers = (VARIABLES[key][3] for key in self if key in VARIABLES) + return set(tier for tier in tiers if tier) + + +class FinalTargetValue(ContextDerivedValue, unicode): + def __new__(cls, context, value=""): if not value: value = 'dist/' - if sandbox['XPI_NAME']: - value += 'xpi-stage/' + sandbox['XPI_NAME'] + if context['XPI_NAME']: + value += 'xpi-stage/' + context['XPI_NAME'] else: value += 'bin' - if sandbox['DIST_SUBDIR']: - value += '/' + sandbox['DIST_SUBDIR'] + if context['DIST_SUBDIR']: + value += '/' + context['DIST_SUBDIR'] return unicode.__new__(cls, value) @@ -947,8 +1100,13 @@ FUNCTIONS = { } # Special variables. These complement VARIABLES. +# +# Each entry is a tuple of: +# +# (function returning the corresponding value from a given context, type, docs) +# SPECIAL_VARIABLES = { - 'TOPSRCDIR': (str, + 'TOPSRCDIR': (lambda context: context.config.topsrcdir, str, """Constant defining the top source directory. The top source directory is the parent directory containing the source @@ -956,7 +1114,7 @@ SPECIAL_VARIABLES = { cloned repository. """), - 'TOPOBJDIR': (str, + 'TOPOBJDIR': (lambda context: context.config.topobjdir, str, """Constant defining the top object directory. The top object directory is the parent directory which will contain @@ -964,7 +1122,7 @@ SPECIAL_VARIABLES = { directory." """), - 'RELATIVEDIR': (str, + 'RELATIVEDIR': (lambda context: context.relsrcdir, str, """Constant defining the relative path of this file. The relative path is from ``TOPSRCDIR``. This is defined as relative @@ -972,20 +1130,21 @@ SPECIAL_VARIABLES = { files have been included using ``include()``. """), - 'SRCDIR': (str, + 'SRCDIR': (lambda context: context.srcdir, str, """Constant defining the source directory of this file. This is the path inside ``TOPSRCDIR`` where this file is located. It is the same as ``TOPSRCDIR + RELATIVEDIR``. """), - 'OBJDIR': (str, + 'OBJDIR': (lambda context: context.objdir, str, """The path to the object directory for this file. Is is the same as ``TOPOBJDIR + RELATIVEDIR``. """), - 'CONFIG': (dict, + 'CONFIG': (lambda context: ReadOnlyKeyedDefaultDict( + lambda key: context.config.substs_unicode.get(key)), dict, """Dictionary containing the current configuration variables. All the variables defined by the configuration system are available @@ -996,16 +1155,6 @@ SPECIAL_VARIABLES = { Access to an unknown variable will return None. """), - - '__builtins__': (dict, - """Exposes Python built-in types. - - The set of exposed Python built-ins is currently: - - - True - - False - - None - """), } # Deprecation hints. diff --git a/python/mozbuild/mozbuild/frontend/data.py b/python/mozbuild/mozbuild/frontend/data.py index d172bb5a7a1..b6ac777c746 100644 --- a/python/mozbuild/mozbuild/frontend/data.py +++ b/python/mozbuild/mozbuild/frontend/data.py @@ -25,7 +25,7 @@ from mozbuild.util import ( StrictOrderingOnAppendList, ) import mozpack.path as mozpath -from .sandbox_symbols import FinalTargetValue +from .context import FinalTargetValue class TreeMetadata(object): @@ -49,46 +49,46 @@ class ReaderSummary(TreeMetadata): self.total_emitter_execution_time = total_emitter_execution_time -class SandboxDerived(TreeMetadata): - """Build object derived from a single MozbuildSandbox instance. +class ContextDerived(TreeMetadata): + """Build object derived from a single Context instance. - It holds fields common to all sandboxes. This class is likely never - instantiated directly but is instead derived from. + It holds fields common to all context derived classes. This class is likely + never instantiated directly but is instead derived from. """ __slots__ = ( 'objdir', 'relativedir', - 'sandbox_all_paths', - 'sandbox_path', + 'context_all_paths', + 'context_path', 'srcdir', 'topobjdir', 'topsrcdir', ) - def __init__(self, sandbox): + def __init__(self, context): TreeMetadata.__init__(self) - # Capture the files that were evaluated to build this sandbox. - self.sandbox_main_path = sandbox.main_path - self.sandbox_all_paths = sandbox.all_paths + # Capture the files that were evaluated to fill this context. + self.context_main_path = context.main_path + self.context_all_paths = context.all_paths # Basic directory state. - self.topsrcdir = sandbox['TOPSRCDIR'] - self.topobjdir = sandbox['TOPOBJDIR'] + self.topsrcdir = context.config.topsrcdir + self.topobjdir = context.config.topobjdir - self.relativedir = sandbox['RELATIVEDIR'] - self.srcdir = sandbox['SRCDIR'] - self.objdir = sandbox['OBJDIR'] + self.relativedir = context.relsrcdir + self.srcdir = context.srcdir + self.objdir = context.objdir - self.config = sandbox.config + self.config = context.config @property def relobjdir(self): return mozpath.relpath(self.objdir, self.topobjdir) -class DirectoryTraversal(SandboxDerived): +class DirectoryTraversal(ContextDerived): """Describes how directory traversal for building should work. This build object is likely only of interest to the recursive make backend. @@ -106,15 +106,15 @@ class DirectoryTraversal(SandboxDerived): 'tier_dirs', ) - def __init__(self, sandbox): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context): + ContextDerived.__init__(self, context) self.dirs = [] self.test_dirs = [] self.tier_dirs = OrderedDict() -class BaseConfigSubstitution(SandboxDerived): +class BaseConfigSubstitution(ContextDerived): """Base class describing autogenerated files as part of config.status.""" __slots__ = ( @@ -123,8 +123,8 @@ class BaseConfigSubstitution(SandboxDerived): 'relpath', ) - def __init__(self, sandbox): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context): + ContextDerived.__init__(self, context) self.input_path = None self.output_path = None @@ -139,7 +139,7 @@ class HeaderFileSubstitution(BaseConfigSubstitution): """Describes a header file that will be generated using substitutions.""" -class VariablePassthru(SandboxDerived): +class VariablePassthru(ContextDerived): """A dict of variables to pass through to backend.mk unaltered. The purpose of this object is to facilitate rapid transitioning of @@ -150,11 +150,11 @@ class VariablePassthru(SandboxDerived): """ __slots__ = ('variables') - def __init__(self, sandbox): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context): + ContextDerived.__init__(self, context) self.variables = {} -class XPIDLFile(SandboxDerived): +class XPIDLFile(ContextDerived): """Describes an XPIDL file to be compiled.""" __slots__ = ( @@ -162,20 +162,20 @@ class XPIDLFile(SandboxDerived): 'source_path', ) - def __init__(self, sandbox, source, module): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, source, module): + ContextDerived.__init__(self, context) self.source_path = source self.basename = mozpath.basename(source) self.module = module -class Defines(SandboxDerived): - """Sandbox container object for DEFINES, which is an OrderedDict. +class Defines(ContextDerived): + """Context derived container object for DEFINES, which is an OrderedDict. """ __slots__ = ('defines') - def __init__(self, sandbox, defines): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, defines): + ContextDerived.__init__(self, context) self.defines = defines def get_defines(self): @@ -187,88 +187,90 @@ class Defines(SandboxDerived): else: yield('-D%s=%s' % (define, shell_quote(value))) -class Exports(SandboxDerived): - """Sandbox container object for EXPORTS, which is a HierarchicalStringList. +class Exports(ContextDerived): + """Context derived container object for EXPORTS, which is a + HierarchicalStringList. - We need an object derived from SandboxDerived for use in the backend, so + We need an object derived from ContextDerived for use in the backend, so this object fills that role. It just has a reference to the underlying HierarchicalStringList, which is created when parsing EXPORTS. """ __slots__ = ('exports', 'dist_install') - def __init__(self, sandbox, exports, dist_install=True): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, exports, dist_install=True): + ContextDerived.__init__(self, context) self.exports = exports self.dist_install = dist_install -class Resources(SandboxDerived): - """Sandbox container object for RESOURCE_FILES, which is a HierarchicalStringList, - with an extra ``.preprocess`` property on each entry. +class Resources(ContextDerived): + """Context derived container object for RESOURCE_FILES, which is a + HierarchicalStringList, with an extra ``.preprocess`` property on each + entry. The local defines plus anything in ACDEFINES are stored in ``defines`` as a dictionary, for any files that need preprocessing. """ __slots__ = ('resources', 'defines') - def __init__(self, sandbox, resources, defines=None): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, resources, defines=None): + ContextDerived.__init__(self, context) self.resources = resources defs = {} - defs.update(sandbox.config.defines) + defs.update(context.config.defines) if defines: defs.update(defines) self.defines = defs -class IPDLFile(SandboxDerived): +class IPDLFile(ContextDerived): """Describes an individual .ipdl source file.""" __slots__ = ( 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class WebIDLFile(SandboxDerived): +class WebIDLFile(ContextDerived): """Describes an individual .webidl source file.""" __slots__ = ( 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class GeneratedEventWebIDLFile(SandboxDerived): +class GeneratedEventWebIDLFile(ContextDerived): """Describes an individual .webidl source file.""" __slots__ = ( 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class TestWebIDLFile(SandboxDerived): +class TestWebIDLFile(ContextDerived): """Describes an individual test-only .webidl source file.""" __slots__ = ( 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class PreprocessedTestWebIDLFile(SandboxDerived): +class PreprocessedTestWebIDLFile(ContextDerived): """Describes an individual test-only .webidl source file that requires preprocessing.""" @@ -276,24 +278,24 @@ class PreprocessedTestWebIDLFile(SandboxDerived): 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class PreprocessedWebIDLFile(SandboxDerived): +class PreprocessedWebIDLFile(ContextDerived): """Describes an individual .webidl source file that requires preprocessing.""" __slots__ = ( 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class GeneratedWebIDLFile(SandboxDerived): +class GeneratedWebIDLFile(ContextDerived): """Describes an individual .webidl source file that is generated from build rules.""" @@ -301,21 +303,21 @@ class GeneratedWebIDLFile(SandboxDerived): 'basename', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.basename = path -class ExampleWebIDLInterface(SandboxDerived): +class ExampleWebIDLInterface(ContextDerived): """An individual WebIDL interface to generate.""" __slots__ = ( 'name', ) - def __init__(self, sandbox, name): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, name): + ContextDerived.__init__(self, context) self.name = name @@ -324,15 +326,15 @@ class LinkageWrongKindError(Exception): """Error thrown when trying to link objects of the wrong kind""" -class Linkable(SandboxDerived): - """Generic sandbox container object for programs and libraries""" +class Linkable(ContextDerived): + """Generic context derived container object for programs and libraries""" __slots__ = ( 'linked_libraries', 'linked_system_libs', ) - def __init__(self, sandbox): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context): + ContextDerived.__init__(self, context) self.linked_libraries = [] self.linked_system_libs = [] @@ -362,7 +364,8 @@ class Linkable(SandboxDerived): class BaseProgram(Linkable): - """Sandbox container object for programs, which is a unicode string. + """Context derived container object for programs, which is a unicode + string. This class handles automatically appending a binary suffix to the program name. @@ -372,10 +375,10 @@ class BaseProgram(Linkable): """ __slots__ = ('program') - def __init__(self, sandbox, program, is_unit_test=False): - Linkable.__init__(self, sandbox) + def __init__(self, context, program, is_unit_test=False): + Linkable.__init__(self, context) - bin_suffix = sandbox['CONFIG'].get(self.SUFFIX_VAR, '') + bin_suffix = context.config.substs.get(self.SUFFIX_VAR, '') if not program.endswith(bin_suffix): program += bin_suffix self.program = program @@ -383,31 +386,32 @@ class BaseProgram(Linkable): class Program(BaseProgram): - """Sandbox container object for PROGRAM""" + """Context derived container object for PROGRAM""" SUFFIX_VAR = 'BIN_SUFFIX' KIND = 'target' class HostProgram(BaseProgram): - """Sandbox container object for HOST_PROGRAM""" + """Context derived container object for HOST_PROGRAM""" SUFFIX_VAR = 'HOST_BIN_SUFFIX' KIND = 'host' class SimpleProgram(BaseProgram): - """Sandbox container object for each program in SIMPLE_PROGRAMS""" + """Context derived container object for each program in SIMPLE_PROGRAMS""" SUFFIX_VAR = 'BIN_SUFFIX' KIND = 'target' class HostSimpleProgram(BaseProgram): - """Sandbox container object for each program in HOST_SIMPLE_PROGRAMS""" + """Context derived container object for each program in + HOST_SIMPLE_PROGRAMS""" SUFFIX_VAR = 'HOST_BIN_SUFFIX' KIND = 'host' class BaseLibrary(Linkable): - """Generic sandbox container object for libraries.""" + """Generic context derived container object for libraries.""" __slots__ = ( 'basename', 'lib_name', @@ -415,15 +419,15 @@ class BaseLibrary(Linkable): 'refs', ) - def __init__(self, sandbox, basename): - Linkable.__init__(self, sandbox) + def __init__(self, context, basename): + Linkable.__init__(self, context) self.basename = self.lib_name = basename if self.lib_name: self.lib_name = '%s%s%s' % ( - sandbox.config.lib_prefix, + context.config.lib_prefix, self.lib_name, - sandbox.config.lib_suffix + context.config.lib_suffix ) self.import_name = self.lib_name @@ -431,32 +435,32 @@ class BaseLibrary(Linkable): class Library(BaseLibrary): - """Sandbox container object for a library""" + """Context derived container object for a library""" KIND = 'target' __slots__ = ( 'is_sdk', ) - def __init__(self, sandbox, basename, real_name=None, is_sdk=False): - BaseLibrary.__init__(self, sandbox, real_name or basename) + def __init__(self, context, basename, real_name=None, is_sdk=False): + BaseLibrary.__init__(self, context, real_name or basename) self.basename = basename self.is_sdk = is_sdk class StaticLibrary(Library): - """Sandbox container object for a static library""" + """Context derived container object for a static library""" __slots__ = ( 'link_into', ) - def __init__(self, sandbox, basename, real_name=None, is_sdk=False, + def __init__(self, context, basename, real_name=None, is_sdk=False, link_into=None): - Library.__init__(self, sandbox, basename, real_name, is_sdk) + Library.__init__(self, context, basename, real_name, is_sdk) self.link_into = link_into class SharedLibrary(Library): - """Sandbox container object for a shared library""" + """Context derived container object for a shared library""" __slots__ = ( 'soname', 'variant', @@ -466,10 +470,10 @@ class SharedLibrary(Library): COMPONENT = 2 MAX_VARIANT = 3 - def __init__(self, sandbox, basename, real_name=None, is_sdk=False, + def __init__(self, context, basename, real_name=None, is_sdk=False, soname=None, variant=None): assert(variant in range(1, self.MAX_VARIANT) or variant is None) - Library.__init__(self, sandbox, basename, real_name, is_sdk) + Library.__init__(self, context, basename, real_name, is_sdk) self.variant = variant self.lib_name = real_name or basename assert self.lib_name @@ -478,20 +482,20 @@ class SharedLibrary(Library): self.import_name = self.lib_name else: self.import_name = '%s%s%s' % ( - sandbox.config.import_prefix, + context.config.import_prefix, self.lib_name, - sandbox.config.import_suffix, + context.config.import_suffix, ) self.lib_name = '%s%s%s' % ( - sandbox.config.dll_prefix, + context.config.dll_prefix, self.lib_name, - sandbox.config.dll_suffix, + context.config.dll_suffix, ) if soname: self.soname = '%s%s%s' % ( - sandbox.config.dll_prefix, + context.config.dll_prefix, soname, - sandbox.config.dll_suffix, + context.config.dll_suffix, ) else: self.soname = self.lib_name @@ -502,21 +506,21 @@ class ExternalLibrary(object): class ExternalStaticLibrary(StaticLibrary, ExternalLibrary): - """Sandbox container for static libraries built by an external build - system.""" + """Context derived container for static libraries built by an external + build system.""" class ExternalSharedLibrary(SharedLibrary, ExternalLibrary): - """Sandbox container for shared libraries built by an external build - system.""" + """Context derived container for shared libraries built by an external + build system.""" class HostLibrary(BaseLibrary): - """Sandbox container object for a host library""" + """Context derived container object for a host library""" KIND = 'host' -class TestManifest(SandboxDerived): +class TestManifest(ContextDerived): """Represents a manifest file containing information about tests.""" __slots__ = ( @@ -563,9 +567,9 @@ class TestManifest(SandboxDerived): 'dupe_manifest', ) - def __init__(self, sandbox, path, manifest, flavor=None, + def __init__(self, context, path, manifest, flavor=None, install_prefix=None, relpath=None, dupe_manifest=False): - SandboxDerived.__init__(self, sandbox) + ContextDerived.__init__(self, context) self.path = path self.directory = mozpath.dirname(path) @@ -581,32 +585,32 @@ class TestManifest(SandboxDerived): self.external_installs = set() -class LocalInclude(SandboxDerived): +class LocalInclude(ContextDerived): """Describes an individual local include path.""" __slots__ = ( 'path', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.path = path -class GeneratedInclude(SandboxDerived): +class GeneratedInclude(ContextDerived): """Describes an individual generated include path.""" __slots__ = ( 'path', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.path = path -class PerSourceFlag(SandboxDerived): +class PerSourceFlag(ContextDerived): """Describes compiler flags specified for individual source files.""" __slots__ = ( @@ -614,14 +618,14 @@ class PerSourceFlag(SandboxDerived): 'flags', ) - def __init__(self, sandbox, file_name, flags): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, file_name, flags): + ContextDerived.__init__(self, context) self.file_name = file_name self.flags = flags -class JARManifest(SandboxDerived): +class JARManifest(ContextDerived): """Describes an individual JAR manifest file and how to process it. This class isn't very useful for optimizing backends yet because we don't @@ -632,13 +636,13 @@ class JARManifest(SandboxDerived): 'path', ) - def __init__(self, sandbox, path): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, path): + ContextDerived.__init__(self, context) self.path = path -class JavaScriptModules(SandboxDerived): +class JavaScriptModules(ContextDerived): """Describes a JavaScript module.""" __slots__ = ( @@ -646,15 +650,15 @@ class JavaScriptModules(SandboxDerived): 'flavor', ) - def __init__(self, sandbox, modules, flavor): - super(JavaScriptModules, self).__init__(sandbox) + def __init__(self, context, modules, flavor): + super(JavaScriptModules, self).__init__(context) self.modules = modules self.flavor = flavor -class SandboxWrapped(SandboxDerived): - """Generic sandbox container object for a wrapped rich object. +class ContextWrapped(ContextDerived): + """Generic context derived container object for a wrapped rich object. Use this wrapper class to shuttle a rich build system object completely defined in moz.build files through the tree metadata @@ -665,8 +669,8 @@ class SandboxWrapped(SandboxDerived): 'wrapped', ) - def __init__(self, sandbox, wrapped): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context, wrapped): + ContextDerived.__init__(self, context) self.wrapped = wrapped @@ -701,7 +705,7 @@ class JavaJarData(object): self.javac_flags = list(javac_flags) -class InstallationTarget(SandboxDerived): +class InstallationTarget(ContextDerived): """Describes the rules that affect where files get installed to.""" __slots__ = ( @@ -711,13 +715,13 @@ class InstallationTarget(SandboxDerived): 'enabled' ) - def __init__(self, sandbox): - SandboxDerived.__init__(self, sandbox) + def __init__(self, context): + ContextDerived.__init__(self, context) - self.xpiname = sandbox.get('XPI_NAME', '') - self.subdir = sandbox.get('DIST_SUBDIR', '') - self.target = sandbox['FINAL_TARGET'] - self.enabled = not sandbox.get('NO_DIST_INSTALL', False) + self.xpiname = context.get('XPI_NAME', '') + self.subdir = context.get('DIST_SUBDIR', '') + self.target = context['FINAL_TARGET'] + self.enabled = not context.get('NO_DIST_INSTALL', False) def is_custom(self): """Returns whether or not the target is not derived from the default diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index f619287f6c2..41e32328e00 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -25,6 +25,7 @@ import mozinfo from .data import ( ConfigFileSubstitution, + ContextWrapped, Defines, DirectoryTraversal, Exports, @@ -52,7 +53,6 @@ from .data import ( Program, ReaderSummary, Resources, - SandboxWrapped, SharedLibrary, SimpleProgram, StaticLibrary, @@ -63,13 +63,9 @@ from .data import ( XPIDLFile, ) -from .reader import ( - MozbuildSandbox, - SandboxValidationError, -) +from .reader import SandboxValidationError -from .gyp_reader import GypSandbox -from .sandbox import GlobalNamespace +from .context import Context class TreeMetadataEmitter(LoggingMixin): @@ -121,7 +117,7 @@ class TreeMetadataEmitter(LoggingMixin): file_count = 0 sandbox_execution_time = 0.0 emitter_time = 0.0 - sandboxes = {} + contexts = {} def emit_objs(objs): for o in objs: @@ -130,13 +126,13 @@ class TreeMetadataEmitter(LoggingMixin): raise Exception('Unhandled object of type %s' % type(o)) for out in output: - if isinstance(out, (MozbuildSandbox, GypSandbox)): - # Keep all sandboxes around, we will need them later. - sandboxes[out['OBJDIR']] = out + if isinstance(out, Context): + # Keep all contexts around, we will need them later. + contexts[out.objdir] = out start = time.time() # We need to expand the generator for the timings to work. - objs = list(self.emit_from_sandbox(out)) + objs = list(self.emit_from_context(out)) emitter_time += time.time() - start for o in emit_objs(objs): yield o @@ -149,14 +145,14 @@ class TreeMetadataEmitter(LoggingMixin): raise Exception('Unhandled output type: %s' % type(out)) start = time.time() - objs = list(self._emit_libs_derived(sandboxes)) + objs = list(self._emit_libs_derived(contexts)) emitter_time += time.time() - start for o in emit_objs(objs): yield o yield ReaderSummary(file_count, sandbox_execution_time, emitter_time) - def _emit_libs_derived(self, sandboxes): + def _emit_libs_derived(self, contexts): # First do FINAL_LIBRARY linkage. for lib in (l for libs in self._libs.values() for l in libs): if not isinstance(lib, StaticLibrary) or not lib.link_into: @@ -164,7 +160,7 @@ class TreeMetadataEmitter(LoggingMixin): if lib.link_into not in self._libs: raise SandboxValidationError( 'FINAL_LIBRARY ("%s") does not match any LIBRARY_NAME' - % lib.link_into, sandboxes[lib.objdir]) + % lib.link_into, contexts[lib.objdir]) candidates = self._libs[lib.link_into] # When there are multiple candidates, but all are in the same @@ -181,11 +177,11 @@ class TreeMetadataEmitter(LoggingMixin): 'FINAL_LIBRARY ("%s") matches a LIBRARY_NAME defined in ' 'multiple places:\n %s' % (lib.link_into, '\n '.join(l.objdir for l in candidates)), - sandboxes[lib.objdir]) + contexts[lib.objdir]) # Next, USE_LIBS linkage. - for sandbox, obj, variable in self._linkage: - self._link_libraries(sandbox, obj, variable) + for context, obj, variable in self._linkage: + self._link_libraries(context, obj, variable) def recurse_refs(lib): for o in lib.refs: @@ -206,7 +202,7 @@ class TreeMetadataEmitter(LoggingMixin): 'library names:\n %s\n\nMaybe you can remove the ' 'static "%s" library?' % (lib.basename, '\n '.join(shared_libs), lib.basename), - sandboxes[lib.objdir]) + contexts[lib.objdir]) def recurse_libs(lib): for obj in lib.linked_libraries: @@ -227,7 +223,7 @@ class TreeMetadataEmitter(LoggingMixin): if p in sent_passthru: continue sent_passthru.add(p) - passthru = VariablePassthru(sandboxes[p]) + passthru = VariablePassthru(contexts[p]) passthru.variables['FINAL_LIBRARY'] = lib.basename yield passthru yield lib @@ -240,21 +236,21 @@ class TreeMetadataEmitter(LoggingMixin): 'target': 'LIBRARY_NAME', } - def _link_libraries(self, sandbox, obj, variable): + def _link_libraries(self, context, obj, variable): """Add linkage declarations to a given object.""" assert isinstance(obj, Linkable) extra = [] # Add stdc++compat library when wanted and needed compat_varname = 'MOZ_LIBSTDCXX_%s_VERSION' % obj.KIND.upper() - if sandbox.config.substs.get(compat_varname) \ + if context.config.substs.get(compat_varname) \ and not isinstance(obj, (StaticLibrary, HostLibrary)): extra.append({ 'target': 'stdc++compat', 'host': 'host_stdc++compat', }[obj.KIND]) - for path in sandbox.get(variable, []) + extra: + for path in context.get(variable, []) + extra: force_static = path.startswith('static:') and obj.KIND == 'target' if force_static: path = path[7:] @@ -284,7 +280,7 @@ class TreeMetadataEmitter(LoggingMixin): raise SandboxValidationError( '%s contains "%s", but there is no "%s" %s in %s.' % (variable, path, name, - self.LIBRARY_NAME_VAR[obj.KIND], dir), sandbox) + self.LIBRARY_NAME_VAR[obj.KIND], dir), context) if len(candidates) > 1: # If there's more than one remaining candidate, it could be @@ -307,17 +303,17 @@ class TreeMetadataEmitter(LoggingMixin): raise SandboxValidationError( '%s contains "static:%s", but there is no static ' '"%s" %s in %s.' % (variable, path, name, - self.LIBRARY_NAME_VAR[obj.KIND], dir), sandbox) + self.LIBRARY_NAME_VAR[obj.KIND], dir), context) raise SandboxValidationError( '%s contains "static:%s", but there is no static "%s" ' '%s in the tree' % (variable, name, name, - self.LIBRARY_NAME_VAR[obj.KIND]), sandbox) + self.LIBRARY_NAME_VAR[obj.KIND]), context) if not candidates: raise SandboxValidationError( '%s contains "%s", which does not match any %s in the tree.' % (variable, path, self.LIBRARY_NAME_VAR[obj.KIND]), - sandbox) + context) elif len(candidates) > 1: paths = (mozpath.join(l.relativedir, 'moz.build') @@ -326,7 +322,7 @@ class TreeMetadataEmitter(LoggingMixin): '%s contains "%s", which matches a %s defined in multiple ' 'places:\n %s' % (variable, path, self.LIBRARY_NAME_VAR[obj.KIND], - '\n '.join(paths)), sandbox) + '\n '.join(paths)), context) elif force_static and not isinstance(candidates[0], StaticLibrary): raise SandboxValidationError( @@ -334,7 +330,7 @@ class TreeMetadataEmitter(LoggingMixin): 'in %s. You may want to add FORCE_STATIC_LIB=True in ' '%s/moz.build, or remove "static:".' % (variable, path, name, candidates[0].relobjdir, candidates[0].relobjdir), - sandbox) + context) elif isinstance(obj, StaticLibrary) and isinstance(candidates[0], SharedLibrary): @@ -342,80 +338,70 @@ class TreeMetadataEmitter(LoggingMixin): obj.link_library(candidates[0]) # Link system libraries from OS_LIBS/HOST_OS_LIBS. - for lib in sandbox.get(variable.replace('USE', 'OS'), []): + for lib in context.get(variable.replace('USE', 'OS'), []): obj.link_system_library(lib) @memoize def _get_external_library(self, dir, name, force_static): # Create ExternalStaticLibrary or ExternalSharedLibrary object with a - # mock sandbox more or less truthful about where the external library - # is. - sandbox = GlobalNamespace() - sandbox.config = self.config - sandbox.main_path = dir - sandbox.all_paths = set([dir]) - with sandbox.allow_all_writes() as s: - s['TOPSRCDIR'] = self.config.topsrcdir - s['TOPOBJDIR'] = self.config.topobjdir - s['RELATIVEDIR'] = dir - s['SRCDIR'] = mozpath.join(self.config.topsrcdir, dir) - s['OBJDIR'] = mozpath.join(self.config.topobjdir, dir) - + # context more or less truthful about where the external library is. + context = Context(config=self.config) + context.add_source(mozpath.join(self.config.topsrcdir, dir, 'dummy')) if force_static: - return ExternalStaticLibrary(sandbox, name) + return ExternalStaticLibrary(context, name) else: - return ExternalSharedLibrary(sandbox, name) + return ExternalSharedLibrary(context, name) - def emit_from_sandbox(self, sandbox): - """Convert a MozbuildSandbox to tree metadata objects. + def emit_from_context(self, context): + """Convert a Context to tree metadata objects. - This is a generator of mozbuild.frontend.data.SandboxDerived instances. + This is a generator of mozbuild.frontend.data.ContextDerived instances. """ # We always emit a directory traversal descriptor. This is needed by # the recursive make backend. - for o in self._emit_directory_traversal_from_sandbox(sandbox): yield o + for o in self._emit_directory_traversal_from_context(context): yield o - for path in sandbox['CONFIGURE_SUBST_FILES']: - yield self._create_substitution(ConfigFileSubstitution, sandbox, + for path in context['CONFIGURE_SUBST_FILES']: + yield self._create_substitution(ConfigFileSubstitution, context, path) - for path in sandbox['CONFIGURE_DEFINE_FILES']: - yield self._create_substitution(HeaderFileSubstitution, sandbox, + for path in context['CONFIGURE_DEFINE_FILES']: + yield self._create_substitution(HeaderFileSubstitution, context, path) # XPIDL source files get processed and turned into .h and .xpt files. # If there are multiple XPIDL files in a directory, they get linked # together into a final .xpt, which has the name defined by # XPIDL_MODULE. - xpidl_module = sandbox['XPIDL_MODULE'] + xpidl_module = context['XPIDL_MODULE'] - if sandbox['XPIDL_SOURCES'] and not xpidl_module: + if context['XPIDL_SOURCES'] and not xpidl_module: raise SandboxValidationError('XPIDL_MODULE must be defined if ' - 'XPIDL_SOURCES is defined.', sandbox) + 'XPIDL_SOURCES is defined.', context) - if xpidl_module and not sandbox['XPIDL_SOURCES']: + if xpidl_module and not context['XPIDL_SOURCES']: raise SandboxValidationError('XPIDL_MODULE cannot be defined ' - 'unless there are XPIDL_SOURCES', sandbox) + 'unless there are XPIDL_SOURCES', context) - if sandbox['XPIDL_SOURCES'] and sandbox['NO_DIST_INSTALL']: + if context['XPIDL_SOURCES'] and context['NO_DIST_INSTALL']: self.log(logging.WARN, 'mozbuild_warning', dict( - path=sandbox.main_path), + path=context.main_path), '{path}: NO_DIST_INSTALL has no effect on XPIDL_SOURCES.') - for idl in sandbox['XPIDL_SOURCES']: - yield XPIDLFile(sandbox, mozpath.join(sandbox['SRCDIR'], idl), + for idl in context['XPIDL_SOURCES']: + yield XPIDLFile(context, mozpath.join(context.srcdir, idl), xpidl_module) for symbol in ('SOURCES', 'HOST_SOURCES', 'UNIFIED_SOURCES'): - for src in (sandbox[symbol] or []): - if not os.path.exists(mozpath.join(sandbox['SRCDIR'], src)): + for src in (context[symbol] or []): + if not os.path.exists(mozpath.join(context.srcdir, src)): raise SandboxValidationError('File listed in %s does not ' - 'exist: \'%s\'' % (symbol, src), sandbox) + 'exist: \'%s\'' % (symbol, src), context) # Proxy some variables as-is until we have richer classes to represent # them. We should aim to keep this set small because it violates the # desired abstraction of the build definition away from makefiles. - passthru = VariablePassthru(sandbox) + passthru = VariablePassthru(context) varlist = [ 'ANDROID_GENERATED_RESFILES', 'ANDROID_RES_DIRS', @@ -441,19 +427,20 @@ class TreeMetadataEmitter(LoggingMixin): 'LD_VERSION_SCRIPT', ] for v in varlist: - if v in sandbox and sandbox[v]: - passthru.variables[v] = sandbox[v] + if v in context and context[v]: + passthru.variables[v] = context[v] for v in ['CFLAGS', 'CXXFLAGS', 'CMFLAGS', 'CMMFLAGS', 'LDFLAGS']: - if v in sandbox and sandbox[v]: - passthru.variables['MOZBUILD_' + v] = sandbox[v] + if v in context and context[v]: + passthru.variables['MOZBUILD_' + v] = context[v] # NO_VISIBILITY_FLAGS is slightly different - if sandbox['NO_VISIBILITY_FLAGS']: + if context['NO_VISIBILITY_FLAGS']: passthru.variables['VISIBILITY_FLAGS'] = '' - if sandbox['DELAYLOAD_DLLS']: - passthru.variables['DELAYLOAD_LDFLAGS'] = [('-DELAYLOAD:%s' % dll) for dll in sandbox['DELAYLOAD_DLLS']] + if context['DELAYLOAD_DLLS']: + passthru.variables['DELAYLOAD_LDFLAGS'] = [('-DELAYLOAD:%s' % dll) + for dll in context['DELAYLOAD_DLLS']] passthru.variables['USE_DELAYIMP'] = True varmap = dict( @@ -486,24 +473,24 @@ class TreeMetadataEmitter(LoggingMixin): varmap.update(dict(('GENERATED_%s' % k, v) for k, v in varmap.items() if k in ('SOURCES', 'UNIFIED_SOURCES'))) for variable, mapping in varmap.items(): - for f in sandbox[variable]: + for f in context[variable]: ext = mozpath.splitext(f)[1] if ext not in mapping: raise SandboxValidationError( - '%s has an unknown file type.' % f, sandbox) + '%s has an unknown file type.' % f, context) l = passthru.variables.setdefault(mapping[ext], []) l.append(f) if variable.startswith('GENERATED_'): l = passthru.variables.setdefault('GARBAGE', []) l.append(f) - no_pgo = sandbox.get('NO_PGO') - sources = sandbox.get('SOURCES', []) + no_pgo = context.get('NO_PGO') + sources = context.get('SOURCES', []) no_pgo_sources = [f for f in sources if sources[f].no_pgo] if no_pgo: if no_pgo_sources: raise SandboxValidationError('NO_PGO and SOURCES[...].no_pgo ' - 'cannot be set at the same time', sandbox) + 'cannot be set at the same time', context) passthru.variables['NO_PROFILE_GUIDED_OPTIMIZE'] = no_pgo if no_pgo_sources: passthru.variables['NO_PROFILE_GUIDED_OPTIMIZE'] = no_pgo_sources @@ -511,60 +498,60 @@ class TreeMetadataEmitter(LoggingMixin): sources_with_flags = [f for f in sources if sources[f].flags] for f in sources_with_flags: ext = mozpath.splitext(f)[1] - yield PerSourceFlag(sandbox, f, sources[f].flags) + yield PerSourceFlag(context, f, sources[f].flags) - exports = sandbox.get('EXPORTS') + exports = context.get('EXPORTS') if exports: - yield Exports(sandbox, exports, - dist_install=not sandbox.get('NO_DIST_INSTALL', False)) + yield Exports(context, exports, + dist_install=not context.get('NO_DIST_INSTALL', False)) - defines = sandbox.get('DEFINES') + defines = context.get('DEFINES') if defines: - yield Defines(sandbox, defines) + yield Defines(context, defines) - resources = sandbox.get('RESOURCE_FILES') + resources = context.get('RESOURCE_FILES') if resources: - yield Resources(sandbox, resources, defines) + yield Resources(context, resources, defines) for kind, cls in [('PROGRAM', Program), ('HOST_PROGRAM', HostProgram)]: - program = sandbox.get(kind) + program = context.get(kind) if program: if program in self._binaries: raise SandboxValidationError( 'Cannot use "%s" as %s name, ' 'because it is already used in %s' % (program, kind, - self._binaries[program].relativedir), sandbox) - self._binaries[program] = cls(sandbox, program) - self._linkage.append((sandbox, self._binaries[program], + self._binaries[program].relativedir), context) + self._binaries[program] = cls(context, program) + self._linkage.append((context, self._binaries[program], kind.replace('PROGRAM', 'USE_LIBS'))) for kind, cls in [ ('SIMPLE_PROGRAMS', SimpleProgram), ('CPP_UNIT_TESTS', SimpleProgram), ('HOST_SIMPLE_PROGRAMS', HostSimpleProgram)]: - for program in sandbox[kind]: + for program in context[kind]: if program in self._binaries: raise SandboxValidationError( 'Cannot use "%s" in %s, ' 'because it is already used in %s' % (program, kind, - self._binaries[program].relativedir), sandbox) - self._binaries[program] = cls(sandbox, program, + self._binaries[program].relativedir), context) + self._binaries[program] = cls(context, program, is_unit_test=kind == 'CPP_UNIT_TESTS') - self._linkage.append((sandbox, self._binaries[program], + self._linkage.append((context, self._binaries[program], 'HOST_USE_LIBS' if kind == 'HOST_SIMPLE_PROGRAMS' else 'USE_LIBS')) - extra_js_modules = sandbox.get('EXTRA_JS_MODULES') + extra_js_modules = context.get('EXTRA_JS_MODULES') if extra_js_modules: - yield JavaScriptModules(sandbox, extra_js_modules, 'extra') + yield JavaScriptModules(context, extra_js_modules, 'extra') - extra_pp_js_modules = sandbox.get('EXTRA_PP_JS_MODULES') + extra_pp_js_modules = context.get('EXTRA_PP_JS_MODULES') if extra_pp_js_modules: - yield JavaScriptModules(sandbox, extra_pp_js_modules, 'extra_pp') + yield JavaScriptModules(context, extra_pp_js_modules, 'extra_pp') - test_js_modules = sandbox.get('TESTING_JS_MODULES') + test_js_modules = context.get('TESTING_JS_MODULES') if test_js_modules: - yield JavaScriptModules(sandbox, test_js_modules, 'testing') + yield JavaScriptModules(context, test_js_modules, 'testing') simple_lists = [ ('GENERATED_EVENTS_WEBIDL_FILES', GeneratedEventWebIDLFile), @@ -578,62 +565,61 @@ class TreeMetadataEmitter(LoggingMixin): ('WEBIDL_FILES', WebIDLFile), ('WEBIDL_EXAMPLE_INTERFACES', ExampleWebIDLInterface), ] - for sandbox_var, klass in simple_lists: - for name in sandbox.get(sandbox_var, []): - yield klass(sandbox, name) + for context_var, klass in simple_lists: + for name in context.get(context_var, []): + yield klass(context, name) - if sandbox.get('FINAL_TARGET') or sandbox.get('XPI_NAME') or \ - sandbox.get('DIST_SUBDIR'): - yield InstallationTarget(sandbox) + if context.get('FINAL_TARGET') or context.get('XPI_NAME') or \ + context.get('DIST_SUBDIR'): + yield InstallationTarget(context) - host_libname = sandbox.get('HOST_LIBRARY_NAME') - libname = sandbox.get('LIBRARY_NAME') + host_libname = context.get('HOST_LIBRARY_NAME') + libname = context.get('LIBRARY_NAME') if host_libname: if host_libname == libname: raise SandboxValidationError('LIBRARY_NAME and ' - 'HOST_LIBRARY_NAME must have a different value', sandbox) - lib = HostLibrary(sandbox, host_libname) + 'HOST_LIBRARY_NAME must have a different value', context) + lib = HostLibrary(context, host_libname) self._libs[host_libname].append(lib) - self._linkage.append((sandbox, lib, 'HOST_USE_LIBS')) + self._linkage.append((context, lib, 'HOST_USE_LIBS')) - final_lib = sandbox.get('FINAL_LIBRARY') + final_lib = context.get('FINAL_LIBRARY') if not libname and final_lib: # If no LIBRARY_NAME is given, create one. - libname = sandbox['RELATIVEDIR'].replace('/', '_') + libname = context.relsrcdir.replace('/', '_') - static_lib = sandbox.get('FORCE_STATIC_LIB') - shared_lib = sandbox.get('FORCE_SHARED_LIB') + static_lib = context.get('FORCE_STATIC_LIB') + shared_lib = context.get('FORCE_SHARED_LIB') - static_name = sandbox.get('STATIC_LIBRARY_NAME') - shared_name = sandbox.get('SHARED_LIBRARY_NAME') + static_name = context.get('STATIC_LIBRARY_NAME') + shared_name = context.get('SHARED_LIBRARY_NAME') - is_framework = sandbox.get('IS_FRAMEWORK') - is_component = sandbox.get('IS_COMPONENT') + is_framework = context.get('IS_FRAMEWORK') + is_component = context.get('IS_COMPONENT') - soname = sandbox.get('SONAME') + soname = context.get('SONAME') shared_args = {} static_args = {} if final_lib: - if isinstance(sandbox, MozbuildSandbox): - if static_lib: - raise SandboxValidationError( - 'FINAL_LIBRARY implies FORCE_STATIC_LIB. ' - 'Please remove the latter.', sandbox) + if static_lib: + raise SandboxValidationError( + 'FINAL_LIBRARY implies FORCE_STATIC_LIB. ' + 'Please remove the latter.', context) if shared_lib: raise SandboxValidationError( 'FINAL_LIBRARY conflicts with FORCE_SHARED_LIB. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) if is_framework: raise SandboxValidationError( 'FINAL_LIBRARY conflicts with IS_FRAMEWORK. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) if is_component: raise SandboxValidationError( 'FINAL_LIBRARY conflicts with IS_COMPONENT. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) static_args['link_into'] = final_lib static_lib = True @@ -642,15 +628,15 @@ class TreeMetadataEmitter(LoggingMixin): if shared_lib: raise SandboxValidationError( 'IS_COMPONENT implies FORCE_SHARED_LIB. ' - 'Please remove the latter.', sandbox) + 'Please remove the latter.', context) if is_framework: raise SandboxValidationError( 'IS_COMPONENT conflicts with IS_FRAMEWORK. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) if static_lib: raise SandboxValidationError( 'IS_COMPONENT conflicts with FORCE_STATIC_LIB. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) shared_lib = True shared_args['variant'] = SharedLibrary.COMPONENT @@ -658,30 +644,32 @@ class TreeMetadataEmitter(LoggingMixin): if shared_lib: raise SandboxValidationError( 'IS_FRAMEWORK implies FORCE_SHARED_LIB. ' - 'Please remove the latter.', sandbox) + 'Please remove the latter.', context) if soname: raise SandboxValidationError( 'IS_FRAMEWORK conflicts with SONAME. ' - 'Please remove one.', sandbox) + 'Please remove one.', context) shared_lib = True shared_args['variant'] = SharedLibrary.FRAMEWORK if static_name: if not static_lib: raise SandboxValidationError( - 'STATIC_LIBRARY_NAME requires FORCE_STATIC_LIB', sandbox) + 'STATIC_LIBRARY_NAME requires FORCE_STATIC_LIB', + context) static_args['real_name'] = static_name if shared_name: if not shared_lib: raise SandboxValidationError( - 'SHARED_LIBRARY_NAME requires FORCE_SHARED_LIB', sandbox) + 'SHARED_LIBRARY_NAME requires FORCE_SHARED_LIB', + context) shared_args['real_name'] = shared_name if soname: if not shared_lib: raise SandboxValidationError( - 'SONAME requires FORCE_SHARED_LIB', sandbox) + 'SONAME requires FORCE_SHARED_LIB', context) shared_args['soname'] = soname if not static_lib and not shared_lib: @@ -689,7 +677,7 @@ class TreeMetadataEmitter(LoggingMixin): # If both a shared and a static library are created, only the # shared library is meant to be a SDK library. - if sandbox.get('SDK_LIBRARY'): + if context.get('SDK_LIBRARY'): if shared_lib: shared_args['is_sdk'] = True elif static_lib: @@ -701,36 +689,36 @@ class TreeMetadataEmitter(LoggingMixin): 'Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, ' 'but neither STATIC_LIBRARY_NAME or ' 'SHARED_LIBRARY_NAME is set. At least one is required.', - sandbox) + context) if static_name and not shared_name and static_name == libname: raise SandboxValidationError( 'Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, ' 'but STATIC_LIBRARY_NAME is the same as LIBRARY_NAME, ' 'and SHARED_LIBRARY_NAME is unset. Please either ' 'change STATIC_LIBRARY_NAME or LIBRARY_NAME, or set ' - 'SHARED_LIBRARY_NAME.', sandbox) + 'SHARED_LIBRARY_NAME.', context) if shared_name and not static_name and shared_name == libname: raise SandboxValidationError( 'Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, ' 'but SHARED_LIBRARY_NAME is the same as LIBRARY_NAME, ' 'and STATIC_LIBRARY_NAME is unset. Please either ' 'change SHARED_LIBRARY_NAME or LIBRARY_NAME, or set ' - 'STATIC_LIBRARY_NAME.', sandbox) + 'STATIC_LIBRARY_NAME.', context) if shared_name and static_name and shared_name == static_name: raise SandboxValidationError( 'Both FORCE_STATIC_LIB and FORCE_SHARED_LIB are True, ' 'but SHARED_LIBRARY_NAME is the same as ' 'STATIC_LIBRARY_NAME. Please change one of them.', - sandbox) + context) if shared_lib: - lib = SharedLibrary(sandbox, libname, **shared_args) + lib = SharedLibrary(context, libname, **shared_args) self._libs[libname].append(lib) - self._linkage.append((sandbox, lib, 'USE_LIBS')) + self._linkage.append((context, lib, 'USE_LIBS')) if static_lib: - lib = StaticLibrary(sandbox, libname, **static_args) + lib = StaticLibrary(context, libname, **static_args) self._libs[libname].append(lib) - self._linkage.append((sandbox, lib, 'USE_LIBS')) + self._linkage.append((context, lib, 'USE_LIBS')) # While there are multiple test manifests, the behavior is very similar # across them. We enforce this by having common handling of all @@ -763,61 +751,61 @@ class TreeMetadataEmitter(LoggingMixin): ) for prefix, info in test_manifests.items(): - for path in sandbox.get('%s_MANIFESTS' % prefix, []): - for obj in self._process_test_manifest(sandbox, info, path): + for path in context.get('%s_MANIFESTS' % prefix, []): + for obj in self._process_test_manifest(context, info, path): yield obj for flavor in ('crashtest', 'reftest'): - for path in sandbox.get('%s_MANIFESTS' % flavor.upper(), []): - for obj in self._process_reftest_manifest(sandbox, flavor, path): + for path in context.get('%s_MANIFESTS' % flavor.upper(), []): + for obj in self._process_reftest_manifest(context, flavor, path): yield obj - jar_manifests = sandbox.get('JAR_MANIFESTS', []) + jar_manifests = context.get('JAR_MANIFESTS', []) if len(jar_manifests) > 1: raise SandboxValidationError('While JAR_MANIFESTS is a list, ' - 'it is currently limited to one value.', sandbox) + 'it is currently limited to one value.', context) for path in jar_manifests: - yield JARManifest(sandbox, mozpath.join(sandbox['SRCDIR'], path)) + yield JARManifest(context, mozpath.join(context.srcdir, path)) # Temporary test to look for jar.mn files that creep in without using # the new declaration. Before, we didn't require jar.mn files to # declared anywhere (they were discovered). This will detect people # relying on the old behavior. - if os.path.exists(os.path.join(sandbox['SRCDIR'], 'jar.mn')): + if os.path.exists(os.path.join(context.srcdir, 'jar.mn')): if 'jar.mn' not in jar_manifests: raise SandboxValidationError('A jar.mn exists but it ' 'is not referenced in the moz.build file. ' - 'Please define JAR_MANIFESTS.', sandbox) + 'Please define JAR_MANIFESTS.', context) - for name, jar in sandbox.get('JAVA_JAR_TARGETS', {}).items(): - yield SandboxWrapped(sandbox, jar) + for name, jar in context.get('JAVA_JAR_TARGETS', {}).items(): + yield ContextWrapped(context, jar) - for name, data in sandbox.get('ANDROID_ECLIPSE_PROJECT_TARGETS', {}).items(): - yield SandboxWrapped(sandbox, data) + for name, data in context.get('ANDROID_ECLIPSE_PROJECT_TARGETS', {}).items(): + yield ContextWrapped(context, data) if passthru.variables: yield passthru - def _create_substitution(self, cls, sandbox, path): + def _create_substitution(self, cls, context, path): if os.path.isabs(path): path = path[1:] - sub = cls(sandbox) - sub.input_path = mozpath.join(sandbox['SRCDIR'], '%s.in' % path) - sub.output_path = mozpath.join(sandbox['OBJDIR'], path) + sub = cls(context) + sub.input_path = mozpath.join(context.srcdir, '%s.in' % path) + sub.output_path = mozpath.join(context.objdir, path) sub.relpath = path return sub - def _process_test_manifest(self, sandbox, info, manifest_path): + def _process_test_manifest(self, context, info, manifest_path): flavor, install_root, install_subdir, filter_inactive = info manifest_path = mozpath.normpath(manifest_path) - path = mozpath.normpath(mozpath.join(sandbox['SRCDIR'], manifest_path)) + path = mozpath.normpath(mozpath.join(context.srcdir, manifest_path)) manifest_dir = mozpath.dirname(path) manifest_reldir = mozpath.dirname(mozpath.relpath(path, - sandbox['TOPSRCDIR'])) + context.config.topsrcdir)) install_prefix = mozpath.join(install_root, install_subdir) try: @@ -825,9 +813,9 @@ class TreeMetadataEmitter(LoggingMixin): defaults = m.manifest_defaults[os.path.normpath(path)] if not m.tests and not 'support-files' in defaults: raise SandboxValidationError('Empty test manifest: %s' - % path, sandbox) + % path, context) - obj = TestManifest(sandbox, path, m, flavor=flavor, + obj = TestManifest(context, path, m, flavor=flavor, install_prefix=install_prefix, relpath=mozpath.join(manifest_reldir, mozpath.basename(path)), dupe_manifest='dupe-manifest' in defaults) @@ -844,7 +832,7 @@ class TreeMetadataEmitter(LoggingMixin): if missing: raise SandboxValidationError('Test manifest (%s) lists ' 'test that does not exist: %s' % ( - path, ', '.join(missing)), sandbox) + path, ', '.join(missing)), context) out_dir = mozpath.join(install_prefix, manifest_reldir) if 'install-to-subdir' in defaults: @@ -943,7 +931,7 @@ class TreeMetadataEmitter(LoggingMixin): except KeyError: raise SandboxValidationError('Error processing test ' 'manifest %s: entry in generated-files not present ' - 'elsewhere in manifest: %s' % (path, f), sandbox) + 'elsewhere in manifest: %s' % (path, f), context) obj.external_installs.add(mozpath.join(out_dir, f)) @@ -952,14 +940,14 @@ class TreeMetadataEmitter(LoggingMixin): raise SandboxValidationError('Error processing test ' 'manifest file %s: %s' % (path, '\n'.join(traceback.format_exception(*sys.exc_info()))), - sandbox) + context) - def _process_reftest_manifest(self, sandbox, flavor, manifest_path): + def _process_reftest_manifest(self, context, flavor, manifest_path): manifest_path = mozpath.normpath(manifest_path) manifest_full_path = mozpath.normpath(mozpath.join( - sandbox['SRCDIR'], manifest_path)) + context.srcdir, manifest_path)) manifest_reldir = mozpath.dirname(mozpath.relpath(manifest_full_path, - sandbox['TOPSRCDIR'])) + context.config.topsrcdir)) manifest = reftest.ReftestManifest() manifest.load(manifest_full_path) @@ -967,7 +955,7 @@ class TreeMetadataEmitter(LoggingMixin): # reftest manifests don't come from manifest parser. But they are # similar enough that we can use the same emitted objects. Note # that we don't perform any installs for reftests. - obj = TestManifest(sandbox, manifest_full_path, manifest, + obj = TestManifest(context, manifest_full_path, manifest, flavor=flavor, install_prefix='%s/' % flavor, relpath=mozpath.join(manifest_reldir, mozpath.basename(manifest_path))) @@ -986,19 +974,19 @@ class TreeMetadataEmitter(LoggingMixin): yield obj - def _emit_directory_traversal_from_sandbox(self, sandbox): - o = DirectoryTraversal(sandbox) - o.dirs = sandbox.get('DIRS', []) - o.test_dirs = sandbox.get('TEST_DIRS', []) - o.affected_tiers = sandbox.get_affected_tiers() + def _emit_directory_traversal_from_context(self, context): + o = DirectoryTraversal(context) + o.dirs = context.get('DIRS', []) + o.test_dirs = context.get('TEST_DIRS', []) + o.affected_tiers = context.get_affected_tiers() # Some paths have a subconfigure, yet also have a moz.build. Those # shouldn't end up in self._external_paths. self._external_paths -= { o.relobjdir } - if 'TIERS' in sandbox: - for tier in sandbox['TIERS']: - o.tier_dirs[tier] = sandbox['TIERS'][tier]['regular'] + \ - sandbox['TIERS'][tier]['external'] + if 'TIERS' in context: + for tier in context['TIERS']: + o.tier_dirs[tier] = context['TIERS'][tier]['regular'] + \ + context['TIERS'][tier]['external'] yield o diff --git a/python/mozbuild/mozbuild/frontend/gyp_reader.py b/python/mozbuild/mozbuild/frontend/gyp_reader.py index 51a19c24ece..1af7c57d8a9 100644 --- a/python/mozbuild/mozbuild/frontend/gyp_reader.py +++ b/python/mozbuild/mozbuild/frontend/gyp_reader.py @@ -9,11 +9,15 @@ import time import os import mozpack.path as mozpath from mozpack.files import FileFinder -from .sandbox import ( - alphabetical_sorted, - GlobalNamespace, +from .sandbox import alphabetical_sorted +from .context import ( + Context, + VARIABLES, +) +from mozbuild.util import ( + List, + memoize, ) -from .sandbox_symbols import VARIABLES from .reader import SandboxValidationError # Define this module as gyp.generator.mozbuild so that gyp can use it @@ -48,22 +52,28 @@ for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', generator_default_variables[unused] = b'' -class GypSandbox(GlobalNamespace): - """Class mimicking MozbuildSandbox for processing of the data - extracted from Gyp by a mozbuild backend. +class GypContext(Context): + """Specialized Context for use with data extracted from Gyp. - Inherits from GlobalNamespace because it doesn't need the extra - functionality from Sandbox. + config is the ConfigEnvironment for this context. + relobjdir is the object directory that will be used for this context, + relative to the topobjdir defined in the ConfigEnvironment. """ - def __init__(self, main_path, dependencies_paths=[]): - self.main_path = main_path - self.all_paths = set([main_path]) | set(dependencies_paths) - self.execution_time = 0 - GlobalNamespace.__init__(self, allowed_variables=VARIABLES) + def __init__(self, config, relobjdir): + self._relobjdir = relobjdir + Context.__init__(self, allowed_variables=self.VARIABLES(), config=config) - def get_affected_tiers(self): - tiers = (VARIABLES[key][3] for key in self if key in VARIABLES) - return set(tier for tier in tiers if tier) + @classmethod + @memoize + def VARIABLES(cls): + """Returns the allowed variables for a GypContext.""" + # Using a class method instead of a class variable to hide the content + # from sphinx. + return dict(VARIABLES, + IS_GYP_DIR=(bool, bool, '', None), + EXTRA_ASSEMBLER_FLAGS=(List, list, '', None), + EXTRA_COMPILE_FLAGS=(List, list, '', None), + ) def encode(value): @@ -73,7 +83,7 @@ def encode(value): def read_from_gyp(config, path, output, vars, non_unified_sources = set()): - """Read a gyp configuration and emits GypSandboxes for the backend to + """Read a gyp configuration and emits GypContexts for the backend to process. config is a ConfigEnvironment, path is the path to a root gyp configuration @@ -115,33 +125,29 @@ def read_from_gyp(config, path, output, vars, non_unified_sources = set()): # gives us paths normalized with forward slash separator. for target in gyp.common.AllTargets(flat_list, targets, path.replace(b'/', os.sep)): build_file, target_name, toolset = gyp.common.ParseQualifiedTarget(target) + + # Each target is given its own objdir. The base of that objdir + # is derived from the relative path from the root gyp file path + # to the current build_file, placed under the given output + # directory. Since several targets can be in a given build_file, + # separate them in subdirectories using the build_file basename + # and the target_name. + reldir = mozpath.relpath(mozpath.dirname(build_file), + mozpath.dirname(path)) + subdir = '%s_%s' % ( + mozpath.splitext(mozpath.basename(build_file))[0], + target_name, + ) + # Emit a context for each target. + context = GypContext(config, mozpath.relpath( + mozpath.join(output, reldir, subdir), config.topobjdir)) + context.add_source(mozpath.abspath(build_file)) # The list of included files returned by gyp are relative to build_file - included_files = [mozpath.abspath(mozpath.join(mozpath.dirname(build_file), f)) - for f in data[build_file]['included_files']] - # Emit a sandbox for each target. - sandbox = GypSandbox(mozpath.abspath(build_file), included_files) - sandbox.config = config + for f in data[build_file]['included_files']: + context.add_source(mozpath.abspath(mozpath.join( + mozpath.dirname(build_file), f))) - with sandbox.allow_all_writes() as d: - topsrcdir = d['TOPSRCDIR'] = config.topsrcdir - d['TOPOBJDIR'] = config.topobjdir - relsrcdir = d['RELATIVEDIR'] = mozpath.relpath(mozpath.dirname(build_file), config.topsrcdir) - d['SRCDIR'] = mozpath.join(topsrcdir, relsrcdir) - - # Each target is given its own objdir. The base of that objdir - # is derived from the relative path from the root gyp file path - # to the current build_file, placed under the given output - # directory. Since several targets can be in a given build_file, - # separate them in subdirectories using the build_file basename - # and the target_name. - reldir = mozpath.relpath(mozpath.dirname(build_file), - mozpath.dirname(path)) - subdir = '%s_%s' % ( - mozpath.splitext(mozpath.basename(build_file))[0], - target_name, - ) - d['OBJDIR'] = mozpath.join(output, reldir, subdir) - d['IS_GYP_DIR'] = True + context['IS_GYP_DIR'] = True spec = targets[target] @@ -155,16 +161,15 @@ def read_from_gyp(config, path, output, vars, non_unified_sources = set()): if spec['type'] == 'none': continue elif spec['type'] == 'static_library': - sandbox['FORCE_STATIC_LIB'] = True # Remove leading 'lib' from the target_name if any, and use as # library name. name = spec['target_name'] if name.startswith('lib'): name = name[3:] - # The sandbox expects an unicode string. - sandbox['LIBRARY_NAME'] = name.decode('utf-8') + # The context expects an unicode string. + context['LIBRARY_NAME'] = name.decode('utf-8') # gyp files contain headers and asm sources in sources lists. - sources = set(mozpath.normpath(mozpath.join(sandbox['SRCDIR'], f)) + sources = set(mozpath.normpath(mozpath.join(context.srcdir, f)) for f in spec.get('sources', []) if mozpath.splitext(f)[-1] != '.h') asm_sources = set(f for f in sources if f.endswith('.S')) @@ -172,31 +177,30 @@ def read_from_gyp(config, path, output, vars, non_unified_sources = set()): unified_sources = sources - non_unified_sources - asm_sources sources -= unified_sources all_sources |= sources - # The sandbox expects alphabetical order when adding sources - sandbox['SOURCES'] = alphabetical_sorted(sources) - sandbox['UNIFIED_SOURCES'] = alphabetical_sorted(unified_sources) + # The context expects alphabetical order when adding sources + context['SOURCES'] = alphabetical_sorted(sources) + context['UNIFIED_SOURCES'] = alphabetical_sorted(unified_sources) for define in target_conf.get('defines', []): if '=' in define: name, value = define.split('=', 1) - sandbox['DEFINES'][name] = value + context['DEFINES'][name] = value else: - sandbox['DEFINES'][define] = True + context['DEFINES'][define] = True for include in target_conf.get('include_dirs', []): - sandbox['LOCAL_INCLUDES'] += [include] + context['LOCAL_INCLUDES'] += [include] - with sandbox.allow_all_writes() as d: - d['EXTRA_ASSEMBLER_FLAGS'] = target_conf.get('asflags_mozilla', []) - d['EXTRA_COMPILE_FLAGS'] = target_conf.get('cflags_mozilla', []) + context['EXTRA_ASSEMBLER_FLAGS'] = target_conf.get('asflags_mozilla', []) + context['EXTRA_COMPILE_FLAGS'] = target_conf.get('cflags_mozilla', []) else: # Ignore other types than static_library because we don't have # anything using them, and we're not testing them. They can be # added when that becomes necessary. raise NotImplementedError('Unsupported gyp target type: %s' % spec['type']) - sandbox.execution_time = time.time() - time_start - yield sandbox + context.execution_time = time.time() - time_start + yield context time_start = time.time() # remainder = non_unified_sources - all_sources # if remainder: diff --git a/python/mozbuild/mozbuild/frontend/mach_commands.py b/python/mozbuild/mozbuild/frontend/mach_commands.py index 7cc967dd9ec..1ba8ec2fef8 100644 --- a/python/mozbuild/mozbuild/frontend/mach_commands.py +++ b/python/mozbuild/mozbuild/frontend/mach_commands.py @@ -34,7 +34,7 @@ class MozbuildFileCommands(MachCommandBase): variable_reference, ) - import mozbuild.frontend.sandbox_symbols as m + import mozbuild.frontend.context as m if name_only: for s in sorted(m.VARIABLES.keys()): diff --git a/python/mozbuild/mozbuild/frontend/reader.py b/python/mozbuild/mozbuild/frontend/reader.py index 1d3389b112f..7f5c7d1ba4a 100644 --- a/python/mozbuild/mozbuild/frontend/reader.py +++ b/python/mozbuild/mozbuild/frontend/reader.py @@ -9,13 +9,8 @@ r"""Read build frontend files into data structures. In terms of code architecture, the main interface is BuildReader. BuildReader starts with a root mozbuild file. It creates a new execution environment for -this file, which is represented by the Sandbox class. The Sandbox class is what -defines what is allowed to execute in an individual mozbuild file. The Sandbox -consists of a local and global namespace, which are modeled by the -LocalNamespace and GlobalNamespace classes, respectively. The global namespace -contains all of the takeaway information from the execution. The local -namespace is for throwaway local variables and its contents are discarded after -execution. +this file, which is represented by the Sandbox class. The Sandbox class is used +to fill a Context, representing the output of an individual mozbuild file. The The BuildReader contains basic logic for traversing a tree of mozbuild files. It does this by examining specific variables populated during execution. @@ -55,10 +50,12 @@ from .sandbox import ( Sandbox, ) -from .sandbox_symbols import ( +from .context import ( + Context, FUNCTIONS, VARIABLES, DEPRECATION_HINTS, + SPECIAL_VARIABLES, ) if sys.version_info.major == 2: @@ -112,58 +109,39 @@ class MozbuildSandbox(Sandbox): We expose a few useful functions and expose the set of variables defining Mozilla's build system. - """ - def __init__(self, config, path, metadata={}): - """Create an empty mozbuild Sandbox. - config is a ConfigStatus instance (the output of configure). path is - the path of the main mozbuild file that is being executed. It is used - to compute encountered relative paths. - """ - Sandbox.__init__(self, allowed_variables=VARIABLES) + context is a Context instance. + + metadata is a dict of metadata that can be used during the sandbox + evaluation. + """ + def __init__(self, context, metadata={}): + assert isinstance(context, Context) + + Sandbox.__init__(self, context) self._log = logging.getLogger(__name__) - self.config = config self.metadata = dict(metadata) + exports = self.metadata.get('exports', {}) + self.exports = set(exports.keys()) + context.update(exports) - topobjdir = config.topobjdir - topsrcdir = config.topsrcdir + def __getitem__(self, key): + if key in SPECIAL_VARIABLES: + return SPECIAL_VARIABLES[key][0](self._context) + if key in FUNCTIONS: + return getattr(self, FUNCTIONS[key][0]) + return Sandbox.__getitem__(self, key) - if not mozpath.basedir(path, [topsrcdir]): - if config.external_source_dir and \ - mozpath.basedir(path, [config.external_source_dir]): - topsrcdir = config.external_source_dir - - self.topsrcdir = topsrcdir - - relpath = mozpath.relpath(path, topsrcdir) - reldir = mozpath.dirname(relpath) - - if mozpath.dirname(relpath) == 'js/src' and \ - not config.substs.get('JS_STANDALONE'): - config = ConfigEnvironment.from_config_status( - mozpath.join(topobjdir, reldir, 'config.status')) - config.topobjdir = topobjdir - self.config = config - - with self._globals.allow_all_writes() as d: - d['TOPSRCDIR'] = topsrcdir - d['TOPOBJDIR'] = topobjdir - d['RELATIVEDIR'] = reldir - d['SRCDIR'] = mozpath.join(topsrcdir, reldir).rstrip('/') - d['OBJDIR'] = mozpath.join(topobjdir, reldir).rstrip('/') - - d['CONFIG'] = ReadOnlyDefaultDict(lambda: None, - self.config.substs_unicode) - - # Register functions. - for name, func in FUNCTIONS.items(): - d[name] = getattr(self, func[0]) - - # Initialize the exports that we need in the global. - extra_vars = self.metadata.get('exports', dict()) - self._globals.update(extra_vars) + def __setitem__(self, key, value): + if key in SPECIAL_VARIABLES or key in FUNCTIONS: + raise KeyError() + if key in self.exports: + self._context[key] = value + self.exports.remove(key) + return + Sandbox.__setitem__(self, key, value) def normalize_path(self, path, filesystem_absolute=False, srcdir=None): """Normalizes paths. @@ -180,25 +158,28 @@ class MozbuildSandbox(Sandbox): if os.path.isabs(path): if filesystem_absolute: return path - roots = [self.topsrcdir] - if self.config.external_source_dir: - roots.append(self.config.external_source_dir) + roots = [self._context.config.topsrcdir] + if self._context.config.external_source_dir: + roots.append(self._context.config.external_source_dir) for root in roots: - # mozpath.join would ignore the self.topsrcdir argument if we - # passed in the absolute path, so omit the leading / + # mozpath.join would ignore the self._context.config.topsrcdir + # argument if we passed in the absolute path, so omit the + # leading / p = mozpath.normpath(mozpath.join(root, path[1:])) if os.path.exists(p): return p - # mozpath.join would ignore the self.topsrcdir argument if we passed - # in the absolute path, so omit the leading / - return mozpath.normpath(mozpath.join(self.topsrcdir, path[1:])) + # mozpath.join would ignore the self.condig.topsrcdir argument if + # we passed in the absolute path, so omit the leading / + return mozpath.normpath( + mozpath.join(self._context.config.topsrcdir, path[1:])) elif srcdir: return mozpath.normpath(mozpath.join(srcdir, path)) elif len(self._execution_stack): return mozpath.normpath(mozpath.join( mozpath.dirname(self._execution_stack[-1]), path)) else: - return mozpath.normpath(mozpath.join(self.topsrcdir, path)) + return mozpath.normpath( + mozpath.join(self._context.config.topsrcdir, path)) def exec_file(self, path, filesystem_absolute=False): """Override exec_file to normalize paths and restrict file loading. @@ -211,7 +192,7 @@ class MozbuildSandbox(Sandbox): # protection, so it is omitted. normalized_path = self.normalize_path(path, filesystem_absolute=filesystem_absolute) - if not is_read_allowed(normalized_path, self.config): + if not is_read_allowed(normalized_path, self._context.config): raise SandboxLoadError(list(self._execution_stack), sys.exc_info()[2], illegal_path=path) @@ -288,10 +269,10 @@ class MozbuildSandbox(Sandbox): raise Exception('Variable has already been exported: %s' % varname) try: - # Doing a regular self._globals[varname] causes a set as a side + # Doing a regular self._context[varname] causes a set as a side # effect. By calling the dict method instead, we don't have any # side effects. - exports[varname] = dict.__getitem__(self._globals, varname) + exports[varname] = dict.__getitem__(self._context, varname) except KeyError: self.last_name_error = KeyError('global_ns', 'get_unknown', varname) raise self.last_name_error @@ -320,9 +301,9 @@ class MozbuildSandbox(Sandbox): class SandboxValidationError(Exception): """Represents an error encountered when validating sandbox results.""" - def __init__(self, message, sandbox): + def __init__(self, message, context): Exception.__init__(self, message) - self.sandbox = sandbox + self.context = context def __str__(self): s = StringIO() @@ -333,7 +314,7 @@ class SandboxValidationError(Exception): s.write('The error occurred while processing the following file or ') s.write('one of the files it includes:\n') s.write('\n') - s.write(' %s/moz.build\n' % self.sandbox['SRCDIR']) + s.write(' %s/moz.build\n' % self.context.srcdir) s.write('\n') s.write('The error occurred when validating the result of ') @@ -646,15 +627,13 @@ class BuildReader(object): The reader can optionally call a callable after each sandbox is evaluated but before its evaluated content is processed. This gives callers the - opportunity to modify sandboxes before side-effects occur from their - content. This callback receives the ``Sandbox`` that was evaluated. The - return value is ignored. + opportunity to modify contexts before side-effects occur from their + content. This callback receives the ``Context`` containing the result of + each sandbox evaluation. Its return value is ignored. """ def __init__(self, config, sandbox_post_eval_cb=None): self.config = config - self.topsrcdir = config.topsrcdir - self.topobjdir = config.topobjdir self._sandbox_post_eval_cb = sandbox_post_eval_cb self._log = logging.getLogger(__name__) @@ -667,10 +646,10 @@ class BuildReader(object): This starts with the tree's top-most moz.build file and descends into all linked moz.build files until all relevant files have been evaluated. - This is a generator of Sandbox instances. As each moz.build file is - read, a new Sandbox is created and emitted. + This is a generator of Context instances. As each moz.build file is + read, a new Context is created and emitted. """ - path = mozpath.join(self.topsrcdir, 'moz.build') + path = mozpath.join(self.config.topsrcdir, 'moz.build') return self.read_mozbuild(path, self.config, read_tiers=True, filesystem_absolute=True) @@ -681,7 +660,7 @@ class BuildReader(object): filesystem walk to discover every moz.build file rather than relying on data from executed moz.build files to drive traversal. - This is a generator of Sandbox instances. + This is a generator of Context instances. """ # In the future, we may traverse moz.build files by looking # for DIRS references in the AST, even if a directory is added behind @@ -694,11 +673,11 @@ class BuildReader(object): 'obj*', } - finder = FileFinder(self.topsrcdir, find_executables=False, + finder = FileFinder(self.config.topsrcdir, find_executables=False, ignore=ignore) for path, f in finder.find('**/moz.build'): - path = os.path.join(self.topsrcdir, path) + path = os.path.join(self.config.topsrcdir, path) for s in self.read_mozbuild(path, self.config, descend=False, filesystem_absolute=True, read_tiers=True): yield s @@ -723,10 +702,9 @@ class BuildReader(object): directories and files per variable values. Arbitrary metadata in the form of a dict can be passed into this - function. This metadata will be attached to the emitted output. This - feature is intended to facilitate the build reader injecting state and - annotations into moz.build files that is independent of the sandbox's - execution context. + function. This feature is intended to facilitate the build reader + injecting state and annotations into moz.build files that is + independent of the sandbox's execution context. Traversal is performed depth first (for no particular reason). """ @@ -774,35 +752,57 @@ class BuildReader(object): self._read_files.add(path) time_start = time.time() - sandbox = MozbuildSandbox(config, path, metadata=metadata) + + topobjdir = config.topobjdir + + if not mozpath.basedir(path, [config.topsrcdir]): + external = config.external_source_dir + if external and mozpath.basedir(path, [external]): + config = ConfigEnvironment.from_config_status( + mozpath.join(topobjdir, 'config.status')) + config.topsrcdir = external + config.external_source_dir = None + + relpath = mozpath.relpath(path, config.topsrcdir) + reldir = mozpath.dirname(relpath) + + if mozpath.dirname(relpath) == 'js/src' and \ + not config.substs.get('JS_STANDALONE'): + config = ConfigEnvironment.from_config_status( + mozpath.join(topobjdir, reldir, 'config.status')) + config.topobjdir = topobjdir + config.external_source_dir = None + + context = Context(VARIABLES, config) + sandbox = MozbuildSandbox(context, metadata=metadata) sandbox.exec_file(path, filesystem_absolute=filesystem_absolute) - sandbox.execution_time = time.time() - time_start + context.execution_time = time.time() - time_start if self._sandbox_post_eval_cb: - self._sandbox_post_eval_cb(sandbox) + self._sandbox_post_eval_cb(context) # We first collect directories populated in variables. dir_vars = ['DIRS'] - if sandbox.config.substs.get('ENABLE_TESTS', False) == '1': + if context.config.substs.get('ENABLE_TESTS', False) == '1': dir_vars.append('TEST_DIRS') - dirs = [(v, sandbox[v]) for v in dir_vars if v in sandbox] + dirs = [(v, context[v]) for v in dir_vars if v in context] curdir = mozpath.dirname(path) - gyp_sandboxes = [] - for target_dir in sandbox['GYP_DIRS']: - gyp_dir = sandbox['GYP_DIRS'][target_dir] + gyp_contexts = [] + for target_dir in context['GYP_DIRS']: + gyp_dir = context['GYP_DIRS'][target_dir] for v in ('input', 'variables'): if not getattr(gyp_dir, v): raise SandboxValidationError('Missing value for ' - 'GYP_DIRS["%s"].%s' % (target_dir, v), sandbox) + 'GYP_DIRS["%s"].%s' % (target_dir, v), context) - # The make backend assumes sandboxes for sub-directories are - # emitted after their parent, so accumulate the gyp sandboxes. - # We could emit the parent sandbox before processing gyp - # configuration, but we need to add the gyp objdirs to that sandbox + # The make backend assumes contexts for sub-directories are + # emitted after their parent, so accumulate the gyp contexts. + # We could emit the parent context before processing gyp + # configuration, but we need to add the gyp objdirs to that context # first. from .gyp_reader import read_from_gyp non_unified_sources = set() @@ -810,27 +810,27 @@ class BuildReader(object): source = mozpath.normpath(mozpath.join(curdir, s)) if not os.path.exists(source): raise SandboxValidationError('Cannot find %s.' % source, - sandbox) + context) non_unified_sources.add(source) - for gyp_sandbox in read_from_gyp(sandbox.config, + for gyp_context in read_from_gyp(context.config, mozpath.join(curdir, gyp_dir.input), - mozpath.join(sandbox['OBJDIR'], + mozpath.join(context.objdir, target_dir), gyp_dir.variables, non_unified_sources = non_unified_sources): - gyp_sandbox.update(gyp_dir.sandbox_vars) - gyp_sandboxes.append(gyp_sandbox) + gyp_context.update(gyp_dir.sandbox_vars) + gyp_contexts.append(gyp_context) - for gyp_sandbox in gyp_sandboxes: + for gyp_context in gyp_contexts: if self._sandbox_post_eval_cb: - self._sandbox_post_eval_cb(gyp_sandbox) + self._sandbox_post_eval_cb(gyp_context) - sandbox['DIRS'].append(mozpath.relpath(gyp_sandbox['OBJDIR'], sandbox['OBJDIR'])) + context['DIRS'].append(mozpath.relpath(gyp_context.objdir, context.objdir)) - yield sandbox + yield context - for gyp_sandbox in gyp_sandboxes: - yield gyp_sandbox + for gyp_context in gyp_contexts: + yield gyp_context # Traverse into referenced files. @@ -843,7 +843,7 @@ class BuildReader(object): if d in recurse_info: raise SandboxValidationError( 'Directory (%s) registered multiple times in %s' % ( - d, var), sandbox) + d, var), context) recurse_info[d] = {} if 'exports' in sandbox.metadata: @@ -851,19 +851,19 @@ class BuildReader(object): recurse_info[d]['exports'] = dict(sandbox.metadata['exports']) # We also have tiers whose members are directories. - if 'TIERS' in sandbox: + if 'TIERS' in context: if not read_tiers: raise SandboxValidationError( - 'TIERS defined but it should not be', sandbox) + 'TIERS defined but it should not be', context) - for tier, values in sandbox['TIERS'].items(): + for tier, values in context['TIERS'].items(): # We don't descend into external directories because external by # definition is external to the build system. for d in values['regular']: if d in recurse_info: raise SandboxValidationError( 'Tier directory (%s) registered multiple ' - 'times in %s' % (d, tier), sandbox) + 'times in %s' % (d, tier), context) recurse_info[d] = {'check_external': True} for relpath, child_metadata in recurse_info.items(): @@ -876,15 +876,15 @@ class BuildReader(object): # because it isn't necessary. If there are symlinks in the srcdir, # that's not our problem. We're not a hosted application: we don't # need to worry about security too much. - if not is_read_allowed(child_path, sandbox.config): + if not is_read_allowed(child_path, context.config): raise SandboxValidationError( 'Attempting to process file outside of allowed paths: %s' % - child_path, sandbox) + child_path, context) if not descend: continue - for res in self.read_mozbuild(child_path, sandbox.config, + for res in self.read_mozbuild(child_path, context.config, read_tiers=False, filesystem_absolute=True, metadata=child_metadata): yield res diff --git a/python/mozbuild/mozbuild/frontend/sandbox.py b/python/mozbuild/mozbuild/frontend/sandbox.py index aa8b935894d..5d2f21e6f08 100644 --- a/python/mozbuild/mozbuild/frontend/sandbox.py +++ b/python/mozbuild/mozbuild/frontend/sandbox.py @@ -8,11 +8,8 @@ This module contains classes for Python sandboxes that execute in a highly-controlled environment. The main class is `Sandbox`. This provides an execution environment for Python -code. - -The behavior inside sandboxes is mostly regulated by the `GlobalNamespace` and -`LocalNamespace` classes. These represent the global and local namespaces in -the sandbox, respectively. +code and is used to fill a Context instance for the takeaway information from +the execution. Code in this module takes a different approach to exception handling compared to what you'd see elsewhere in Python. Arguments to built-in exceptions like @@ -29,12 +26,7 @@ import sys from contextlib import contextmanager from mozbuild.util import ReadOnlyDict - - -class SandboxDerivedValue(object): - """Classes deriving from this one receive a special treatment in a - sandbox GlobalNamespace. See GlobalNamespace documentation. - """ +from context import Context def alphabetical_sorted(iterable, cmp=None, key=lambda x: x.lower(), @@ -45,213 +37,6 @@ def alphabetical_sorted(iterable, cmp=None, key=lambda x: x.lower(), return sorted(iterable, cmp, key, reverse) -class GlobalNamespace(dict): - """Represents the globals namespace in a sandbox. - - This is a highly specialized dictionary employing light magic. - - At the crux we have the concept of a restricted keys set. Only very - specific keys may be retrieved or mutated. The rules are as follows: - - - The '__builtins__' key is hardcoded and is read-only. - - The set of variables that can be assigned or accessed during - execution is passed into the constructor. - - When variables are assigned to, we verify assignment is allowed. Assignment - is allowed if the variable is known (set defined at constructor time) and - if the value being assigned is the expected type (also defined at - constructor time). - - When variables are read, we first try to read the existing value. If a - value is not found and it is defined in the allowed variables set, we - return a new instance of the class for that variable. We don't assign - default instances until they are accessed because this makes debugging - the end-result much simpler. Instead of a data structure with lots of - empty/default values, you have a data structure with only the values - that were read or touched. - - Instances of variables classes are created by invoking class_name(), - except when class_name derives from SandboxDerivedValue, in which - case class_name(instance_of_the_global_namespace) is invoked. - A value is added to those calls when instances are created during - assignment (setitem). - - Instantiators of this class are given a backdoor to perform setting of - arbitrary values. e.g. - - ns = GlobalNamespace() - with ns.allow_all_writes(): - ns['foo'] = True - - ns['bar'] = True # KeyError raised. - """ - - # The default set of builtins. - BUILTINS = ReadOnlyDict({ - # Only real Python built-ins should go here. - 'None': None, - 'False': False, - 'True': True, - 'sorted': alphabetical_sorted, - 'int': int, - }) - - def __init__(self, allowed_variables=None, builtins=None): - """Create a new global namespace having specific variables. - - allowed_variables is a dict of the variables that can be queried and - mutated. Keys in this dict are the strings representing keys in this - namespace which are valid. Values are tuples of stored type, assigned - type, default value, and a docstring describing the purpose of the variable. - - builtins is the value to use for the special __builtins__ key. If not - defined, the BUILTINS constant attached to this class is used. The - __builtins__ object is read-only. - """ - builtins = builtins or self.BUILTINS - - assert isinstance(builtins, ReadOnlyDict) - - dict.__init__(self, {'__builtins__': builtins}) - - self._allowed_variables = allowed_variables or {} - - # We need to record this because it gets swallowed as part of - # evaluation. - self.last_name_error = None - - self._allow_all_writes = False - - self._allow_one_mutation = set() - - def __getitem__(self, name): - try: - return dict.__getitem__(self, name) - except KeyError: - pass - - # The variable isn't present yet. Fall back to VARIABLES. - default = self._allowed_variables.get(name, None) - if default is None: - self.last_name_error = KeyError('global_ns', 'get_unknown', name) - raise self.last_name_error - - # If the default is specifically a lambda (or, rather, any function--but - # not a class that can be called), then it is actually a rule to - # generate the default that should be used. - default = default[0] - if issubclass(default, SandboxDerivedValue): - value = default(self) - else: - value = default() - - dict.__setitem__(self, name, value) - return dict.__getitem__(self, name) - - def __setitem__(self, name, value): - if self._allow_all_writes: - dict.__setitem__(self, name, value) - self._allow_one_mutation.add(name) - return - - # Forbid assigning over a previously set value. Interestingly, when - # doing FOO += ['bar'], python actually does something like: - # foo = namespace.__getitem__('FOO') - # foo.__iadd__(['bar']) - # namespace.__setitem__('FOO', foo) - # This means __setitem__ is called with the value that is already - # in the dict, when doing +=, which is permitted. - if name in self._allow_one_mutation: - self._allow_one_mutation.remove(name) - elif name in self and dict.__getitem__(self, name) is not value: - raise KeyError('global_ns', 'reassign', name) - - # We don't need to check for name.isupper() here because LocalNamespace - # only sends variables our way if isupper() is True. - stored_type, input_type, docs, tier = \ - self._allowed_variables.get(name, (None, None, None, None)) - - # Variable is unknown. - if stored_type is None: - self.last_name_error = KeyError('global_ns', 'set_unknown', name, - value) - raise self.last_name_error - - # If the incoming value is not the type we store, we try to convert - # it to that type. This relies on proper coercion rules existing. This - # is the responsibility of whoever defined the symbols: a type should - # not be in the allowed set if the constructor function for the stored - # type does not accept an instance of that type. - if not isinstance(value, stored_type): - if not isinstance(value, input_type): - self.last_name_error = ValueError('global_ns', 'set_type', name, - value, input_type) - raise self.last_name_error - - if issubclass(stored_type, SandboxDerivedValue): - value = stored_type(self, value) - else: - value = stored_type(value) - - dict.__setitem__(self, name, value) - - @contextmanager - def allow_all_writes(self): - """Allow any variable to be written to this instance. - - This is used as a context manager. When activated, all writes - (__setitem__ calls) are allowed. When the context manager is exited, - the instance goes back to its default behavior of only allowing - whitelisted mutations. - """ - self._allow_all_writes = True - yield self - self._allow_all_writes = False - - # dict.update doesn't call our __setitem__, so we have to override it. - def update(self, other): - for name, value in other.items(): - self.__setitem__(name, value) - - -class LocalNamespace(dict): - """Represents the locals namespace in a Sandbox. - - This behaves like a dict except with some additional behavior tailored - to our sandbox execution model. - - Under normal rules of exec(), doing things like += could have interesting - consequences. Keep in mind that a += is really a read, followed by the - creation of a new variable, followed by a write. If the read came from the - global namespace, then the write would go to the local namespace, resulting - in fragmentation. This is not desired. - - LocalNamespace proxies reads and writes for global-looking variables - (read: UPPERCASE) to the global namespace. This means that attempting to - read or write an unknown variable results in exceptions raised from the - GlobalNamespace. - """ - def __init__(self, global_ns): - """Create a local namespace associated with a GlobalNamespace.""" - dict.__init__({}) - - self._globals = global_ns - self.last_name_error = None - - def __getitem__(self, name): - if name.isupper(): - return self._globals[name] - - return dict.__getitem__(self, name) - - def __setitem__(self, name, value): - if name.isupper(): - self._globals[name] = value - return - - dict.__setitem__(self, name, value) - - class SandboxError(Exception): def __init__(self, file_stack): self.file_stack = file_stack @@ -287,11 +72,12 @@ class SandboxLoadError(SandboxError): self.read_error = read_error -class Sandbox(object): +class Sandbox(dict): """Represents a sandbox for executing Python code. - This class both provides a sandbox for execution of a single mozbuild - frontend file as well as an interface to the results of that execution. + This class provides a sandbox for execution of a single mozbuild frontend + file. The results of that execution is stored in the Context instance given + as the ``context`` argument. Sandbox is effectively a glorified wrapper around compile() + exec(). You point it at some Python code and it executes it. The main difference from @@ -301,29 +87,41 @@ class Sandbox(object): prevents executed code from doing things like import modules, open files, etc. - Sandboxes are bound to a mozconfig instance. These objects are produced by - the output of configure. + Sandbox instances act as global namespace for the sandboxed execution + itself. They shall not be used to access the results of the execution. + Those results are available in the given Context instance after execution. - Sandbox instances can be accessed like dictionaries to facilitate result - retrieval. e.g. foo = sandbox['FOO']. Direct assignment is not allowed. + The Sandbox itself is responsible for enforcing rules such as forbidding + reassignment of variables. - Each sandbox has associated with it a GlobalNamespace and LocalNamespace. - Only data stored in the GlobalNamespace is retrievable via the dict - interface. This is because the local namespace should be irrelevant: it - should only contain throwaway variables. + Implementation note: Sandbox derives from dict because exec() insists that + what it is given for namespaces is a dict. """ - def __init__(self, allowed_variables=None, builtins=None): - """Initialize a Sandbox ready for execution. + # The default set of builtins. + BUILTINS = ReadOnlyDict({ + # Only real Python built-ins should go here. + 'None': None, + 'False': False, + 'True': True, + 'sorted': alphabetical_sorted, + 'int': int, + }) - The arguments are proxied to GlobalNamespace.__init__. + def __init__(self, context, builtins=None): + """Initialize a Sandbox ready for execution. """ - self._globals = GlobalNamespace(allowed_variables=allowed_variables, - builtins=builtins) - self._allowed_variables = allowed_variables - self._locals = LocalNamespace(self._globals) + self._builtins = builtins or self.BUILTINS + dict.__setitem__(self, '__builtins__', self._builtins) + + assert isinstance(self._builtins, ReadOnlyDict) + assert isinstance(context, Context) + + self._context = context self._execution_stack = [] - self.main_path = None - self.all_paths = set() + + # We need to record this because it gets swallowed as part of + # evaluation. + self._last_name_error = None def exec_file(self, path): """Execute code at a path in the sandbox. @@ -343,7 +141,7 @@ class Sandbox(object): self.exec_source(source, path) - def exec_source(self, source, path): + def exec_source(self, source, path=''): """Execute Python code within a string. The passed string should contain Python code to be executed. The string @@ -355,10 +153,8 @@ class Sandbox(object): """ self._execution_stack.append(path) - if self.main_path is None: - self.main_path = path - - self.all_paths.add(path) + if path: + self._context.add_source(path) # We don't have to worry about bytecode generation here because we are # too low-level for that. However, we could add bytecode generation via @@ -368,23 +164,24 @@ class Sandbox(object): # compile() inherits the __future__ from the module by default. We # do want Unicode literals. code = compile(source, path, 'exec') - exec(code, self._globals, self._locals) + # We use ourself as the global namespace for the execution. There + # is no need for a separate local namespace as moz.build execution + # is flat, namespace-wise. + exec(code, self) except SandboxError as e: raise e except NameError as e: - # A NameError is raised when a local or global could not be found. + # A NameError is raised when a variable could not be found. # The original KeyError has been dropped by the interpreter. - # However, we should have it cached in our namespace instances! + # However, we should have it cached in our instance! # Unless a script is doing something wonky like catching NameError # itself (that would be silly), if there is an exception on the # global namespace, that's our error. actual = e - if self._globals.last_name_error is not None: - actual = self._globals.last_name_error - elif self._locals.last_name_error is not None: - actual = self._locals.last_name_error + if self._last_name_error is not None: + actual = self._last_name_error raise SandboxExecutionError(list(self._execution_stack), type(actual), actual, sys.exc_info()[2]) @@ -398,26 +195,43 @@ class Sandbox(object): finally: self._execution_stack.pop() - # Dict interface proxies reads to global namespace. - def __len__(self): - return len(self._globals) + def __getitem__(self, key): + if key.isupper(): + try: + return self._context[key] + except Exception as e: + self._last_name_error = e + raise - def __getitem__(self, name): - return self._globals[name] + return dict.__getitem__(self, key) - def __iter__(self): - return iter(self._globals) + def __setitem__(self, key, value): + if key in self._builtins or key == '__builtins__': + raise KeyError('Cannot reassign builtins') - def iterkeys(self): - return self.__iter__() + if key.isupper(): + # Forbid assigning over a previously set value. Interestingly, when + # doing FOO += ['bar'], python actually does something like: + # foo = namespace.__getitem__('FOO') + # foo.__iadd__(['bar']) + # namespace.__setitem__('FOO', foo) + # This means __setitem__ is called with the value that is already + # in the dict, when doing +=, which is permitted. + if key in self._context and self._context[key] is not value: + raise KeyError('global_ns', 'reassign', key) - def __contains__(self, key): - return key in self._globals + self._context[key] = value + else: + dict.__setitem__(self, key, value) def get(self, key, default=None): - return self._globals.get(key, default) + raise NotImplementedError('Not supported') - def get_affected_tiers(self): - tiers = (self._allowed_variables[key][3] for key in self - if key in self._allowed_variables) - return set(tier for tier in tiers if tier) + def __len__(self): + raise NotImplementedError('Not supported') + + def __iter__(self): + raise NotImplementedError('Not supported') + + def __contains__(self, key): + raise NotImplementedError('Not supported') diff --git a/python/mozbuild/mozbuild/sphinx.py b/python/mozbuild/mozbuild/sphinx.py index eaf3a9ea7cb..e6c3ba04151 100644 --- a/python/mozbuild/mozbuild/sphinx.py +++ b/python/mozbuild/mozbuild/sphinx.py @@ -75,7 +75,7 @@ def variable_reference(v, st_type, in_type, doc, tier): return lines -def special_reference(v, typ, doc): +def special_reference(v, func, typ, doc): lines = [ v, '-' * len(v), diff --git a/python/mozbuild/mozbuild/test/common.py b/python/mozbuild/mozbuild/test/common.py index eb246de7805..c2095f72425 100644 --- a/python/mozbuild/mozbuild/test/common.py +++ b/python/mozbuild/mozbuild/test/common.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals -import os - from mach.logging import LoggingManager from mozbuild.util import ReadOnlyDict @@ -37,6 +35,3 @@ class MockConfig(object): self.defines = self.substs self.external_source_dir = None - - def child_path(self, p): - return os.path.join(self.topsrcdir, p) diff --git a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build index c271ec3908c..9cc068caaa7 100644 --- a/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build +++ b/python/mozbuild/mozbuild/test/frontend/data/inheriting-variables/foo/baz/moz.build @@ -3,3 +3,5 @@ # 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/. + +XPIDL_MODULE = 'baz' diff --git a/python/mozbuild/mozbuild/test/frontend/test_context.py b/python/mozbuild/mozbuild/test/frontend/test_context.py new file mode 100644 index 00000000000..14a048bbd40 --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/test_context.py @@ -0,0 +1,133 @@ +# 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/. + +import unittest + +from mozunit import main + +from mozbuild.frontend.context import ( + Context, + FUNCTIONS, + SPECIAL_VARIABLES, + VARIABLES, +) + + +class TestContext(unittest.TestCase): + def test_defaults(self): + test = Context({ + 'foo': (int, int, '', None), + 'bar': (bool, bool, '', None), + 'baz': (dict, dict, '', None), + }) + + self.assertEqual(test.keys(), []) + + self.assertEqual(test['foo'], 0) + + self.assertEqual(set(test.keys()), { 'foo' }) + + self.assertEqual(test['bar'], False) + + self.assertEqual(set(test.keys()), { 'foo', 'bar' }) + + self.assertEqual(test['baz'], {}) + + self.assertEqual(set(test.keys()), { 'foo', 'bar', 'baz' }) + + with self.assertRaises(KeyError): + test['qux'] + + self.assertEqual(set(test.keys()), { 'foo', 'bar', 'baz' }) + + def test_type_check(self): + test = Context({ + 'foo': (int, int, '', None), + 'baz': (dict, list, '', None), + }) + + test['foo'] = 5 + + self.assertEqual(test['foo'], 5) + + with self.assertRaises(ValueError): + test['foo'] = {} + + self.assertEqual(test['foo'], 5) + + with self.assertRaises(KeyError): + test['bar'] = True + + test['baz'] = [('a', 1), ('b', 2)] + + self.assertEqual(test['baz'], { 'a': 1, 'b': 2 }) + + def test_update(self): + test = Context({ + 'foo': (int, int, '', None), + 'bar': (bool, bool, '', None), + 'baz': (dict, list, '', None), + }) + + self.assertEqual(test.keys(), []) + + with self.assertRaises(ValueError): + test.update(bar=True, foo={}) + + self.assertEqual(test.keys(), []) + + test.update(bar=True, foo=1) + + self.assertEqual(set(test.keys()), { 'foo', 'bar' }) + self.assertEqual(test['foo'], 1) + self.assertEqual(test['bar'], True) + + test.update([('bar', False), ('foo', 2)]) + self.assertEqual(test['foo'], 2) + self.assertEqual(test['bar'], False) + + test.update([('foo', 0), ('baz', { 'a': 1, 'b': 2 })]) + self.assertEqual(test['foo'], 0) + self.assertEqual(test['baz'], { 'a': 1, 'b': 2 }) + + test.update([('foo', 42), ('baz', [('c', 3), ('d', 4)])]) + self.assertEqual(test['foo'], 42) + self.assertEqual(test['baz'], { 'c': 3, 'd': 4 }) + + +class TestSymbols(unittest.TestCase): + def _verify_doc(self, doc): + # Documentation should be of the format: + # """SUMMARY LINE + # + # EXTRA PARAGRAPHS + # """ + + self.assertNotIn('\r', doc) + + lines = doc.split('\n') + + # No trailing whitespace. + for line in lines[0:-1]: + self.assertEqual(line, line.rstrip()) + + self.assertGreater(len(lines), 0) + self.assertGreater(len(lines[0].strip()), 0) + + # Last line should be empty. + self.assertEqual(lines[-1].strip(), '') + + def test_documentation_formatting(self): + for typ, inp, doc, tier in VARIABLES.values(): + self._verify_doc(doc) + + for attr, args, doc in FUNCTIONS.values(): + self._verify_doc(doc) + + for func, typ, doc in SPECIAL_VARIABLES.values(): + self._verify_doc(doc) + + +if __name__ == '__main__': + main() diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 9a4bb30084a..59572abe558 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -89,8 +89,8 @@ class TestEmitterBasic(unittest.TestCase): self.assertIsInstance(o, DirectoryTraversal) self.assertEqual(o.test_dirs, []) self.assertEqual(len(o.tier_dirs), 0) - self.assertTrue(os.path.isabs(o.sandbox_main_path)) - self.assertEqual(len(o.sandbox_all_paths), 1) + self.assertTrue(os.path.isabs(o.context_main_path)) + self.assertEqual(len(o.context_all_paths), 1) reldirs = [o.relativedir for o in objs] self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar']) diff --git a/python/mozbuild/mozbuild/test/frontend/test_namespaces.py b/python/mozbuild/mozbuild/test/frontend/test_namespaces.py index 7784e7f6575..ef5b48367a3 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_namespaces.py +++ b/python/mozbuild/mozbuild/test/frontend/test_namespaces.py @@ -8,24 +8,16 @@ import unittest from mozunit import main -from mozbuild.frontend.sandbox import ( - GlobalNamespace, - LocalNamespace, +from mozbuild.frontend.context import ( + Context, + VARIABLES, ) -from mozbuild.frontend.sandbox_symbols import VARIABLES - - -class TestGlobalNamespace(unittest.TestCase): - def test_builtins(self): - ns = GlobalNamespace() - - self.assertIn('__builtins__', ns) - self.assertEqual(ns['__builtins__']['True'], True) +class TestContext(unittest.TestCase): def test_key_rejection(self): # Lowercase keys should be rejected during normal operation. - ns = GlobalNamespace(allowed_variables=VARIABLES) + ns = Context(allowed_variables=VARIABLES) with self.assertRaises(KeyError) as ke: ns['foo'] = True @@ -49,13 +41,13 @@ class TestGlobalNamespace(unittest.TestCase): def test_allowed_set(self): self.assertIn('DIRS', VARIABLES) - ns = GlobalNamespace(allowed_variables=VARIABLES) + ns = Context(allowed_variables=VARIABLES) ns['DIRS'] = ['foo'] self.assertEqual(ns['DIRS'], ['foo']) def test_value_checking(self): - ns = GlobalNamespace(allowed_variables=VARIABLES) + ns = Context(allowed_variables=VARIABLES) # Setting to a non-allowed type should not work. with self.assertRaises(ValueError) as ve: @@ -68,88 +60,14 @@ class TestGlobalNamespace(unittest.TestCase): self.assertTrue(e[3]) self.assertEqual(e[4], list) - def test_allow_all_writes(self): - ns = GlobalNamespace(allowed_variables=VARIABLES) - - with ns.allow_all_writes() as d: - d['foo'] = True - self.assertTrue(d['foo']) - - with self.assertRaises(KeyError) as ke: - ns['bar'] = False - - self.assertEqual(ke.exception.args[1], 'set_unknown') - - ns['DIRS'] = [] - with self.assertRaises(KeyError) as ke: - ns['DIRS'] = [] - - e = ke.exception.args - self.assertEqual(e[0], 'global_ns') - self.assertEqual(e[1], 'reassign') - self.assertEqual(e[2], 'DIRS') - - with ns.allow_all_writes() as d: - d['DIST_SUBDIR'] = 'foo' - - self.assertEqual(ns['DIST_SUBDIR'], 'foo') - ns['DIST_SUBDIR'] = 'bar' - self.assertEqual(ns['DIST_SUBDIR'], 'bar') - with self.assertRaises(KeyError) as ke: - ns['DIST_SUBDIR'] = 'baz' - - e = ke.exception.args - self.assertEqual(e[0], 'global_ns') - self.assertEqual(e[1], 'reassign') - self.assertEqual(e[2], 'DIST_SUBDIR') - - self.assertTrue(d['foo']) - def test_key_checking(self): # Checking for existence of a key should not populate the key if it # doesn't exist. - g = GlobalNamespace(allowed_variables=VARIABLES) + g = Context(allowed_variables=VARIABLES) self.assertFalse('DIRS' in g) self.assertFalse('DIRS' in g) -class TestLocalNamespace(unittest.TestCase): - def test_locals(self): - g = GlobalNamespace(allowed_variables=VARIABLES) - l = LocalNamespace(g) - - l['foo'] = ['foo'] - self.assertEqual(l['foo'], ['foo']) - - l['foo'] += ['bar'] - self.assertEqual(l['foo'], ['foo', 'bar']) - - def test_global_proxy_reads(self): - g = GlobalNamespace(allowed_variables=VARIABLES) - g['DIRS'] = ['foo'] - - l = LocalNamespace(g) - - self.assertEqual(l['DIRS'], g['DIRS']) - - # Reads to missing UPPERCASE vars should result in KeyError. - with self.assertRaises(KeyError) as ke: - v = l['FOO'] - - e = ke.exception - self.assertEqual(e.args[0], 'global_ns') - self.assertEqual(e.args[1], 'get_unknown') - - def test_global_proxy_writes(self): - g = GlobalNamespace(allowed_variables=VARIABLES) - l = LocalNamespace(g) - - l['DIRS'] = ['foo'] - - self.assertEqual(l['DIRS'], ['foo']) - self.assertEqual(g['DIRS'], ['foo']) - - if __name__ == '__main__': main() diff --git a/python/mozbuild/mozbuild/test/frontend/test_reader.py b/python/mozbuild/mozbuild/test/frontend/test_reader.py index 12feb2b985c..824f9b000c5 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_reader.py +++ b/python/mozbuild/mozbuild/test/frontend/test_reader.py @@ -47,26 +47,26 @@ class TestBuildReader(unittest.TestCase): def test_dirs_traversal_simple(self): reader = self.reader('traversal-simple') - sandboxes = list(reader.read_topsrcdir()) + contexts = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 4) + self.assertEqual(len(contexts), 4) def test_dirs_traversal_no_descend(self): reader = self.reader('traversal-simple') - path = mozpath.join(reader.topsrcdir, 'moz.build') + path = mozpath.join(reader.config.topsrcdir, 'moz.build') self.assertTrue(os.path.exists(path)) - sandboxes = list(reader.read_mozbuild(path, reader.config, + contexts = list(reader.read_mozbuild(path, reader.config, filesystem_absolute=True, descend=False)) - self.assertEqual(len(sandboxes), 1) + self.assertEqual(len(contexts), 1) def test_dirs_traversal_all_variables(self): reader = self.reader('traversal-all-vars', enable_tests=True) - sandboxes = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 3) + contexts = list(reader.read_topsrcdir()) + self.assertEqual(len(contexts), 3) def test_tier_subdir(self): # add_tier_dir() should fail when not in the top directory. @@ -79,15 +79,15 @@ class TestBuildReader(unittest.TestCase): # Ensure relative directories are traversed. reader = self.reader('traversal-relative-dirs') - sandboxes = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 3) + contexts = list(reader.read_topsrcdir()) + self.assertEqual(len(contexts), 3) def test_repeated_dirs_ignored(self): # Ensure repeated directories are ignored. reader = self.reader('traversal-repeated-dirs') - sandboxes = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 3) + contexts = list(reader.read_topsrcdir()) + self.assertEqual(len(contexts), 3) def test_outside_topsrcdir(self): # References to directories outside the topsrcdir should fail. @@ -236,17 +236,17 @@ class TestBuildReader(unittest.TestCase): def test_inheriting_variables(self): reader = self.reader('inheriting-variables') - sandboxes = list(reader.read_topsrcdir()) + contexts = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 4) - self.assertEqual([sandbox['RELATIVEDIR'] for sandbox in sandboxes], + self.assertEqual(len(contexts), 4) + self.assertEqual([context.relsrcdir for context in contexts], ['', 'foo', 'foo/baz', 'bar']) - self.assertEqual([sandbox['XPIDL_MODULE'] for sandbox in sandboxes], - ['foobar', 'foobar', 'foobar', 'foobar']) + self.assertEqual([context['XPIDL_MODULE'] for context in contexts], + ['foobar', 'foobar', 'baz', 'foobar']) def test_process_eval_callback(self): - def strip_dirs(sandbox): - sandbox['DIRS'][:] = [] + def strip_dirs(context): + context['DIRS'][:] = [] count[0] += 1 reader = self.reader('traversal-simple', @@ -254,9 +254,9 @@ class TestBuildReader(unittest.TestCase): count = [0] - sandboxes = list(reader.read_topsrcdir()) + contexts = list(reader.read_topsrcdir()) - self.assertEqual(len(sandboxes), 1) + self.assertEqual(len(contexts), 1) self.assertEqual(len(count), 1) diff --git a/python/mozbuild/mozbuild/test/frontend/test_sandbox.py b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py index e1db024ac1b..98c0a97a13f 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_sandbox.py +++ b/python/mozbuild/mozbuild/test/frontend/test_sandbox.py @@ -16,11 +16,13 @@ from mozbuild.frontend.reader import ( ) from mozbuild.frontend.sandbox import ( + Sandbox, SandboxExecutionError, SandboxLoadError, ) -from mozbuild.frontend.sandbox_symbols import ( +from mozbuild.frontend.context import ( + Context, FUNCTIONS, SPECIAL_VARIABLES, VARIABLES, @@ -35,110 +37,34 @@ test_data_path = mozpath.join(test_data_path, 'data') class TestSandbox(unittest.TestCase): - def sandbox(self, relpath='moz.build', data_path=None): - config = None - - if data_path is not None: - config = MockConfig(mozpath.join(test_data_path, data_path)) - else: - config = MockConfig() - - return MozbuildSandbox(config, config.child_path(relpath)) - - def test_default_state(self): - sandbox = self.sandbox() - config = sandbox.config - - self.assertEqual(sandbox['TOPSRCDIR'], config.topsrcdir) - self.assertEqual(sandbox['TOPOBJDIR'], - mozpath.abspath(config.topobjdir)) - self.assertEqual(sandbox['RELATIVEDIR'], '') - self.assertEqual(sandbox['SRCDIR'], config.topsrcdir) - self.assertEqual(sandbox['OBJDIR'], - mozpath.abspath(config.topobjdir).replace(os.sep, '/')) - - def test_symbol_presence(self): - # Ensure no discrepancies between the master symbol table and what's in - # the sandbox. - sandbox = self.sandbox() - - all_symbols = set() - all_symbols |= set(FUNCTIONS.keys()) - all_symbols |= set(SPECIAL_VARIABLES.keys()) - - for symbol in sandbox: - self.assertIn(symbol, all_symbols) - all_symbols.remove(symbol) - - self.assertEqual(len(all_symbols), 0) - - def test_path_calculation(self): - sandbox = self.sandbox('foo/bar/moz.build') - config = sandbox.config - - self.assertEqual(sandbox['RELATIVEDIR'], 'foo/bar') - self.assertEqual(sandbox['SRCDIR'], '/'.join([config.topsrcdir, - 'foo/bar'])) - self.assertEqual(sandbox['OBJDIR'], - mozpath.abspath('/'.join([config.topobjdir, 'foo/bar'])).replace(os.sep, '/')) - - def test_config_access(self): - sandbox = self.sandbox() - config = sandbox.config - - self.assertIn('CONFIG', sandbox) - self.assertEqual(sandbox['CONFIG']['MOZ_TRUE'], '1') - self.assertEqual(sandbox['CONFIG']['MOZ_FOO'], config.substs['MOZ_FOO']) - - # Access to an undefined substitution should return None. - self.assertNotIn('MISSING', sandbox['CONFIG']) - self.assertIsNone(sandbox['CONFIG']['MISSING']) - - # Should shouldn't be allowed to assign to the config. - with self.assertRaises(Exception): - sandbox['CONFIG']['FOO'] = '' - - def test_dict_interface(self): - sandbox = self.sandbox() - config = sandbox.config - - self.assertFalse('foo' in sandbox) - self.assertFalse('FOO' in sandbox) - - self.assertTrue(sandbox.get('foo', True)) - self.assertEqual(sandbox.get('TOPSRCDIR'), config.topsrcdir) - self.assertGreater(len(sandbox), 6) - - for key in sandbox: - continue - - for key in sandbox.iterkeys(): - continue + def sandbox(self): + return Sandbox(Context(VARIABLES)) def test_exec_source_success(self): sandbox = self.sandbox() + context = sandbox._context - sandbox.exec_source('foo = True', 'foo.py') + sandbox.exec_source('foo = True', mozpath.abspath('foo.py')) - self.assertNotIn('foo', sandbox) - self.assertEqual(sandbox.main_path, 'foo.py') - self.assertEqual(sandbox.all_paths, set(['foo.py'])) + self.assertNotIn('foo', context) + self.assertEqual(context.main_path, mozpath.abspath('foo.py')) + self.assertEqual(context.all_paths, set([mozpath.abspath('foo.py')])) def test_exec_compile_error(self): sandbox = self.sandbox() with self.assertRaises(SandboxExecutionError) as se: - sandbox.exec_source('2f23;k;asfj', 'foo.py') + sandbox.exec_source('2f23;k;asfj', mozpath.abspath('foo.py')) - self.assertEqual(se.exception.file_stack, ['foo.py']) + self.assertEqual(se.exception.file_stack, [mozpath.abspath('foo.py')]) self.assertIsInstance(se.exception.exc_value, SyntaxError) - self.assertEqual(sandbox.main_path, 'foo.py') + self.assertEqual(sandbox._context.main_path, mozpath.abspath('foo.py')) def test_exec_import_denied(self): sandbox = self.sandbox() with self.assertRaises(SandboxExecutionError) as se: - sandbox.exec_source('import sys', 'import.py') + sandbox.exec_source('import sys') self.assertIsInstance(se.exception, SandboxExecutionError) self.assertEqual(se.exception.exc_type, ImportError) @@ -146,8 +72,8 @@ class TestSandbox(unittest.TestCase): def test_exec_source_multiple(self): sandbox = self.sandbox() - sandbox.exec_source('DIRS = ["foo"]', 'foo.py') - sandbox.exec_source('DIRS += ["bar"]', 'foo.py') + sandbox.exec_source('DIRS = ["foo"]') + sandbox.exec_source('DIRS += ["bar"]') self.assertEqual(sandbox['DIRS'], ['foo', 'bar']) @@ -155,7 +81,7 @@ class TestSandbox(unittest.TestCase): sandbox = self.sandbox() with self.assertRaises(SandboxExecutionError) as se: - sandbox.exec_source('ILLEGAL = True', 'foo.py') + sandbox.exec_source('ILLEGAL = True') e = se.exception self.assertIsInstance(e.exc_value, KeyError) @@ -167,9 +93,9 @@ class TestSandbox(unittest.TestCase): def test_exec_source_reassign(self): sandbox = self.sandbox() - sandbox.exec_source('DIRS = ["foo"]', 'foo.py') + sandbox.exec_source('DIRS = ["foo"]') with self.assertRaises(SandboxExecutionError) as se: - sandbox.exec_source('DIRS = ["bar"]', 'foo.py') + sandbox.exec_source('DIRS = ["bar"]') self.assertEqual(sandbox['DIRS'], ['foo']) e = se.exception @@ -180,10 +106,116 @@ class TestSandbox(unittest.TestCase): self.assertEqual(e.args[1], 'reassign') self.assertEqual(e.args[2], 'DIRS') + def test_exec_source_reassign_builtin(self): + sandbox = self.sandbox() + + with self.assertRaises(SandboxExecutionError) as se: + sandbox.exec_source('True = 1') + + e = se.exception + self.assertIsInstance(e.exc_value, KeyError) + + e = se.exception.exc_value + self.assertEqual(e.args[0], 'Cannot reassign builtins') + + +class TestMozbuildSandbox(unittest.TestCase): + def sandbox(self, data_path=None): + config = None + + if data_path is not None: + config = MockConfig(mozpath.join(test_data_path, data_path)) + else: + config = MockConfig() + + return MozbuildSandbox(Context(VARIABLES, config)) + + def test_default_state(self): + sandbox = self.sandbox() + sandbox._context.add_source(sandbox.normalize_path('moz.build')) + config = sandbox._context.config + + self.assertEqual(sandbox['TOPSRCDIR'], config.topsrcdir) + self.assertEqual(sandbox['TOPOBJDIR'], config.topobjdir) + self.assertEqual(sandbox['RELATIVEDIR'], '') + self.assertEqual(sandbox['SRCDIR'], config.topsrcdir) + self.assertEqual(sandbox['OBJDIR'], config.topobjdir) + + def test_symbol_presence(self): + # Ensure no discrepancies between the master symbol table and what's in + # the sandbox. + sandbox = self.sandbox() + sandbox._context.add_source(sandbox.normalize_path('moz.build')) + + all_symbols = set() + all_symbols |= set(FUNCTIONS.keys()) + all_symbols |= set(SPECIAL_VARIABLES.keys()) + + for symbol in all_symbols: + self.assertIsNotNone(sandbox[symbol]) + + def test_path_calculation(self): + sandbox = self.sandbox() + sandbox._context.add_source(sandbox.normalize_path('foo/bar/moz.build')) + config = sandbox._context.config + + self.assertEqual(sandbox['TOPSRCDIR'], config.topsrcdir) + self.assertEqual(sandbox['TOPOBJDIR'], config.topobjdir) + self.assertEqual(sandbox['RELATIVEDIR'], 'foo/bar') + self.assertEqual(sandbox['SRCDIR'], + mozpath.join(config.topsrcdir, 'foo/bar')) + self.assertEqual(sandbox['OBJDIR'], + mozpath.join(config.topobjdir, 'foo/bar')) + + def test_config_access(self): + sandbox = self.sandbox() + config = sandbox._context.config + + self.assertEqual(sandbox['CONFIG']['MOZ_TRUE'], '1') + self.assertEqual(sandbox['CONFIG']['MOZ_FOO'], config.substs['MOZ_FOO']) + + # Access to an undefined substitution should return None. + self.assertNotIn('MISSING', sandbox['CONFIG']) + self.assertIsNone(sandbox['CONFIG']['MISSING']) + + # Should shouldn't be allowed to assign to the config. + with self.assertRaises(Exception): + sandbox['CONFIG']['FOO'] = '' + + def test_special_variables(self): + sandbox = self.sandbox() + + for k in SPECIAL_VARIABLES: + with self.assertRaises(KeyError): + sandbox[k] = 0 + + def test_exec_source_reassign_exported(self): + config = MockConfig() + + exports = {'DIST_SUBDIR': 'browser'} + + sandbox = MozbuildSandbox(Context(VARIABLES, config), + metadata={'exports': exports}) + + self.assertEqual(sandbox['DIST_SUBDIR'], 'browser') + + sandbox.exec_source('DIST_SUBDIR = "foo"') + with self.assertRaises(SandboxExecutionError) as se: + sandbox.exec_source('DIST_SUBDIR = "bar"') + + self.assertEqual(sandbox['DIST_SUBDIR'], 'foo') + e = se.exception + self.assertIsInstance(e.exc_value, KeyError) + + e = se.exception.exc_value + self.assertEqual(e.args[0], 'global_ns') + self.assertEqual(e.args[1], 'reassign') + self.assertEqual(e.args[2], 'DIST_SUBDIR') + def test_add_tier_dir_regular_str(self): sandbox = self.sandbox() - sandbox.exec_source('add_tier_dir("t1", "foo")', 'foo.py') + sandbox.exec_source('add_tier_dir("t1", "foo")') self.assertEqual(sandbox['TIERS']['t1'], {'regular': ['foo'], 'external': []}) @@ -191,7 +223,7 @@ class TestSandbox(unittest.TestCase): def test_add_tier_dir_regular_list(self): sandbox = self.sandbox() - sandbox.exec_source('add_tier_dir("t1", ["foo", "bar"])', 'foo.py') + sandbox.exec_source('add_tier_dir("t1", ["foo", "bar"])') self.assertEqual(sandbox['TIERS']['t1'], {'regular': ['foo', 'bar'], 'external': []}) @@ -199,7 +231,7 @@ class TestSandbox(unittest.TestCase): def test_add_tier_dir_external(self): sandbox = self.sandbox() - sandbox.exec_source('add_tier_dir("t1", "foo", external=True)', 'foo.py') + sandbox.exec_source('add_tier_dir("t1", "foo", external=True)') self.assertEqual(sandbox['TIERS']['t1'], {'regular': [], 'external': ['foo']}) @@ -215,17 +247,17 @@ add_tier_dir('t3', 'biz') add_tier_dir('t1', 'bat') ''' - sandbox.exec_source(source, 'foo.py') + sandbox.exec_source(source) self.assertEqual([k for k in sandbox['TIERS'].keys()], ['t1', 't2', 't3']) def test_tier_multiple_registration(self): sandbox = self.sandbox() - sandbox.exec_source('add_tier_dir("t1", "foo")', 'foo.py') + sandbox.exec_source('add_tier_dir("t1", "foo")') with self.assertRaises(SandboxExecutionError): - sandbox.exec_source('add_tier_dir("t1", "foo")', 'foo.py') + sandbox.exec_source('add_tier_dir("t1", "foo")') def test_include_basic(self): sandbox = self.sandbox(data_path='include-basic') @@ -233,9 +265,9 @@ add_tier_dir('t1', 'bat') sandbox.exec_file('moz.build') self.assertEqual(sandbox['DIRS'], ['foo', 'bar']) - self.assertEqual(sandbox.main_path, - mozpath.join(sandbox['TOPSRCDIR'], 'moz.build')) - self.assertEqual(len(sandbox.all_paths), 2) + self.assertEqual(sandbox._context.main_path, + sandbox.normalize_path('moz.build')) + self.assertEqual(len(sandbox._context.all_paths), 2) def test_include_outside_topsrcdir(self): sandbox = self.sandbox(data_path='include-outside-topsrcdir') @@ -260,7 +292,7 @@ add_tier_dir('t1', 'bat') self.assertEqual(args[1], 'set_unknown') self.assertEqual(args[2], 'ILLEGAL') - expected_stack = [mozpath.join(sandbox.config.topsrcdir, p) for p in [ + expected_stack = [mozpath.join(sandbox._context.config.topsrcdir, p) for p in [ 'moz.build', 'included-1.build', 'included-2.build']] self.assertEqual(e.file_stack, expected_stack) @@ -296,7 +328,7 @@ add_tier_dir('t1', 'bat') sandbox = self.sandbox() with self.assertRaises(SandboxCalledError) as sce: - sandbox.exec_source('error("This is an error.")', 'test.py') + sandbox.exec_source('error("This is an error.")') e = sce.exception self.assertEqual(e.message, 'This is an error.') @@ -304,8 +336,7 @@ add_tier_dir('t1', 'bat') def test_substitute_config_files(self): sandbox = self.sandbox() - sandbox.exec_source('CONFIGURE_SUBST_FILES += ["bar", "foo"]', - 'test.py') + sandbox.exec_source('CONFIGURE_SUBST_FILES += ["bar", "foo"]') self.assertEqual(sandbox['CONFIGURE_SUBST_FILES'], ['bar', 'foo']) def test_invalid_utf8_substs(self): @@ -314,7 +345,7 @@ add_tier_dir('t1', 'bat') # This is really mbcs. It's a bunch of invalid UTF-8. config = MockConfig(extra_substs={'BAD_UTF8': b'\x83\x81\x83\x82\x3A'}) - sandbox = MozbuildSandbox(config, '/foo/moz.build') + sandbox = MozbuildSandbox(Context(VARIABLES, config)) self.assertEqual(sandbox['CONFIG']['BAD_UTF8'], u'\ufffd\ufffd\ufffd\ufffd:') @@ -323,7 +354,7 @@ add_tier_dir('t1', 'bat') sandbox = self.sandbox() with self.assertRaises(SandboxExecutionError) as se: - sandbox.exec_source('EXPORTS = "foo.h"', 'foo.py') + sandbox.exec_source('EXPORTS = "foo.h"') self.assertEqual(se.exception.exc_type, ValueError) diff --git a/python/mozbuild/mozbuild/test/frontend/test_sandbox_symbols.py b/python/mozbuild/mozbuild/test/frontend/test_sandbox_symbols.py deleted file mode 100644 index b324f0d2560..00000000000 --- a/python/mozbuild/mozbuild/test/frontend/test_sandbox_symbols.py +++ /dev/null @@ -1,50 +0,0 @@ -# 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/. - -import unittest - -from mozunit import main - -from mozbuild.frontend.sandbox_symbols import ( - FUNCTIONS, - SPECIAL_VARIABLES, - VARIABLES, -) - - -class TestSymbols(unittest.TestCase): - def _verify_doc(self, doc): - # Documentation should be of the format: - # """SUMMARY LINE - # - # EXTRA PARAGRAPHS - # """ - - self.assertNotIn('\r', doc) - - lines = doc.split('\n') - - # No trailing whitespace. - for line in lines[0:-1]: - self.assertEqual(line, line.rstrip()) - - self.assertGreater(len(lines), 0) - self.assertGreater(len(lines[0].strip()), 0) - - # Last line should be empty. - self.assertEqual(lines[-1].strip(), '') - - def test_documentation_formatting(self): - for typ, inp, doc, tier in VARIABLES.values(): - self._verify_doc(doc) - - for attr, args, doc in FUNCTIONS.values(): - self._verify_doc(doc) - - for typ, doc in SPECIAL_VARIABLES.values(): - self._verify_doc(doc) - - -if __name__ == '__main__': - main() diff --git a/python/mozbuild/mozbuild/test/test_containers.py b/python/mozbuild/mozbuild/test/test_containers.py index f1c77404400..9eb7df1ded7 100644 --- a/python/mozbuild/mozbuild/test/test_containers.py +++ b/python/mozbuild/mozbuild/test/test_containers.py @@ -7,11 +7,17 @@ import unittest from mozunit import main from mozbuild.util import ( + KeyedDefaultDict, List, + OrderedDefaultDict, ReadOnlyDefaultDict, ReadOnlyDict, + ReadOnlyKeyedDefaultDict, ) +from collections import OrderedDict + + class TestReadOnlyDict(unittest.TestCase): def test_basic(self): original = {'foo': 1, 'bar': 2} @@ -108,5 +114,83 @@ class TestList(unittest.TestCase): with self.assertRaises(ValueError): test = test + False +class TestOrderedDefaultDict(unittest.TestCase): + def test_simple(self): + original = OrderedDict(foo=1, bar=2) + + test = OrderedDefaultDict(bool, original) + + self.assertEqual(original, test) + + self.assertEqual(test['foo'], 1) + + self.assertEqual(test.keys(), ['foo', 'bar' ]) + + def test_defaults(self): + test = OrderedDefaultDict(bool, {'foo': 1 }) + + self.assertEqual(test['foo'], 1) + + self.assertEqual(test['qux'], False) + + self.assertEqual(test.keys(), ['foo', 'qux' ]) + + +class TestKeyedDefaultDict(unittest.TestCase): + def test_simple(self): + original = {'foo': 1, 'bar': 2 } + + test = KeyedDefaultDict(lambda x: x, original) + + self.assertEqual(original, test) + + self.assertEqual(test['foo'], 1) + + def test_defaults(self): + test = KeyedDefaultDict(lambda x: x, {'foo': 1 }) + + self.assertEqual(test['foo'], 1) + + self.assertEqual(test['qux'], 'qux') + + self.assertEqual(test['bar'], 'bar') + + test['foo'] = 2 + test['qux'] = None + test['baz'] = 'foo' + + self.assertEqual(test['foo'], 2) + + self.assertEqual(test['qux'], None) + + self.assertEqual(test['baz'], 'foo') + + +class TestReadOnlyKeyedDefaultDict(unittest.TestCase): + def test_defaults(self): + test = ReadOnlyKeyedDefaultDict(lambda x: x, {'foo': 1 }) + + self.assertEqual(test['foo'], 1) + + self.assertEqual(test['qux'], 'qux') + + self.assertEqual(test['bar'], 'bar') + + copy = dict(test) + + with self.assertRaises(Exception): + test['foo'] = 2 + + with self.assertRaises(Exception): + test['qux'] = None + + with self.assertRaises(Exception): + test['baz'] = 'foo' + + self.assertEqual(test, copy) + + self.assertEqual(len(test), 3) + + if __name__ == '__main__': main() diff --git a/python/mozbuild/mozbuild/test/test_util.py b/python/mozbuild/mozbuild/test/test_util.py index b9972a219fe..ecd5f103556 100644 --- a/python/mozbuild/mozbuild/test/test_util.py +++ b/python/mozbuild/mozbuild/test/test_util.py @@ -21,6 +21,7 @@ from mozbuild.util import ( FileAvoidWrite, hash_file, memoize, + memoized_property, resolve_target_to_make, MozbuildDeletionError, HierarchicalStringList, @@ -445,6 +446,7 @@ class TestMemoize(unittest.TestCase): self._count += 1 return a + b + self.assertEqual(self._count, 0) self.assertEqual(wrapped(1, 1), 2) self.assertEqual(self._count, 1) self.assertEqual(wrapped(1, 1), 2) @@ -458,6 +460,53 @@ class TestMemoize(unittest.TestCase): self.assertEqual(wrapped(1, 1), 2) self.assertEqual(self._count, 3) + def test_memoize_method(self): + class foo(object): + def __init__(self): + self._count = 0 + + @memoize + def wrapped(self, a, b): + self._count += 1 + return a + b + + instance = foo() + refcount = sys.getrefcount(instance) + self.assertEqual(instance._count, 0) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped(2, 1), 3) + self.assertEqual(instance._count, 2) + self.assertEqual(instance.wrapped(1, 2), 3) + self.assertEqual(instance._count, 3) + self.assertEqual(instance.wrapped(1, 2), 3) + self.assertEqual(instance._count, 3) + self.assertEqual(instance.wrapped(1, 1), 2) + self.assertEqual(instance._count, 3) + + # Memoization of methods is expected to not keep references to + # instances, so the refcount shouldn't have changed after executing the + # memoized method. + self.assertEqual(refcount, sys.getrefcount(instance)) + + def test_memoized_property(self): + class foo(object): + def __init__(self): + self._count = 0 + + @memoized_property + def wrapped(self): + self._count += 1 + return 42 + + instance = foo() + self.assertEqual(instance._count, 0) + self.assertEqual(instance.wrapped, 42) + self.assertEqual(instance._count, 1) + self.assertEqual(instance.wrapped, 42) + self.assertEqual(instance._count, 1) if __name__ == '__main__': main() diff --git a/python/mozbuild/mozbuild/util.py b/python/mozbuild/mozbuild/util.py index 30c57e6803a..e2b0bd478ac 100644 --- a/python/mozbuild/mozbuild/util.py +++ b/python/mozbuild/mozbuild/util.py @@ -10,6 +10,7 @@ from __future__ import unicode_literals import copy import difflib import errno +import functools import hashlib import os import stat @@ -20,7 +21,6 @@ from collections import ( defaultdict, OrderedDict, ) -from functools import wraps from StringIO import StringIO @@ -76,13 +76,10 @@ class ReadOnlyDefaultDict(ReadOnlyDict): ReadOnlyDict.__init__(self, *args, **kwargs) self._default_factory = default_factory - def __getitem__(self, key): - try: - return ReadOnlyDict.__getitem__(self, key) - except KeyError: - value = self._default_factory() - dict.__setitem__(self, key, value) - return value + def __missing__(self, key): + value = self._default_factory() + dict.__setitem__(self, key, value) + return value def ensureParentDir(path): @@ -715,20 +712,66 @@ class OrderedDefaultDict(OrderedDict): OrderedDict.__init__(self, *args, **kwargs) self._default_factory = default_factory - def __getitem__(self, key): - try: - return OrderedDict.__getitem__(self, key) - except KeyError: - value = self[key] = self._default_factory() - return value + def __missing__(self, key): + value = self[key] = self._default_factory() + return value -def memoize(func): - cache = {} +class KeyedDefaultDict(dict): + '''Like a defaultdict, but the default_factory function takes the key as + argument''' + def __init__(self, default_factory, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + self._default_factory = default_factory - @wraps(func) - def wrapper(*args): + def __missing__(self, key): + value = self._default_factory(key) + dict.__setitem__(self, key, value) + return value + + +class ReadOnlyKeyedDefaultDict(KeyedDefaultDict, ReadOnlyDict): + '''Like KeyedDefaultDict, but read-only.''' + + +class memoize(dict): + '''A decorator to memoize the results of function calls depending + on its arguments. + Both functions and instance methods are handled, although in the + instance method case, the results are cache in the instance itself. + ''' + def __init__(self, func): + self.func = func + functools.update_wrapper(self, func) + + def __call__(self, *args): + if args not in self: + self[args] = self.func(*args) + return self[args] + + def method_call(self, instance, *args): + name = '_%s' % self.func.__name__ + if not hasattr(instance, name): + setattr(instance, name, {}) + cache = getattr(instance, name) if args not in cache: - cache[args] = func(*args) + cache[args] = self.func(instance, *args) return cache[args] - return wrapper + + def __get__(self, instance, cls): + return functools.update_wrapper( + functools.partial(self.method_call, instance), self.func) + + +class memoized_property(object): + '''A specialized version of the memoize decorator that works for + class instance properties. + ''' + def __init__(self, func): + self.func = func + + def __get__(self, instance, cls): + name = '_%s' % self.func.__name__ + if not hasattr(instance, name): + setattr(instance, name, self.func(instance)) + return getattr(instance, name) diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp index 5e312dd942b..3681de016be 100644 --- a/toolkit/mozapps/extensions/AddonPathService.cpp +++ b/toolkit/mozapps/extensions/AddonPathService.cpp @@ -9,7 +9,6 @@ #include "nsIURI.h" #include "nsXULAppAPI.h" #include "jsapi.h" -#include "nsCxPusher.h" #include "nsServiceManagerUtils.h" #include "nsLiteralString.h" #include "nsThreadUtils.h" @@ -19,6 +18,7 @@ #include "nsIChromeRegistry.h" #include "nsIJARURI.h" #include "nsJSUtils.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/AddonPathService.h" #include "mozilla/Omnijar.h" diff --git a/tools/docs/mach_commands.py b/tools/docs/mach_commands.py index 7948f2490de..06060845040 100644 --- a/tools/docs/mach_commands.py +++ b/tools/docs/mach_commands.py @@ -40,19 +40,19 @@ class Documentation(MachCommandBase): # We don't care about GYP projects, so don't process them. This makes # scanning faster and may even prevent an exception. - def remove_gyp_dirs(sandbox): - sandbox['GYP_DIRS'][:] = [] + def remove_gyp_dirs(context): + context['GYP_DIRS'][:] = [] reader = BuildReader(self.config_environment, sandbox_post_eval_cb=remove_gyp_dirs) - for sandbox in reader.walk_topsrcdir(): - for dest_dir, source_dir in sandbox['SPHINX_TREES'].items(): - manager.add_tree(os.path.join(sandbox['RELATIVEDIR'], + for context in reader.walk_topsrcdir(): + for dest_dir, source_dir in context['SPHINX_TREES'].items(): + manager.add_tree(os.path.join(context.relsrcdir, source_dir), dest_dir) - for entry in sandbox['SPHINX_PYTHON_PACKAGE_DIRS']: - manager.add_python_package_dir(os.path.join(sandbox['RELATIVEDIR'], + for entry in context['SPHINX_PYTHON_PACKAGE_DIRS']: + manager.add_python_package_dir(os.path.join(context.relsrcdir, entry)) return manager.generate_docs(format) diff --git a/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp b/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp index c091eddf6e5..2515a0b553a 100644 --- a/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp +++ b/xpcom/reflect/xptinfo/xptiInterfaceInfo.cpp @@ -6,10 +6,10 @@ /* Implementation of xptiInterfaceEntry and xptiInterfaceInfo. */ #include "xptiprivate.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/DebugOnly.h" #include "mozilla/XPTInterfaceInfoManager.h" #include "mozilla/PodOperations.h" -#include "nsCxPusher.h" #include "jsapi.h" using namespace mozilla;