From de4ccf09a5f9cb0eaa9573e404b51f8fad2ffdc3 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Thu, 17 Jan 2019 12:45:19 -0500 Subject: [PATCH 01/19] Bug 1494062 - Part 1. Add new crashtest reproducing the blob image wraplist clipping crash. r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D16879 --- .../1494062-blob-image-wraplist-clip.html | 27 +++++++++++++++++++ gfx/tests/crashtests/crashtests.list | 1 + 2 files changed, 28 insertions(+) create mode 100644 gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html diff --git a/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html b/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html new file mode 100644 index 000000000000..670cb2551e7f --- /dev/null +++ b/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/gfx/tests/crashtests/crashtests.list b/gfx/tests/crashtests/crashtests.list index 3c3e6c5151c6..7025b82fd4d6 100644 --- a/gfx/tests/crashtests/crashtests.list +++ b/gfx/tests/crashtests/crashtests.list @@ -181,4 +181,5 @@ load 1513133.html load 1496194.html load 1505934-1.html load 1509123.html +load 1494062.html load texture-allocator-zero-region.html From 3be4c71d8e2fe82dc0416b2a9bc5145f9dba8a62 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Thu, 17 Jan 2019 07:56:12 -0500 Subject: [PATCH 02/19] Bug 1494062 - Part 2. Ensure blob image dirty rects are not clipped too small by containers. r=jrmuizel If an item in a blob image gets a new parent container which clips it to a smaller area than it was previously, we need to ensure the dirty rect does not take into account the new clip when determining the old intersection area. Instead now all old rects for an image are clipped to the image bounds, rather than the image bounds clipped to the aggregate of the ancestor containers. Differential Revision: https://phabricator.services.mozilla.com/D16855 --- gfx/layers/wr/WebRenderCommandBuilder.cpp | 48 +++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp index fd8c26f02adb..bf96f669afe1 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -324,6 +324,9 @@ struct DIGroup { // The current bounds of the blob image, relative to // the top-left of the mLayerBounds. IntRect mImageBounds; + // mImageBounds clipped to the container/parent of the + // current item being processed. + IntRect mClippedImageBounds; Maybe mKey; std::vector> mExternalSurfaces; std::vector> mFonts; @@ -391,8 +394,9 @@ struct DIGroup { LayoutDeviceIntPoint offset = RoundedToInt(bounds.TopLeft()); GP("\n"); GP("CGC offset %d %d\n", offset.x, offset.y); - GP("imageRect %d %d %d %d\n", mImageBounds.x, mImageBounds.y, - mImageBounds.width, mImageBounds.height); + GP("clippedImageRect %d %d %d %d\n", mClippedImageBounds.x, + mClippedImageBounds.y, mClippedImageBounds.width, + mClippedImageBounds.height); /*if (aItem->IsReused() && aData->mGeometry) { return; }*/ @@ -413,7 +417,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x, clippedBounds.y, clippedBounds.width, clippedBounds.height); GP("%d %d, %f %f\n", mLayerBounds.TopLeft().x, mLayerBounds.TopLeft().y, @@ -444,7 +448,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y, aData->mRect.width, aData->mRect.height); @@ -473,7 +477,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); // CGC invariant broken @@ -505,7 +509,7 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, @@ -535,7 +539,7 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("TransformChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, @@ -554,11 +558,11 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("UpdateContainerLayerPropertiesAndDetectChange change\n"); - } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) { - // Make sure we update mRect for mImageBounds changes + } else if (!aData->mImageRect.IsEqualEdges(mClippedImageBounds)) { + // Make sure we update mRect for mClippedImageBounds changes nsRect clippedBounds = clip.ApplyNonRoundedIntersection( geometry->ComputeInvalidationRegion()); IntRect transformedRect = @@ -567,7 +571,7 @@ struct DIGroup { // The invalid rect should contain the old rect and the new rect // but may not because the parent may have been removed. InvalidateRect(aData->mRect); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("ContainerLayer image rect bounds change\n"); } else { @@ -577,14 +581,14 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - auto rect = transformedRect.Intersect(mImageBounds); + auto rect = transformedRect.Intersect(mClippedImageBounds); GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect)); } - } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) { - // Make sure we update mRect for mImageBounds changes + } else if (!aData->mImageRect.IsEqualEdges(mClippedImageBounds)) { + // Make sure we update mRect for mClippedImageBounds changes UniquePtr geometry( aItem->AllocateGeometry(aBuilder)); nsRect clippedBounds = clip.ApplyNonRoundedIntersection( @@ -595,7 +599,7 @@ struct DIGroup { // The invalid rect should contain the old rect and the new rect // but may not because the parent may have been removed. InvalidateRect(aData->mRect); - aData->mRect = transformedRect.Intersect(mImageBounds); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); InvalidateRect(aData->mRect); GP("image rect bounds change\n"); } else { @@ -607,7 +611,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - auto rect = transformedRect.Intersect(mImageBounds); + auto rect = transformedRect.Intersect(mClippedImageBounds); GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect)); @@ -617,7 +621,7 @@ struct DIGroup { aData->mClip = clip; aData->mMatrix = aMatrix; aData->mGroupOffset = mLayerBounds.TopLeft(); - aData->mImageRect = mImageBounds; + aData->mImageRect = mClippedImageBounds; GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height); } @@ -1164,6 +1168,8 @@ void Grouper::ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder, currentGroup->mAppUnitsPerDevPixel; groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds; groupData->mFollowingGroup.mImageBounds = currentGroup->mImageBounds; + groupData->mFollowingGroup.mClippedImageBounds = + currentGroup->mClippedImageBounds; groupData->mFollowingGroup.mScale = currentGroup->mScale; groupData->mFollowingGroup.mResidualOffset = currentGroup->mResidualOffset; @@ -1221,8 +1227,9 @@ void Grouper::ConstructItemInsideInactive( // Temporarily restrict the image bounds to the bounds of the container so // that clipped children within the container know about the clip. - IntRect oldImageBounds = aGroup->mImageBounds; - aGroup->mImageBounds = aGroup->mImageBounds.Intersect(data->mRect); + IntRect oldClippedImageBounds = aGroup->mClippedImageBounds; + aGroup->mClippedImageBounds = + aGroup->mClippedImageBounds.Intersect(data->mRect); if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { gfx::Size scale(1, 1); @@ -1267,7 +1274,7 @@ void Grouper::ConstructItemInsideInactive( } GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count()); - aGroup->mImageBounds = oldImageBounds; + aGroup->mClippedImageBounds = oldClippedImageBounds; } /* This is just a copy of nsRect::ScaleToOutsidePixels with an offset added in. @@ -1375,6 +1382,7 @@ void WebRenderCommandBuilder::DoGroupingForDisplayList( group.mAppUnitsPerDevPixel, residualOffset)); group.mImageBounds = IntRect(0, 0, group.mLayerBounds.width, group.mLayerBounds.height); + group.mClippedImageBounds = group.mImageBounds; group.mPaintRect = LayerIntRect::FromUnknownRect( ScaleToOutsidePixelsOffset(aWrappingItem->GetPaintRect(), scale.width, From 90dd6354034c8b5daa4ff87355a3d036ca6b615a Mon Sep 17 00:00:00 2001 From: Gurzau Raul Date: Thu, 17 Jan 2019 23:07:36 +0200 Subject: [PATCH 03/19] Backed out 2 changesets (bug 1494062) for failing at /tests/gfx/tests/crashtests/1494062.html on a CLOSED TREE Backed out changeset 50f859a24037 (bug 1494062) Backed out changeset 23b890a3fc78 (bug 1494062) --- gfx/layers/wr/WebRenderCommandBuilder.cpp | 48 ++++++++----------- .../1494062-blob-image-wraplist-clip.html | 27 ----------- gfx/tests/crashtests/crashtests.list | 1 - 3 files changed, 20 insertions(+), 56 deletions(-) delete mode 100644 gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp index bf96f669afe1..fd8c26f02adb 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -324,9 +324,6 @@ struct DIGroup { // The current bounds of the blob image, relative to // the top-left of the mLayerBounds. IntRect mImageBounds; - // mImageBounds clipped to the container/parent of the - // current item being processed. - IntRect mClippedImageBounds; Maybe mKey; std::vector> mExternalSurfaces; std::vector> mFonts; @@ -394,9 +391,8 @@ struct DIGroup { LayoutDeviceIntPoint offset = RoundedToInt(bounds.TopLeft()); GP("\n"); GP("CGC offset %d %d\n", offset.x, offset.y); - GP("clippedImageRect %d %d %d %d\n", mClippedImageBounds.x, - mClippedImageBounds.y, mClippedImageBounds.width, - mClippedImageBounds.height); + GP("imageRect %d %d %d %d\n", mImageBounds.x, mImageBounds.y, + mImageBounds.width, mImageBounds.height); /*if (aItem->IsReused() && aData->mGeometry) { return; }*/ @@ -417,7 +413,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x, clippedBounds.y, clippedBounds.width, clippedBounds.height); GP("%d %d, %f %f\n", mLayerBounds.TopLeft().x, mLayerBounds.TopLeft().y, @@ -448,7 +444,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y, aData->mRect.width, aData->mRect.height); @@ -477,7 +473,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); // CGC invariant broken @@ -509,7 +505,7 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, @@ -539,7 +535,7 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("TransformChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, @@ -558,11 +554,11 @@ struct DIGroup { ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); InvalidateRect(aData->mRect.Intersect(mImageBounds)); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("UpdateContainerLayerPropertiesAndDetectChange change\n"); - } else if (!aData->mImageRect.IsEqualEdges(mClippedImageBounds)) { - // Make sure we update mRect for mClippedImageBounds changes + } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) { + // Make sure we update mRect for mImageBounds changes nsRect clippedBounds = clip.ApplyNonRoundedIntersection( geometry->ComputeInvalidationRegion()); IntRect transformedRect = @@ -571,7 +567,7 @@ struct DIGroup { // The invalid rect should contain the old rect and the new rect // but may not because the parent may have been removed. InvalidateRect(aData->mRect); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("ContainerLayer image rect bounds change\n"); } else { @@ -581,14 +577,14 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - auto rect = transformedRect.Intersect(mClippedImageBounds); + auto rect = transformedRect.Intersect(mImageBounds); GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect)); } - } else if (!aData->mImageRect.IsEqualEdges(mClippedImageBounds)) { - // Make sure we update mRect for mClippedImageBounds changes + } else if (!aData->mImageRect.IsEqualEdges(mImageBounds)) { + // Make sure we update mRect for mImageBounds changes UniquePtr geometry( aItem->AllocateGeometry(aBuilder)); nsRect clippedBounds = clip.ApplyNonRoundedIntersection( @@ -599,7 +595,7 @@ struct DIGroup { // The invalid rect should contain the old rect and the new rect // but may not because the parent may have been removed. InvalidateRect(aData->mRect); - aData->mRect = transformedRect.Intersect(mClippedImageBounds); + aData->mRect = transformedRect.Intersect(mImageBounds); InvalidateRect(aData->mRect); GP("image rect bounds change\n"); } else { @@ -611,7 +607,7 @@ struct DIGroup { IntRect transformedRect = ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel, mLayerBounds.TopLeft()); - auto rect = transformedRect.Intersect(mClippedImageBounds); + auto rect = transformedRect.Intersect(mImageBounds); GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); MOZ_RELEASE_ASSERT(rect.IsEqualEdges(aData->mRect)); @@ -621,7 +617,7 @@ struct DIGroup { aData->mClip = clip; aData->mMatrix = aMatrix; aData->mGroupOffset = mLayerBounds.TopLeft(); - aData->mImageRect = mClippedImageBounds; + aData->mImageRect = mImageBounds; GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height); } @@ -1168,8 +1164,6 @@ void Grouper::ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder, currentGroup->mAppUnitsPerDevPixel; groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds; groupData->mFollowingGroup.mImageBounds = currentGroup->mImageBounds; - groupData->mFollowingGroup.mClippedImageBounds = - currentGroup->mClippedImageBounds; groupData->mFollowingGroup.mScale = currentGroup->mScale; groupData->mFollowingGroup.mResidualOffset = currentGroup->mResidualOffset; @@ -1227,9 +1221,8 @@ void Grouper::ConstructItemInsideInactive( // Temporarily restrict the image bounds to the bounds of the container so // that clipped children within the container know about the clip. - IntRect oldClippedImageBounds = aGroup->mClippedImageBounds; - aGroup->mClippedImageBounds = - aGroup->mClippedImageBounds.Intersect(data->mRect); + IntRect oldImageBounds = aGroup->mImageBounds; + aGroup->mImageBounds = aGroup->mImageBounds.Intersect(data->mRect); if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { gfx::Size scale(1, 1); @@ -1274,7 +1267,7 @@ void Grouper::ConstructItemInsideInactive( } GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count()); - aGroup->mClippedImageBounds = oldClippedImageBounds; + aGroup->mImageBounds = oldImageBounds; } /* This is just a copy of nsRect::ScaleToOutsidePixels with an offset added in. @@ -1382,7 +1375,6 @@ void WebRenderCommandBuilder::DoGroupingForDisplayList( group.mAppUnitsPerDevPixel, residualOffset)); group.mImageBounds = IntRect(0, 0, group.mLayerBounds.width, group.mLayerBounds.height); - group.mClippedImageBounds = group.mImageBounds; group.mPaintRect = LayerIntRect::FromUnknownRect( ScaleToOutsidePixelsOffset(aWrappingItem->GetPaintRect(), scale.width, diff --git a/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html b/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html deleted file mode 100644 index 670cb2551e7f..000000000000 --- a/gfx/tests/crashtests/1494062-blob-image-wraplist-clip.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/gfx/tests/crashtests/crashtests.list b/gfx/tests/crashtests/crashtests.list index 7025b82fd4d6..3c3e6c5151c6 100644 --- a/gfx/tests/crashtests/crashtests.list +++ b/gfx/tests/crashtests/crashtests.list @@ -181,5 +181,4 @@ load 1513133.html load 1496194.html load 1505934-1.html load 1509123.html -load 1494062.html load texture-allocator-zero-region.html From 589065448065236e7d418a685985cf2a65b17e74 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Thu, 17 Jan 2019 21:49:07 +0100 Subject: [PATCH 04/19] Bug 1520684 part 1 - Add plumbing for handling physical/logical corner values. r=emilio --- servo/components/style/logical_geometry.rs | 51 +++++++++++++++++++ servo/components/style/properties/data.py | 9 +++- .../style/properties/helpers.mako.rs | 15 +++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/servo/components/style/logical_geometry.rs b/servo/components/style/logical_geometry.rs index b6cc9ef5bc67..63aa5f138a46 100644 --- a/servo/components/style/logical_geometry.rs +++ b/servo/components/style/logical_geometry.rs @@ -171,6 +171,49 @@ impl WritingMode { } } + #[inline] + fn physical_sides_to_corner(block_side: PhysicalSide, inline_side: PhysicalSide) -> PhysicalCorner { + match (block_side, inline_side) { + (PhysicalSide::Top, PhysicalSide::Left) | + (PhysicalSide::Left, PhysicalSide::Top) => PhysicalCorner::TopLeft, + (PhysicalSide::Top, PhysicalSide::Right) | + (PhysicalSide::Right, PhysicalSide::Top) => PhysicalCorner::TopRight, + (PhysicalSide::Bottom, PhysicalSide::Right) | + (PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight, + (PhysicalSide::Bottom, PhysicalSide::Left) | + (PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft, + _ => unreachable!("block and inline sides must be orthogonal") + } + } + + #[inline] + pub fn start_start_physical_corner(&self) -> PhysicalCorner { + WritingMode::physical_sides_to_corner( + self.block_start_physical_side(), + self.inline_start_physical_side()) + } + + #[inline] + pub fn start_end_physical_corner(&self) -> PhysicalCorner { + WritingMode::physical_sides_to_corner( + self.block_start_physical_side(), + self.inline_end_physical_side()) + } + + #[inline] + pub fn end_start_physical_corner(&self) -> PhysicalCorner { + WritingMode::physical_sides_to_corner( + self.block_end_physical_side(), + self.inline_start_physical_side()) + } + + #[inline] + pub fn end_end_physical_corner(&self) -> PhysicalCorner { + WritingMode::physical_sides_to_corner( + self.block_end_physical_side(), + self.inline_end_physical_side()) + } + #[inline] pub fn block_flow_direction(&self) -> BlockFlowDirection { match (self.is_vertical(), self.is_vertical_lr()) { @@ -1314,3 +1357,11 @@ pub enum PhysicalSide { Bottom, Left, } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PhysicalCorner { + TopLeft, + TopRight, + BottomRight, + BottomLeft, +} diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py index fa4f55bc9e57..39b5743f4a91 100644 --- a/servo/components/style/properties/data.py +++ b/servo/components/style/properties/data.py @@ -8,10 +8,13 @@ PHYSICAL_SIDES = ["top", "right", "bottom", "left"] LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"] PHYSICAL_SIZES = ["width", "height"] LOGICAL_SIZES = ["block-size", "inline-size"] +PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"] +LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"] # bool is True when logical ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [(side, True) for side in LOGICAL_SIDES] ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [(size, True) for size in LOGICAL_SIZES] +ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [(corner, True) for corner in LOGICAL_CORNERS] SYSTEM_FONT_LONGHANDS = """font_family font_size font_style font_variant_caps font_stretch font_kerning @@ -239,12 +242,14 @@ class Longhand(object): def all_physical_mapped_properties(self): assert self.logical logical_side = None - for s in LOGICAL_SIDES + LOGICAL_SIZES: + for s in LOGICAL_SIDES + LOGICAL_SIZES + LOGICAL_CORNERS: if s in self.name: assert not logical_side logical_side = s assert logical_side - physical = PHYSICAL_SIDES if logical_side in LOGICAL_SIDES else PHYSICAL_SIZES + physical = PHYSICAL_SIDES if logical_side in LOGICAL_SIDES else \ + PHYSICAL_SIZES if logical_side in LOGICAL_SIZES else \ + LOGICAL_CORNERS return [self.name.replace(logical_side, physical_side).replace("inset-", "") for physical_side in physical] diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs index b37842ddc574..2139ff2be712 100644 --- a/servo/components/style/properties/helpers.mako.rs +++ b/servo/components/style/properties/helpers.mako.rs @@ -4,7 +4,7 @@ <%! from data import Keyword, to_rust_ident, to_camel_case - from data import LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS + from data import LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, PHYSICAL_SIDES, LOGICAL_SIZES, SYSTEM_FONT_LONGHANDS %> <%def name="predefined_type(name, type, initial_value, parse_method='parse', @@ -842,12 +842,16 @@ <% side = None size = None + corner = None maybe_side = [s for s in LOGICAL_SIDES if s in name] maybe_size = [s for s in LOGICAL_SIZES if s in name] + maybe_corner = [s for s in LOGICAL_CORNERS if s in name] if len(maybe_side) == 1: side = maybe_side[0] elif len(maybe_size) == 1: size = maybe_size[0] + elif len(maybe_corner) == 1: + corner = maybe_corner[0] def phys_ident(side, phy_side): return to_rust_ident(name.replace(side, phy_side).replace("inset-", "")) %> @@ -860,6 +864,15 @@ } % endfor } + % elif corner is not None: + use crate::logical_geometry::PhysicalCorner; + match wm.${to_rust_ident(corner)}_physical_corner() { + % for phy_corner in PHYSICAL_CORNERS: + PhysicalCorner::${to_camel_case(phy_corner)} => { + ${caller.inner(physical_ident=phys_ident(corner, phy_corner))} + } + % endfor + } % elif size is not None: <% # (horizontal, vertical) From 9c04886e64d40d784989e82bec7649cb64f2b25c Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Thu, 17 Jan 2019 21:49:07 +0100 Subject: [PATCH 05/19] Bug 1520684 part 2 - [css-logical] Implement the flow-relative border-radius properties. r=emilio --- .../server/actors/animation-type-longhand.js | 4 + .../shared/css/generated/properties-db.js | 52 +++++++++++ layout/style/test/property_database.js | 88 +++++++++++++++++++ .../test/test_transitions_per_property.html | 4 + .../style/properties/longhands/border.mako.rs | 20 +++-- .../logical-box-border-radius.html | 18 ++++ .../resources/test-box-properties.js | 53 ++++++++++- 7 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 testing/web-platform/tests/css/css-logical/logical-box-border-radius.html diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 32ec268bae6f..3e170445249c 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -283,6 +283,10 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "border-bottom-right-radius", "border-top-left-radius", "border-top-right-radius", + "border-start-start-radius", + "border-start-end-radius", + "border-end-start-radius", + "border-end-end-radius", "bottom", "column-gap", "column-width", diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index c63167b6ce80..2c99513199b9 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -2857,6 +2857,10 @@ exports.CSS_PROPERTIES = { "border-inline-end-color", "border-inline-end-style", "border-inline-end-width", + "border-start-start-radius", + "border-start-end-radius", + "border-end-start-radius", + "border-end-end-radius", "margin-block-start", "margin-block-end", "margin-inline-start", @@ -4128,6 +4132,30 @@ exports.CSS_PROPERTIES = { "unset" ] }, + "border-end-end-radius": { + "isInherited": false, + "subproperties": [ + "border-end-end-radius" + ], + "supports": [], + "values": [ + "inherit", + "initial", + "unset" + ] + }, + "border-end-start-radius": { + "isInherited": false, + "subproperties": [ + "border-end-start-radius" + ], + "supports": [], + "values": [ + "inherit", + "initial", + "unset" + ] + }, "border-image": { "isInherited": false, "subproperties": [ @@ -4757,6 +4785,30 @@ exports.CSS_PROPERTIES = { "unset" ] }, + "border-start-end-radius": { + "isInherited": false, + "subproperties": [ + "border-start-end-radius" + ], + "supports": [], + "values": [ + "inherit", + "initial", + "unset" + ] + }, + "border-start-start-radius": { + "isInherited": false, + "subproperties": [ + "border-start-start-radius" + ], + "supports": [], + "values": [ + "inherit", + "initial", + "unset" + ] + }, "border-style": { "isInherited": false, "subproperties": [ diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 8a9dc66c9454..2919b629cbfe 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -1592,6 +1592,94 @@ var gCSSProperties = { ], invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] }, + "border-start-start-radius": { + domProp: "borderStartStartRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] + }, + "border-start-end-radius": { + domProp: "borderStartEndRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] + }, + "border-end-start-radius": { + domProp: "borderEndStartRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] + }, + "border-end-end-radius": { + domProp: "borderEndEndRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] + }, "border-inline-start": { domProp: "borderInlineStart", inherited: false, diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html index c6130fde880c..91d866b8a67e 100644 --- a/layout/style/test/test_transitions_per_property.html +++ b/layout/style/test/test_transitions_per_property.html @@ -64,6 +64,10 @@ var supported_properties = { "border-bottom-right-radius": [ test_radius_transition ], "border-top-left-radius": [ test_radius_transition ], "border-top-right-radius": [ test_radius_transition ], + "border-start-start-radius": [ test_radius_transition ], + "border-start-end-radius": [ test_radius_transition ], + "border-end-start-radius": [ test_radius_transition ], + "border-end-end-radius": [ test_radius_transition ], "-moz-box-flex": [ test_float_zeroToOne_transition, test_float_aboveOne_transition, test_float_zeroToOne_clamped ], diff --git a/servo/components/style/properties/longhands/border.mako.rs b/servo/components/style/properties/longhands/border.mako.rs index 79df20d78123..b9d3425f84a8 100644 --- a/servo/components/style/properties/longhands/border.mako.rs +++ b/servo/components/style/properties/longhands/border.mako.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Keyword, Method, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %> +<% from data import Keyword, Method, ALL_CORNERS, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %> <% data.new_style_struct("Border", inherited=False, additional_methods=[Method("border_" + side + "_has_nonzero_width", @@ -70,17 +70,27 @@ ${helpers.gecko_keyword_conversion( )} // FIXME(#4126): when gfx supports painting it, make this Size2D -% for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]: +% for corner in ALL_CORNERS: + <% + corner_name = corner[0] + is_logical = corner[1] + if is_logical: + prefixes = None + else: + prefixes = "webkit" + %> ${helpers.predefined_type( - "border-" + corner + "-radius", + "border-%s-radius" % corner_name, "BorderCornerRadius", "computed::BorderCornerRadius::zero()", "parse", - extra_prefixes="webkit", - spec="https://drafts.csswg.org/css-backgrounds/#border-%s-radius" % corner, + extra_prefixes=prefixes, + spec=maybe_logical_spec(corner, "radius"), boxed=True, flags="APPLIES_TO_FIRST_LETTER", animation_value_type="BorderCornerRadius", + logical_group="border-radius", + logical=is_logical, )} % endfor diff --git a/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html b/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html new file mode 100644 index 000000000000..81b8fa0fece7 --- /dev/null +++ b/testing/web-platform/tests/css/css-logical/logical-box-border-radius.html @@ -0,0 +1,18 @@ + + +CSS Logical Properties: flow-relative border-radius + + + + + + +
+ + diff --git a/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js b/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js index 1f17ff296ff2..ef1854f97de4 100644 --- a/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js +++ b/testing/web-platform/tests/css/css-logical/resources/test-box-properties.js @@ -66,6 +66,41 @@ export function createBoxPropertyGroup(property, descriptor) { return {logical, physical, shorthands, type, prerequisites, property}; } +/** + * Creates a group physical and logical box-corner properties. + * + * @param {string} property + * A string representing the property names, like "border-*-radius". + * @param {Object} descriptor + * @param {string|string[]} descriptor.type + * Describes the kind of values accepted by the property, like "length". + * Must be a key or a collection of keys from the `testValues` object. + * @param {Object={}} descriptor.prerequisites + * Represents property declarations that are needed by `property` to work. + * For example, border-width properties require a border style. + */ +export function createCornerPropertyGroup(property, descriptor) { + const logical = {}; + const physical = {}; + const shorthands = {}; + for (const logicalCorner of ["start-start", "start-end", "end-start", "end-end"]) { + const prop = property.replace("*", logicalCorner); + const [block_side, inline_side] = logicalCorner.split("-"); + const b = "block" + block_side.charAt(0).toUpperCase() + block_side.slice(1); + const i = "inline" + inline_side.charAt(0).toUpperCase() + inline_side.slice(1); + const index = b + "-" + i; // e.g. "blockStart-inlineEnd" + logical[index] = prop; + } + let prerequisites = ""; + for (const physicalCorner of ["top-left", "top-right", "bottom-left", "bottom-right"]) { + const prop = property.replace("*", physicalCorner); + physical[physicalCorner] = prop; + prerequisites += makeDeclaration(descriptor.prerequisites, physicalCorner); + } + const type = [].concat(descriptor.type); + return {logical, physical, shorthands, type, prerequisites, property}; +} + /** * Creates a group of physical and logical sizing properties. * @@ -101,6 +136,7 @@ export function runTests(group) { const logicals = Object.values(group.logical); const physicals = Object.values(group.physical); const shorthands = group.shorthands ? Object.entries(group.shorthands) : null; + const is_corner = group.property == "border-*-radius"; test(function() { const expected = []; @@ -141,7 +177,22 @@ export function runTests(group) { const associated = {}; for (const [logicalSide, logicalProp] of Object.entries(group.logical)) { - const physicalProp = group.physical[writingMode[logicalSide]]; + let physicalProp; + if (is_corner) { + const [ block_side, inline_side] = logicalSide.split("-"); + const physicalSide1 = writingMode[block_side]; + const physicalSide2 = writingMode[inline_side]; + let physicalCorner; + // mirror "left-top" to "top-left" etc + if (["top", "bottom"].includes(physicalSide1)) { + physicalCorner = physicalSide1 + "-" + physicalSide2; + } else { + physicalCorner = physicalSide2 + "-" + physicalSide1; + } + physicalProp = group.physical[physicalCorner]; + } else { + physicalProp = group.physical[writingMode[logicalSide]]; + } associated[logicalProp] = physicalProp; associated[physicalProp] = logicalProp; } From b888841ef51cb6d654f334ac657cfcd96220d658 Mon Sep 17 00:00:00 2001 From: Lee Salzman Date: Thu, 17 Jan 2019 16:49:18 -0500 Subject: [PATCH 06/19] Bug 1495282 - cherry-pick Skia fix for Mac font gamma. r=mstange --- gfx/skia/skia/src/ports/SkFontHost_mac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp index 7814d2cff09d..df7866d9bc37 100644 --- a/gfx/skia/skia/src/ports/SkFontHost_mac.cpp +++ b/gfx/skia/skia/src/ports/SkFontHost_mac.cpp @@ -1447,7 +1447,7 @@ void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) { if ((glyph.fMaskFormat == SkMask::kLCD16_Format) || (glyph.fMaskFormat == SkMask::kA8_Format && requestSmooth - && smooth_behavior() == SmoothBehavior::subpixel)) + && smooth_behavior() != SmoothBehavior::none)) { const uint8_t* linear = gLinearCoverageFromCGLCDValue.data(); From bde8f820fb6cf1c2794515bfbe5103588f0d0ab3 Mon Sep 17 00:00:00 2001 From: Andrew Swan Date: Thu, 17 Jan 2019 11:12:46 -0800 Subject: [PATCH 07/19] Bug 1519762 Fix a regression with browser.downloads.search() r=kmag The fixes in bug 1503760 inadvertently broke searches of downloads that have not yet started receiving data (which have a value of -1 for the totalBytes property). That regression is fixed here. Differential Revision: https://phabricator.services.mozilla.com/D16889 --HG-- extra : rebase_source : d6abf7e5bdc2ae10e690df56c060b30166fc75f7 extra : amend_source : 6580b889860d4d28f216fd92522ecd539a8f7d1d --- .../extensions/parent/ext-downloads.js | 6 +-- .../extensions/schemas/downloads.json | 6 +-- .../xpcshell/test_ext_downloads_search.js | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/toolkit/components/extensions/parent/ext-downloads.js b/toolkit/components/extensions/parent/ext-downloads.js index 212fa5dffa92..f18ad9a948e7 100644 --- a/toolkit/components/extensions/parent/ext-downloads.js +++ b/toolkit/components/extensions/parent/ext-downloads.js @@ -266,8 +266,8 @@ const downloadQuery = query => { // const endedBefore = normalizeDownloadTime(query.endedBefore, true); // const endedAfter = normalizeDownloadTime(query.endedAfter, false); - const totalBytesGreater = query.totalBytesGreater; - const totalBytesLess = query.totalBytesLess != null ? query.totalBytesLess : Number.MAX_VALUE; + const totalBytesGreater = query.totalBytesGreater !== null ? query.totalBytesGreater : -1; + const totalBytesLess = query.totalBytesLess !== null ? query.totalBytesLess : Number.MAX_VALUE; // Handle options for which we can have a regular expression and/or // an explicit value to match. @@ -323,7 +323,7 @@ const downloadQuery = query => { // todo endedBefore, endedAfter if (item.totalBytes == -1) { - if (query.totalBytesGreater != null || query.totalBytesLess != null) { + if (query.totalBytesGreater !== null || query.totalBytesLess !== null) { return false; } } else if (item.totalBytes <= totalBytesGreater || item.totalBytes >= totalBytesLess) { diff --git a/toolkit/components/extensions/schemas/downloads.json b/toolkit/components/extensions/schemas/downloads.json index 712f7051b8c1..88119638b812 100644 --- a/toolkit/components/extensions/schemas/downloads.json +++ b/toolkit/components/extensions/schemas/downloads.json @@ -112,7 +112,8 @@ }, "mime": { "description": "The file's MIME type.", - "type": "string" + "type": "string", + "optional": true }, "startTime": { "description": "Number of milliseconds between the unix epoch and when this download began.", @@ -257,8 +258,7 @@ "totalBytesGreater": { "description": "Limits results to downloads whose totalBytes is greater than the given integer.", "optional": true, - "type": "number", - "default": -1 + "type": "number" }, "totalBytesLess": { "description": "Limits results to downloads whose totalBytes is less than the given integer.", diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js index 995044a94207..5c0914624708 100644 --- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js @@ -2,6 +2,8 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; +PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/); + ChromeUtils.import("resource://gre/modules/Downloads.jsm"); const server = createHttpServer(); @@ -424,3 +426,49 @@ add_task(async function test_search() { await extension.unload(); }); + +// Test that downloads with totalBytes of -1 (ie, that have not yet started) +// work properly. See bug 1519762 for details of a past regression in +// this area. +add_task(async function test_inprogress() { + let resume, resumePromise = new Promise(resolve => { resume = resolve; }); + server.registerPathHandler("/slow", async (request, response) => { + response.processAsync(); + await resumePromise; + response.setHeader("Content-type", "text/plain"); + response.write(""); + response.finish(); + }); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["downloads"], + }, + background() { + browser.test.onMessage.addListener(async (msg, url) => { + let id = await browser.downloads.download({url}); + let full = await browser.downloads.search({id}); + + browser.test.assertEq(full.length, 1, + "Found new download in search results"); + browser.test.assertEq(full[0].totalBytes, -1, + "New download still has totalBytes == -1"); + + browser.downloads.onChanged.addListener(info => { + if (info.id == id && info.state.current == "complete") { + browser.test.notifyPass("done"); + } + }); + + browser.test.sendMessage("started"); + }); + }, + }); + + await extension.startup(); + extension.sendMessage("go", `${BASE}/slow`); + await extension.awaitMessage("started"); + resume(); + await extension.awaitFinish("done"); + await extension.unload(); +}); From d6276560500d633034292cb0bd50de6176837077 Mon Sep 17 00:00:00 2001 From: Jorg K Date: Fri, 18 Jan 2019 00:26:04 +0100 Subject: [PATCH 08/19] Bug 1520944 - add missing include of WindowProxyHolder.h to SessionStoreUtils.cpp. r=mccr8 a=RyanVM --- toolkit/components/sessionstore/SessionStoreUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp index 593ea9526d54..c84f59f3bae4 100644 --- a/toolkit/components/sessionstore/SessionStoreUtils.cpp +++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/HTMLSelectElement.h" #include "mozilla/dom/HTMLTextAreaElement.h" #include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/WindowProxyHolder.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentList.h" #include "nsContentUtils.h" From 462e1ab742e6888be2eeb86d1e90ade5e11d2287 Mon Sep 17 00:00:00 2001 From: Eugen Sawin Date: Fri, 18 Jan 2019 00:06:48 +0100 Subject: [PATCH 09/19] Bug 1517641 - [1.0] Add experimental ad-blocking category to the Tracking Protection API. Differential Revision: https://phabricator.services.mozilla.com/D16700 --- mobile/android/geckoview/api.txt | 1 + .../org/mozilla/geckoview/GeckoSession.java | 6 +++++- .../mozilla/geckoview/TrackingProtection.java | 18 ++++++++++++++++++ .../mozilla/geckoview/doc-files/CHANGELOG.md | 4 +++- modules/libpref/init/all.js | 4 ++-- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt index 71e4282a00dd..e2a748916274 100644 --- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -621,6 +621,7 @@ package org.mozilla.geckoview { public static interface GeckoSession.TrackingProtectionDelegate { method @android.support.annotation.UiThread public void onTrackerBlocked(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, int); field public static final int CATEGORY_AD = 1; + field public static final int CATEGORY_AD_EXT = 64; field public static final int CATEGORY_ALL = 31; field public static final int CATEGORY_ANALYTIC = 2; field public static final int CATEGORY_CONTENT = 8; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 950cfa0fc15b..0dc134ebd86b 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -3816,7 +3816,7 @@ public class GeckoSession implements Parcelable { @IntDef(flag = true, value = { CATEGORY_NONE, CATEGORY_AD, CATEGORY_ANALYTIC, CATEGORY_SOCIAL, CATEGORY_CONTENT, CATEGORY_ALL, - CATEGORY_TEST }) + CATEGORY_TEST, CATEGORY_AD_EXT }) /* package */ @interface Category {} static final int CATEGORY_NONE = 0; @@ -3844,6 +3844,10 @@ public class GeckoSession implements Parcelable { * Block all known trackers. */ static final int CATEGORY_ALL = (1 << 5) - 1; + /** + * Experimental: Block advertisements. + */ + static final int CATEGORY_AD_EXT = 1 << 6; /** * A tracking element has been blocked from loading. diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java index 9ab5bb4f1caa..527b44b5de89 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TrackingProtection.java @@ -14,6 +14,13 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; private static final String ANALYTIC = "analytics-track-digest256"; private static final String SOCIAL = "social-track-digest256"; private static final String CONTENT = "content-track-digest256"; + private static final String[] AD_EXT = new String[] { + "fanboy-annoyance-digest256", + "fanboy-social-digest256", + "easylist-digest25", + "easyprivacy-digest25", + "adguard-digest25" + }; /* package */ static String buildPrefValue(int categories) { StringBuilder builder = new StringBuilder(); @@ -36,6 +43,11 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; if ((categories & TrackingProtectionDelegate.CATEGORY_CONTENT) != 0) { builder.append(CONTENT).append(','); } + if ((categories & TrackingProtectionDelegate.CATEGORY_AD_EXT) != 0) { + for (final String l: AD_EXT) { + builder.append(l).append(','); + } + } // Trim final ','. return builder.substring(0, builder.length() - 1); } @@ -57,6 +69,12 @@ import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate; if (list.indexOf(CONTENT) != -1) { category |= TrackingProtectionDelegate.CATEGORY_CONTENT; } + for (final String l: AD_EXT) { + if (list.indexOf(l) != -1) { + category |= TrackingProtectionDelegate.CATEGORY_AD_EXT; + break; + } + } return category; } } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md index bbd5e20f8fce..007614408144 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -26,6 +26,8 @@ description: GeckoView API Changelog. - Added `@UiThread` to `GeckoSession.releaseSession` and `GeckoSession.setSession` ## v65 +- Added experimental ad-blocking category to `GeckoSession.TrackingProtectionDelegate`. + - Moved [`CompositorController`][65.1], [`DynamicToolbarAnimator`][65.2], [`OverscrollEdgeEffect`][65.3], [`PanZoomController`][65.4] from `org.mozilla.gecko.gfx` to [`org.mozilla.geckoview`][65.5] @@ -113,4 +115,4 @@ description: GeckoView API Changelog. [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String- [65.25]: ../GeckoResult.html -[api-version]: e7a6a3ed65c75f7cb278b693adfa09cae5238ca2 +[api-version]: 45d1d8774e913a3077d7c489274184fd301f14fc diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 0daed929a730..d986fc07b5c7 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5571,7 +5571,7 @@ pref("urlclassifier.features.cryptomining.blacklistTables", ""); pref("urlclassifier.features.cryptomining.whitelistTables", ""); // These tables will never trigger a gethash call. -pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256"); +pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,goog-passwordwhite-proto,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboy-annoyance-digest256,fanboy-social-digest256,easylist-digest256,easyprivacy-digest256,adguard-digest256"); // Number of random entries to send with a gethash request pref("urlclassifier.gethashnoise", 4); @@ -5640,7 +5640,7 @@ pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozil // Mozilla Safe Browsing provider (for tracking protection and plugin blocking) pref("browser.safebrowsing.provider.mozilla.pver", "2.2"); -pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256"); +pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboy-annoyance-digest256,fanboy-social-digest256,easylist-digest256,easyprivacy-digest256,adguard-digest256"); pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2"); pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2"); // Set to a date in the past to force immediate download in new profiles. From 160765a37c92d3d9e8543af42c71eaf1b40c595b Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Thu, 17 Jan 2019 15:42:01 -0800 Subject: [PATCH 10/19] Bug 1520798 followup: add HTML crashtest for this bug (on top of earlier XUL crashtest). no review, testonly --HG-- extra : rebase_source : bd4f6118b856e095217643c73fda451885597cab --- layout/generic/crashtests/1520798-2.html | 13 +++++++++++++ layout/generic/crashtests/crashtests.list | 1 + 2 files changed, 14 insertions(+) create mode 100644 layout/generic/crashtests/1520798-2.html diff --git a/layout/generic/crashtests/1520798-2.html b/layout/generic/crashtests/1520798-2.html new file mode 100644 index 000000000000..83a27ab4ca7e --- /dev/null +++ b/layout/generic/crashtests/1520798-2.html @@ -0,0 +1,13 @@ + + + + + +
+
Hi
+
+ diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list index 5c7879dd575e..b2964c16917f 100644 --- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -724,3 +724,4 @@ pref(layout.css.column-span.enabled,true) load 1507196.html pref(layout.css.column-span.enabled,true) load 1517033.html pref(layout.css.column-span.enabled,true) load 1517297.html load 1520798-1.xul +load 1520798-2.html From 73a98fae451f31c22cad866c5cb76b927c634c62 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Fri, 18 Jan 2019 11:52:33 +0900 Subject: [PATCH 11/19] Bug 1501576 - Add LabelEmitter. r=jwalden --- js/src/frontend/BytecodeEmitter.cpp | 29 +++--------- js/src/frontend/BytecodeEmitter.h | 2 +- js/src/frontend/LabelEmitter.cpp | 56 +++++++++++++++++++++++ js/src/frontend/LabelEmitter.h | 70 +++++++++++++++++++++++++++++ js/src/frontend/moz.build | 1 + 5 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 js/src/frontend/LabelEmitter.cpp create mode 100644 js/src/frontend/LabelEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index d865b9c50a29..c5c7898bb9aa 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -37,6 +37,7 @@ #include "frontend/ForOfEmitter.h" #include "frontend/ForOfLoopControl.h" #include "frontend/IfEmitter.h" +#include "frontend/LabelEmitter.h" // LabelEmitter #include "frontend/ModuleSharedContext.h" #include "frontend/NameOpEmitter.h" #include "frontend/ParseNode.h" @@ -7498,33 +7499,15 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(UnaryNode* incDec) { // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement( - const LabeledStatement* pn) { - /* - * Emit a JSOP_LABEL instruction. The argument is the offset to the statement - * following the labeled statement. - */ - uint32_t index; - if (!makeAtomIndex(pn->label(), &index)) { + const LabeledStatement* labeledStmt) { + LabelEmitter label(this); + if (!label.emitLabel(labeledStmt->label())) { return false; } - - JumpList top; - if (!emitJump(JSOP_LABEL, &top)) { + if (!emitTree(labeledStmt->statement())) { return false; } - - /* Emit code for the labeled statement. */ - LabelControl controlInfo(this, pn->label(), offset()); - - if (!emitTree(pn->statement())) { - return false; - } - - /* Patch the JSOP_LABEL offset. */ - JumpTarget brk{lastNonJumpTargetOffset()}; - patchJumpsToTarget(top, brk); - - if (!controlInfo.patchBreaks(this)) { + if (!label.emitEnd()) { return false; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 16656b67b259..c7ad1a2a6ed5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -687,7 +687,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { MOZ_MUST_USE bool emitWith(BinaryNode* withNode); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement( - const LabeledStatement* pn); + const LabeledStatement* labeledStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope( LexicalScopeNode* lexicalScope); MOZ_MUST_USE bool emitLexicalScopeBody( diff --git a/js/src/frontend/LabelEmitter.cpp b/js/src/frontend/LabelEmitter.cpp new file mode 100644 index 000000000000..d39b99633603 --- /dev/null +++ b/js/src/frontend/LabelEmitter.cpp @@ -0,0 +1,56 @@ +/* -*- 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 "frontend/LabelEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOP_* + +using namespace js; +using namespace js::frontend; + +bool LabelEmitter::emitLabel(JSAtom* name) { + MOZ_ASSERT(state_ == State::Start); + + // Emit a JSOP_LABEL instruction. The operand is the offset to the + // statement following the labeled statement. + uint32_t index; + if (!bce_->makeAtomIndex(name, &index)) { + return false; + } + if (!bce_->emitJump(JSOP_LABEL, &top_)) { + return false; + } + + controlInfo_.emplace(bce_, name, bce_->offset()); + +#ifdef DEBUG + state_ = State::Label; +#endif + return true; +} + +bool LabelEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Label); + + // Patch the JSOP_LABEL offset. + JumpTarget brk{bce_->lastNonJumpTargetOffset()}; + bce_->patchJumpsToTarget(top_, brk); + + // Patch the break/continue to this label. + if (!controlInfo_->patchBreaks(bce_)) { + return false; + } + + controlInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LabelEmitter.h b/js/src/frontend/LabelEmitter.h new file mode 100644 index 000000000000..d644d6b05e39 --- /dev/null +++ b/js/src/frontend/LabelEmitter.h @@ -0,0 +1,70 @@ +/* -*- 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 frontend_LabelEmitter_h +#define frontend_LabelEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/BytecodeControlStructures.h" // LabelControl +#include "frontend/JumpList.h" // JumpList + +class JSAtom; + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting labeled statement. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `label: expr;` +// LabelEmitter le(this); +// le.emitLabel(name_of_label); +// emit(expr); +// le.emitEnd(); +// +class MOZ_STACK_CLASS LabelEmitter { + BytecodeEmitter* bce_; + + // The offset of the JSOP_LABEL. + JumpList top_; + + mozilla::Maybe controlInfo_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitLabel +-------+ emitEnd +-----+ + // | Start |---------->| Label |-------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitLabel. + Label, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit LabelEmitter(BytecodeEmitter* bce) : bce_(bce) {} + + MOZ_MUST_USE bool emitLabel(JSAtom* name); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build index c55a021d4371..e53106af28f5 100644 --- a/js/src/frontend/moz.build +++ b/js/src/frontend/moz.build @@ -41,6 +41,7 @@ UNIFIED_SOURCES += [ 'ForOfLoopControl.cpp', 'IfEmitter.cpp', 'JumpList.cpp', + 'LabelEmitter.cpp', 'NameFunctions.cpp', 'NameOpEmitter.cpp', 'ParseContext.cpp', From ccda4fb3c83d93f05369291c3dbf6263c5cca1a9 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Fri, 18 Jan 2019 11:52:33 +0900 Subject: [PATCH 12/19] Bug 1501577 - Add PropertyEmitter, ObjectEmitter, and ClassEmitter. r=jorendorff --- js/src/frontend/BytecodeEmitter.cpp | 705 ++++++++++-------------- js/src/frontend/BytecodeEmitter.h | 7 +- js/src/frontend/ObjectEmitter.cpp | 813 ++++++++++++++++++++++++++++ js/src/frontend/ObjectEmitter.h | 722 ++++++++++++++++++++++++ js/src/frontend/moz.build | 1 + 5 files changed, 1820 insertions(+), 428 deletions(-) create mode 100644 js/src/frontend/ObjectEmitter.cpp create mode 100644 js/src/frontend/ObjectEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index c5c7898bb9aa..90873674c20e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -40,6 +40,7 @@ #include "frontend/LabelEmitter.h" // LabelEmitter #include "frontend/ModuleSharedContext.h" #include "frontend/NameOpEmitter.h" +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter #include "frontend/ParseNode.h" #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" @@ -3031,24 +3032,30 @@ bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, if (maybeFun->isKind(ParseNodeKind::Function)) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. - JSFunction* fun = maybeFun->as().funbox()->function(); - - // The inferred name may already be set if this function is an - // interpreted lazy function and we OOM'ed after we set the inferred - // name the first time. - if (fun->hasInferredName()) { - MOZ_ASSERT(fun->isInterpretedLazy()); - MOZ_ASSERT(fun->inferredName() == name); - - return true; - } - - fun->setInferredName(name); - return true; + return setFunName(maybeFun->as().funbox()->function(), name); } MOZ_ASSERT(maybeFun->isKind(ParseNodeKind::ClassDecl)); + return emitSetClassConstructorName(name); +} + +bool BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) { + // The inferred name may already be set if this function is an interpreted + // lazy function and we OOM'ed after we set the inferred name the first + // time. + if (fun->hasInferredName()) { + MOZ_ASSERT(fun->isInterpretedLazy()); + MOZ_ASSERT(fun->inferredName() == name); + + return true; + } + + fun->setInferredName(name); + return true; +} + +bool BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) { uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) { return false; @@ -7550,27 +7557,31 @@ bool BytecodeEmitter::emitConditionalExpression( return true; } -bool BytecodeEmitter::emitPropertyList(ListNode* obj, - MutableHandlePlainObject objp, +bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type) { + // [stack] CTOR? OBJ + for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { // TODO(khyperia): Implement private field access. return false; } - if (!updateSourceCoordNotes(propdef->pn_pos.begin)) { - return false; - } // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(ParseNodeKind::MutateProto)) { + // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); - if (!emitTree(propdef->as().kid())) { + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; } - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) { + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ return false; } continue; @@ -7578,193 +7589,216 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, if (propdef->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) { + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; } - if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL return false; } - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) { + if (!pe.emitSpread()) { + // [stack] OBJ return false; } - - objp.set(nullptr); continue; } - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) { + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction(); + JSOp op = propdef->getOp(); + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || + op == JSOP_INITPROP_SETTER); + + auto emitValue = [this, &propVal, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL return false; } - if (!emit1(JSOP_POP)) { + + if (propVal->isKind(ParseNodeKind::Function) && + propVal->as().funbox()->needsHomeObject()) { + FunctionBox* funbox = propVal->as().funbox(); + MOZ_ASSERT(funbox->function()->allowSuperProperty()); + + if (!pe.emitInitHomeObject(funbox->asyncKind())) { + // [stack] CTOR? OBJ CTOR? KEY? FUN + return false; + } + } + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (key->isKind(ParseNodeKind::NumberExpr)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + continue; } - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->as().left(); - bool isIndex = false; - if (key->isKind(ParseNodeKind::NumberExpr)) { - if (!emitNumberOp(key->as().value())) { - return false; - } - isIndex = true; - } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || - key->isKind(ParseNodeKind::StringExpr)) { - // EmitClass took care of constructor already. + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + // emitClass took care of constructor already. if (type == ClassBody && key->as().atom() == cx->names().constructor && !propdef->as().isStatic()) { continue; } - } else { - MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); - if (!emitComputedPropertyName(&key->as())) { + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; } - isIndex = true; + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + RootedFunction anonFunction(cx); + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP); + + if (propVal->isKind(ParseNodeKind::Function)) { + // When the value is function, we set the function's name + // at the compile-time, instead of emitting SETFUNNAME. + FunctionBox* funbox = propVal->as().funbox(); + anonFunction = funbox->function(); + } else { + // Only object literal can have a property where key is + // name and value is an anonymous class. + // + // ({ foo: class {} }); + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(propVal->isKind(ParseNodeKind::ClassDecl)); + } + } + + RootedAtom keyAtom(cx, key->as().atom()); + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass, + anonFunction)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + continue; } - /* Emit code for the property initializer. */ - ParseNode* propVal = propdef->as().right(); - if (!emitTree(propVal)) { + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL return false; } - JSOp op = propdef->getOp(); - MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || - op == JSOP_INITPROP_SETTER); - - FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER - ? FunctionPrefixKind::Get - : op == JSOP_INITPROP_SETTER - ? FunctionPrefixKind::Set - : FunctionPrefixKind::None; - - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) { - objp.set(nullptr); - } - - if (propVal->isKind(ParseNodeKind::Function) && - propVal->as().funbox()->needsHomeObject()) { - FunctionBox* funbox = propVal->as().funbox(); - MOZ_ASSERT(funbox->function()->allowSuperProperty()); - bool isAsync = funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) { + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ return false; } - } - if (!emitDupAt(1 + isIndex + isAsync)) { - return false; - } - if (!emit1(JSOP_INITHOMEOBJECT)) { - return false; - } - if (isAsync) { - if (!emit1(JSOP_POP)) { + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ return false; } - } - } - - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: - op = JSOP_INITHIDDENPROP; - break; - case JSOP_INITPROP_GETTER: - op = JSOP_INITHIDDENPROP_GETTER; - break; - case JSOP_INITPROP_SETTER: - op = JSOP_INITHIDDENPROP_SETTER; - break; - default: - MOZ_CRASH("Invalid op"); - } - } - - if (isIndex) { - objp.set(nullptr); - switch (op) { - case JSOP_INITPROP: - op = JSOP_INITELEM; - break; - case JSOP_INITHIDDENPROP: - op = JSOP_INITHIDDENELEM; - break; - case JSOP_INITPROP_GETTER: - op = JSOP_INITELEM_GETTER; - break; - case JSOP_INITHIDDENPROP_GETTER: - op = JSOP_INITHIDDENELEM_GETTER; - break; - case JSOP_INITPROP_SETTER: - op = JSOP_INITELEM_SETTER; - break; - case JSOP_INITHIDDENPROP_SETTER: - op = JSOP_INITHIDDENELEM_SETTER; - break; - default: - MOZ_CRASH("Invalid op"); - } - if (propVal->isDirectRHSAnonFunction()) { - if (!emitDupAt(1)) { + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ return false; } - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { - return false; - } - } - if (!emit1(op)) { - return false; - } - } else { - MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || - key->isKind(ParseNodeKind::StringExpr)); - - uint32_t index; - if (!makeAtomIndex(key->as().atom(), &index)) { - return false; - } - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->as().atom())); - if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue, - JSPROP_ENUMERATE)) { - return false; - } - if (objp->inDictionaryMode()) { - objp.set(nullptr); - } - } - - if (propVal->isDirectRHSAnonFunction()) { - MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); - - RootedAtom keyName(cx, key->as().atom()); - if (!setOrEmitSetFunName(propVal, keyName)) { - return false; - } - } - if (!emitIndex32(op, index)) { - return false; - } - } - - if (extraPop) { - if (!emit1(JSOP_POP)) { - return false; - } + break; + default: + MOZ_CRASH("Invalid op"); } } return true; @@ -7778,43 +7812,22 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) { return emitSingletonInitialiser(objNode); } - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = this->offset(); - if (!emitNewInit()) { + // [stack] + + ObjectEmitter oe(this); + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ return false; } - - // Try to construct the shape of the object as we go, so we can emit a - // JSOP_NEWOBJECT with the final shape instead. - // In the case of computed property names and indices, we cannot fix the - // shape at bytecode compile time. When the shape cannot be determined, - // |obj| is nulled out. - - // No need to do any guessing for the object kind, since we know the upper - // bound of how many properties we plan to have. - gc::AllocKind kind = gc::GetGCObjectKind(objNode->count()); - RootedPlainObject obj( - cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) { + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ return false; } - - if (!emitPropertyList(objNode, &obj, ObjectLiteral)) { + if (!oe.emitEnd()) { + // [stack] OBJ return false; } - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) { - return false; - } - } - return true; } @@ -8390,7 +8403,11 @@ bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) { } bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { - NameOpEmitter noe(this, name->name(), NameOpEmitter::Kind::Initialize); + return emitLexicalInitialization(name->name()); +} + +bool BytecodeEmitter::emitLexicalInitialization(JSAtom* name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -8407,272 +8424,108 @@ bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { return true; } -// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 -// (BindingClassDeclarationEvaluation). -bool BytecodeEmitter::emitClass(ClassNode* classNode) { - ClassNames* names = classNode->names(); - ParseNode* heritageExpression = classNode->heritage(); - ListNode* classMembers = classNode->memberList(); - CodeNode* constructor = nullptr; - for (ParseNode* mn : classMembers->contents()) { +static MOZ_ALWAYS_INLINE CodeNode* FindConstructor(JSContext* cx, + ListNode* classMethods) { + for (ParseNode* mn : classMethods->contents()) { if (mn->is()) { // TODO(khyperia): Implement private field access. - return false; + continue; } + ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(ParseNodeKind::ObjectPropertyName) || methodName.isKind(ParseNodeKind::StringExpr)) && methodName.as().atom() == cx->names().constructor) { - constructor = &method.method(); - break; + return &method.method(); } } - bool savedStrictness = sc->setLocalStrictMode(true); + return nullptr; +} - Maybe tdzCache; - Maybe emitterScope; +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool BytecodeEmitter::emitClass(ClassNode* classNode) { + ClassNames* names = classNode->names(); + ParseNode* heritageExpression = classNode->heritage(); + ListNode* classMembers = classNode->memberList(); + CodeNode* constructor = FindConstructor(cx, classMembers); + + // [stack] + + ClassEmitter ce(this); + RootedAtom innerName(cx); + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; if (names) { - tdzCache.emplace(this); - emitterScope.emplace(this); - if (!emitterScope->enterLexical(this, ScopeKind::Lexical, - classNode->scopeBindings())) { + innerName = names->innerBinding()->name(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->name()); + MOZ_ASSERT(names->outerBinding()->name() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + + if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) { + // [stack] return false; } } - // Pseudocode for class declarations: - // - // class extends BaseExpression { - // constructor() { ... } - // ... - // } - // - // - // if defined { - // let heritage = BaseExpression; - // - // if (heritage !== null) { - // funProto = heritage; - // objProto = heritage.prototype; - // } else { - // funProto = %FunctionPrototype%; - // objProto = null; - // } - // } else { - // objProto = %ObjectPrototype%; - // } - // - // let homeObject = ObjectCreate(objProto); - // - // if defined { - // if defined { - // cons = DefineMethod(, proto=homeObject, - // funProto=funProto); - // } else { - // cons = DefineMethod(, proto=homeObject); - // } - // } else { - // if defined { - // cons = DefaultDerivedConstructor(proto=homeObject, - // funProto=funProto); - // } else { - // cons = DefaultConstructor(proto=homeObject); - // } - // } - // - // cons.prototype = homeObject; - // homeObject.constructor = cons; - // - // EmitPropertyList(...) - // This is kind of silly. In order to the get the home object defined on // the constructor, we have to make it second, but we want the prototype // on top for EmitPropertyList, because we expect static properties to be // rarer. The result is a few more swaps than we would like. Such is life. - if (heritageExpression) { - InternalIfEmitter ifThenElse(this); - + bool isDerived = !!heritageExpression; + if (isDerived) { if (!emitTree(heritageExpression)) { - // [stack] ... HERITAGE + // [stack] HERITAGE return false; } - - // Heritage must be null or a non-generator constructor - if (!emit1(JSOP_CHECKCLASSHERITAGE)) { - // [stack] ... HERITAGE - return false; - } - - // [IF] (heritage !== null) - if (!emit1(JSOP_DUP)) { - // [stack] ... HERITAGE HERITAGE - return false; - } - if (!emit1(JSOP_NULL)) { - // [stack] ... HERITAGE HERITAGE NULL - return false; - } - if (!emit1(JSOP_STRICTNE)) { - // [stack] ... HERITAGE NE - return false; - } - - // [THEN] funProto = heritage, objProto = heritage.prototype - if (!ifThenElse.emitThenElse()) { - return false; - } - if (!emit1(JSOP_DUP)) { - // [stack] ... HERITAGE HERITAGE - return false; - } - if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) { - // [stack] ... HERITAGE PROTO - return false; - } - - // [ELSE] funProto = %FunctionPrototype%, objProto = null - if (!ifThenElse.emitElse()) { - return false; - } - if (!emit1(JSOP_POP)) { - // [stack] ... - return false; - } - if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) { - // [stack] ... PROTO - return false; - } - if (!emit1(JSOP_NULL)) { - // [stack] ... PROTO NULL - return false; - } - - // [ENDIF] - if (!ifThenElse.emitEnd()) { - return false; - } - - if (!emit1(JSOP_OBJWITHPROTO)) { - // [stack] ... HERITAGE HOMEOBJ - return false; - } - if (!emit1(JSOP_SWAP)) { - // [stack] ... HOMEOBJ HERITAGE + if (!ce.emitDerivedClass(innerName)) { + // [stack] HERITAGE HOMEOBJ return false; } } else { - if (!emitNewInit()) { - // [stack] ... HOMEOBJ + if (!ce.emitClass(innerName)) { + // [stack] HOMEOBJ return false; } } // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE // is not used, an implicit value of %FunctionPrototype% is implied. - if (constructor) { - if (!emitFunction(constructor, !!heritageExpression)) { - // [stack] ... HOMEOBJ CONSTRUCTOR + bool needsHomeObject = constructor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(constructor, isDerived)) { + // [stack] HOMEOBJ CTOR return false; } - if (constructor->funbox()->needsHomeObject()) { - if (!emitDupAt(1)) { - // [stack] ... HOMEOBJ CONSTRUCTOR HOMEOBJ - return false; - } - if (!emit1(JSOP_INITHOMEOBJECT)) { - // [stack] ... HOMEOBJ CONSTRUCTOR - return false; - } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; } } else { - // In the case of default class constructors, emit the start and end - // offsets in the source buffer as source notes so that when we - // actually make the constructor during execution, we can give it the - // correct toString output. - ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin); - ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end); - if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) { + if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin), + Some(classNode->pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; } - - JSAtom* name = names ? names->innerBinding()->as().atom() - : cx->names().empty; - if (heritageExpression) { - if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) { - // [stack] ... HOMEOBJ CONSTRUCTOR - return false; - } - } else { - if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) { - // [stack] ... HOMEOBJ CONSTRUCTOR - return false; - } - } } - - if (!emit1(JSOP_SWAP)) { - // [stack] ... CONSTRUCTOR HOMEOBJ + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ return false; } - - if (!emit1(JSOP_DUP2)) { - // [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR return false; } - if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) { - // [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR - return false; - } - if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) { - // [stack] ... CONSTRUCTOR HOMEOBJ - return false; - } - - RootedPlainObject obj(cx); - if (!emitPropertyList(classMembers, &obj, ClassBody)) { - // [stack] ... CONSTRUCTOR HOMEOBJ - return false; - } - - if (!emit1(JSOP_POP)) { - // [stack] ... CONSTRUCTOR - return false; - } - - if (names) { - NameNode* innerName = names->innerBinding(); - if (!emitLexicalInitialization(innerName)) { - // [stack] ... CONSTRUCTOR - return false; - } - - // Pop the inner scope. - if (!emitterScope->leave(this)) { - return false; - } - emitterScope.reset(); - - if (NameNode* outerName = names->outerBinding()) { - if (!emitLexicalInitialization(outerName)) { - // [stack] ... CONSTRUCTOR - return false; - } - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!emit1(JSOP_POP)) { - // [stack] ... - return false; - } - } - } - - // The CONSTRUCTOR is left on stack if this is an expression. - - MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index c7ad1a2a6ed5..d996b3da62c1 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -112,6 +112,7 @@ class CallOrNewEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; +class PropertyEmitter; class TDZCheckCache; struct MOZ_STACK_CLASS BytecodeEmitter { @@ -604,8 +605,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); - MOZ_MUST_USE bool emitPropertyList(ListNode* obj, - MutableHandlePlainObject objp, + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are @@ -783,6 +783,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter { MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); + MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); + MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); @@ -851,6 +853,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter { MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody); MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name); + MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); // Emit bytecode for the spread operator. // diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 000000000000..55f9c42281c4 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,813 @@ +/* -*- 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 "frontend/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/IfEmitter.h" // IfEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "frontend/SourceNotes.h" // SRC_* +#include "gc/AllocKind.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "vm/BytecodeUtil.h" // IsHiddenInitOp +#include "vm/JSContext.h" // JSContext +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Opcodes.h" // JSOP_* +#include "vm/Runtime.h" // JSAtomState (cx->names()) + +#include "gc/ObjectKind-inl.h" // GetGCObjectKind +#include "vm/JSAtom-inl.h" // AtomToId +#include "vm/JSObject-inl.h" // NewBuiltinClassInstance + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) + : bce_(bce), obj_(bce->cx) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) { + return false; + } + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() { + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOP_MUTATEPROTO)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand( + const Maybe& spreadPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + if (!bce_->updateSourceCoordNotes(*spreadPos)) { + return false; + } + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() { + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp( + const Maybe& keyPos, bool isStatic, bool isIndexOrComputed) { + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) { + return false; + } + } + + if (isStatic_) { + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey( + const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey( + const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOP_TOID)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject( + FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + bool isAsync = kind == FunctionAsyncKind::AsyncFunction; + if (isAsync) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED + return false; + } + } + + // There are the following values on the stack conditionally, between + // HOMEOBJ and FUN: + // * the 2nd CTOR if isStatic_ + // * KEY if isIndexOrComputed_ + // * WRAPPED if isAsync + // + // JSOP_INITHOMEOBJECT uses one of the following: + // * HOMEOBJ if !isStatic_ + // (`super.foo` points the super prototype property) + // * the 2nd CTOR if isStatic_ + // (`super.foo` points the super constructor property) + if (!bce_->emitDupAt(1 + isIndexOrComputed_ + isAsync)) { + // [stack] # non-static method + // [stack] CTOR? HOMEOBJ CTOR KEY? WRAPPED? FUN CTOR + // [stack] # static method + // [stack] CTOR? HOMEOBJ KEY? WRAPPED? FUN HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_INITHOMEOBJECT)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN + return false; + } + if (isAsync) { + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED + return false; + } + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInitProp( + JS::Handle key, bool isPropertyAnonFunctionOrClass /* = false */, + JS::Handle anonFunction /* = nullptr */) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, + isPropertyAnonFunctionOrClass, anonFunction); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle key) { + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitIndexProp( + bool isPropertyAnonFunctionOrClass /* = false */) { + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitIndexGetter() { + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, false); +} + +bool PropertyEmitter::emitInitIndexSetter() { + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, false); +} + +bool PropertyEmitter::emitInitComputedProp( + bool isPropertyAnonFunctionOrClass /* = false */) { + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitComputedGetter() { + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, true); +} + +bool PropertyEmitter::emitInitComputedSetter() { + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, true); +} + +bool PropertyEmitter::emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction) { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || + op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || + op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); + + // [stack] CTOR? OBJ CTOR? VAL + + uint32_t index; + if (!bce_->makeAtomIndex(key, &index)) { + return false; + } + + if (obj_) { + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!obj_->inDictionaryMode()); + JS::Rooted propKey(bce_->cx, AtomToId(key)); + if (!NativeDefineDataProperty(bce_->cx, obj_, propKey, UndefinedHandleValue, + JSPROP_ENUMERATE)) { + return false; + } + if (obj_->inDictionaryMode()) { + obj_ = nullptr; + } + } + + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP); + + if (anonFunction) { + if (!bce_->setFunName(anonFunction, key)) { + return false; + } + } else { + // NOTE: This is setting the constructor's name of the class which is + // the property value. Not of the enclosing class. + if (!bce_->emitSetClassConstructorName(key)) { + // [stack] CTOR? OBJ CTOR? FUN + return false; + } + } + } + + if (!bce_->emitIndex32(op, index)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed( + JSOp op, FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || + op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (isPropertyAnonFunctionOrClass) { + if (!bce_->emitDupAt(1)) { + // [stack] CTOR? OBJ CTOR? KEY FUN FUN + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { + // [stack] CTOR? OBJ CTOR? KEY FUN + return false; + } + } + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() { + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + top_ = bce_->offset(); + if (!bce_->emitNewInit()) { + // [stack] OBJ + return false; + } + + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); + obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); + if (!obj_) { + return false; + } + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitEnd() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + + if (obj_) { + // The object survived and has a predictable shape: update the original + // bytecode. + if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { + // [stack] OBJ + return false; + } + } + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) { + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() { + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() { + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) { + isClass_ = true; +} + +bool ClassEmitter::emitScopeForNamedClass( + JS::Handle scopeBindings) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCacheForInnerName_.emplace(bce_); + innerNameScope_.emplace(bce_); + if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) { + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + return true; +} + +bool ClassEmitter::emitClass(JS::Handle name) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] + + setName(name); + isDerived_ = false; + + if (!bce_->emitNewInit()) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(JS::Handle name) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] + + setName(name); + isDerived_ = true; + + InternalIfEmitter ifThenElse(bce_); + + // Heritage must be null or a non-generator constructor + if (!bce_->emit1(JSOP_CHECKCLASSHERITAGE)) { + // [stack] HERITAGE + return false; + } + + // [IF] (heritage !== null) + if (!bce_->emit1(JSOP_DUP)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emit1(JSOP_NULL)) { + // [stack] HERITAGE HERITAGE NULL + return false; + } + if (!bce_->emit1(JSOP_STRICTNE)) { + // [stack] HERITAGE NE + return false; + } + + // [THEN] funProto = heritage, objProto = heritage.prototype + if (!ifThenElse.emitThenElse()) { + return false; + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_GETPROP)) { + // [stack] HERITAGE PROTO + return false; + } + + // [ELSE] funProto = %FunctionPrototype%, objProto = null + if (!ifThenElse.emitElse()) { + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + if (!bce_->emit2(JSOP_BUILTINPROTO, JSProto_Function)) { + // [stack] PROTO + return false; + } + if (!bce_->emit1(JSOP_NULL)) { + // [stack] PROTO NULL + return false; + } + + // [ENDIF] + if (!ifThenElse.emitEnd()) { + return false; + } + + if (!bce_->emit1(JSOP_OBJWITHPROTO)) { + // [stack] HERITAGE HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] HOMEOBJ HERITAGE + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +void ClassEmitter::setName(JS::Handle name) { + name_ = name; + if (!name_) { + name_ = bce_->cx->names().empty; + } +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emitDupAt(1)) { + // [stack] HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_INITHOMEOBJECT)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, + const Maybe& classEnd) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + if (classStart && classEnd) { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), + ptrdiff_t(*classEnd))) { + return false; + } + } + + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::initProtoAndCtor() { + // [stack] HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor); + + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR + return false; + } + + if (name_ != bce_->cx->names().empty) { + MOZ_ASSERT(tdzCacheForInnerName_.isSome()); + MOZ_ASSERT(innerNameScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + + if (!innerNameScope_->leave(bce_)) { + return false; + } + innerNameScope_.reset(); + + if (kind == Kind::Declaration) { + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + tdzCacheForInnerName_.reset(); + } else { + // [stack] CTOR + + MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 000000000000..73d3c67ab8cc --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,722 @@ +/* -*- 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 frontend_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t, ptrdiff_t +#include // uint32_t + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/JSAtom.h" // JSAtom +#include "vm/JSFunction.h" // JSFunction, FunctionPrefixKind +#include "vm/JSScript.h" // FunctionAsyncKind +#include "vm/NativeObject.h" // PlainObject +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter { + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + + // An object which keeps the shape of this object literal. + // This fields is reset to nullptr whenever the object literal turns out to + // have at least one numeric, computed, spread or __proto__ property, or + // the object becomes dictionary mode. + // This field is used only in ObjectEmitter. + JS::Rooted obj_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInitProp | + // | | emitInitGetter | + // | | emitInitSetter | + // | +------------------------------------------------------>+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexProp | + // | | emitInitIndexGetter | + // | | emitInitIndexSetter | + // | +---------------------------------------------------->+ + // | | + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitComputedProp | + // | | emitInitComputedGetter | + // | | emitInitComputedSetter | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInitProp, emitInitGetter, emitInitSetter, + // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter, + // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject( + FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction); + + // @param key + // Property key + // @param isPropertyAnonFunctionOrClass + // True if the property value is an anonymous function or + // an anonymous class + // @param anonFunction + // The anonymous function object for property value + MOZ_MUST_USE bool emitInitProp( + JS::Handle key, bool isPropertyAnonFunctionOrClass = false, + JS::Handle anonFunction = nullptr); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle key); + + MOZ_MUST_USE bool emitInitIndexProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitIndexGetter(); + MOZ_MUST_USE bool emitInitIndexSetter(); + + MOZ_MUST_USE bool emitInitComputedProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitComputedGetter(); + MOZ_MUST_USE bool emitInitComputedSetter(); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe& keyPos, bool isStatic, bool isComputed); + + // @param op + // Opcode for initializing property + // @param prefixKind + // None, Get, or Set + // @param key + // Atom of the property if the property key is not computed + // @param isPropertyAnonFunctionOrClass + // True if the property is either an anonymous function or an + // anonymous class + // @param anonFunction + // Anonymous function object for the property + MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op, + FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass); + + MOZ_MUST_USE bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function); +// oe.emitInitProp(atom_of_prop, true, function_object); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(atom_of_prop); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(offset_of_triple_dots)); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter { + private: + // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later + // when the object is known to have a fixed shape. + ptrdiff_t top_ = 0; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + MOZ_MUST_USE bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode { + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class {}` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(FunctionAsyncKind::Async); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter { + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe innerNameScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+------------------------------------>+-+ + // +-------+ | ^ | + // | [named class] | | + // | emitScopeForNamedClass +-------+ | | + // +-------------------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | + // | emitInitConstructor +-----------------+ + // +-+--------------------------->+->| InitConstructor |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +---------------------------------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ClassState { + // The initial state. + Start, + + // After calling emitScopeForNamedClass. + Scope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor or emitInitDefaultConstructor. + InitConstructor, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; +#endif + + JS::Rooted name_; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScopeForNamedClass( + JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + MOZ_MUST_USE bool emitClass(JS::Handle name); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle name); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor( + const mozilla::Maybe& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + void setName(JS::Handle name); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build index e53106af28f5..779c5b3e9577 100644 --- a/js/src/frontend/moz.build +++ b/js/src/frontend/moz.build @@ -44,6 +44,7 @@ UNIFIED_SOURCES += [ 'LabelEmitter.cpp', 'NameFunctions.cpp', 'NameOpEmitter.cpp', + 'ObjectEmitter.cpp', 'ParseContext.cpp', 'ParseNode.cpp', 'PropOpEmitter.cpp', From b141aa3e7bfa65e02ed704365ea721726e9cdb8e Mon Sep 17 00:00:00 2001 From: David Major Date: Thu, 17 Jan 2019 21:59:06 -0500 Subject: [PATCH 13/19] Bug 1520880: Silence an unused variable warning in the stubbed-out ARM64 interceptor. r=aklotz --- mozglue/misc/interceptor/PatcherDetour.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mozglue/misc/interceptor/PatcherDetour.h b/mozglue/misc/interceptor/PatcherDetour.h index 413b8a846691..4c3d5fe4eaf1 100644 --- a/mozglue/misc/interceptor/PatcherDetour.h +++ b/mozglue/misc/interceptor/PatcherDetour.h @@ -12,6 +12,7 @@ #include "mozilla/ScopeExit.h" #include "mozilla/TypedEnumBits.h" +#include "mozilla/Unused.h" #define COPY_CODES(NBYTES) \ do { \ @@ -137,6 +138,7 @@ class WindowsDllDetourPatcher final : public WindowsDllPatcherBase { continue; } #elif defined(_M_ARM64) + Unused << opcode1; MOZ_RELEASE_ASSERT(false, "Shouldn't get here"); #else #error "Unknown processor type" From 793ed8be234e98ff2efff60e37b66a0485f68bb3 Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Thu, 17 Jan 2019 14:08:30 +1100 Subject: [PATCH 14/19] Bug 1520357 - Rename the JSON formatting entry points r=jonco I'm always forgetting which code path is which. So give both these functions clearer names that say if they're used by profiling or telemetry. --HG-- extra : rebase_source : 8edcabba510bcf7170b7e071f7cb3a21be23b0e4 --- dom/base/nsJSEnvironment.cpp | 2 +- js/public/GCAPI.h | 7 ++++--- js/src/gc/GC.cpp | 8 ++++---- js/src/jsapi-tests/testGCHooks.cpp | 2 +- xpcom/base/CycleCollectedJSRuntime.cpp | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index bdd944f53377..18fa634dc597 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2174,7 +2174,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, if (StaticPrefs::javascript_options_mem_notify() || Telemetry::CanRecordExtended()) { nsString json; - json.Adopt(aDesc.formatJSON(aCx, PR_Now())); + json.Adopt(aDesc.formatJSONTelemetry(aCx, PR_Now())); RefPtr notify = new NotifyGCEndRunnable(std::move(json)); SystemGroup::Dispatch(TaskCategory::GarbageCollection, diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 4abbc8b5f08f..41c94e3f31d4 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -634,15 +634,16 @@ struct JS_PUBLIC_API GCDescription { char16_t* formatSliceMessage(JSContext* cx) const; char16_t* formatSummaryMessage(JSContext* cx) const; - char16_t* formatJSON(JSContext* cx, uint64_t timestamp) const; mozilla::TimeStamp startTime(JSContext* cx) const; mozilla::TimeStamp endTime(JSContext* cx) const; mozilla::TimeStamp lastSliceStart(JSContext* cx) const; mozilla::TimeStamp lastSliceEnd(JSContext* cx) const; - JS::UniqueChars sliceToJSON(JSContext* cx) const; - JS::UniqueChars summaryToJSON(JSContext* cx) const; + char16_t* formatJSONTelemetry(JSContext* cx, uint64_t timestamp) const; + + JS::UniqueChars sliceToJSONProfiler(JSContext* cx) const; + JS::UniqueChars formatJSONProfiler(JSContext* cx) const; JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSContext* cx) const; }; diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index 442478a82bfc..a57edf1dddb7 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -8592,8 +8592,8 @@ JS::dbg::GarbageCollectionEvent::Ptr JS::GCDescription::toGCEvent( cx->runtime()->gc.majorGCCount()); } -char16_t* JS::GCDescription::formatJSON(JSContext* cx, - uint64_t timestamp) const { +char16_t* JS::GCDescription::formatJSONTelemetry(JSContext* cx, + uint64_t timestamp) const { UniqueChars cstr = cx->runtime()->gc.stats().renderJsonMessage(timestamp); size_t nchars = strlen(cstr.get()); @@ -8623,13 +8623,13 @@ TimeStamp JS::GCDescription::lastSliceEnd(JSContext* cx) const { return cx->runtime()->gc.stats().slices().back().end; } -JS::UniqueChars JS::GCDescription::sliceToJSON(JSContext* cx) const { +JS::UniqueChars JS::GCDescription::sliceToJSONProfiler(JSContext* cx) const { size_t slices = cx->runtime()->gc.stats().slices().length(); MOZ_ASSERT(slices > 0); return cx->runtime()->gc.stats().renderJsonSlice(slices - 1); } -JS::UniqueChars JS::GCDescription::summaryToJSON(JSContext* cx) const { +JS::UniqueChars JS::GCDescription::formatJSONProfiler(JSContext* cx) const { return cx->runtime()->gc.stats().renderJsonMessage(0, false); } diff --git a/js/src/jsapi-tests/testGCHooks.cpp b/js/src/jsapi-tests/testGCHooks.cpp index 9648e74bfdcb..93160f4d5561 100644 --- a/js/src/jsapi-tests/testGCHooks.cpp +++ b/js/src/jsapi-tests/testGCHooks.cpp @@ -24,7 +24,7 @@ static void NonIncrementalGCSliceCallback(JSContext* cx, if (progress == GC_CYCLE_END) { mozilla::UniquePtr summary(desc.formatSummaryMessage(cx)); mozilla::UniquePtr message(desc.formatSliceMessage(cx)); - mozilla::UniquePtr json(desc.formatJSON(cx, 0)); + mozilla::UniquePtr json(desc.formatJSONTelemetry(cx, 0)); } } diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 901930444eda..83dfde8f2afe 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -800,12 +800,12 @@ void CycleCollectedJSRuntime::TraverseNativeRoots( profiler_add_marker( "GCMajor", MakeUnique( aDesc.startTime(aContext), aDesc.endTime(aContext), - aDesc.summaryToJSON(aContext))); + aDesc.formatJSONProfiler(aContext))); } else if (aProgress == JS::GC_SLICE_END) { profiler_add_marker("GCSlice", MakeUnique( aDesc.lastSliceStart(aContext), aDesc.lastSliceEnd(aContext), - aDesc.sliceToJSON(aContext))); + aDesc.sliceToJSONProfiler(aContext))); } } #endif From 5c5463c4f0135be2c262dc483d8775265b806c0f Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Fri, 18 Jan 2019 14:42:58 +1100 Subject: [PATCH 15/19] Bug 1520357 - renderJsonMessage is now parametized for how it was called r=jonco --HG-- extra : rebase_source : 9e3d2e751db83c8028d487a9aa104b0eb156bea3 --- js/src/gc/GC.cpp | 6 ++++-- js/src/gc/Statistics.cpp | 5 ++--- js/src/gc/Statistics.h | 12 ++++++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index a57edf1dddb7..15c9161ad5ef 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -8594,7 +8594,8 @@ JS::dbg::GarbageCollectionEvent::Ptr JS::GCDescription::toGCEvent( char16_t* JS::GCDescription::formatJSONTelemetry(JSContext* cx, uint64_t timestamp) const { - UniqueChars cstr = cx->runtime()->gc.stats().renderJsonMessage(timestamp); + UniqueChars cstr = cx->runtime()->gc.stats().renderJsonMessage(timestamp, + gcstats::Statistics::JSONUse::TELEMETRY); size_t nchars = strlen(cstr.get()); UniqueTwoByteChars out(js_pod_malloc(nchars + 1)); @@ -8630,7 +8631,8 @@ JS::UniqueChars JS::GCDescription::sliceToJSONProfiler(JSContext* cx) const { } JS::UniqueChars JS::GCDescription::formatJSONProfiler(JSContext* cx) const { - return cx->runtime()->gc.stats().renderJsonMessage(0, false); + return cx->runtime()->gc.stats().renderJsonMessage(0, + js::gcstats::Statistics::JSONUse::PROFILER); } JS_PUBLIC_API JS::UniqueChars JS::MinorGcToJSON(JSContext* cx) { diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 7be1f1480010..5c9bd20a699e 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -585,8 +585,7 @@ void Statistics::writeLogMessage(const char* fmt, ...) { } #endif -UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, - bool includeSlices) const { +UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, Statistics::JSONUse use) const { /* * The format of the JSON message is specified by the GCMajorMarkerPayload * type in perf.html @@ -609,7 +608,7 @@ UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, json.property("status", "completed"); // JSON Key #1 formatJsonDescription(timestamp, json); // #2-22 - if (includeSlices) { + if (use == Statistics::JSONUse::TELEMETRY) { json.beginListProperty("slices_list"); // #23 for (unsigned i = 0; i < slices_.length(); i++) { formatJsonSlice(i, json); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 10cc68e0e0cb..2a9f4f431cd4 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -286,10 +286,14 @@ struct Statistics { // Print total profile times on shutdown. void printTotalProfileTimes(); - // Return JSON for a whole major GC, optionally including detailed - // per-slice data. - UniqueChars renderJsonMessage(uint64_t timestamp, - bool includeSlices = true) const; + enum JSONUse { + TELEMETRY, + PROFILER + }; + + // Return JSON for a whole major GC. If use == PROFILER then + // detailed per-slice data will be included. + UniqueChars renderJsonMessage(uint64_t timestamp, JSONUse use) const; // Return JSON for the timings of just the given slice. UniqueChars renderJsonSlice(size_t sliceNum) const; From ef86d9e531473d5b22ebdfa14e07e123f89c3fd2 Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Tue, 8 Jan 2019 12:43:39 +1100 Subject: [PATCH 16/19] Bug 1517409 - (part 1) Fix path in comments r=jonco Update this path in these comments since the file it refers to was moved. --HG-- extra : rebase_source : 08d4d7e08874abeb52c534a16cec3f4c5363a91c --- js/src/gc/Statistics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 5c9bd20a699e..91dd4cbd407b 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -629,7 +629,7 @@ void Statistics::formatJsonDescription(uint64_t timestamp, JSONPrinter& json) const { // If you change JSON properties here, please update: // Telemetry ping code: - // toolkit/components/telemetry/GCTelemetry.jsm + // toolkit/components/telemetry/other/GCTelemetry.jsm // Telemetry documentation: // toolkit/components/telemetry/docs/data/main-ping.rst // Telemetry tests: @@ -693,7 +693,7 @@ void Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice, JSONPrinter& json) const { // If you change JSON properties here, please update: // Telemetry ping code: - // toolkit/components/telemetry/GCTelemetry.jsm + // toolkit/components/telemetry/other/GCTelemetry.jsm // Telemetry documentation: // toolkit/components/telemetry/docs/data/main-ping.rst // Telemetry tests: From 6966ea635d1f4392ff15e529eb13d85e7c05c87a Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Fri, 11 Jan 2019 16:16:14 +1100 Subject: [PATCH 17/19] Bug 1517409 - (part 2) Rename HeapUsage to HeapSize r=jonco --HG-- extra : rebase_source : 6ff3ef839321829499574cc85c266e8c6f506911 --- js/src/builtin/TestingFunctions.cpp | 4 ++-- js/src/gc/Allocator.cpp | 6 +++--- js/src/gc/GC.cpp | 26 +++++++++++++------------- js/src/gc/GCRuntime.h | 4 ++-- js/src/gc/Heap.h | 8 ++++---- js/src/gc/Nursery.cpp | 2 +- js/src/gc/Statistics.cpp | 2 +- js/src/gc/Zone.cpp | 2 +- js/src/gc/Zone.h | 4 ++-- js/src/jsfriendapi.cpp | 2 +- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 488d717ef606..bab1e61c3e6a 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -418,7 +418,7 @@ static bool GC(JSContext* cx, unsigned argc, Value* vp) { } #ifndef JS_MORE_DETERMINISTIC - size_t preBytes = cx->runtime()->gc.usage.gcBytes(); + size_t preBytes = cx->runtime()->gc.heapSize.gcBytes(); #endif if (zone) { @@ -433,7 +433,7 @@ static bool GC(JSContext* cx, unsigned argc, Value* vp) { char buf[256] = {'\0'}; #ifndef JS_MORE_DETERMINISTIC SprintfLiteral(buf, "before %zu, after %zu\n", preBytes, - cx->runtime()->gc.usage.gcBytes()); + cx->runtime()->gc.heapSize.gcBytes()); #endif return ReturnStringCopy(cx, args, buf); } diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index bec15c42fbfd..0d846da72921 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -349,7 +349,7 @@ bool GCRuntime::gcIfNeededAtAllocation(JSContext* cx) { // an incremental GC, we're growing faster than we're GCing, so stop // the world and do a full, non-incremental GC right now, if possible. if (isIncrementalGCInProgress() && - cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) { + cx->zone()->zoneSize.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) { PrepareZoneForGC(cx->zone()); gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW); } @@ -567,11 +567,11 @@ Arena* GCRuntime::allocateArena(Chunk* chunk, Zone* zone, AllocKind thingKind, // Fail the allocation if we are over our heap size limits. if ((checkThresholds != ShouldCheckThresholds::DontCheckThresholds) && - (usage.gcBytes() >= tunables.gcMaxBytes())) + (heapSize.gcBytes() >= tunables.gcMaxBytes())) return nullptr; Arena* arena = chunk->allocateArena(rt, zone, thingKind, lock); - zone->usage.addGCArena(); + zone->zoneSize.addGCArena(); // Trigger an incremental slice if needed. if (checkThresholds != ShouldCheckThresholds::DontCheckThresholds) { diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index 15c9161ad5ef..cdfbd6ce4447 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -892,7 +892,7 @@ void Chunk::updateChunkListAfterFree(JSRuntime* rt, const AutoLockGC& lock) { } void GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) { - arena->zone->usage.removeGCArena(); + arena->zone->zoneSize.removeGCArena(); arena->chunk()->releaseArena(rt, arena, lock); } @@ -902,7 +902,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) atomsZone(nullptr), stats_(rt), marker(rt), - usage(nullptr), + heapSize(nullptr), rootsHash(256), nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers. @@ -1379,7 +1379,7 @@ bool GCRuntime::setParameter(JSGCParamKey key, uint32_t value, return false; } for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { - zone->threshold.updateAfterGC(zone->usage.gcBytes(), GC_NORMAL, + zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), GC_NORMAL, tunables, schedulingState, lock); } } @@ -1607,7 +1607,7 @@ void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) { default: tunables.resetParameter(key, lock); for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { - zone->threshold.updateAfterGC(zone->usage.gcBytes(), GC_NORMAL, + zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), GC_NORMAL, tunables, schedulingState, lock); } } @@ -1685,7 +1685,7 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) { case JSGC_MAX_MALLOC_BYTES: return mallocCounter.maxBytes(); case JSGC_BYTES: - return uint32_t(usage.gcBytes()); + return uint32_t(heapSize.gcBytes()); case JSGC_MODE: return uint32_t(mode); case JSGC_UNUSED_CHUNKS: @@ -3280,7 +3280,7 @@ void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock) { MOZ_ASSERT(!JS::RuntimeHeapIsCollecting()); - size_t usedBytes = zone->usage.gcBytes(); + size_t usedBytes = zone->zoneSize.gcBytes(); size_t thresholdBytes = zone->threshold.gcTriggerBytes(); if (usedBytes >= thresholdBytes) { @@ -3378,7 +3378,7 @@ void GCRuntime::maybeGC(Zone* zone) { float threshold = zone->threshold.eagerAllocTrigger( schedulingState.inHighFrequencyGCMode()); - float usedBytes = zone->usage.gcBytes(); + float usedBytes = zone->zoneSize.gcBytes(); if (usedBytes > 1024 * 1024 && usedBytes >= threshold && !isIncrementalGCInProgress() && !isBackgroundSweeping()) { stats().recordTrigger(usedBytes, threshold); @@ -5782,7 +5782,7 @@ IncrementalProgress GCRuntime::endSweepingSweepGroup(FreeOp* fop, for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) { AutoLockGC lock(rt); zone->changeGCState(Zone::Sweep, Zone::Finished); - zone->threshold.updateAfterGC(zone->usage.gcBytes(), invocationKind, + zone->threshold.updateAfterGC(zone->zoneSize.gcBytes(), invocationKind, tunables, schedulingState, lock); zone->updateAllGCMallocCountersOnGCEnd(lock); zone->arenas.unmarkPreMarkedFreeCells(); @@ -7258,7 +7258,7 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC( continue; } - if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) { + if (zone->zoneSize.gcBytes() >= zone->threshold.gcTriggerBytes()) { CheckZoneIsScheduled(zone, reason, "GC bytes"); budget.makeUnlimited(); stats().nonincremental(AbortReason::GCBytesTrigger); @@ -7314,7 +7314,7 @@ class AutoScheduleZonesForGC { // This is a heuristic to reduce the total number of collections. bool inHighFrequencyMode = gc->schedulingState.inHighFrequencyGCMode(); - if (zone->usage.gcBytes() >= + if (zone->zoneSize.gcBytes() >= zone->threshold.eagerAllocTrigger(inHighFrequencyMode)) { zone->scheduleGC(); } @@ -8136,7 +8136,7 @@ void GCRuntime::mergeRealms(Realm* source, Realm* target) { targetZoneIsCollecting); target->zone()->addTenuredAllocsSinceMinorGC( source->zone()->getAndResetTenuredAllocsSinceMinorGC()); - target->zone()->usage.adopt(source->zone()->usage); + target->zone()->zoneSize.adopt(source->zone()->zoneSize); target->zone()->adoptUniqueIds(source->zone()); target->zone()->adoptMallocBytes(source->zone()); @@ -8723,7 +8723,7 @@ namespace MemInfo { static bool GCBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setNumber(double(cx->runtime()->gc.usage.gcBytes())); + args.rval().setNumber(double(cx->runtime()->gc.heapSize.gcBytes())); return true; } @@ -8772,7 +8772,7 @@ static bool MinorGCCountGetter(JSContext* cx, unsigned argc, Value* vp) { static bool ZoneGCBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setNumber(double(cx->zone()->usage.gcBytes())); + args.rval().setNumber(double(cx->zone()->zoneSize.gcBytes())); return true; } diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index adf61fdccdf4..8f96df8c871f 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -714,8 +714,8 @@ class GCRuntime { Vector unmarkGrayStack; - /* Track heap usage for this runtime. */ - HeapUsage usage; + /* Track heap size for this runtime. */ + HeapSize heapSize; /* GC scheduling state and parameters. */ GCSchedulingTunables tunables; diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 43ce7748da23..f641634888a0 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -842,12 +842,12 @@ static_assert( * Tracks the used sizes for owned heap data and automatically maintains the * memory usage relationship between GCRuntime and Zones. */ -class HeapUsage { +class HeapSize { /* * A heap usage that contains our parent's heap usage, or null if this is * the top-level usage container. */ - HeapUsage* const parent_; + HeapSize* const parent_; /* * The approximate number of bytes in use on the GC heap, to the nearest @@ -861,7 +861,7 @@ class HeapUsage { gcBytes_; public: - explicit HeapUsage(HeapUsage* parent) : parent_(parent), gcBytes_(0) {} + explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {} size_t gcBytes() const { return gcBytes_; } @@ -880,7 +880,7 @@ class HeapUsage { } /* Pair to adoptArenas. Adopts the attendant usage statistics. */ - void adopt(HeapUsage& other) { + void adopt(HeapSize& other) { gcBytes_ += other.gcBytes_; other.gcBytes_ = 0; } diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index adb217fc9b61..3c020ebd1234 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -828,7 +828,7 @@ void js::Nursery::collect(JS::gcreason::Reason reason) { // We ignore gcMaxBytes when allocating for minor collection. However, if we // overflowed, we disable the nursery. The next time we allocate, we'll fail // because gcBytes >= gcMaxBytes. - if (rt->gc.usage.gcBytes() >= tunables().gcMaxBytes()) { + if (rt->gc.heapSize.gcBytes() >= tunables().gcMaxBytes()) { disable(); } // Disable the nursery if the user changed the configuration setting. The diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 91dd4cbd407b..86dbac354eaf 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -970,7 +970,7 @@ void Statistics::beginGC(JSGCInvocationKind kind) { gckind = kind; nonincrementalReason_ = gc::AbortReason::None; - preBytes = runtime->gc.usage.gcBytes(); + preBytes = runtime->gc.heapSize.gcBytes(); startingMajorGCNumber = runtime->gc.majorGCCount(); startingSliceNumber = runtime->gc.gcNumber(); } diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index d5cf1f3595d2..9994aec574a2 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -52,7 +52,7 @@ JS::Zone::Zone(JSRuntime* rt) functionToStringCache_(this), keepAtomsCount(this, 0), purgeAtomsDeferred(this, 0), - usage(&rt->gc.usage), + zoneSize(&rt->gc.heapSize), threshold(), gcDelayBytes(0), tenuredStrings(this, 0), diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index eeaaaa4dfbb2..1a22ae99c9ec 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -544,8 +544,8 @@ class Zone : public JS::shadow::Zone, return functionToStringCache_.ref(); } - // Track heap usage under this Zone. - js::gc::HeapUsage usage; + // Track heap size under this Zone. + js::gc::HeapSize zoneSize; // Thresholds used to trigger GC. js::gc::ZoneHeapThreshold threshold; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 8aac0f009ddc..00c63c5667e6 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -1415,5 +1415,5 @@ JS_FRIEND_API JS::Value js::MaybeGetScriptPrivate(JSObject* object) { } JS_FRIEND_API uint64_t js::GetGCHeapUsageForObjectZone(JSObject* obj) { - return obj->zone()->usage.gcBytes(); + return obj->zone()->zoneSize.gcBytes(); } From 01b779d015b911e9015c38650f89cfbfb34da53f Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Tue, 15 Jan 2019 11:44:48 +1100 Subject: [PATCH 18/19] Bug 1517409 - (part 3) Rename Statustics::preBytes to preHeapSize r=jonco --HG-- extra : rebase_source : 5d6c7afe2da4d10b34ed4198e3ac6d7c5149a7ff --- js/src/gc/Statistics.cpp | 10 +++++----- js/src/gc/Statistics.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 86dbac354eaf..6b8aca58956d 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -327,7 +327,7 @@ UniqueChars Statistics::formatCompactSummaryMessage() const { zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount, zoneStats.collectedCompartmentCount, zoneStats.compartmentCount, zoneStats.sweptCompartmentCount, - double(preBytes) / bytesPerMiB, + double(preHeapSize) / bytesPerMiB, counts[COUNT_NEW_CHUNK] - counts[COUNT_DESTROY_CHUNK], counts[COUNT_NEW_CHUNK] + counts[COUNT_DESTROY_CHUNK]); if (!fragments.append(DuplicateString(buffer))) { @@ -449,7 +449,7 @@ UniqueChars Statistics::formatDetailedDescription() const { zoneStats.compartmentCount, zoneStats.sweptCompartmentCount, getCount(COUNT_MINOR_GC), getCount(COUNT_STOREBUFFER_OVERFLOW), mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest), - double(preBytes) / bytesPerMiB, + double(preHeapSize) / bytesPerMiB, getCount(COUNT_NEW_CHUNK) - getCount(COUNT_DESTROY_CHUNK), getCount(COUNT_NEW_CHUNK) + getCount(COUNT_DESTROY_CHUNK), double(ArenaSize * getCount(COUNT_ARENA_RELOCATED)) / bytesPerMiB, @@ -675,7 +675,7 @@ void Statistics::formatJsonDescription(uint64_t timestamp, json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_)); // #16 } - json.property("allocated_bytes", preBytes); // #17 + json.property("allocated_bytes", preHeapSize); // #17 uint32_t addedChunks = getCount(COUNT_NEW_CHUNK); if (addedChunks) { json.property("added_chunks", addedChunks); // #18 @@ -741,7 +741,7 @@ Statistics::Statistics(JSRuntime* rt) gcDebugFile(nullptr), nonincrementalReason_(gc::AbortReason::None), allocsSinceMinorGC({0, 0}), - preBytes(0), + preHeapSize(0), thresholdTriggered(false), triggerAmount(0.0), triggerThreshold(0.0), @@ -970,7 +970,7 @@ void Statistics::beginGC(JSGCInvocationKind kind) { gckind = kind; nonincrementalReason_ = gc::AbortReason::None; - preBytes = runtime->gc.heapSize.gcBytes(); + preHeapSize = runtime->gc.heapSize.gcBytes(); startingMajorGCNumber = runtime->gc.majorGCCount(); startingSliceNumber = runtime->gc.gcNumber(); } diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 2a9f4f431cd4..fc5f5adb073c 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -360,8 +360,8 @@ struct Statistics { uint32_t tenured; } allocsSinceMinorGC; - /* Allocated space before the GC started. */ - size_t preBytes; + /* Heap size before the GC started. */ + size_t preHeapSize; /* If the GC was triggered by exceeding some threshold, record the * threshold and the value that exceeded it. */ From 7cace07078ba60c7d051d941cc47ab419ceb5a2a Mon Sep 17 00:00:00 2001 From: Paul Bone Date: Thu, 17 Jan 2019 16:29:10 +1100 Subject: [PATCH 19/19] Bug 1517409 - (part 4) Add a postCapacity measurement r=jonco Alternative version of part 4 which does not add new telemetry. --HG-- extra : rebase_source : fa733a8d1394e1eca4c9f43e0c8ee1c250e736a3 extra : histedit_source : fa838e0cdbaf9c287a9997a2d85f2aa773920987%2Ccbc0208c43057dc5f8424281d90a08d9265d9fbe --- js/src/gc/Statistics.cpp | 11 +++++++++-- js/src/gc/Statistics.h | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 6b8aca58956d..6338bb90f314 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -606,7 +606,7 @@ UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, Statistics::JSONUs json.beginObject(); json.property("status", "completed"); // JSON Key #1 - formatJsonDescription(timestamp, json); // #2-22 + formatJsonDescription(timestamp, json, use); // #2-22 if (use == Statistics::JSONUse::TELEMETRY) { json.beginListProperty("slices_list"); // #23 @@ -626,7 +626,8 @@ UniqueChars Statistics::renderJsonMessage(uint64_t timestamp, Statistics::JSONUs } void Statistics::formatJsonDescription(uint64_t timestamp, - JSONPrinter& json) const { + JSONPrinter& json, + JSONUse use) const { // If you change JSON properties here, please update: // Telemetry ping code: // toolkit/components/telemetry/other/GCTelemetry.jsm @@ -676,6 +677,10 @@ void Statistics::formatJsonDescription(uint64_t timestamp, ExplainAbortReason(nonincrementalReason_)); // #16 } json.property("allocated_bytes", preHeapSize); // #17 + if (use == Statistics::JSONUse::PROFILER) { + json.property("post_heap_size", postHeapSize); + } + uint32_t addedChunks = getCount(COUNT_NEW_CHUNK); if (addedChunks) { json.property("added_chunks", addedChunks); // #18 @@ -742,6 +747,7 @@ Statistics::Statistics(JSRuntime* rt) nonincrementalReason_(gc::AbortReason::None), allocsSinceMinorGC({0, 0}), preHeapSize(0), + postHeapSize(0), thresholdTriggered(false), triggerAmount(0.0), triggerThreshold(0.0), @@ -978,6 +984,7 @@ void Statistics::beginGC(JSGCInvocationKind kind) { void Statistics::endGC() { TimeDuration sccTotal, sccLongest; sccDurations(&sccTotal, &sccLongest); + postHeapSize = runtime->gc.heapSize.gcBytes(); runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC, !zoneStats.isFullCollection()); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index fc5f5adb073c..4f48ac26caac 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -292,7 +292,7 @@ struct Statistics { }; // Return JSON for a whole major GC. If use == PROFILER then - // detailed per-slice data will be included. + // detailed per-slice data and some other fields will be included. UniqueChars renderJsonMessage(uint64_t timestamp, JSONUse use) const; // Return JSON for the timings of just the given slice. @@ -360,8 +360,9 @@ struct Statistics { uint32_t tenured; } allocsSinceMinorGC; - /* Heap size before the GC started. */ + /* Heap size before and after the GC ran. */ size_t preHeapSize; + size_t postHeapSize; /* If the GC was triggered by exceeding some threshold, record the * threshold and the value that exceeded it. */ @@ -443,7 +444,7 @@ struct Statistics { UniqueChars formatDetailedPhaseTimes(const PhaseTimeTable& phaseTimes) const; UniqueChars formatDetailedTotals() const; - void formatJsonDescription(uint64_t timestamp, JSONPrinter&) const; + void formatJsonDescription(uint64_t timestamp, JSONPrinter&, JSONUse) const; void formatJsonSliceDescription(unsigned i, const SliceData& slice, JSONPrinter&) const; void formatJsonPhaseTimes(const PhaseTimeTable& phaseTimes,