mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
chore: cherry-pick 27 changes from chromium, v8, angle, skia, pdfium, libaom (#51137)
* chore: cherry-pick b149a5c62d76 from angle * chore: cherry-pick 4073d491fb55 from chromium * chore: cherry-pick 0566b2f5f0d1 from skia * chore: cherry-pick 8c1ead5a699f from chromium * chore: cherry-pick 8b08fb7c9dce from chromium * chore: cherry-pick be87466afecb from chromium * chore: cherry-pick c215f8e6f049 from chromium * chore: cherry-pick 036e5e8f69be from v8 * chore: cherry-pick a6357144e7bf from chromium * chore: cherry-pick 3f9969421ad5 from skia * chore: cherry-pick ca8a943c247c from pdfium * chore: cherry-pick 07398289d921 from v8 * chore: cherry-pick 41bfbc009df8 from chromium * chore: cherry-pick 4002a66778d2 from chromium * chore: cherry-pick 23865499a86a from chromium * chore: cherry-pick 7c11e1188705 from dawn * chore: cherry-pick c81f01b469c4 from chromium * chore: cherry-pick 1b69067db7d2 from chromium * chore: cherry-pick d513cd2fe668 from chromium * chore: cherry-pick bb8d4c29dfdb from chromium * chore: cherry-pick 847b11ad2fa3 from chromium * chore: cherry-pick bce2e6728279 from pdfium * chore: cherry-pick eeb3e031eb89 from chromium * chore: cherry-pick a068030f5179 from v8 * chore: cherry-pick 4 changes from libaom and add new patch dirs to config.json * chore: update patches (e sync --3 resolved; drop dawn — no M146 upstream merge) * chore: update patches --------- Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
This commit is contained in:
1
patches/angle/.patches
Normal file
1
patches/angle/.patches
Normal file
@@ -0,0 +1 @@
|
||||
cherry-pick-b149a5c62d76.patch
|
||||
117
patches/angle/cherry-pick-b149a5c62d76.patch
Normal file
117
patches/angle/cherry-pick-b149a5c62d76.patch
Normal file
@@ -0,0 +1,117 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Geoff Lang <geofflang@chromium.org>
|
||||
Date: Fri, 27 Mar 2026 16:13:31 -0400
|
||||
Subject: GL: Fix pack state for BlitGL::copySubTextureCPUReadback
|
||||
|
||||
copySubTextureCPUReadback does both ReadPixels and TexImage calls and
|
||||
needs to make sure the client's pack states are not used. It does this
|
||||
but in the wrong order causing an invalid pack state to be used for the
|
||||
ReadPixels call.
|
||||
|
||||
Bug: chromium:490170083
|
||||
Change-Id: I93dcabf52edd6e4e08f999aaa0d96d1fc325211a
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7708753
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Commit-Queue: Geoff Lang <geofflang@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/gl/BlitGL.cpp b/src/libANGLE/renderer/gl/BlitGL.cpp
|
||||
index 19780ad029525229cae6a9d07bf6e82dd72ee1aa..787405f204c3adf773b96e943c5f70e8e2aa7e5e 100644
|
||||
--- a/src/libANGLE/renderer/gl/BlitGL.cpp
|
||||
+++ b/src/libANGLE/renderer/gl/BlitGL.cpp
|
||||
@@ -852,10 +852,10 @@ angle::Result BlitGL::copySubTextureCPUReadback(const gl::Context *context,
|
||||
readFunction = angle::ReadColor<angle::R8G8B8A8, GLfloat>;
|
||||
}
|
||||
|
||||
- gl::PixelUnpackState unpack;
|
||||
- unpack.alignment = 1;
|
||||
- ANGLE_TRY(mStateManager->setPixelUnpackState(context, unpack));
|
||||
- ANGLE_TRY(mStateManager->setPixelUnpackBuffer(context, nullptr));
|
||||
+ gl::PixelPackState pack;
|
||||
+ pack.alignment = 1;
|
||||
+ ANGLE_TRY(mStateManager->setPixelPackState(context, pack));
|
||||
+ ANGLE_TRY(mStateManager->setPixelPackBuffer(context, nullptr));
|
||||
ANGLE_GL_TRY(context, mFunctions->readPixels(readPixelsArea.x, readPixelsArea.y,
|
||||
readPixelsArea.width, readPixelsArea.height,
|
||||
readPixelsFormat, GL_UNSIGNED_BYTE, sourceMemory));
|
||||
@@ -870,10 +870,10 @@ angle::Result BlitGL::copySubTextureCPUReadback(const gl::Context *context,
|
||||
destInternalFormatInfo.format, destInternalFormatInfo.componentType, readPixelsArea.width,
|
||||
readPixelsArea.height, 1, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha);
|
||||
|
||||
- gl::PixelPackState pack;
|
||||
- pack.alignment = 1;
|
||||
- ANGLE_TRY(mStateManager->setPixelPackState(context, pack));
|
||||
- ANGLE_TRY(mStateManager->setPixelPackBuffer(context, nullptr));
|
||||
+ gl::PixelUnpackState unpack;
|
||||
+ unpack.alignment = 1;
|
||||
+ ANGLE_TRY(mStateManager->setPixelUnpackState(context, unpack));
|
||||
+ ANGLE_TRY(mStateManager->setPixelUnpackBuffer(context, nullptr));
|
||||
|
||||
nativegl::TexSubImageFormat texSubImageFormat =
|
||||
nativegl::GetTexSubImageFormat(mFunctions, mFeatures, destFormat, destType);
|
||||
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
|
||||
index 74e572d7a9ecb1503fdb46d273b77ab07375719f..201c64ab200def996ffd7d0f8f85a60446e3ba5b 100644
|
||||
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
|
||||
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
|
||||
@@ -80,6 +80,7 @@
|
||||
42264831 : FramebufferTest_ES3.AttachmentsWithUnequalDimensions/* = SKIP_FOR_CAPTURE
|
||||
42264831 : FramebufferTest_ES3.ChangeAttachmentThenInvalidateAndDraw/* = SKIP_FOR_CAPTURE
|
||||
42264831 : FramebufferTest_ES3.RenderAndInvalidateImmutableTextureWithBeyondMaxLevel/* = SKIP_FOR_CAPTURE
|
||||
+490170083 : CopyTextureTestES3.SRGBWithPackParameters/* = SKIP_FOR_CAPTURE
|
||||
|
||||
# The following tests fail with forceRobustResourceInit
|
||||
# They were accidentally passing until http://crrev/c/5588816
|
||||
diff --git a/src/tests/gl_tests/CopyTextureTest.cpp b/src/tests/gl_tests/CopyTextureTest.cpp
|
||||
index cd9bc970b7f646b3e9f037cca3d9e623f8f6afb3..44effc45e132819396c792fbcf947c320bbed0a4 100644
|
||||
--- a/src/tests/gl_tests/CopyTextureTest.cpp
|
||||
+++ b/src/tests/gl_tests/CopyTextureTest.cpp
|
||||
@@ -1988,6 +1988,50 @@ TEST_P(CopyTextureTestDest, AlphaCopyWithRGB)
|
||||
EXPECT_PIXEL_COLOR_EQ(0, 0, expectedPixels);
|
||||
}
|
||||
|
||||
+// Regression test for TextureGL doing CPU readback when a PBO is bound
|
||||
+TEST_P(CopyTextureTestES3, SRGBWithPackParameters)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!checkExtensions());
|
||||
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_sRGB"));
|
||||
+
|
||||
+ GLColor originalPixels(50u, 100u, 150u, 155u);
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D, mTextures[1]);
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &originalPixels);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D, mTextures[0]);
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA_EXT, 1, 1, 0, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE,
|
||||
+ nullptr);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ GLFramebuffer dstFBO;
|
||||
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFBO);
|
||||
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0],
|
||||
+ 0);
|
||||
+
|
||||
+ // Should have no effect on the copy
|
||||
+ glPixelStorei(GL_PACK_SKIP_PIXELS, 100);
|
||||
+ glPixelStorei(GL_PACK_SKIP_ROWS, 100);
|
||||
+ glPixelStorei(GL_PACK_ROW_LENGTH, 100);
|
||||
+ glPixelStorei(GL_PACK_ALIGNMENT, 8);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ std::array<uint8_t, 100 * 100 * 4 * 2> bigPackBuffer = {0};
|
||||
+ glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, bigPackBuffer.data());
|
||||
+
|
||||
+ glCopySubTextureCHROMIUM(mTextures[1], 0, GL_TEXTURE_2D, mTextures[0], 0, 0, 0, 0, 0, 1, 1,
|
||||
+ false, false, false);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
||||
+ glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
||||
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
+
|
||||
+ EXPECT_PIXEL_COLOR_EQ(0, 0, originalPixels);
|
||||
+}
|
||||
+
|
||||
// Bug where TEXTURE_SWIZZLE_RGBA was not reset after the Luminance workaround. (crbug.com/1022080)
|
||||
TEST_P(CopyTextureTestES3, LuminanceWorkaroundTextureSwizzleBug)
|
||||
{
|
||||
@@ -155,3 +155,18 @@ cherry-pick-fc10b0d6304d.patch
|
||||
cherry-pick-41c622eea273.patch
|
||||
fix_initialize_com_on_desktopmedialistcapturethread_on_windows.patch
|
||||
fix_use_fresh_lazynow_for_onendworkitemimpl_after_didruntask.patch
|
||||
cherry-pick-4073d491fb55.patch
|
||||
cherry-pick-8c1ead5a699f.patch
|
||||
cherry-pick-8b08fb7c9dce.patch
|
||||
cherry-pick-be87466afecb.patch
|
||||
cherry-pick-c215f8e6f049.patch
|
||||
cherry-pick-a6357144e7bf.patch
|
||||
cherry-pick-41bfbc009df8.patch
|
||||
cherry-pick-4002a66778d2.patch
|
||||
cherry-pick-23865499a86a.patch
|
||||
cherry-pick-c81f01b469c4.patch
|
||||
cherry-pick-1b69067db7d2.patch
|
||||
cherry-pick-d513cd2fe668.patch
|
||||
cherry-pick-bb8d4c29dfdb.patch
|
||||
cherry-pick-847b11ad2fa3.patch
|
||||
cherry-pick-eeb3e031eb89.patch
|
||||
|
||||
103
patches/chromium/cherry-pick-1b69067db7d2.patch
Normal file
103
patches/chromium/cherry-pick-1b69067db7d2.patch
Normal file
@@ -0,0 +1,103 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Vasilii Sukhanov <vasilii@chromium.org>
|
||||
Date: Wed, 8 Apr 2026 07:48:21 -0700
|
||||
Subject: Fix cross-domain password leak via manual-fallback preview
|
||||
|
||||
In PasswordManualFallbackFlow::DidSelectSuggestion, when a user selects
|
||||
a password suggestion, the browser process sends the cleartext password
|
||||
to the renderer for previewing. If the suggestion is cross-domain, this
|
||||
leak happens without consent or auth.
|
||||
|
||||
This CL fixes this by omitting the password in the preview message for
|
||||
all the cases by sending the fake string.
|
||||
|
||||
Fixed: 498269651
|
||||
Change-Id: Ic9546114c453f05de1030f05c7a9830b39d73038
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7735152
|
||||
Commit-Queue: Vasilii Sukhanov <vasilii@chromium.org>
|
||||
Reviewed-by: Anna Tsvirchkova <atsvirchkova@google.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1611490}
|
||||
|
||||
diff --git a/components/password_manager/core/browser/password_manual_fallback_flow.cc b/components/password_manager/core/browser/password_manual_fallback_flow.cc
|
||||
index d65be8e82a2a8dd202d5eb1644ea3db9f59c18d4..14e18916fbac51665f9f94d99c8bc6ef8afbc112 100644
|
||||
--- a/components/password_manager/core/browser/password_manual_fallback_flow.cc
|
||||
+++ b/components/password_manager/core/browser/password_manual_fallback_flow.cc
|
||||
@@ -211,12 +211,13 @@ void PasswordManualFallbackFlow::DidSelectSuggestion(
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
+ const auto payload =
|
||||
+ suggestion.GetPayload<Suggestion::PasswordSuggestionDetails>();
|
||||
password_manager_driver_->PreviewSuggestionById(
|
||||
form->username_element_renderer_id,
|
||||
form->password_element_renderer_id,
|
||||
GetUsernameFromLabel(suggestion.labels[0][0].value),
|
||||
- suggestion.GetPayload<Suggestion::PasswordSuggestionDetails>()
|
||||
- .password);
|
||||
+ std::u16string(payload.password.length(), '*'));
|
||||
break;
|
||||
}
|
||||
case autofill::SuggestionType::kPasswordFieldByFieldFilling:
|
||||
diff --git a/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc b/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
|
||||
index 866ca1f10b017f48a444742788f3965320647f7c..8789288a9630a921fe0cf79680cf41a54c619e38 100644
|
||||
--- a/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
|
||||
+++ b/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
|
||||
@@ -656,7 +656,7 @@ TEST_F(PasswordManualFallbackFlowTest,
|
||||
EXPECT_CALL(driver(), PreviewSuggestionById(form.username_element_renderer_id,
|
||||
form.password_element_renderer_id,
|
||||
std::u16string(u"username"),
|
||||
- std::u16string(u"password")));
|
||||
+ std::u16string(u"********")));
|
||||
Suggestion suggestion = autofill::test::CreateAutofillSuggestion(
|
||||
SuggestionType::kPasswordEntry, u"google.com",
|
||||
CreateTestPasswordDetails());
|
||||
@@ -667,6 +667,40 @@ TEST_F(PasswordManualFallbackFlowTest,
|
||||
flow().DidSelectSuggestion(suggestion);
|
||||
}
|
||||
|
||||
+// Test that password manual fallback suggestion is previewed without password
|
||||
+// if the suggestion is cross-domain.
|
||||
+TEST_F(PasswordManualFallbackFlowTest,
|
||||
+ SelectFillFullFormSuggestion_CrossDomain_TriggeredOnAPasswordForm) {
|
||||
+ InitializeFlow();
|
||||
+ ProcessPasswordStoreUpdates();
|
||||
+
|
||||
+ PasswordForm form;
|
||||
+ form.username_element_renderer_id = MakeFieldRendererId();
|
||||
+ form.password_element_renderer_id = MakeFieldRendererId();
|
||||
+ // Simulate that the field is/isn't classified as target filling password.
|
||||
+ EXPECT_CALL(password_form_cache(),
|
||||
+ GetPasswordForm(_, form.username_element_renderer_id))
|
||||
+ .WillRepeatedly(Return(&form));
|
||||
+
|
||||
+ flow().RunFlow(form.username_element_renderer_id, gfx::RectF{},
|
||||
+ TextDirection::LEFT_TO_RIGHT);
|
||||
+
|
||||
+ // Expect that the password is empty in the preview call.
|
||||
+ EXPECT_CALL(driver(), PreviewSuggestionById(form.username_element_renderer_id,
|
||||
+ form.password_element_renderer_id,
|
||||
+ std::u16string(u"username"),
|
||||
+ std::u16string(u"********")));
|
||||
+ Suggestion suggestion = autofill::test::CreateAutofillSuggestion(
|
||||
+ SuggestionType::kPasswordEntry, u"google.com",
|
||||
+ Suggestion::PasswordSuggestionDetails(u"username", u"password",
|
||||
+ "https://cross-domain.com/",
|
||||
+ u"cross-domain.com",
|
||||
+ /*is_cross_domain=*/true));
|
||||
+ suggestion.labels = {{Suggestion::Text(u"username")}};
|
||||
+ suggestion.acceptability = Suggestion::Acceptability::kAcceptable;
|
||||
+ flow().DidSelectSuggestion(suggestion);
|
||||
+}
|
||||
+
|
||||
// Test that only password field is previewed if the credential doesn't have
|
||||
// a username saved for it.
|
||||
TEST_F(PasswordManualFallbackFlowTest,
|
||||
@@ -687,7 +721,7 @@ TEST_F(PasswordManualFallbackFlowTest,
|
||||
EXPECT_CALL(driver(), PreviewSuggestionById(FieldRendererId(),
|
||||
form.password_element_renderer_id,
|
||||
std::u16string(),
|
||||
- std::u16string(u"password")));
|
||||
+ std::u16string(u"********")));
|
||||
Suggestion suggestion = autofill::test::CreateAutofillSuggestion(
|
||||
SuggestionType::kPasswordEntry, u"google.com",
|
||||
CreateTestPasswordDetails());
|
||||
224
patches/chromium/cherry-pick-23865499a86a.patch
Normal file
224
patches/chromium/cherry-pick-23865499a86a.patch
Normal file
@@ -0,0 +1,224 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Fergal Daly <fergal@chromium.org>
|
||||
Date: Sun, 12 Apr 2026 20:37:39 -0700
|
||||
Subject: [M146] Fix UAF in FileSystemAccessChangeSource.
|
||||
|
||||
Original change's description:
|
||||
> Fix UAF in FileSystemAccessChangeSource.
|
||||
>
|
||||
> `DidInitialize` calls any outstanding initialization callbacks but a
|
||||
> callback can delete this. The code guards against this in its access
|
||||
> of `initialization_callbacks_` but not `initialization_result_`.
|
||||
>
|
||||
> This fix keeps a copy of the result on the stack.
|
||||
>
|
||||
> This also adds a test which fails with ASAN before the fix is applied
|
||||
> and passes after.
|
||||
>
|
||||
> The basic test code was written by Gemini.
|
||||
>
|
||||
> Fixed: 497880137
|
||||
> Change-Id: I046831db23cb4b8e41964910e2aede9b1be0db7f
|
||||
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7728464
|
||||
> Auto-Submit: Fergal Daly <fergal@chromium.org>
|
||||
> Reviewed-by: Ming-Ying Chung <mych@chromium.org>
|
||||
> Commit-Queue: Ming-Ying Chung <mych@chromium.org>
|
||||
> Cr-Commit-Position: refs/heads/main@{#1610499}
|
||||
|
||||
(cherry picked from commit c0390bcd64ba1fd6594fbc9f6246a1649662d683)
|
||||
|
||||
Bug: 500247135,497880137
|
||||
Change-Id: I046831db23cb4b8e41964910e2aede9b1be0db7f
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7754020
|
||||
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
|
||||
Auto-Submit: Chrome Cherry Picker <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
|
||||
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
|
||||
Cr-Commit-Position: refs/branch-heads/7680@{#3929}
|
||||
Cr-Branched-From: 76b7d80e5cda23fe6537eed26d68c92e995c7f39-refs/heads/main@{#1582197}
|
||||
|
||||
diff --git a/content/browser/file_system_access/file_system_access_change_source.cc b/content/browser/file_system_access/file_system_access_change_source.cc
|
||||
index 566dc1ea40b43a54b33d70e82a20ff5695b57b5e..48bd867a9d3d140eaf515ea7bc1613231f7e79e9 100644
|
||||
--- a/content/browser/file_system_access/file_system_access_change_source.cc
|
||||
+++ b/content/browser/file_system_access/file_system_access_change_source.cc
|
||||
@@ -71,13 +71,14 @@ void FileSystemAccessChangeSource::DidInitialize(
|
||||
CHECK(!initialization_result_.has_value());
|
||||
CHECK(!initialization_callbacks_.empty());
|
||||
|
||||
- initialization_result_ = std::move(result);
|
||||
+ // The callbacks may cause |this| to be deleted, so we should only use
|
||||
+ // stack-based objects below.
|
||||
+ initialization_result_ = result->Clone();
|
||||
|
||||
- // Move the callbacks to the stack since they may cause |this| to be deleted.
|
||||
auto initialization_callbacks = std::move(initialization_callbacks_);
|
||||
initialization_callbacks_.clear();
|
||||
for (auto& callback : initialization_callbacks) {
|
||||
- std::move(callback).Run(initialization_result_->Clone());
|
||||
+ std::move(callback).Run(result->Clone());
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/content/browser/file_system_access/file_system_access_change_source_unittest.cc b/content/browser/file_system_access/file_system_access_change_source_unittest.cc
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..b0f15909bebda29fc2ec689a6d3b15d797dcc722
|
||||
--- /dev/null
|
||||
+++ b/content/browser/file_system_access/file_system_access_change_source_unittest.cc
|
||||
@@ -0,0 +1,146 @@
|
||||
+// Copyright 2026 The Chromium Authors
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+#include "content/browser/file_system_access/file_system_access_change_source.h"
|
||||
+
|
||||
+#include "base/files/scoped_temp_dir.h"
|
||||
+#include "base/functional/bind.h"
|
||||
+#include "base/memory/scoped_refptr.h"
|
||||
+#include "base/task/sequenced_task_runner.h"
|
||||
+#include "base/test/task_environment.h"
|
||||
+#include "base/test/test_future.h"
|
||||
+#include "content/browser/file_system_access/file_system_access_watch_scope.h"
|
||||
+#include "storage/browser/file_system/file_system_context.h"
|
||||
+#include "storage/browser/file_system/file_system_url.h"
|
||||
+#include "storage/browser/quota/quota_manager_proxy.h"
|
||||
+#include "storage/browser/test/test_file_system_context.h"
|
||||
+#include "testing/gmock/include/gmock/gmock.h"
|
||||
+#include "testing/gtest/include/gtest/gtest.h"
|
||||
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom.h"
|
||||
+
|
||||
+namespace content {
|
||||
+
|
||||
+namespace {
|
||||
+
|
||||
+class MockRawChangeObserver
|
||||
+ : public FileSystemAccessChangeSource::RawChangeObserver {
|
||||
+ public:
|
||||
+ MOCK_METHOD(void,
|
||||
+ OnRawChange,
|
||||
+ (const storage::FileSystemURL& changed_url,
|
||||
+ bool error,
|
||||
+ const FileSystemAccessChangeSource::ChangeInfo& change_info,
|
||||
+ const FileSystemAccessWatchScope& scope),
|
||||
+ (override));
|
||||
+ MOCK_METHOD(void,
|
||||
+ OnUsageChange,
|
||||
+ (size_t old_usage,
|
||||
+ size_t new_usage,
|
||||
+ const FileSystemAccessWatchScope& scope),
|
||||
+ (override));
|
||||
+ MOCK_METHOD(void,
|
||||
+ OnSourceBeingDestroyed,
|
||||
+ (FileSystemAccessChangeSource * source),
|
||||
+ (override));
|
||||
+};
|
||||
+
|
||||
+class FakeChangeSource : public FileSystemAccessChangeSource {
|
||||
+ public:
|
||||
+ FakeChangeSource(
|
||||
+ FileSystemAccessWatchScope scope,
|
||||
+ scoped_refptr<storage::FileSystemContext> file_system_context)
|
||||
+ : FileSystemAccessChangeSource(std::move(scope),
|
||||
+ std::move(file_system_context)) {}
|
||||
+ ~FakeChangeSource() override = default;
|
||||
+
|
||||
+ // FileSystemAccessChangeSource:
|
||||
+ void Initialize(
|
||||
+ base::OnceCallback<void(blink::mojom::FileSystemAccessErrorPtr)>
|
||||
+ on_source_initialized) override {
|
||||
+ base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
||||
+ FROM_HERE, base::BindOnce(std::move(on_source_initialized),
|
||||
+ blink::mojom::FileSystemAccessError::New(
|
||||
+ blink::mojom::FileSystemAccessStatus::kOk,
|
||||
+ base::File::FILE_OK, "")));
|
||||
+ }
|
||||
+
|
||||
+ void Signal(const storage::FileSystemURL& changed_url,
|
||||
+ bool error = false,
|
||||
+ ChangeInfo change_info = ChangeInfo()) {
|
||||
+ NotifyOfChange(changed_url, error, change_info);
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+} // namespace
|
||||
+
|
||||
+class FileSystemAccessChangeSourceTest : public testing::Test {
|
||||
+ public:
|
||||
+ FileSystemAccessChangeSourceTest()
|
||||
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
|
||||
+
|
||||
+ void SetUp() override {
|
||||
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
|
||||
+ file_system_context_ = storage::CreateFileSystemContextForTesting(
|
||||
+ /*quota_manager_proxy=*/nullptr, dir_.GetPath());
|
||||
+ }
|
||||
+
|
||||
+ protected:
|
||||
+ base::test::TaskEnvironment task_environment_;
|
||||
+ base::ScopedTempDir dir_;
|
||||
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
|
||||
+};
|
||||
+
|
||||
+TEST_F(FileSystemAccessChangeSourceTest, CreateAndInitialize) {
|
||||
+ auto file_path = dir_.GetPath().AppendASCII("file");
|
||||
+ auto file_url = file_system_context_->CreateCrackedFileSystemURL(
|
||||
+ blink::StorageKey(), storage::kFileSystemTypeLocal, file_path);
|
||||
+
|
||||
+ auto scope = FileSystemAccessWatchScope::GetScopeForFileWatch(file_url);
|
||||
+ FakeChangeSource source(scope, file_system_context_);
|
||||
+
|
||||
+ base::test::TestFuture<blink::mojom::FileSystemAccessErrorPtr> future;
|
||||
+ source.EnsureInitialized(future.GetCallback());
|
||||
+ EXPECT_EQ(future.Get()->status, blink::mojom::FileSystemAccessStatus::kOk);
|
||||
+}
|
||||
+
|
||||
+TEST_F(FileSystemAccessChangeSourceTest, NotifyOfChange) {
|
||||
+ auto file_path = dir_.GetPath().AppendASCII("file");
|
||||
+ auto file_url = file_system_context_->CreateCrackedFileSystemURL(
|
||||
+ blink::StorageKey(), storage::kFileSystemTypeLocal, file_path);
|
||||
+
|
||||
+ auto scope = FileSystemAccessWatchScope::GetScopeForFileWatch(file_url);
|
||||
+ FakeChangeSource source(scope, file_system_context_);
|
||||
+
|
||||
+ MockRawChangeObserver observer;
|
||||
+ source.AddObserver(&observer);
|
||||
+
|
||||
+ EXPECT_CALL(observer, OnRawChange(testing::Eq(file_url), testing::IsFalse(),
|
||||
+ testing::_, testing::Eq(scope)));
|
||||
+ source.Signal(file_url);
|
||||
+
|
||||
+ source.RemoveObserver(&observer);
|
||||
+}
|
||||
+
|
||||
+// A callback passed to `EnsureInitialized` may result in `this` being
|
||||
+// destroyed. This tests that `DidInitialize` (which calls the callbacks) is
|
||||
+// robust to that situation. See https://crbug.com/497880137.
|
||||
+TEST_F(FileSystemAccessChangeSourceTest, TestDestroyFromInitializeCallback) {
|
||||
+ auto file_path = dir_.GetPath().AppendASCII("file");
|
||||
+ auto file_url = file_system_context_->CreateCrackedFileSystemURL(
|
||||
+ blink::StorageKey(), storage::kFileSystemTypeLocal, file_path);
|
||||
+
|
||||
+ auto scope = FileSystemAccessWatchScope::GetScopeForFileWatch(file_url);
|
||||
+ FakeChangeSource* source = new FakeChangeSource(scope, file_system_context_);
|
||||
+
|
||||
+ source->EnsureInitialized(base::BindOnce(
|
||||
+ [](FakeChangeSource* source, blink::mojom::FileSystemAccessErrorPtr) {
|
||||
+ delete source;
|
||||
+ },
|
||||
+ base::Unretained(source)));
|
||||
+ base::test::TestFuture<blink::mojom::FileSystemAccessErrorPtr> future;
|
||||
+ source->EnsureInitialized(future.GetCallback());
|
||||
+ EXPECT_EQ(future.Get()->status, blink::mojom::FileSystemAccessStatus::kOk);
|
||||
+}
|
||||
+
|
||||
+} // namespace content
|
||||
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
|
||||
index 07cbf495717714d71d977a8820e08050c3062526..f5d72a89c7229bf8e897c90660feca482ac82594 100644
|
||||
--- a/content/test/BUILD.gn
|
||||
+++ b/content/test/BUILD.gn
|
||||
@@ -2656,6 +2656,7 @@ test("content_unittests") {
|
||||
"../browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc",
|
||||
"../browser/file_system/browser_file_system_helper_unittest.cc",
|
||||
"../browser/file_system/file_system_operation_runner_unittest.cc",
|
||||
+ "../browser/file_system_access/file_system_access_change_source_unittest.cc",
|
||||
"../browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc",
|
||||
"../browser/file_system_access/file_system_access_file_handle_impl_unittest.cc",
|
||||
"../browser/file_system_access/file_system_access_file_modification_host_impl_unittest.cc",
|
||||
38
patches/chromium/cherry-pick-4002a66778d2.patch
Normal file
38
patches/chromium/cherry-pick-4002a66778d2.patch
Normal file
@@ -0,0 +1,38 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Date: Wed, 1 Apr 2026 11:19:52 -0700
|
||||
Subject: Fix potential double free in deserializing CopyOutputResults
|
||||
|
||||
Skia always runs release proc of the SkBitmap::installPixels [1].
|
||||
|
||||
[1] https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/core/SkBitmap.h;drc=405f385dce2db578ff3b2301686d231ee8f0b042;l=586
|
||||
|
||||
Bug: 497846428
|
||||
Change-Id: I4d3fd2e676fa7aa74e1022cbd2c9d9db8970a90c
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718978
|
||||
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Reviewed-by: Joe Mason <joenotcharles@google.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1608675}
|
||||
|
||||
diff --git a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
|
||||
index 59fbc85e257778d1743688716b314ed983f2f324..9d6bdd42622dffd90d39d6977bdec169baf98170 100644
|
||||
--- a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
|
||||
+++ b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
|
||||
@@ -111,12 +111,14 @@ bool StructTraits<viz::mojom::BitmapInSharedMemoryDataView, SkBitmap>::Read(
|
||||
return false;
|
||||
}
|
||||
|
||||
- if (!sk_bitmap->installPixels(image_info, mapping_ptr->memory(),
|
||||
+ // Skia guarantees that it will call release proc, so we pass release()'ed
|
||||
+ // pointer into it.
|
||||
+ void* bitmap_memory = mapping_ptr->memory();
|
||||
+ if (!sk_bitmap->installPixels(image_info, bitmap_memory,
|
||||
data.row_bytes(), &DeleteSharedMemoryMapping,
|
||||
- mapping_ptr.get())) {
|
||||
+ mapping_ptr.release())) {
|
||||
return false;
|
||||
}
|
||||
- mapping_ptr.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
213
patches/chromium/cherry-pick-4073d491fb55.patch
Normal file
213
patches/chromium/cherry-pick-4073d491fb55.patch
Normal file
@@ -0,0 +1,213 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Matt Menke <mmenke@chromium.org>
|
||||
Date: Fri, 27 Mar 2026 09:19:35 -0700
|
||||
Subject: Make SpdySession::CreateStream() call DoDrainSession()
|
||||
asynchronously.
|
||||
|
||||
Calling it synchronously would tear down all SpdyStreams immediately,
|
||||
informing their consumers of the error. This could have side effects
|
||||
that affect the caller trying to create the stream, so was unsafe.
|
||||
|
||||
This does introduce a state where a SpdySession is going away, but
|
||||
neither DoDrainSession() nor StartGoingAway() was invoked. The
|
||||
SpdySession never reached such a state before this CL, but this state
|
||||
was used before - when there was a network change, we used to move
|
||||
SpdySessions into such a state. This behavior was removed because we
|
||||
ended up never actually closing those sockets, which could effectively
|
||||
blackhole a destination. Since this CL posts a task to drain the
|
||||
session, that shouldn't happen here.
|
||||
|
||||
The code is robust against extra DoDrainSession() calls, so it should
|
||||
be fine if the session discovers through another path it should start
|
||||
draining or otherwise going away,
|
||||
|
||||
Bug: 493628982
|
||||
Change-Id: I23f1517b67fb55edd50d6e8fc8f1b4d8328e8ec5
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7701714
|
||||
Reviewed-by: Kenichi Ishibashi <bashi@chromium.org>
|
||||
Commit-Queue: mmenke <mmenke@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1606281}
|
||||
|
||||
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
|
||||
index 957ebf883fef96af8fea16cb883e8c663dce8b2b..eb811b8b5dc382cf1fbd4cc75a63c416d71f746b 100644
|
||||
--- a/net/socket/socket_test_util.cc
|
||||
+++ b/net/socket/socket_test_util.cc
|
||||
@@ -1251,7 +1251,7 @@ void MockTCPClientSocket::Disconnect() {
|
||||
bool MockTCPClientSocket::IsConnected() const {
|
||||
if (!data_)
|
||||
return false;
|
||||
- return connected_ && !peer_closed_connection_;
|
||||
+ return connected_ && !peer_closed_connection_ && !data_->silently_closed();
|
||||
}
|
||||
|
||||
bool MockTCPClientSocket::IsConnectedAndIdle() const {
|
||||
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
|
||||
index 204fcd3eb95a283783f508153137328e162bbde9..78ebf2140902f14e1f6506c6a1e3654e5fc8c4ff 100644
|
||||
--- a/net/socket/socket_test_util.h
|
||||
+++ b/net/socket/socket_test_util.h
|
||||
@@ -447,6 +447,12 @@ class SocketDataProvider {
|
||||
MockConnect connect_data() const { return connect_; }
|
||||
void set_connect_data(const MockConnect& connect) { connect_ = connect; }
|
||||
|
||||
+ // Makes IsConnected() start returning false for any socket using `this`,
|
||||
+ // without any read or write error. Useful for simulating cases where an
|
||||
+ // IsConnected() call is the first time a socket is revealed to be closed.
|
||||
+ void set_silently_closed() { silently_closed_ = true; }
|
||||
+ bool silently_closed() const { return silently_closed_; }
|
||||
+
|
||||
private:
|
||||
// Called to inform subclasses of initialization.
|
||||
virtual void Reset() = 0;
|
||||
@@ -459,6 +465,8 @@ class SocketDataProvider {
|
||||
// This reflects the default state of TCPClientSockets.
|
||||
bool no_delay_ = true;
|
||||
|
||||
+ bool silently_closed_ = false;
|
||||
+
|
||||
KeepAliveState keep_alive_state_ = KeepAliveState::kDefault;
|
||||
int keep_alive_delay_ = 0;
|
||||
|
||||
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
|
||||
index 7465a28030a65eb4964e537134c60ac64ab54f25..9dfbb5dd63802add67931d2aa0b06aa62d6d7be1 100644
|
||||
--- a/net/spdy/spdy_session.cc
|
||||
+++ b/net/spdy/spdy_session.cc
|
||||
@@ -1682,7 +1682,9 @@ int SpdySession::CreateStream(const SpdyStreamRequest& request,
|
||||
UMA_HISTOGRAM_BOOLEAN("Net.SpdySession.CreateStreamWithSocketConnected",
|
||||
socket_->IsConnected());
|
||||
if (!socket_->IsConnected()) {
|
||||
- DoDrainSession(
|
||||
+ // Since there may be a consumer of the session on the stack, can't call
|
||||
+ // DoDrainSession() synchronously, as it may result in reentrancy.
|
||||
+ DoDrainSessionAsync(
|
||||
ERR_CONNECTION_CLOSED,
|
||||
"Tried to create SPDY stream for a closed socket connection.");
|
||||
return ERR_CONNECTION_CLOSED;
|
||||
@@ -2674,6 +2676,23 @@ void SpdySession::DoDrainSession(Error err,
|
||||
MaybePostWriteLoop();
|
||||
}
|
||||
|
||||
+void SpdySession::DoDrainSessionAsync(Error err,
|
||||
+ std::string description,
|
||||
+ bool force_send_go_away) {
|
||||
+ // Make this unavailable to prevent consumers from pulling it from the session
|
||||
+ // pool again, which could result in an infinite loop, or otherwise running
|
||||
+ // into this error again rather than trying a new connection.
|
||||
+ MakeUnavailable(err);
|
||||
+
|
||||
+ // This will close the socket and inform consumers asynchronously. If
|
||||
+ // something happens before this task runs (like a read error), that should
|
||||
+ // not cause issues, since DoDrainSession() does nothing if already draining.
|
||||
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||||
+ FROM_HERE,
|
||||
+ base::BindOnce(&SpdySession::DoDrainSession, weak_factory_.GetWeakPtr(),
|
||||
+ err, std::move(description), force_send_go_away));
|
||||
+}
|
||||
+
|
||||
void SpdySession::LogAbandonedStream(SpdyStream* stream, Error status) {
|
||||
DCHECK(stream);
|
||||
stream->LogStreamError(status, "Abandoned.");
|
||||
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
|
||||
index b36f8fae637e49cb82141cf59d123a49c9c931ac..a5896d76fbd73c5dfd0abcae77d5dfaadbaa3d54 100644
|
||||
--- a/net/spdy/spdy_session.h
|
||||
+++ b/net/spdy/spdy_session.h
|
||||
@@ -857,6 +857,15 @@ class NET_EXPORT SpdySession
|
||||
const std::string& description,
|
||||
bool force_send_go_away = false);
|
||||
|
||||
+ // Immediately marks a session as unavailable, to prevent reuse, and posts a
|
||||
+ // task to call DoDrainSession (if the session is drained for some other
|
||||
+ // reason in the meantime, that is fine). This should be used instead of
|
||||
+ // DoDrainSession when there may be a consumer of the SpdySession on the
|
||||
+ // stack, so as to avoid reentrancy.
|
||||
+ void DoDrainSessionAsync(Error err,
|
||||
+ std::string description,
|
||||
+ bool force_send_go_away = false);
|
||||
+
|
||||
// Called right before closing a (possibly-inactive) stream for a
|
||||
// reason other than being requested to by the stream.
|
||||
void LogAbandonedStream(SpdyStream* stream, Error status);
|
||||
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
|
||||
index 44c9ea47b39fe8c3efbe5e39f0b6d028fa73af97..6e808d05d16f144afdfdae229ec4c8f110d4fde0 100644
|
||||
--- a/net/spdy/spdy_session_unittest.cc
|
||||
+++ b/net/spdy/spdy_session_unittest.cc
|
||||
@@ -976,10 +976,14 @@ TEST_F(SpdySessionTest, CreateStreamAfterGoAway) {
|
||||
EXPECT_TRUE(session_->IsStreamActive(1));
|
||||
|
||||
SpdyStreamRequest stream_request;
|
||||
+ // Note that `can_send_early` is needed to bypass confirming the handshake. If
|
||||
+ // this regresses, may need to do what other tests to, and use
|
||||
+ // CreateStreamSynchronously() to create an initial SpdyStream and set up the
|
||||
+ // socket.
|
||||
int rv = stream_request.StartRequest(
|
||||
- SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_, false, MEDIUM,
|
||||
- SocketTag(), NetLogWithSource(), CompletionOnceCallback(),
|
||||
- TRAFFIC_ANNOTATION_FOR_TESTS);
|
||||
+ SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_,
|
||||
+ /*can_send_early=*/true, MEDIUM, SocketTag(), NetLogWithSource(),
|
||||
+ CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS);
|
||||
EXPECT_THAT(rv, IsError(ERR_FAILED));
|
||||
|
||||
EXPECT_TRUE(session_);
|
||||
@@ -2835,6 +2839,62 @@ TEST_F(SpdySessionTest, CancelTwoStalledCreateStream) {
|
||||
EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST));
|
||||
}
|
||||
|
||||
+// Check that SpdyStreamRequest::StartRequest() does not synchronously notify
|
||||
+// live streams of their destruction when it notices the socket has been closed.
|
||||
+// This can racily happen when a new request occurs before a read error from the
|
||||
+// socket is processed. This synchronously informing other streams of their
|
||||
+// destruction could result in modifying objects that are on the top of the
|
||||
+// callstack due to shared state, which can lead to bugs.
|
||||
+TEST_F(SpdySessionTest,
|
||||
+ SpdyStreamRequestStartRequestAsynchronouslyNotifiesOtherStreams) {
|
||||
+ StaticSocketDataProvider data;
|
||||
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
|
||||
+ AddSSLSocketData();
|
||||
+
|
||||
+ CreateNetworkSession();
|
||||
+ CreateSpdySession();
|
||||
+
|
||||
+ // Create a stream on the session, and set up a delegate to watch it.
|
||||
+ base::WeakPtr<SpdyStream> spdy_stream =
|
||||
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_,
|
||||
+ test_url_, MEDIUM, NetLogWithSource());
|
||||
+ test::StreamDelegateDoNothing delegate(spdy_stream);
|
||||
+ spdy_stream->SetDelegate(&delegate);
|
||||
+
|
||||
+ // Close the socket, without a read/write event, to simulate the
|
||||
+ // StartRequest() being the first call to notice the socket is closed.
|
||||
+ data.set_silently_closed();
|
||||
+
|
||||
+ // Start a StreamRequest request. Note that `can_send_early` must be true to
|
||||
+ // avoid calling MockSSLClientSocket::ConfirmHandshake(), will cause the
|
||||
+ // request not to check the state of the connection, while it waits for the
|
||||
+ // SSL handshake to be confirmed (that handshake confirmation check will also
|
||||
+ // cause the MockSSLClientSocket to CHECK, if it happens, as the socket is
|
||||
+ // closed).
|
||||
+ SpdyStreamRequest request;
|
||||
+ int rv = request.StartRequest(
|
||||
+ SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_,
|
||||
+ /*can_send_early=*/true, LOWEST, SocketTag(), NetLogWithSource(),
|
||||
+ base::BindOnce([](int result) {
|
||||
+ ADD_FAILURE()
|
||||
+ << "Callback should not be invoked on synchronous completion";
|
||||
+ }),
|
||||
+ TRAFFIC_ANNOTATION_FOR_TESTS);
|
||||
+ // The request should synchronously fail.
|
||||
+ EXPECT_THAT(rv, IsError(ERR_CONNECTION_CLOSED));
|
||||
+
|
||||
+ // The session should be flagged as going away, and should no longer be
|
||||
+ // available but should still exist.
|
||||
+ EXPECT_TRUE(session_->IsGoingAway());
|
||||
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
|
||||
+ // The first stream should not have been closed synchronously. Instead, a task
|
||||
+ // should have been posted to close it.
|
||||
+ EXPECT_FALSE(delegate.StreamIsClosed());
|
||||
+
|
||||
+ // Wait for the stream to be closed.
|
||||
+ EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED));
|
||||
+}
|
||||
+
|
||||
// Test that SpdySession::DoReadLoop reads data from the socket
|
||||
// without yielding. This test makes 32k - 1 bytes of data available
|
||||
// on the socket for reading. It then verifies that it has read all
|
||||
60
patches/chromium/cherry-pick-41bfbc009df8.patch
Normal file
60
patches/chromium/cherry-pick-41bfbc009df8.patch
Normal file
@@ -0,0 +1,60 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tommy Steimel <steimel@chromium.org>
|
||||
Date: Tue, 31 Mar 2026 16:11:55 -0700
|
||||
Subject: [Media Session] Don't assume there is still 1 normal player
|
||||
|
||||
There are some actions in MediaSessionImpl that are only available when
|
||||
there is exactly 1 normal player, so when they're called, there's a
|
||||
DCHECK that we do in fact have 1 normal player. However, since Mojo
|
||||
calls are asynchronous, it's possible for one of these actions to be
|
||||
legitimately called with 1 normal player, but by the time it runs there
|
||||
are either 0 or 2+ normal players.
|
||||
|
||||
This CL changes these instances to no longer DCHECK that there is 1
|
||||
normal player and instead just early return if there isn't.
|
||||
|
||||
Bug: 497412658
|
||||
Change-Id: I0fdf3c6779c224db996091b2fd463bc3cb9464f3
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7719021
|
||||
Reviewed-by: Benjamin Keen <bkeen@google.com>
|
||||
Commit-Queue: Tommy Steimel <steimel@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1608166}
|
||||
|
||||
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
|
||||
index 542bd7ee9ab011b94d41517286334b75a94b47f8..24115ab4024a981334f1f9111d9017b37bf634b4 100644
|
||||
--- a/content/browser/media/session/media_session_impl.cc
|
||||
+++ b/content/browser/media/session/media_session_impl.cc
|
||||
@@ -1290,7 +1290,6 @@ void MediaSessionImpl::EnterPictureInPicture() {
|
||||
return;
|
||||
}
|
||||
|
||||
- DCHECK_EQ(normal_players_.size(), 1u);
|
||||
if (normal_players_.size() != 1u) {
|
||||
// There should be one and only one player when we enter picture-in-picture.
|
||||
return;
|
||||
@@ -1355,13 +1354,23 @@ void MediaSessionImpl::Raise() {
|
||||
}
|
||||
|
||||
void MediaSessionImpl::SetMute(bool mute) {
|
||||
- DCHECK_EQ(normal_players_.size(), 1u);
|
||||
+ // The SetMute action should only be available when there is one normal
|
||||
+ // player, though due to the asynchronous nature of mojo, we may no longer
|
||||
+ // have 1 normal player. In that case, just return.
|
||||
+ if (normal_players_.size() != 1u) {
|
||||
+ return;
|
||||
+ }
|
||||
normal_players_.begin()->first.observer->OnSetMute(
|
||||
normal_players_.begin()->first.player_id, mute);
|
||||
}
|
||||
|
||||
void MediaSessionImpl::RequestMediaRemoting() {
|
||||
- DCHECK_EQ(normal_players_.size(), 1u);
|
||||
+ // The RequestMediaRemoting action should only be available when there is one
|
||||
+ // normal player, though due to the asynchronous nature of mojo, we may no
|
||||
+ // longer have 1 normal player. In that case, just return.
|
||||
+ if (normal_players_.size() != 1u) {
|
||||
+ return;
|
||||
+ }
|
||||
normal_players_.begin()->first.observer->OnRequestMediaRemoting(
|
||||
normal_players_.begin()->first.player_id);
|
||||
}
|
||||
203
patches/chromium/cherry-pick-847b11ad2fa3.patch
Normal file
203
patches/chromium/cherry-pick-847b11ad2fa3.patch
Normal file
@@ -0,0 +1,203 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Joey Arhar <jarhar@chromium.org>
|
||||
Date: Fri, 10 Apr 2026 12:19:11 -0700
|
||||
Subject: Fix crashes when restoring <selectedcontent> with <input>
|
||||
|
||||
When restoring form control state, the DOM could be modified to add or
|
||||
remove more listed elements to the form if a select element is being
|
||||
restored which has a selectedcontent element.
|
||||
|
||||
Fixed: 499384399
|
||||
Change-Id: I18f69c30ae25396c53625f7a3172626b79de3ae3
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7732030
|
||||
Reviewed-by: Joey Arhar <jarhar@chromium.org>
|
||||
Commit-Queue: Joey Arhar <jarhar@chromium.org>
|
||||
Reviewed-by: Dominic Farolino <dom@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1613032}
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/html/forms/form_controller.cc b/third_party/blink/renderer/core/html/forms/form_controller.cc
|
||||
index cb0e37de57188c17dcdec899573b4a6c6396932d..214eb921095c52c02e4a16b4783897953dff2a79 100644
|
||||
--- a/third_party/blink/renderer/core/html/forms/form_controller.cc
|
||||
+++ b/third_party/blink/renderer/core/html/forms/form_controller.cc
|
||||
@@ -492,8 +492,10 @@ void FormController::RestoreControlStateIn(HTMLFormElement& form) {
|
||||
if (!document_->HasFinishedParsing())
|
||||
return;
|
||||
EventQueueScope scope;
|
||||
- const ListedElement::List& elements = form.ListedElements();
|
||||
- for (const auto& control : elements) {
|
||||
+ // Make a copy of the list because the DOM could be modified during
|
||||
+ // restoration of a <select> with a <selectedcontent> element.
|
||||
+ ListedElement::List elements_copy(form.ListedElements());
|
||||
+ for (const auto& control : elements_copy) {
|
||||
if (!control->ClassSupportsStateRestore())
|
||||
continue;
|
||||
if (OwnerFormForState(*control) != &form)
|
||||
@@ -550,7 +552,11 @@ void FormController::RestoreAllControlsInDocumentOrder() {
|
||||
return;
|
||||
HeapHashSet<Member<HTMLFormElement>> finished_forms;
|
||||
EventQueueScope scope;
|
||||
- for (auto& control : document_state_->GetControlList()) {
|
||||
+ // Make a copy of the list because the DOM could be modified during
|
||||
+ // restoration of a <select> with a <selectedcontent> element.
|
||||
+ DocumentState::ControlList control_list_copy(
|
||||
+ document_state_->GetControlList());
|
||||
+ for (auto& control : control_list_copy) {
|
||||
auto* owner = OwnerFormForState(*control);
|
||||
if (!owner)
|
||||
RestoreControlStateFor(*control);
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/resources/selectedcontent-input.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/resources/selectedcontent-input.html
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..847f42ac304835c2049cf434a4dec68814ad533c
|
||||
--- /dev/null
|
||||
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/resources/selectedcontent-input.html
|
||||
@@ -0,0 +1,27 @@
|
||||
+<!DOCTYPE html>
|
||||
+<style>
|
||||
+select,::picker(select) {
|
||||
+ appearance: base-select;
|
||||
+}
|
||||
+</style>
|
||||
+<form action="blank.html">
|
||||
+ <select>
|
||||
+ <button>
|
||||
+ <selectedcontent></selectedcontent>
|
||||
+ </button>
|
||||
+ <option id=one>one</option>
|
||||
+ <option id=two>two</option>
|
||||
+ </select>
|
||||
+</form>
|
||||
+
|
||||
+<script>
|
||||
+window.createInput = () => {
|
||||
+ const selectedcontent = document.querySelector('selectedcontent');
|
||||
+ const input = document.createElement('input');
|
||||
+ window.input = input;
|
||||
+ input.name = 'input';
|
||||
+ selectedcontent.innerHTML = '';
|
||||
+ selectedcontent.appendChild(input);
|
||||
+};
|
||||
+window.createInput();
|
||||
+</script>
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.html
|
||||
deleted file mode 100644
|
||||
index da5fe450abbae0d19826021f114cc6388f97bc57..0000000000000000000000000000000000000000
|
||||
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.html
|
||||
+++ /dev/null
|
||||
@@ -1,35 +0,0 @@
|
||||
-<!DOCTYPE html>
|
||||
-<link rel=author href="mailto:jarhar@chromium.org">
|
||||
-<link rel=help href="https://github.com/whatwg/html/issues/9799">
|
||||
-<script src="/resources/testharness.js"></script>
|
||||
-<script src="/resources/testharnessreport.js"></script>
|
||||
-<script src="/resources/testdriver.js"></script>
|
||||
-<script src="/resources/testdriver-vendor.js"></script>
|
||||
-
|
||||
-<iframe src="resources/selectedcontent-restore-iframe.html"></iframe>
|
||||
-
|
||||
-<script>
|
||||
-const iframe = document.querySelector('iframe');
|
||||
-promise_test(async () => {
|
||||
- await new Promise(resolve => iframe.onload = resolve);
|
||||
- await test_driver.bless();
|
||||
-
|
||||
- iframe.contentDocument.querySelector('select').value = 'two';
|
||||
- assert_equals(iframe.contentDocument.querySelector('select').value, 'two',
|
||||
- 'Assining two to select.value should work.');
|
||||
- iframe.contentDocument.querySelector('form').submit();
|
||||
- await new Promise(resolve => iframe.onload = resolve);
|
||||
-
|
||||
- await test_driver.bless();
|
||||
- iframe.contentWindow.history.back();
|
||||
- await new Promise(resolve => iframe.onload = resolve);
|
||||
- await new Promise(requestAnimationFrame);
|
||||
- await new Promise(requestAnimationFrame);
|
||||
-
|
||||
- assert_equals(iframe.contentDocument.querySelector('select').value, 'two',
|
||||
- 'The selects value should be restored after navigating back.');
|
||||
- assert_equals(iframe.contentDocument.querySelector('selectedcontent').innerHTML,
|
||||
- iframe.contentDocument.querySelector('option[value=two]').innerHTML,
|
||||
- 'selectedcontent.innerHTML should match the selected <option>');
|
||||
-}, '<selectedcontent> should be up to date after form restoration.');
|
||||
-</script>
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.optional.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.optional.html
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..1d0064659cd9df06d6267261bf0b39b3fb29aeef
|
||||
--- /dev/null
|
||||
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-select-element/customizable-select/selectedcontent-restore.optional.html
|
||||
@@ -0,0 +1,76 @@
|
||||
+<!DOCTYPE html>
|
||||
+<link rel=author href="mailto:jarhar@chromium.org">
|
||||
+<link rel=help href="https://github.com/whatwg/html/issues/9799">
|
||||
+<script src="/resources/testharness.js"></script>
|
||||
+<script src="/resources/testharnessreport.js"></script>
|
||||
+<script src="/resources/testdriver.js"></script>
|
||||
+<script src="/resources/testdriver-vendor.js"></script>
|
||||
+
|
||||
+<!-- This test is marked optional because form control restoration is not explicitly specified. -->
|
||||
+
|
||||
+<iframe id=iframe1 src="resources/selectedcontent-restore-iframe.html"></iframe>
|
||||
+<iframe id=iframe2 src="resources/selectedcontent-input.html"></iframe>
|
||||
+
|
||||
+<script>
|
||||
+const iframe1 = document.getElementById('iframe1');
|
||||
+const iframe2 = document.getElementById('iframe2');
|
||||
+const iframe1load = new Promise(resolve => iframe1.onload = resolve);
|
||||
+const iframe2load = new Promise(resolve => iframe2.onload = resolve);
|
||||
+
|
||||
+promise_test(async () => {
|
||||
+ await iframe1load;
|
||||
+ await test_driver.bless();
|
||||
+
|
||||
+ iframe1.contentDocument.querySelector('select').value = 'two';
|
||||
+ assert_equals(iframe1.contentDocument.querySelector('select').value, 'two',
|
||||
+ 'Assigning two to select.value should work.');
|
||||
+ iframe1.contentDocument.querySelector('form').submit();
|
||||
+ await new Promise(resolve => iframe1.onload = resolve);
|
||||
+
|
||||
+ await test_driver.bless();
|
||||
+ iframe1.contentWindow.history.back();
|
||||
+ // Form controls are restored immediately after the load event is fired, so
|
||||
+ // one rAF is added after awaiting the load event. See
|
||||
+ // LocalDOMWindow::DispatchLoadAndPageshowEvents.
|
||||
+ await new Promise(resolve => iframe1.onload = resolve);
|
||||
+ await new Promise(requestAnimationFrame);
|
||||
+
|
||||
+ assert_equals(iframe1.contentDocument.querySelector('select').value, 'two',
|
||||
+ 'The selects value should be restored after navigating back.');
|
||||
+ assert_equals(iframe1.contentDocument.querySelector('selectedcontent').innerHTML,
|
||||
+ iframe1.contentDocument.querySelector('option[value=two]').innerHTML,
|
||||
+ 'selectedcontent.innerHTML should match the selected <option>');
|
||||
+}, '<selectedcontent> should be up to date after form restoration.');
|
||||
+
|
||||
+promise_test(async () => {
|
||||
+ await iframe2load;
|
||||
+ await test_driver.bless();
|
||||
+
|
||||
+ iframe2.contentDocument.querySelector('select').value = 'two';
|
||||
+ iframe2.contentWindow.createInput();
|
||||
+ iframe2.contentDocument.querySelector('input').value = 'value';
|
||||
+ iframe2.contentDocument.querySelector('form').submit();
|
||||
+ await new Promise(resolve => iframe2.onload = resolve);
|
||||
+
|
||||
+ await test_driver.bless();
|
||||
+ iframe2.contentWindow.history.back();
|
||||
+ // Form controls are restored immediately after the load event is fired, so
|
||||
+ // one rAF is added after awaiting the load event. See
|
||||
+ // LocalDOMWindow::DispatchLoadAndPageshowEvents.
|
||||
+ await new Promise(resolve => iframe2.onload = resolve);
|
||||
+ await new Promise(requestAnimationFrame);
|
||||
+
|
||||
+ // A crash would happen here because the form restoration code would iterate
|
||||
+ // over all of the form controls and remove an input element to restore during
|
||||
+ // restoration of the selectedcontent element, then try to restore the
|
||||
+ // disconnected input.
|
||||
+
|
||||
+ assert_equals(iframe2.contentDocument.querySelector('select').value, 'two',
|
||||
+ 'The selects value should be restored after navigating back.');
|
||||
+ assert_equals(iframe2.contentDocument.querySelector('selectedcontent').innerHTML,
|
||||
+ iframe2.contentDocument.getElementById('two').innerHTML,
|
||||
+ 'selectedcontent.innerHTML should match the selected <option>');
|
||||
+ assert_equals(iframe2.contentWindow.input.value, '',
|
||||
+ 'The text inputs value should not be restored because it was removed before restoring.');
|
||||
+}, '<input> inside <selectedcontent> should be restored after form submission.');
|
||||
+</script>
|
||||
67
patches/chromium/cherry-pick-8b08fb7c9dce.patch
Normal file
67
patches/chromium/cherry-pick-8b08fb7c9dce.patch
Normal file
@@ -0,0 +1,67 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: p0-tato <smartphonewithbear@gmail.com>
|
||||
Date: Tue, 14 Apr 2026 13:14:30 -0700
|
||||
Subject: [M146] Fix dangling pointers in OpenXrSpatialFrameworkManager
|
||||
|
||||
Original change's description:
|
||||
> Fix dangling pointers in OpenXrSpatialFrameworkManager
|
||||
>
|
||||
> Pointers to vector elements were collected during emplace_back,
|
||||
> which invalidates them on reallocation. Split into two loops
|
||||
> and reserve the correct capacity.
|
||||
>
|
||||
> Bug: 497724498
|
||||
> Change-Id: I204534bc1bd1522fe03db86f03c2c3e0d285631c
|
||||
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7735242
|
||||
> Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
|
||||
> Reviewed-by: Brian Sheedy <bsheedy@chromium.org>
|
||||
> Reviewed-by: Brandon Jones <bajones@chromium.org>
|
||||
> Cr-Commit-Position: refs/heads/main@{#1613990}
|
||||
|
||||
(cherry picked from commit b173791bf4026a6bb43124f7c5f46cfa4539c014)
|
||||
|
||||
Bug: 502440265,497724498
|
||||
Change-Id: I204534bc1bd1522fe03db86f03c2c3e0d285631c
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7759844
|
||||
Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
|
||||
Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
|
||||
Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
|
||||
Cr-Commit-Position: refs/branch-heads/7680@{#3944}
|
||||
Cr-Branched-From: 76b7d80e5cda23fe6537eed26d68c92e995c7f39-refs/heads/main@{#1582197}
|
||||
|
||||
diff --git a/AUTHORS b/AUTHORS
|
||||
index 7cc777b399ab46f88b6b1809bf6fd0cb22170694..505480b09c1d41b1facf4e2b165bad86b1815127 100644
|
||||
--- a/AUTHORS
|
||||
+++ b/AUTHORS
|
||||
@@ -729,6 +729,7 @@ Jihoon Chung <jihoon@gmail.com>
|
||||
Jihun Brent Kim <devgrapher@gmail.com>
|
||||
Jihwan Marc Kim <bluewhale.marc@gmail.com>
|
||||
Jihye Hyun <jijinny26@gmail.com>
|
||||
+Jihyeon Jeong <smartphonewithbear@gmail.com>
|
||||
Jihyeon Lee <wlgus7464@gmail.com>
|
||||
Jim Wu <lofoz.tw@gmail.com>
|
||||
Jin Yang <jin.a.yang@intel.com>
|
||||
diff --git a/device/vr/openxr/openxr_spatial_framework_manager.cc b/device/vr/openxr/openxr_spatial_framework_manager.cc
|
||||
index 520f25230c427bf775333910530d1ad841f3ad71..5c93d694aa5a2259c683f1d521611046293195a2 100644
|
||||
--- a/device/vr/openxr/openxr_spatial_framework_manager.cc
|
||||
+++ b/device/vr/openxr/openxr_spatial_framework_manager.cc
|
||||
@@ -71,12 +71,15 @@ OpenXrSpatialFrameworkManager::OpenXrSpatialFrameworkManager(
|
||||
// to help abstract some of the details of creating the child structs, even
|
||||
// though at present we only have a configuration base.
|
||||
std::vector<OpenXrSpatialCapabilityConfigurationBase> capability_configs;
|
||||
- std::vector<XrSpatialCapabilityConfigurationBaseHeaderEXT*>
|
||||
- capability_config_ptrs;
|
||||
+ capability_configs.reserve(capability_configuration.size());
|
||||
for (auto& [capability, components] : capability_configuration) {
|
||||
capability_configs.emplace_back(capability, components);
|
||||
- capability_config_ptrs.push_back(
|
||||
- capability_configs.back().GetAsBaseHeader());
|
||||
+ }
|
||||
+
|
||||
+ std::vector<XrSpatialCapabilityConfigurationBaseHeaderEXT*>
|
||||
+ capability_config_ptrs;
|
||||
+ for (auto& config : capability_configs) {
|
||||
+ capability_config_ptrs.push_back(config.GetAsBaseHeader());
|
||||
}
|
||||
|
||||
XrSpatialContextCreateInfoEXT create_info = {
|
||||
179
patches/chromium/cherry-pick-8c1ead5a699f.patch
Normal file
179
patches/chromium/cherry-pick-8c1ead5a699f.patch
Normal file
@@ -0,0 +1,179 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Hiroki Nakagawa <nhiroki@chromium.org>
|
||||
Date: Sun, 29 Mar 2026 18:12:59 -0700
|
||||
Subject: Prerender: Update PrerenderNewTabHandle destruction
|
||||
|
||||
Updates PrerenderNewTabHandle::CancelPrerendering to be a static method
|
||||
CancelPrerenderingAndDestroy that takes ownership of the handle.
|
||||
|
||||
Defers the destruction of PrerenderNewTabHandle using DeleteSoon() to
|
||||
ensure the owned WebContentsImpl is not destroyed synchronously during
|
||||
processing or iteration of handles.
|
||||
|
||||
Adds a browser test NewTabPrerenderCancellationByBrowsingDataRemover to
|
||||
verify safe destruction.
|
||||
|
||||
Bug: 497053588
|
||||
Change-Id: I97944ce9a5d1df486adb3e2438a8b381ee9f298e
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7710193
|
||||
Reviewed-by: Huanpo Lin <robertlin@chromium.org>
|
||||
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1606854}
|
||||
|
||||
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
|
||||
index 0b8550aad9a12563f870cd07f35338e4484ca5d0..3b18b20c19366dd124d75f544243d9bcc919b9dc 100644
|
||||
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
|
||||
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
|
||||
@@ -3903,6 +3903,39 @@ IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
|
||||
PrerenderFinalStatus::kTabClosedWithoutUserGesture);
|
||||
}
|
||||
|
||||
+// Tests that trigger cancellation via BrowsingDataRemover (e.g.,
|
||||
+// Clear-Site-Data) is handled safely without causing Use-After-Free due to
|
||||
+// synchronous destruction of the WebContentsImpl during iteration.
|
||||
+IN_PROC_BROWSER_TEST_F(PrerenderTargetHintBrowserTest,
|
||||
+ NewTabPrerenderCancellationByBrowsingDataRemover) {
|
||||
+ const GURL initial_url = GetUrl("/empty.html");
|
||||
+ const GURL prerendering_url = GetUrl("/empty.html?prerender");
|
||||
+
|
||||
+ // Navigate to an initial page.
|
||||
+ ASSERT_TRUE(NavigateToURL(shell(), initial_url));
|
||||
+
|
||||
+ // Start prerendering.
|
||||
+ PrerenderHostId host_id = prerender_helper()->AddPrerender(
|
||||
+ prerendering_url, /*eagerness=*/std::nullopt, "_blank");
|
||||
+ auto* prerender_web_contents =
|
||||
+ test::PrerenderTestHelper::GetPrerenderWebContents(host_id);
|
||||
+ WebContentsDestroyedWatcher wc_destroyed_watcher(prerender_web_contents);
|
||||
+
|
||||
+ // Trigger browsing data removal which will call CancelHostsByOriginFilter.
|
||||
+ BrowsingDataRemover* remover =
|
||||
+ web_contents_impl()->GetBrowserContext()->GetBrowsingDataRemover();
|
||||
+ BrowsingDataRemoverCompletionObserver completion_observer(remover);
|
||||
+ remover->RemoveAndReply(base::Time::Min(), base::Time::Max(),
|
||||
+ BrowsingDataRemover::DATA_TYPE_CACHE,
|
||||
+ BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
|
||||
+ &completion_observer);
|
||||
+ completion_observer.BlockUntilCompletion();
|
||||
+
|
||||
+ // WebContents created for the new-tab trigger will be destroyed safely.
|
||||
+ wc_destroyed_watcher.Wait();
|
||||
+ EXPECT_FALSE(prerender_helper()->HasNewTabHandle(host_id));
|
||||
+}
|
||||
+
|
||||
// Tests that prerendering is cancelled if a network request for the
|
||||
// navigation results in an empty response with 404 status.
|
||||
IN_PROC_BROWSER_TEST_P(PrerenderTargetAgnosticBrowserTest,
|
||||
diff --git a/content/browser/preloading/prerender/prerender_host_registry.cc b/content/browser/preloading/prerender/prerender_host_registry.cc
|
||||
index 0cdaa3f62b31cf4abe7f568e480c3c5d354516f2..26e71de66d6e3ad2ace9e2bf1ad4fa7e416cae0a 100644
|
||||
--- a/content/browser/preloading/prerender/prerender_host_registry.cc
|
||||
+++ b/content/browser/preloading/prerender/prerender_host_registry.cc
|
||||
@@ -1133,14 +1133,8 @@ bool PrerenderHostRegistry::CancelNewTabHostInternal(
|
||||
prerender_new_tab_handle_by_id_.erase(iter);
|
||||
NotifyCancel(handle->prerender_host_id(), reason);
|
||||
|
||||
- if (reason.final_status() == PrerenderFinalStatus::kSpeculationRuleRemoved) {
|
||||
- auto& new_tab_registry = handle->GetPrerenderHostRegistry();
|
||||
- new_tab_registry.SchedulePendingDeletionPrerenderNewTabHandle(
|
||||
- std::move(handle));
|
||||
- new_tab_registry.CancelHost(prerender_host_id, reason);
|
||||
- } else {
|
||||
- handle->CancelPrerendering(reason);
|
||||
- }
|
||||
+ PrerenderNewTabHandle::CancelPrerenderingAndDestroy(std::move(handle),
|
||||
+ reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1829,6 +1823,7 @@ void PrerenderHostRegistry::DeletePendingDeletionHosts(
|
||||
}
|
||||
|
||||
void PrerenderHostRegistry::SchedulePendingDeletionPrerenderNewTabHandle(
|
||||
+ base::PassKey<PrerenderNewTabHandle>,
|
||||
std::unique_ptr<PrerenderNewTabHandle> handle) {
|
||||
CHECK(!pending_deletion_new_tab_prerender_handle_);
|
||||
pending_deletion_new_tab_prerender_handle_ = std::move(handle);
|
||||
diff --git a/content/browser/preloading/prerender/prerender_host_registry.h b/content/browser/preloading/prerender/prerender_host_registry.h
|
||||
index 7f84a1edac3fdbdd3904940a7cff453d71bcd866..d3d41e1c42605d5b317b28fc75102beece44643c 100644
|
||||
--- a/content/browser/preloading/prerender/prerender_host_registry.h
|
||||
+++ b/content/browser/preloading/prerender/prerender_host_registry.h
|
||||
@@ -288,6 +288,9 @@ class CONTENT_EXPORT PrerenderHostRegistry
|
||||
|
||||
PrerenderHostId GetPrerenderHostIdForNavigation(
|
||||
NavigationRequest* navigation_request);
|
||||
+ void SchedulePendingDeletionPrerenderNewTabHandle(
|
||||
+ base::PassKey<PrerenderNewTabHandle>,
|
||||
+ std::unique_ptr<PrerenderNewTabHandle> handle);
|
||||
|
||||
private:
|
||||
// WebContentsObserver implementation:
|
||||
@@ -310,8 +313,6 @@ class CONTENT_EXPORT PrerenderHostRegistry
|
||||
void ScheduleToDeleteAbandonedHost(
|
||||
std::unique_ptr<PrerenderHost> prerender_host,
|
||||
const PrerenderCancellationReason& cancellation_reason);
|
||||
- void SchedulePendingDeletionPrerenderNewTabHandle(
|
||||
- std::unique_ptr<PrerenderNewTabHandle> handle);
|
||||
|
||||
void DeleteAbandonedHosts();
|
||||
|
||||
diff --git a/content/browser/preloading/prerender/prerender_new_tab_handle.cc b/content/browser/preloading/prerender/prerender_new_tab_handle.cc
|
||||
index 5c4bc5e6ff6a0fdb6911db60f286899a36f4266b..99b5bd9648649db43a70e091e9837bf75ac87a89 100644
|
||||
--- a/content/browser/preloading/prerender/prerender_new_tab_handle.cc
|
||||
+++ b/content/browser/preloading/prerender/prerender_new_tab_handle.cc
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "content/browser/preloading/preloading_data_impl.h"
|
||||
#include "content/browser/preloading/prerender/prerender_host.h"
|
||||
#include "content/browser/preloading/prerender/prerender_host_registry.h"
|
||||
+#include "content/browser/preloading/prerender/prerender_metrics.h"
|
||||
#include "content/browser/web_contents/web_contents_impl.h"
|
||||
#include "content/common/frame.mojom.h"
|
||||
#include "content/public/browser/web_contents_delegate.h"
|
||||
@@ -103,9 +104,29 @@ PrerenderHostId PrerenderNewTabHandle::StartPrerendering(
|
||||
return prerender_host_id_;
|
||||
}
|
||||
|
||||
-void PrerenderNewTabHandle::CancelPrerendering(
|
||||
+// static
|
||||
+void PrerenderNewTabHandle::CancelPrerenderingAndDestroy(
|
||||
+ std::unique_ptr<PrerenderNewTabHandle> handle,
|
||||
const PrerenderCancellationReason& reason) {
|
||||
- GetPrerenderHostRegistry().CancelHost(prerender_host_id_, reason);
|
||||
+ auto& registry = handle->GetPrerenderHostRegistry();
|
||||
+ PrerenderHostId host_id = handle->prerender_host_id();
|
||||
+
|
||||
+ if (reason.final_status() == PrerenderFinalStatus::kSpeculationRuleRemoved) {
|
||||
+ // Defer destruction of the handle until the pagehide event is fired in a
|
||||
+ // prerendered page in a new tab. The event is fired only when prerendering
|
||||
+ // is intentionally cancelled by an initiator page (i.e., Speculation rule
|
||||
+ // is removed).
|
||||
+ registry.SchedulePendingDeletionPrerenderNewTabHandle(
|
||||
+ base::PassKey<PrerenderNewTabHandle>(), std::move(handle));
|
||||
+ } else {
|
||||
+ // Defer destruction of the handle to avoid synchronous destruction of the
|
||||
+ // owned WebContentsImpl. This prevents Use-After-Free if this is called
|
||||
+ // while iterating over a snapshot of raw pointers to all WebContents (e.g.,
|
||||
+ // in BrowsingDataRemoverImpl::RemoveImpl).
|
||||
+ base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
|
||||
+ FROM_HERE, std::move(handle));
|
||||
+ }
|
||||
+ registry.CancelHost(host_id, reason);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebContentsImpl>
|
||||
diff --git a/content/browser/preloading/prerender/prerender_new_tab_handle.h b/content/browser/preloading/prerender/prerender_new_tab_handle.h
|
||||
index eae10a2834f66463919b81e8fe02d8e115edef0a..bfb969ca9894b2edddb92efcee73793f38bcaf48 100644
|
||||
--- a/content/browser/preloading/prerender/prerender_new_tab_handle.h
|
||||
+++ b/content/browser/preloading/prerender/prerender_new_tab_handle.h
|
||||
@@ -49,8 +49,10 @@ class PrerenderNewTabHandle {
|
||||
const PreloadingPredictor& enacting_predictor,
|
||||
PreloadingConfidence confidence);
|
||||
|
||||
- // Cancels prerendering started in `web_contents_`.
|
||||
- void CancelPrerendering(const PrerenderCancellationReason& reason);
|
||||
+ // Cancels prerendering and schedules the destruction of the handle.
|
||||
+ static void CancelPrerenderingAndDestroy(
|
||||
+ std::unique_ptr<PrerenderNewTabHandle> handle,
|
||||
+ const PrerenderCancellationReason& reason);
|
||||
|
||||
// Passes the ownership of `web_contents_` to the caller if it's available for
|
||||
// new tab navigation with given params.
|
||||
211
patches/chromium/cherry-pick-a6357144e7bf.patch
Normal file
211
patches/chromium/cherry-pick-a6357144e7bf.patch
Normal file
@@ -0,0 +1,211 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Lukasz Anforowicz <lukasza@chromium.org>
|
||||
Date: Wed, 1 Apr 2026 15:44:32 -0700
|
||||
Subject: [rust png] Invalidate `already_started_frame_` after clearing frames.
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This CL ensures that `already_started_frame_` doesn't become stale
|
||||
after:
|
||||
|
||||
* Re-allocating the memory buffer backing a frame.
|
||||
* Failing a call to `startIncrementalDecode`
|
||||
|
||||
The regression tests in this CL have been mostly created by Gemini CLI
|
||||
and then reviewed and cleaned up by the author.
|
||||
|
||||
Fixed: 496282147
|
||||
Change-Id: I92349cc2a0d7b9d1d401ab7256cb941a4d8383d1
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7703000
|
||||
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
|
||||
Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1608827}
|
||||
|
||||
diff --git a/third_party/blink/renderer/platform/graphics/image_frame_generator_test.cc b/third_party/blink/renderer/platform/graphics/image_frame_generator_test.cc
|
||||
index 8a8536968c9b0c08ce0962ac9c6b551b8a76e0ba..0bda0dd5b6ed0edd62322b74fbaaa10cdf926475 100644
|
||||
--- a/third_party/blink/renderer/platform/graphics/image_frame_generator_test.cc
|
||||
+++ b/third_party/blink/renderer/platform/graphics/image_frame_generator_test.cc
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "third_party/blink/renderer/platform/graphics/image_frame_generator.h"
|
||||
|
||||
#include <memory>
|
||||
+
|
||||
#include "base/features.h"
|
||||
#include "base/location.h"
|
||||
#include "base/test/metrics/histogram_tester.h"
|
||||
@@ -39,8 +40,10 @@
|
||||
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
|
||||
#include "third_party/blink/renderer/platform/testing/task_environment.h"
|
||||
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
|
||||
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
|
||||
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
|
||||
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
|
||||
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
|
||||
#include "third_party/blink/renderer/platform/wtf/vector.h"
|
||||
|
||||
namespace blink {
|
||||
@@ -413,4 +416,65 @@ TEST_F(ImageFrameGeneratorTest, clearMultiFrameDecoder) {
|
||||
EXPECT_EQ(kNotFound, requested_clear_except_frame_);
|
||||
}
|
||||
|
||||
+// This is a regression test for https://crbug.com/496282147.
|
||||
+//
|
||||
+// This is a more realistic, product-like, almost-end-to-end version of the
|
||||
+// `AnimatedPNGTests.ClearingPartiallyDecodedFrame` unit test.
|
||||
+TEST_F(ImageFrameGeneratorTest, ClearingPartiallyDecodedFrame) {
|
||||
+ StringBuilder file_path;
|
||||
+ file_path.Append(test::BlinkWebTestsDir());
|
||||
+ file_path.Append(
|
||||
+ "/images/resources/png-animated-three-independent-frames.png");
|
||||
+ std::optional<Vector<char>> full_data_vec =
|
||||
+ test::ReadFromFile(file_path.ToString());
|
||||
+ ASSERT_TRUE(full_data_vec);
|
||||
+ base::span<const uint8_t> full_data = base::as_byte_span(*full_data_vec);
|
||||
+ SkISize size(50, 50);
|
||||
+
|
||||
+ // Can't reuse `generator_` from `SetUp`, because it sets `is_multi_frame` to
|
||||
+ // `false`. Can't use `SetFrameCount`, because this test needs to use a real
|
||||
+ // `SkiaImageDecoderBase` decoder, rather than `UseMockImageDecoderFactory`.
|
||||
+ constexpr bool kIsMultiframe = true;
|
||||
+ const Vector<SkISize> kSupportedSizes = {};
|
||||
+ generator_ =
|
||||
+ ImageFrameGenerator::Create(size, kIsMultiframe, ColorBehavior::kTag,
|
||||
+ cc::AuxImage::kDefault, kSupportedSizes);
|
||||
+
|
||||
+ // Partially decode frame 1.
|
||||
+ //
|
||||
+ // `fcTL` chunk starts at offset 180. `fdAT` at 218.
|
||||
+ // Let's provide 240 bytes - in the middle of `fdAT` chunk.
|
||||
+ //
|
||||
+ // After this step `SkiaImageDecoderBase::already_started_frame_` is `1`.
|
||||
+ SkBitmap bitmap;
|
||||
+ bitmap.allocN32Pixels(size.width(), size.height());
|
||||
+ cc::PaintImage::GeneratorClientId client_id =
|
||||
+ cc::PaintImage::GetNextGeneratorClientId();
|
||||
+ auto partial_data = SharedBuffer::Create(full_data.first(240u));
|
||||
+ auto segment_reader = SegmentReader::CreateFromSharedBuffer(partial_data);
|
||||
+ bool success = generator_->DecodeAndScale(segment_reader.get(),
|
||||
+ /*all_data_received=*/false, 1,
|
||||
+ bitmap.pixmap(), client_id);
|
||||
+ EXPECT_TRUE(success);
|
||||
+
|
||||
+ // Decode an out-of-bounds frame to clear the cache and transitively call
|
||||
+ // `ImageFrame::ClearPixelData`.
|
||||
+ success = generator_->DecodeAndScale(segment_reader.get(),
|
||||
+ /*all_data_received=*/false, 1000,
|
||||
+ bitmap.pixmap(), client_id);
|
||||
+ EXPECT_FALSE(success);
|
||||
+
|
||||
+ // Resume decoding of frame 1. Despite starting with
|
||||
+ // `SkiaImageDecoderBase::already_started_frame_` set to `1` this operation
|
||||
+ // needs to call `SkCodec::startIncrementalDecode` because the old buffer has
|
||||
+ // been freed in the previous step.
|
||||
+ auto full_shared_buffer = SharedBuffer::Create(full_data);
|
||||
+ auto full_segment_reader =
|
||||
+ SegmentReader::CreateFromSharedBuffer(full_shared_buffer);
|
||||
+ success = generator_->DecodeAndScale(full_segment_reader.get(),
|
||||
+ /*all_data_received=*/true, 1,
|
||||
+ bitmap.pixmap(), client_id);
|
||||
+ EXPECT_TRUE(success);
|
||||
+}
|
||||
+
|
||||
} // namespace blink
|
||||
diff --git a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
|
||||
index ae4343652b2bd536a70846a73d7c35befb6584e6..b47a2c1da2156440a5dfa6b6f601c9dd8dc5db47 100644
|
||||
--- a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
|
||||
+++ b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
|
||||
@@ -879,6 +879,53 @@ TEST(AnimatedPNGTests, IncrementalDecodeOfDifferentFrame) {
|
||||
EXPECT_EQ(frame1->GetStatus(), ImageFrame::kFrameComplete);
|
||||
}
|
||||
|
||||
+// This is a regression test for https://crbug.com/496282147.
|
||||
+//
|
||||
+// This test uses `blink::ImageDecoder` and `blink::ImageFrame` APIs in a way
|
||||
+// that doesn't necessarily reflect how they would actually be used in the
|
||||
+// product (e.g. calling `ClearPixelData` and/or calling `Append` instead of
|
||||
+// `SetData`). This nevertheless seems like a valid test, because:
|
||||
+//
|
||||
+// * Supporting all usage patterns allowed by the public APIs (and the type
|
||||
+// system) seems more robust then 1) adding extra requirements on the caller
|
||||
+// of these APIs (such as never clearing a partially decoded frame), and/or 2)
|
||||
+// discovering the callers that may violate such requirements.
|
||||
+// * A separate `ImageFrameGeneratorTest.ClearingPartiallyDecodedFrame` test
|
||||
+// shows how a similr usage pattern is indeed reachable via web-exposed APIs.
|
||||
+TEST(AnimatedPNGTests, ClearingPartiallyDecodedFrame) {
|
||||
+ Vector<char> full_data = ReadFile(
|
||||
+ "/images/resources/"
|
||||
+ "png-animated-idat-part-of-animation.png");
|
||||
+ ASSERT_FALSE(full_data.empty());
|
||||
+ auto decoder = CreatePNGDecoder();
|
||||
+
|
||||
+ // Provide only enough data for the first frame to be partial.
|
||||
+ const size_t kPartialDataSize = 160;
|
||||
+ scoped_refptr<SharedBuffer> data =
|
||||
+ SharedBuffer::Create(base::span(full_data).first(kPartialDataSize));
|
||||
+ decoder->SetData(data.get(), false);
|
||||
+
|
||||
+ // Partially decode frame 0.
|
||||
+ ImageFrame* frame0 = decoder->DecodeFrameBufferAtIndex(0);
|
||||
+ ASSERT_TRUE(frame0);
|
||||
+ EXPECT_EQ(frame0->GetStatus(), ImageFrame::kFramePartial);
|
||||
+
|
||||
+ // Manually clear frame 0 pixel data.
|
||||
+ frame0->ClearPixelData();
|
||||
+
|
||||
+ // Provide more data by appending to the same `SharedBuffer`.
|
||||
+ // This avoids clobbering the decoder state with a new `SetData` call.
|
||||
+ data->Append(base::span(full_data).subspan(kPartialDataSize));
|
||||
+
|
||||
+ // Try to decode frame 0 again. This verifies that
|
||||
+ // `SkCodec::startIncrementalDecode` has been called to reinitialize decoding
|
||||
+ // state - avoiding writing to the memory buffer that has been freed by
|
||||
+ // `ClearPixelData` above.
|
||||
+ frame0 = decoder->DecodeFrameBufferAtIndex(0);
|
||||
+ ASSERT_TRUE(frame0);
|
||||
+ EXPECT_EQ(frame0->GetStatus(), ImageFrame::kFrameComplete);
|
||||
+}
|
||||
+
|
||||
// Verify that a malformatted PNG, where the IEND appears before any frame data
|
||||
// (IDAT), invalidates the decoder.
|
||||
TEST(AnimatedPNGTests, VerifyIENDBeforeIDATInvalidatesDecoder) {
|
||||
diff --git a/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.cc b/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.cc
|
||||
index a6766b5d51ddf64eab40ebe838e0a5daaef44dba..f4a8c849986d41d36baffa11d66b3d9bdc9bdc96 100644
|
||||
--- a/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.cc
|
||||
+++ b/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.cc
|
||||
@@ -301,6 +301,11 @@ void SkiaImageDecoderBase::Decode(wtf_size_t index) {
|
||||
UpdateAggressivePurging(current_frame_index);
|
||||
|
||||
if (frame.GetStatus() == ImageFrame::kFrameEmpty) {
|
||||
+ // `AllocatePixelData` (or `TakeBitmapDataIfWritable` / `CopyBitmapData`)
|
||||
+ // calls mean that we can't reuse old buffer pointers that may have been
|
||||
+ // stashed in `SkCodec` by previous `startIncrementalDecode` calls.
|
||||
+ already_started_frame_.reset();
|
||||
+
|
||||
wtf_size_t required_previous_frame_index =
|
||||
frame.RequiredPreviousFrameIndex();
|
||||
if (required_previous_frame_index == kNotFound) {
|
||||
@@ -404,6 +409,7 @@ void SkiaImageDecoderBase::Decode(wtf_size_t index) {
|
||||
options.fPriorFrame = prior_frame_;
|
||||
options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
|
||||
|
||||
+ already_started_frame_.reset();
|
||||
SkCodec::Result start_incremental_decode_result =
|
||||
codec_->startIncrementalDecode(image_info, frame.Bitmap().getPixels(),
|
||||
frame.Bitmap().rowBytes(), &options);
|
||||
diff --git a/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.h b/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.h
|
||||
index ab2c197704a522261ffa40171ca31f03c0cdbf1c..86726262a31b09c602de4697fa5cc8e2bd51fd7e 100644
|
||||
--- a/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.h
|
||||
+++ b/third_party/blink/renderer/platform/image-decoders/skia/skia_image_decoder_base.h
|
||||
@@ -98,8 +98,9 @@ class PLATFORM_EXPORT SkiaImageDecoderBase : public ImageDecoder {
|
||||
const wtf_size_t reading_offset_ = 0;
|
||||
|
||||
// Number of a frame for which calling `SkCodec::incrementalDecode` is okay.
|
||||
- // Set after calling `SkCodec::startIncrementalDecode` and reset after
|
||||
- // `SkCodec::incrementalDecode` succeeds or encounters a non-resumable error.
|
||||
+ // Set after calling `SkCodec::startIncrementalDecode` and reset after either
|
||||
+ // 1) the memory buffer of that frame was reset or 2) the frame was completely
|
||||
+ // decoded (successfully, or with a non-resumable error).
|
||||
std::optional<wtf_size_t> already_started_frame_;
|
||||
};
|
||||
|
||||
94
patches/chromium/cherry-pick-bb8d4c29dfdb.patch
Normal file
94
patches/chromium/cherry-pick-bb8d4c29dfdb.patch
Normal file
@@ -0,0 +1,94 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Date: Fri, 10 Apr 2026 23:37:43 -0700
|
||||
Subject: [M146] [gpu] Fix OOB write due to unvalidated get_offset
|
||||
|
||||
Original change's description:
|
||||
> [gpu] Fix OOB write due to unvalidated get_offset
|
||||
>
|
||||
> A compromised GPU process can provide an invalid get_offset to the
|
||||
> CommandBufferHelper (e.g., via shared memory). This offset is used to
|
||||
> calculate available space and could lead to out-of-bounds writes in the
|
||||
> Browser process if not validated.
|
||||
>
|
||||
> This change adds a bounds check in
|
||||
> CommandBufferHelper::UpdateCachedState to ensure that the cached
|
||||
> get_offset is within the valid range [0, total_entry_count_]. If an
|
||||
> invalid offset is detected, it forces a context loss, frees the ring
|
||||
> buffer, and marks the helper as unusable, preventing further operations.
|
||||
>
|
||||
> Bug: 498782145
|
||||
> Test: CommandBufferHelperTest.*
|
||||
> Change-Id: I8c64e546ecdc90a5a22d15e57ff762a86a6a6964
|
||||
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7739951
|
||||
> Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
> Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
> Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
> Cr-Commit-Position: refs/heads/main@{#1611853}
|
||||
|
||||
(cherry picked from commit dc5e20c4c055d6952854a566d520211c6d505f74)
|
||||
|
||||
Bug: 498782145
|
||||
Fixed: 500956607
|
||||
Change-Id: Ia726612e0a930ee79460fbd7d795afa4d94e2a7b
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7745786
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
|
||||
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/7680@{#3919}
|
||||
Cr-Branched-From: 76b7d80e5cda23fe6537eed26d68c92e995c7f39-refs/heads/main@{#1582197}
|
||||
|
||||
diff --git a/gpu/command_buffer/client/cmd_buffer_helper.cc b/gpu/command_buffer/client/cmd_buffer_helper.cc
|
||||
index ccda45b133c6a9f2ee60ccc8900bd4a4ce328394..5aea0c81b29b3507099f399c374f3cb372a3100e 100644
|
||||
--- a/gpu/command_buffer/client/cmd_buffer_helper.cc
|
||||
+++ b/gpu/command_buffer/client/cmd_buffer_helper.cc
|
||||
@@ -158,6 +158,17 @@ void CommandBufferHelper::UpdateCachedState(const CommandBuffer::State& state) {
|
||||
service_on_old_buffer_ =
|
||||
(state.set_get_buffer_count != set_get_buffer_count_);
|
||||
cached_get_offset_ = service_on_old_buffer_ ? 0 : state.get_offset;
|
||||
+
|
||||
+ if (!service_on_old_buffer_ &&
|
||||
+ (cached_get_offset_ < 0 || cached_get_offset_ > total_entry_count_)) {
|
||||
+ command_buffer_->ForceLostContext(error::kGuilty);
|
||||
+ FreeRingBuffer();
|
||||
+ usable_ = false;
|
||||
+ context_lost_ = true;
|
||||
+ cached_get_offset_ = 0; // Safe fallback
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
cached_last_token_read_ = state.token;
|
||||
// Don't transition from a lost context to a working context.
|
||||
context_lost_ |= error::IsError(state.error);
|
||||
diff --git a/gpu/command_buffer/client/cmd_buffer_helper_test.cc b/gpu/command_buffer/client/cmd_buffer_helper_test.cc
|
||||
index 1b9254d318ae770ca980d2fed1399a69438afa10..009a87e8bf7a3475f63cd51206868dec187f5e06 100644
|
||||
--- a/gpu/command_buffer/client/cmd_buffer_helper_test.cc
|
||||
+++ b/gpu/command_buffer/client/cmd_buffer_helper_test.cc
|
||||
@@ -67,6 +67,8 @@ class CommandBufferHelperTest : public testing::Test {
|
||||
return helper_->immediate_entry_count_;
|
||||
}
|
||||
|
||||
+ int32_t TotalEntryCount() const { return helper_->total_entry_count_; }
|
||||
+
|
||||
// Adds a command to the buffer through the helper, while adding it as an
|
||||
// expected call on the API mock.
|
||||
void AddCommandWithExpect(error::Error _return,
|
||||
@@ -655,6 +657,17 @@ TEST_F(CommandBufferHelperTest, IsContextLost) {
|
||||
EXPECT_TRUE(helper_->IsContextLost());
|
||||
}
|
||||
|
||||
+TEST_F(CommandBufferHelperTest, TestInvalidGetOffset) {
|
||||
+ EXPECT_FALSE(helper_->IsContextLost());
|
||||
+ EXPECT_TRUE(helper_->usable());
|
||||
+
|
||||
+ command_buffer_->SetGetOffsetForTest(TotalEntryCount() + 1);
|
||||
+ helper_->RefreshCachedToken(); // calls UpdateCachedState internally.
|
||||
+
|
||||
+ EXPECT_TRUE(helper_->IsContextLost());
|
||||
+ EXPECT_FALSE(helper_->usable());
|
||||
+}
|
||||
+
|
||||
// Checks helper's 'flush generation' updates.
|
||||
TEST_F(CommandBufferHelperTest, TestFlushGeneration) {
|
||||
// Explicit flushing only.
|
||||
224
patches/chromium/cherry-pick-be87466afecb.patch
Normal file
224
patches/chromium/cherry-pick-be87466afecb.patch
Normal file
@@ -0,0 +1,224 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jonathan Ross <jonross@chromium.org>
|
||||
Date: Wed, 8 Apr 2026 17:15:45 -0700
|
||||
Subject: gl: Make DCOMPSurfaceRegistry thread-safe
|
||||
|
||||
DCOMPSurfaceRegistry is accessed from both the GPU IO thread (via
|
||||
GpuServiceImpl) and the GPU main scheduler thread (via DCOMPTexture).
|
||||
The underlying base::flat_map is not thread-safe, leading to potential
|
||||
container corruption and crashes (UAF, BOf) during concurrent access.
|
||||
|
||||
This CL adds a base::Lock to protect all accesses to the map and
|
||||
includes a new multi-threaded stress test to verify the fix.
|
||||
|
||||
Bug: 493315759
|
||||
Change-Id: Ibb7ef5e602f222410fde06a61fb3f5e571e7a70f
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737061
|
||||
Reviewed-by: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Commit-Queue: Jonathan Ross <jonross@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1611867}
|
||||
|
||||
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
|
||||
index 3584b693370b5199456608a26ceb763f6e9c3446..1cb66199a0b8adf2035a05fecc411c67180f7e80 100644
|
||||
--- a/ui/gl/BUILD.gn
|
||||
+++ b/ui/gl/BUILD.gn
|
||||
@@ -552,6 +552,7 @@ test("gl_unittests") {
|
||||
if (is_win) {
|
||||
sources += [
|
||||
"dcomp_presenter_unittest.cc",
|
||||
+ "dcomp_surface_registry_unittest.cc",
|
||||
"delegated_ink_point_renderer_gpu_unittest.cc",
|
||||
"gl_fence_win_unittest.cc",
|
||||
"hdr_metadata_helper_win_unittest.cc",
|
||||
diff --git a/ui/gl/dcomp_surface_registry.cc b/ui/gl/dcomp_surface_registry.cc
|
||||
index 352cc298b9ea97361ae2a7d668b7d7e9eb455cd5..410f76f8980438abae32b6c89e7083ae48cf1699 100644
|
||||
--- a/ui/gl/dcomp_surface_registry.cc
|
||||
+++ b/ui/gl/dcomp_surface_registry.cc
|
||||
@@ -3,8 +3,11 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ui/gl/dcomp_surface_registry.h"
|
||||
+
|
||||
+#include "base/check.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/no_destructor.h"
|
||||
+#include "base/synchronization/lock.h"
|
||||
|
||||
namespace gl {
|
||||
|
||||
@@ -20,8 +23,11 @@ base::UnguessableToken DCOMPSurfaceRegistry::RegisterDCOMPSurfaceHandle(
|
||||
base::win::ScopedHandle surface) {
|
||||
DVLOG(1) << __func__;
|
||||
base::UnguessableToken token = base::UnguessableToken::Create();
|
||||
- DCHECK(surface_handle_map_.find(token) == surface_handle_map_.end());
|
||||
- surface_handle_map_[token] = std::move(surface);
|
||||
+ {
|
||||
+ base::AutoLock lock(lock_);
|
||||
+ DCHECK(surface_handle_map_.find(token) == surface_handle_map_.end());
|
||||
+ surface_handle_map_[token] = std::move(surface);
|
||||
+ }
|
||||
DVLOG(1) << __func__ << ": Surface handle registered with token " << token;
|
||||
return token;
|
||||
}
|
||||
@@ -29,12 +35,14 @@ base::UnguessableToken DCOMPSurfaceRegistry::RegisterDCOMPSurfaceHandle(
|
||||
void DCOMPSurfaceRegistry::UnregisterDCOMPSurfaceHandle(
|
||||
const base::UnguessableToken& token) {
|
||||
DVLOG(1) << __func__;
|
||||
+ base::AutoLock lock(lock_);
|
||||
surface_handle_map_.erase(token);
|
||||
}
|
||||
|
||||
base::win::ScopedHandle DCOMPSurfaceRegistry::TakeDCOMPSurfaceHandle(
|
||||
const base::UnguessableToken& token) {
|
||||
DVLOG(1) << __func__;
|
||||
+ base::AutoLock lock(lock_);
|
||||
auto surface_iter = surface_handle_map_.find(token);
|
||||
if (surface_iter != surface_handle_map_.end()) {
|
||||
// Take ownership.
|
||||
diff --git a/ui/gl/dcomp_surface_registry.h b/ui/gl/dcomp_surface_registry.h
|
||||
index 803a3cc6398f0777504063118920998869086d7f..7cd9fdbfe8669bc97d4b664fdb29573ec2ea26de 100644
|
||||
--- a/ui/gl/dcomp_surface_registry.h
|
||||
+++ b/ui/gl/dcomp_surface_registry.h
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/no_destructor.h"
|
||||
+#include "base/synchronization/lock.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "ui/gl/gl_export.h"
|
||||
@@ -44,7 +45,9 @@ class GL_EXPORT DCOMPSurfaceRegistry {
|
||||
~DCOMPSurfaceRegistry();
|
||||
|
||||
base::flat_map<base::UnguessableToken, base::win::ScopedHandle>
|
||||
- surface_handle_map_;
|
||||
+ surface_handle_map_ GUARDED_BY(lock_);
|
||||
+
|
||||
+ base::Lock lock_;
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
diff --git a/ui/gl/dcomp_surface_registry_unittest.cc b/ui/gl/dcomp_surface_registry_unittest.cc
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..595e2388e9f50df33214359ecef0c135d94610b8
|
||||
--- /dev/null
|
||||
+++ b/ui/gl/dcomp_surface_registry_unittest.cc
|
||||
@@ -0,0 +1,118 @@
|
||||
+// Copyright 2026 The Chromium Authors
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+#include "ui/gl/dcomp_surface_registry.h"
|
||||
+
|
||||
+#include <windows.h>
|
||||
+
|
||||
+#include <atomic>
|
||||
+#include <thread>
|
||||
+#include <vector>
|
||||
+
|
||||
+#include "base/memory/raw_ptr.h"
|
||||
+#include "base/synchronization/lock.h"
|
||||
+#include "base/unguessable_token.h"
|
||||
+#include "base/win/scoped_handle.h"
|
||||
+#include "testing/gtest/include/gtest/gtest.h"
|
||||
+
|
||||
+namespace gl {
|
||||
+
|
||||
+namespace {
|
||||
+
|
||||
+class DCOMPSurfaceRegistryTest : public testing::Test {
|
||||
+ public:
|
||||
+ void SetUp() override { registry_ = DCOMPSurfaceRegistry::GetInstance(); }
|
||||
+
|
||||
+ protected:
|
||||
+ raw_ptr<DCOMPSurfaceRegistry> registry_;
|
||||
+};
|
||||
+
|
||||
+} // namespace
|
||||
+
|
||||
+// Stress test for concurrent access to DCOMPSurfaceRegistry using the
|
||||
+// barrier pattern to ensure TSAN consistently catches data races.
|
||||
+//
|
||||
+// Without proper synchronization (e.g., base::Lock), this test would likely
|
||||
+// fail in the following ways:
|
||||
+// 1. Memory Corruption (UAF/HeapBOf): base::flat_map uses a contiguous
|
||||
+// std::vector. If one thread triggers a reallocation during an insertion
|
||||
+// while another thread is searching or erasing, the latter will hold an
|
||||
+// invalidated iterator or pointer.
|
||||
+// 2. Container Inconsistency: Concurrent insertions and erasures can leave
|
||||
+// the map in an unsorted or corrupted state, leading to failed lookups
|
||||
+// for valid tokens.
|
||||
+// 3. Sanitizer Triggers: ASan would detect container-overflow or
|
||||
+// heap-use-after-free, and TSan would flag a data race.
|
||||
+TEST_F(DCOMPSurfaceRegistryTest, ConcurrentRegisterAndTake) {
|
||||
+ const int kOpsPerThread = 100;
|
||||
+
|
||||
+ std::vector<base::UnguessableToken> tokens;
|
||||
+ base::Lock tokens_lock;
|
||||
+
|
||||
+ std::atomic<bool> start_flag{false};
|
||||
+ std::atomic<int> threads_ready{0};
|
||||
+
|
||||
+ auto register_worker = [&]() {
|
||||
+ threads_ready++;
|
||||
+ while (!start_flag.load(std::memory_order_acquire)) {
|
||||
+ std::this_thread::yield();
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < kOpsPerThread; ++i) {
|
||||
+ base::win::ScopedHandle handle(
|
||||
+ ::CreateEvent(nullptr, FALSE, FALSE, nullptr));
|
||||
+ base::UnguessableToken token =
|
||||
+ registry_->RegisterDCOMPSurfaceHandle(std::move(handle));
|
||||
+ {
|
||||
+ base::AutoLock lock(tokens_lock);
|
||||
+ tokens.push_back(token);
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ auto take_worker = [&]() {
|
||||
+ threads_ready++;
|
||||
+ while (!start_flag.load(std::memory_order_acquire)) {
|
||||
+ std::this_thread::yield();
|
||||
+ }
|
||||
+
|
||||
+ int taken = 0;
|
||||
+ while (taken < kOpsPerThread) {
|
||||
+ base::UnguessableToken token;
|
||||
+ {
|
||||
+ base::AutoLock lock(tokens_lock);
|
||||
+ if (!tokens.empty()) {
|
||||
+ token = tokens.back();
|
||||
+ tokens.pop_back();
|
||||
+ }
|
||||
+ }
|
||||
+ if (!token.is_empty()) {
|
||||
+ base::win::ScopedHandle handle =
|
||||
+ registry_->TakeDCOMPSurfaceHandle(token);
|
||||
+ taken++;
|
||||
+ } else {
|
||||
+ std::this_thread::yield();
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ // With the barrier pattern, two threads are sufficient to trigger
|
||||
+ // the race condition for TSAN.
|
||||
+ std::thread t1(register_worker);
|
||||
+ std::thread t2(take_worker);
|
||||
+
|
||||
+ // Wait until both threads are ready at the starting line.
|
||||
+ while (threads_ready.load(std::memory_order_relaxed) < 2) {
|
||||
+ std::this_thread::yield();
|
||||
+ }
|
||||
+
|
||||
+ // Signal the staring flag to allow both threads to race from the initialized
|
||||
+ // state.
|
||||
+ start_flag.store(true, std::memory_order_release);
|
||||
+
|
||||
+ t1.join();
|
||||
+ t2.join();
|
||||
+}
|
||||
+
|
||||
+} // namespace gl
|
||||
114
patches/chromium/cherry-pick-c215f8e6f049.patch
Normal file
114
patches/chromium/cherry-pick-c215f8e6f049.patch
Normal file
@@ -0,0 +1,114 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Anders Hartvoll Ruud <andruud@chromium.org>
|
||||
Date: Tue, 17 Mar 2026 18:43:46 -0700
|
||||
Subject: Iterate on copy of layout subtree roots during LFV::PerformLayout()
|
||||
|
||||
During iteration of LocalFrameView::layout_subtree_root_list_
|
||||
in PerformLayout(), we can do interleaved style and layout tree
|
||||
building due to e.g. container queries. Such layout tree rebuilds
|
||||
can destroy the LayoutObjects being subtree roots,
|
||||
and call LocalFrameView::ClearLayoutSubtreeRoot() in the process,
|
||||
modifying layout_subtree_root_list_ during the iteration.
|
||||
|
||||
To fix this, iterate on a copy of the layout subtree roots instead.
|
||||
Note that even though we do clear layout_subtree_root_list_ immediately
|
||||
after iteration, we can not just std::move the list to a local,
|
||||
since we need to discover (and skip) the roots that were removed
|
||||
during previous iterations.
|
||||
|
||||
Fixed: 491994185
|
||||
Change-Id: I729e3df6e938533467ff4d45e66c666fe27a83c0
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7669842
|
||||
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
|
||||
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1600948}
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
|
||||
index ee76e9f10babe692b891c54a50ae8ee8552ab1c9..4f8ba8fc6b77ea2442ce9c1bddf79efbdf0c58c4 100644
|
||||
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
|
||||
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
|
||||
@@ -750,9 +750,17 @@ void LocalFrameView::PerformLayout() {
|
||||
++add_result.stored_value->value;
|
||||
}
|
||||
}
|
||||
- for (auto& root : layout_subtree_root_list_.Ordered()) {
|
||||
- bool should_rebuild_fragments = false;
|
||||
+ HeapVector<LayoutObjectWithDepth> ordered_roots =
|
||||
+ layout_subtree_root_list_.Ordered();
|
||||
+ for (LayoutObjectWithDepth& root : ordered_roots) {
|
||||
LayoutObject& root_layout_object = *root;
|
||||
+ if (!layout_subtree_root_list_.Contains(root_layout_object)) {
|
||||
+ // A previous iteration removed the entry from the list.
|
||||
+ // This can happen when interleaved style recalc sets the element
|
||||
+ // associated the layout subtree root to display:none.
|
||||
+ continue;
|
||||
+ }
|
||||
+ bool should_rebuild_fragments = false;
|
||||
LayoutBox* container_box = root->ContainingNGBox();
|
||||
if (container_box) {
|
||||
auto it = fragment_tree_spines.find(container_box);
|
||||
diff --git a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.cc b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.cc
|
||||
index f4df5f1bcd9ba21368cf7f178970add45112c56a..36d51800f4b48ff2a98abd09e1eb7f988ca998a3 100644
|
||||
--- a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.cc
|
||||
+++ b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.cc
|
||||
@@ -93,6 +93,10 @@ unsigned LayoutObjectWithDepth::DetermineDepth(LayoutObject* object) {
|
||||
return depth;
|
||||
}
|
||||
|
||||
+bool DepthOrderedLayoutObjectList::Contains(LayoutObject& object) const {
|
||||
+ return Unordered().Contains(&object);
|
||||
+}
|
||||
+
|
||||
const HeapHashSet<Member<LayoutObject>>&
|
||||
DepthOrderedLayoutObjectList::Unordered() const {
|
||||
return data_->objects();
|
||||
diff --git a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
|
||||
index c22ce8c5aba5019c38144206b98f1bd4f9d96683..94d953cc07eb1b7e9338d29b802ea7857bbf28be 100644
|
||||
--- a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
|
||||
+++ b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
|
||||
@@ -59,6 +59,7 @@ class DepthOrderedLayoutObjectList {
|
||||
int size() const;
|
||||
CORE_EXPORT bool IsEmpty() const;
|
||||
|
||||
+ bool Contains(LayoutObject&) const;
|
||||
const HeapHashSet<Member<LayoutObject>>& Unordered() const;
|
||||
const HeapVector<LayoutObjectWithDepth>& Ordered();
|
||||
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/css/css-conditional/container-queries/crashtests/chrome-bug-491994185-crash.html b/third_party/blink/web_tests/external/wpt/css/css-conditional/container-queries/crashtests/chrome-bug-491994185-crash.html
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..cf27c57818d234e079ab485513636961a054080f
|
||||
--- /dev/null
|
||||
+++ b/third_party/blink/web_tests/external/wpt/css/css-conditional/container-queries/crashtests/chrome-bug-491994185-crash.html
|
||||
@@ -0,0 +1,32 @@
|
||||
+<!DOCTYPE html>
|
||||
+<title>Crash Test: Layout subtree root becoming display:none</title>
|
||||
+<link rel="help" href="https://issues.chromium.org/issues/491994185">
|
||||
+<style>
|
||||
+ #container {
|
||||
+ container-type: inline-size;
|
||||
+ }
|
||||
+ @container (min-width: 300px) {
|
||||
+ #victim { display: none; }
|
||||
+ }
|
||||
+</style>
|
||||
+<div style="contain:strict;">
|
||||
+ <div id="herring"></div>
|
||||
+</div>
|
||||
+<div>
|
||||
+ <div style="contain:strict; width:400px;">
|
||||
+ <div id="containerResizer" style="width:10px;">
|
||||
+ <div id="container">
|
||||
+ <div id="victim" style="contain:strict;">
|
||||
+ <div id="innerElm"></div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+ </div>
|
||||
+</div>
|
||||
+<script>
|
||||
+ document.body.offsetTop;
|
||||
+ innerElm.style.height = '40px';
|
||||
+ herring.style.height = '60px';
|
||||
+ containerResizer.style.width = '400px';
|
||||
+ document.body.offsetTop;
|
||||
+</script>
|
||||
47
patches/chromium/cherry-pick-c81f01b469c4.patch
Normal file
47
patches/chromium/cherry-pick-c81f01b469c4.patch
Normal file
@@ -0,0 +1,47 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
|
||||
Date: Tue, 31 Mar 2026 10:03:10 -0700
|
||||
Subject: [a11y] Fix uninitialized memory in
|
||||
get_columnHeaderCells/get_rowHeaderCells
|
||||
|
||||
Both get_columnHeaderCells and get_rowHeaderCells allocate a COM array
|
||||
via CoTaskMemAlloc, but report the requested array size
|
||||
(column_header_ids.size()) to the caller instead of the number of
|
||||
successfully populated elements (index). If a node lookup fails
|
||||
mid-iteration, the trailing array slots contain uninitialized memory
|
||||
that the COM marshaller will treat as valid IUnknown* pointers.
|
||||
|
||||
This CL fixes both methods to return the actual populated count
|
||||
(index), matching the pattern already used by get_targets.
|
||||
|
||||
Fixed: 40833630
|
||||
Change-Id: I596745388199d61eef8261fe0ae6e1d3e773f240
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7717598
|
||||
Reviewed-by: Kevin Babbitt <kbabbitt@microsoft.com>
|
||||
Commit-Queue: Kevin Babbitt <kbabbitt@microsoft.com>
|
||||
Auto-Submit: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
|
||||
Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1607938}
|
||||
|
||||
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
|
||||
index 773834c969ff28b0038d27af2c0351120dba8fc2..f374a0f2e202f2f6eccbab230211f593748974c8 100644
|
||||
--- a/ui/accessibility/platform/ax_platform_node_win.cc
|
||||
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
|
||||
@@ -4258,7 +4258,7 @@ IFACEMETHODIMP AXPlatformNodeWin::get_columnHeaderCells(
|
||||
}
|
||||
}
|
||||
|
||||
- *n_column_header_cells = static_cast<LONG>(column_header_ids.size());
|
||||
+ *n_column_header_cells = static_cast<LONG>(index);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -4312,7 +4312,7 @@ IFACEMETHODIMP AXPlatformNodeWin::get_rowHeaderCells(
|
||||
}
|
||||
}
|
||||
|
||||
- *n_row_header_cells = static_cast<LONG>(row_header_ids.size());
|
||||
+ *n_row_header_cells = static_cast<LONG>(index);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
59
patches/chromium/cherry-pick-d513cd2fe668.patch
Normal file
59
patches/chromium/cherry-pick-d513cd2fe668.patch
Normal file
@@ -0,0 +1,59 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Kenichi Ishibashi <bashi@chromium.org>
|
||||
Date: Fri, 10 Apr 2026 17:14:24 -0700
|
||||
Subject: [CORS] Block forbidden methods for no-cors requests
|
||||
|
||||
Previously, forbidden methods like TRACE and TRACK were allowed when
|
||||
the request mode was no-cors, and only CONNECT was unconditionally
|
||||
blocked.
|
||||
|
||||
This CL updates CorsURLLoaderFactory::IsValidRequest to block all
|
||||
forbidden methods regardless of the request mode. The unit test is
|
||||
also updated to reflect this new restriction.
|
||||
|
||||
Bug: 498765210
|
||||
Change-Id: Ie451a3c2b8fa7aafdebade8b3ba517be3ce255f8
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7743444
|
||||
Reviewed-by: mmenke <mmenke@chromium.org>
|
||||
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1613186}
|
||||
|
||||
diff --git a/services/network/cors/cors_url_loader_factory.cc b/services/network/cors/cors_url_loader_factory.cc
|
||||
index cbc36ef7c5703ebb09fd8c86e5674aea59d8569a..08220f19e3f27500357506eff870ab9126f7bb5f 100644
|
||||
--- a/services/network/cors/cors_url_loader_factory.cc
|
||||
+++ b/services/network/cors/cors_url_loader_factory.cc
|
||||
@@ -908,13 +908,8 @@ bool CorsURLLoaderFactory::IsValidRequest(
|
||||
return false;
|
||||
}
|
||||
|
||||
- // Don't allow forbidden methods for any requests except RequestMode::kNoCors.
|
||||
- // Don't allow CONNECT method for any request.
|
||||
- if ((request.mode != mojom::RequestMode::kNoCors &&
|
||||
- cors::IsForbiddenMethod(request.method)) ||
|
||||
- (request.mode == mojom::RequestMode::kNoCors &&
|
||||
- base::EqualsCaseInsensitiveASCII(
|
||||
- request.method, net::HttpRequestHeaders::kConnectMethod))) {
|
||||
+ // Don't allow forbidden methods.
|
||||
+ if (cors::IsForbiddenMethod(request.method)) {
|
||||
mojo::ReportBadMessage("CorsURLLoaderFactory: Forbidden method");
|
||||
return false;
|
||||
}
|
||||
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
|
||||
index 403f3b4ee828b768d3c6e844bb28208b909ed072..27834acc57d05616bf527af290aa8bd202ee52d9 100644
|
||||
--- a/services/network/cors/cors_url_loader_unittest.cc
|
||||
+++ b/services/network/cors/cors_url_loader_unittest.cc
|
||||
@@ -109,11 +109,10 @@ TEST_F(CorsURLLoaderTest, ForbiddenMethods) {
|
||||
std::string forbidden_method;
|
||||
bool expect_allowed_for_no_cors;
|
||||
} kTestCases[] = {
|
||||
- // CONNECT is never allowed, while TRACE and TRACK are allowed only with
|
||||
- // RequestMode::kNoCors.
|
||||
+ // CONNECT, TRACE and TRACK are not allowed for any mode.
|
||||
{"CONNECT", false},
|
||||
- {"TRACE", true},
|
||||
- {"TRACK", true},
|
||||
+ {"TRACE", false},
|
||||
+ {"TRACK", false},
|
||||
};
|
||||
for (const auto& test_case : kTestCases) {
|
||||
SCOPED_TRACE(test_case.forbidden_method);
|
||||
373
patches/chromium/cherry-pick-eeb3e031eb89.patch
Normal file
373
patches/chromium/cherry-pick-eeb3e031eb89.patch
Normal file
@@ -0,0 +1,373 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Eugene Zemtsov <eugene@chromium.org>
|
||||
Date: Mon, 13 Apr 2026 22:52:33 -0700
|
||||
Subject: [M146] media: Zero-copy VP9 alpha decoding in VpxVideoDecoder
|
||||
|
||||
Original change's description:
|
||||
> media: Zero-copy VP9 alpha decoding in VpxVideoDecoder
|
||||
>
|
||||
> Configures the VP9 alpha decoder to use `memory_pool_` for external
|
||||
> frame buffers, eliminating the need for `libyuv::CopyPlane`.
|
||||
>
|
||||
> The `VideoFrame` now wraps the alpha data directly from the pool using
|
||||
> a second destruction observer. `AllocateAlphaPlaneForFrameBuffer` and
|
||||
> `alpha_data` tracking are removed from `FrameBufferPool`.
|
||||
>
|
||||
> Bug: 500066234
|
||||
> Change-Id: I6e7cf13bcc8a5a1759acfd51961859c4c57fcbf2
|
||||
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737984
|
||||
> Reviewed-by: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
|
||||
> Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
|
||||
> Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
|
||||
> Cr-Commit-Position: refs/heads/main@{#1611919}
|
||||
|
||||
(cherry picked from commit fc79e8cc2dfcc8f7ec8ee9cf0acf0993f32aec27)
|
||||
|
||||
Bug: 501314839,500066234
|
||||
Change-Id: I6e7cf13bcc8a5a1759acfd51961859c4c57fcbf2
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7757063
|
||||
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
|
||||
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/7680@{#3937}
|
||||
Cr-Branched-From: 76b7d80e5cda23fe6537eed26d68c92e995c7f39-refs/heads/main@{#1582197}
|
||||
|
||||
diff --git a/media/base/frame_buffer_pool.cc b/media/base/frame_buffer_pool.cc
|
||||
index e90f07036baab4056398c93a03f8751bbfaa5d69..e2aa3a9243e3ce45b5087853eb2bd7d7dae7acfe 100644
|
||||
--- a/media/base/frame_buffer_pool.cc
|
||||
+++ b/media/base/frame_buffer_pool.cc
|
||||
@@ -56,7 +56,6 @@ struct FrameBufferPool::FrameBuffer {
|
||||
// Not using std::vector<uint8_t> as resize() calls take a really long time
|
||||
// for large buffers.
|
||||
BytesArray data;
|
||||
- BytesArray alpha_data;
|
||||
bool held_by_library = false;
|
||||
// Needs to be a counter since a frame buffer might be used multiple times.
|
||||
int held_by_frame = 0;
|
||||
@@ -148,31 +147,6 @@ void FrameBufferPool::ReleaseFrameBuffer(void* fb_priv) {
|
||||
}
|
||||
}
|
||||
|
||||
-base::span<uint8_t> FrameBufferPool::AllocateAlphaPlaneForFrameBuffer(
|
||||
- size_t min_size,
|
||||
- void* fb_priv) {
|
||||
- base::AutoLock lock(lock_);
|
||||
- DCHECK(fb_priv);
|
||||
-
|
||||
- auto* frame_buffer = static_cast<FrameBuffer*>(fb_priv);
|
||||
- DCHECK(IsUsedLocked(frame_buffer));
|
||||
- if (frame_buffer->alpha_data.size() < min_size) {
|
||||
- // Free the existing |alpha_data| first so that the memory can be reused,
|
||||
- // if possible. Note that the new array is purposely not initialized.
|
||||
- frame_buffer->alpha_data = {};
|
||||
- uint8_t* data = nullptr;
|
||||
- if (force_allocation_error_ ||
|
||||
- !base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data)) ||
|
||||
- !data) {
|
||||
- return {};
|
||||
- }
|
||||
- // SAFETY: We have just allocated `min_size` of memory for `data`.
|
||||
- frame_buffer->alpha_data =
|
||||
- UNSAFE_BUFFERS(BytesArray::FromOwningPointer(data, min_size));
|
||||
- }
|
||||
- return frame_buffer->alpha_data;
|
||||
-}
|
||||
-
|
||||
base::OnceClosure FrameBufferPool::CreateFrameCallback(void* fb_priv) {
|
||||
base::AutoLock lock(lock_);
|
||||
|
||||
@@ -210,10 +184,9 @@ bool FrameBufferPool::OnMemoryDump(
|
||||
size_t bytes_reserved = 0;
|
||||
for (const auto& frame_buffer : frame_buffers_) {
|
||||
if (IsUsedLocked(frame_buffer.get())) {
|
||||
- bytes_used += frame_buffer->data.size() + frame_buffer->alpha_data.size();
|
||||
+ bytes_used += frame_buffer->data.size();
|
||||
}
|
||||
- bytes_reserved +=
|
||||
- frame_buffer->data.size() + frame_buffer->alpha_data.size();
|
||||
+ bytes_reserved += frame_buffer->data.size();
|
||||
}
|
||||
|
||||
memory_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
|
||||
diff --git a/media/base/frame_buffer_pool.h b/media/base/frame_buffer_pool.h
|
||||
index ac839b8e8bfa00d2fea203be5248a56f04cecc71..2ccb01676b0e8e1e3ca1b3cb60f2883538f2f13c 100644
|
||||
--- a/media/base/frame_buffer_pool.h
|
||||
+++ b/media/base/frame_buffer_pool.h
|
||||
@@ -48,11 +48,6 @@ class MEDIA_EXPORT FrameBufferPool
|
||||
// Called when a frame buffer allocation is no longer needed.
|
||||
void ReleaseFrameBuffer(void* fb_priv);
|
||||
|
||||
- // Allocates (or reuses) room for an alpha plane on a given frame buffer.
|
||||
- // |fb_priv| must be a value previously returned by GetFrameBuffer().
|
||||
- base::span<uint8_t> AllocateAlphaPlaneForFrameBuffer(size_t min_size,
|
||||
- void* fb_priv);
|
||||
-
|
||||
// Generates a "no_longer_needed" closure that holds a reference to this pool;
|
||||
// |fb_priv| must be a value previously returned by GetFrameBuffer(). The
|
||||
// callback may be called on any thread.
|
||||
diff --git a/media/base/frame_buffer_pool_unittest.cc b/media/base/frame_buffer_pool_unittest.cc
|
||||
index a5b7bff2b8af3d2f9a531e894ec28e31e7823ac0..4cfdb1520cc18548fd91b2cca8b03a0124de944f 100644
|
||||
--- a/media/base/frame_buffer_pool_unittest.cc
|
||||
+++ b/media/base/frame_buffer_pool_unittest.cc
|
||||
@@ -32,12 +32,6 @@ TEST(FrameBufferPool, BasicFunctionality) {
|
||||
EXPECT_NE(buf1.data(), buf2.data());
|
||||
std::ranges::fill(buf2, 0);
|
||||
|
||||
- auto alpha = pool->AllocateAlphaPlaneForFrameBuffer(kBufferSize, priv1);
|
||||
- ASSERT_FALSE(alpha.empty());
|
||||
- EXPECT_NE(alpha.data(), buf1.data());
|
||||
- EXPECT_NE(alpha.data(), buf2.data());
|
||||
- std::ranges::fill(alpha, 0);
|
||||
-
|
||||
EXPECT_EQ(2u, pool->get_pool_size_for_testing());
|
||||
|
||||
// Frames are not released immediately, so this should still show two frames.
|
||||
@@ -52,7 +46,6 @@ TEST(FrameBufferPool, BasicFunctionality) {
|
||||
EXPECT_EQ(1u, pool->get_pool_size_for_testing());
|
||||
|
||||
std::ranges::fill(buf1, 0);
|
||||
- std::ranges::fill(alpha, 0);
|
||||
|
||||
// This will release all memory since we're in the shutdown state.
|
||||
std::move(frame_release_cb).Run();
|
||||
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
|
||||
index 0be38f7ee110a0084854c571784e9dd3c8144f51..32cd3c423f4f01aa4cbe21ae71bf149f26a1deee 100644
|
||||
--- a/media/filters/vpx_video_decoder.cc
|
||||
+++ b/media/filters/vpx_video_decoder.cc
|
||||
@@ -269,7 +269,21 @@ bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) {
|
||||
|
||||
DCHECK(!vpx_codec_alpha_);
|
||||
vpx_codec_alpha_ = InitializeVpxContext(config);
|
||||
- return !!vpx_codec_alpha_;
|
||||
+ if (!vpx_codec_alpha_) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (config.codec() == VideoCodec::kVP9) {
|
||||
+ if (vpx_codec_set_frame_buffer_functions(
|
||||
+ vpx_codec_alpha_.get(), &GetVP9FrameBuffer, &ReleaseVP9FrameBuffer,
|
||||
+ memory_pool_.get())) {
|
||||
+ DLOG(ERROR) << "Failed to configure external buffers for alpha. "
|
||||
+ << vpx_codec_error(vpx_codec_alpha_.get());
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
}
|
||||
|
||||
void VpxVideoDecoder::CloseDecoder() {
|
||||
@@ -576,20 +590,13 @@ bool VpxVideoDecoder::CopyVpxImageToVideoFrame(
|
||||
if (memory_pool_) {
|
||||
DCHECK_EQ(VideoCodec::kVP9, config_.codec());
|
||||
if (vpx_image_alpha) {
|
||||
+ CHECK_GT(vpx_image_alpha->stride[VPX_PLANE_Y], 0);
|
||||
size_t alpha_plane_size =
|
||||
vpx_image_alpha->stride[VPX_PLANE_Y] * vpx_image_alpha->d_h;
|
||||
- auto alpha_plane = memory_pool_->AllocateAlphaPlaneForFrameBuffer(
|
||||
- alpha_plane_size, vpx_image->fb_priv);
|
||||
- if (alpha_plane.empty()) {
|
||||
- error_status_ = DecoderStatus::Codes::kOutOfMemory;
|
||||
- // In case of OOM, abort copy.
|
||||
- return false;
|
||||
- }
|
||||
- libyuv::CopyPlane(vpx_image_alpha->planes[VPX_PLANE_Y],
|
||||
- vpx_image_alpha->stride[VPX_PLANE_Y],
|
||||
- alpha_plane.data(),
|
||||
- vpx_image_alpha->stride[VPX_PLANE_Y],
|
||||
- vpx_image_alpha->d_w, vpx_image_alpha->d_h);
|
||||
+ // SAFETY: libvpx guarantees that the Y plane has at least `stride * d_h`
|
||||
+ // bytes available.
|
||||
+ auto alpha_plane = UNSAFE_BUFFERS(base::span<uint8_t>(
|
||||
+ vpx_image_alpha->planes[VPX_PLANE_Y], alpha_plane_size));
|
||||
*video_frame = VideoFrame::WrapExternalYuvaData(
|
||||
codec_format, coded_size, gfx::Rect(visible_size), natural_size,
|
||||
vpx_image->stride[VPX_PLANE_Y], vpx_image->stride[VPX_PLANE_U],
|
||||
@@ -605,8 +612,14 @@ bool VpxVideoDecoder::CopyVpxImageToVideoFrame(
|
||||
if (!(*video_frame))
|
||||
return false;
|
||||
|
||||
- video_frame->get()->AddDestructionObserver(
|
||||
- memory_pool_->CreateFrameCallback(vpx_image->fb_priv));
|
||||
+ (*video_frame)
|
||||
+ ->AddDestructionObserver(
|
||||
+ memory_pool_->CreateFrameCallback(vpx_image->fb_priv));
|
||||
+ if (vpx_image_alpha) {
|
||||
+ (*video_frame)
|
||||
+ ->AddDestructionObserver(
|
||||
+ memory_pool_->CreateFrameCallback(vpx_image_alpha->fb_priv));
|
||||
+ }
|
||||
return true;
|
||||
}
|
||||
|
||||
diff --git a/media/filters/vpx_video_decoder.h b/media/filters/vpx_video_decoder.h
|
||||
index 7bcba319954ed43175e42c2dc1b991c5b6129138..2ab3767680ee408215bf2debb6f85c033f45af68 100644
|
||||
--- a/media/filters/vpx_video_decoder.h
|
||||
+++ b/media/filters/vpx_video_decoder.h
|
||||
@@ -104,8 +104,8 @@ class MEDIA_EXPORT VpxVideoDecoder : public OffloadableVideoDecoder {
|
||||
std::unique_ptr<vpx_codec_ctx> vpx_codec_;
|
||||
std::unique_ptr<vpx_codec_ctx> vpx_codec_alpha_;
|
||||
|
||||
- // |memory_pool_| is a single-threaded memory pool used for VP9 decoding
|
||||
- // with no alpha. |frame_pool_| is used for all other cases.
|
||||
+ // |memory_pool_| is a thread-safe memory pool used for zero-copy VP9 decoding
|
||||
+ // (both with and without alpha). |frame_pool_| is used for VP8.
|
||||
scoped_refptr<FrameBufferPool> memory_pool_;
|
||||
VideoFramePool frame_pool_;
|
||||
|
||||
diff --git a/media/filters/vpx_video_decoder_unittest.cc b/media/filters/vpx_video_decoder_unittest.cc
|
||||
index c7f6d13bd825425230b63d87c13466e49f3c3c59..5203645bc8ec89dd93827fc0cbebb92e803faac1 100644
|
||||
--- a/media/filters/vpx_video_decoder_unittest.cc
|
||||
+++ b/media/filters/vpx_video_decoder_unittest.cc
|
||||
@@ -176,6 +176,28 @@ class VpxVideoDecoderTest : public testing::Test {
|
||||
output_frames_.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
+ // Extracts the compressed video data from the AVPacket and also checks for
|
||||
+ // side data containing an alpha channel. If found, it copies the alpha data
|
||||
+ // into the DecoderBuffer's side data. This is necessary because FFmpeg
|
||||
+ // demuxes alpha channel data as side data associated with the video packet.
|
||||
+ static scoped_refptr<DecoderBuffer> CreateBufferWithAlphaFromPacket(
|
||||
+ const AVPacket* packet) {
|
||||
+ auto buffer = DecoderBuffer::CopyFrom(AVPacketData(*packet));
|
||||
+ size_t side_data_size = 0;
|
||||
+ uint8_t* side_data_ptr = av_packet_get_side_data(
|
||||
+ packet, AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, &side_data_size);
|
||||
+ if (side_data_size > 8) {
|
||||
+ // SAFETY: The best we can do here is trust the size reported by ffmpeg.
|
||||
+ auto side_data =
|
||||
+ UNSAFE_BUFFERS(base::span(side_data_ptr, side_data_size));
|
||||
+ if (base::U64FromBigEndian(side_data.first<8u>()) == 1) {
|
||||
+ buffer->WritableSideData().alpha_data =
|
||||
+ base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u));
|
||||
+ }
|
||||
+ }
|
||||
+ return buffer;
|
||||
+ }
|
||||
+
|
||||
MOCK_METHOD1(DecodeDone, void(DecoderStatus));
|
||||
|
||||
base::test::TaskEnvironment task_env_;
|
||||
@@ -293,6 +315,68 @@ TEST_F(VpxVideoDecoderTest, SimpleFrameReuse) {
|
||||
EXPECT_EQ(old_y_data, output_frames_.back()->data(VideoFrame::Plane::kY));
|
||||
}
|
||||
|
||||
+TEST_F(VpxVideoDecoderTest, SimpleAlphaFrameReuse) {
|
||||
+ VideoDecoderConfig config = TestVideoConfig::Normal(VideoCodec::kVP9);
|
||||
+ config.Initialize(
|
||||
+ config.codec(), config.profile(),
|
||||
+ VideoDecoderConfig::AlphaMode::kHasAlpha, config.color_space_info(),
|
||||
+ config.video_transformation(), config.coded_size(), config.visible_rect(),
|
||||
+ config.natural_size(), config.extra_data(), config.encryption_scheme());
|
||||
+ InitializeWithConfig(config);
|
||||
+ scoped_refptr<DecoderBuffer> alpha_frame = ReadTestDataFile("bear-vp9a.webm");
|
||||
+
|
||||
+ // Read frames from the webm file.
|
||||
+ InMemoryUrlProtocol protocol(*alpha_frame, false);
|
||||
+ FFmpegGlue glue(&protocol);
|
||||
+ ASSERT_TRUE(glue.OpenContext());
|
||||
+
|
||||
+ auto packet = ScopedAVPacket::Allocate();
|
||||
+
|
||||
+ // Decode first frame
|
||||
+ ASSERT_GE(av_read_frame(glue.format_context(), packet.get()), 0);
|
||||
+ auto buffer = CreateBufferWithAlphaFromPacket(packet.get());
|
||||
+ Decode(buffer);
|
||||
+ av_packet_unref(packet.get());
|
||||
+
|
||||
+ ASSERT_EQ(1u, output_frames_.size());
|
||||
+ scoped_refptr<VideoFrame> frame = std::move(output_frames_.front());
|
||||
+ EXPECT_EQ(PIXEL_FORMAT_I420A, frame->format());
|
||||
+ const uint8_t* old_y_data = frame->data(VideoFrame::Plane::kY);
|
||||
+ const uint8_t* old_a_data = frame->data(VideoFrame::Plane::kA);
|
||||
+ output_frames_.pop_back();
|
||||
+
|
||||
+ // Clear frame reference to return the frame to the pool.
|
||||
+ frame = nullptr;
|
||||
+
|
||||
+ // Decode second frame.
|
||||
+ Decode(buffer);
|
||||
+ const uint8_t* mid_y_data =
|
||||
+ output_frames_.front()->data(VideoFrame::Plane::kY);
|
||||
+ const uint8_t* mid_a_data =
|
||||
+ output_frames_.front()->data(VideoFrame::Plane::kA);
|
||||
+ output_frames_.clear();
|
||||
+
|
||||
+ // Issuing another decode should reuse buffers from the pool.
|
||||
+ Decode(buffer);
|
||||
+
|
||||
+ ASSERT_EQ(1u, output_frames_.size());
|
||||
+ const uint8_t* new_y_data =
|
||||
+ output_frames_.back()->data(VideoFrame::Plane::kY);
|
||||
+ const uint8_t* new_a_data =
|
||||
+ output_frames_.back()->data(VideoFrame::Plane::kA);
|
||||
+
|
||||
+ // The pool is shared, so buffers might be reused in a different order (e.g. Y
|
||||
+ // might get the buffer previously used for A). Because libvpx allocates the
|
||||
+ // new frame before releasing the old reference frame, we need to check across
|
||||
+ // all previously allocated buffers.
|
||||
+ bool reused_y = new_y_data == old_y_data || new_y_data == old_a_data ||
|
||||
+ new_y_data == mid_y_data || new_y_data == mid_a_data;
|
||||
+ bool reused_a = new_a_data == old_y_data || new_a_data == old_a_data ||
|
||||
+ new_a_data == mid_y_data || new_a_data == mid_a_data;
|
||||
+ EXPECT_TRUE(reused_y);
|
||||
+ EXPECT_TRUE(reused_a);
|
||||
+}
|
||||
+
|
||||
TEST_F(VpxVideoDecoderTest, SimpleFormatChange) {
|
||||
scoped_refptr<DecoderBuffer> large_frame =
|
||||
ReadTestDataFile("vp9-I-frame-1280x720");
|
||||
@@ -312,9 +396,41 @@ TEST_F(VpxVideoDecoderTest, FrameValidAfterPoolDestruction) {
|
||||
|
||||
// Write to the Y plane. The memory tools should detect a
|
||||
// use-after-free if the storage was actually removed by pool destruction.
|
||||
- memset(output_frames_.front()->writable_data(VideoFrame::Plane::kY), 0xff,
|
||||
- output_frames_.front()->rows(VideoFrame::Plane::kY) *
|
||||
- output_frames_.front()->stride(VideoFrame::Plane::kY));
|
||||
+ std::ranges::fill(
|
||||
+ output_frames_.front()->writable_span(VideoFrame::Plane::kY), 0xff);
|
||||
+}
|
||||
+
|
||||
+TEST_F(VpxVideoDecoderTest, AlphaFrameValidAfterPoolDestruction) {
|
||||
+ VideoDecoderConfig config = TestVideoConfig::Normal(VideoCodec::kVP9);
|
||||
+ config.Initialize(
|
||||
+ config.codec(), config.profile(),
|
||||
+ VideoDecoderConfig::AlphaMode::kHasAlpha, config.color_space_info(),
|
||||
+ config.video_transformation(), config.coded_size(), config.visible_rect(),
|
||||
+ config.natural_size(), config.extra_data(), config.encryption_scheme());
|
||||
+ InitializeWithConfig(config);
|
||||
+ scoped_refptr<DecoderBuffer> alpha_frame = ReadTestDataFile("bear-vp9a.webm");
|
||||
+
|
||||
+ InMemoryUrlProtocol protocol(*alpha_frame, false);
|
||||
+ FFmpegGlue glue(&protocol);
|
||||
+ ASSERT_TRUE(glue.OpenContext());
|
||||
+
|
||||
+ auto packet = ScopedAVPacket::Allocate();
|
||||
+ ASSERT_GE(av_read_frame(glue.format_context(), packet.get()), 0);
|
||||
+ auto buffer = CreateBufferWithAlphaFromPacket(packet.get());
|
||||
+ Decode(std::move(buffer));
|
||||
+ av_packet_unref(packet.get());
|
||||
+
|
||||
+ ASSERT_EQ(1u, output_frames_.size());
|
||||
+ EXPECT_EQ(PIXEL_FORMAT_I420A, output_frames_.front()->format());
|
||||
+
|
||||
+ Destroy();
|
||||
+
|
||||
+ // Write to the Y and A planes. The memory tools should detect a
|
||||
+ // use-after-free if the storage was actually removed by pool destruction.
|
||||
+ std::ranges::fill(
|
||||
+ output_frames_.front()->writable_span(VideoFrame::Plane::kY), 0xff);
|
||||
+ std::ranges::fill(
|
||||
+ output_frames_.front()->writable_span(VideoFrame::Plane::kA), 0xff);
|
||||
}
|
||||
|
||||
// The test stream uses profile 2, which needs high bit depth support in libvpx.
|
||||
@@ -362,8 +478,7 @@ TEST_F(VpxVideoDecoderTest, MemoryPoolAllowsMultipleDisplay) {
|
||||
Destroy();
|
||||
|
||||
// ASAN will be very unhappy with this line if the above is incorrect.
|
||||
- memset(last_frame->writable_data(VideoFrame::Plane::kY), 0,
|
||||
- last_frame->row_bytes(VideoFrame::Plane::kY));
|
||||
+ std::ranges::fill(last_frame->writable_span(VideoFrame::Plane::kY), 0);
|
||||
}
|
||||
#endif // !defined(LIBVPX_NO_HIGH_BIT_DEPTH) && !defined(ARCH_CPU_ARM_FAMILY)
|
||||
|
||||
@@ -12,5 +12,9 @@
|
||||
{ "patch_dir": "src/electron/patches/ReactiveObjC", "repo": "src/third_party/squirrel.mac/vendor/ReactiveObjC" },
|
||||
{ "patch_dir": "src/electron/patches/webrtc", "repo": "src/third_party/webrtc" },
|
||||
{ "patch_dir": "src/electron/patches/reclient-configs", "repo": "src/third_party/engflow-reclient-configs" },
|
||||
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" }
|
||||
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" },
|
||||
{ "patch_dir": "src/electron/patches/angle", "repo": "src/third_party/angle" },
|
||||
{ "patch_dir": "src/electron/patches/skia", "repo": "src/third_party/skia" },
|
||||
{ "patch_dir": "src/electron/patches/pdfium", "repo": "src/third_party/pdfium" },
|
||||
{ "patch_dir": "src/electron/patches/libaom", "repo": "src/third_party/libaom/source/libaom" }
|
||||
]
|
||||
|
||||
4
patches/libaom/.patches
Normal file
4
patches/libaom/.patches
Normal file
@@ -0,0 +1,4 @@
|
||||
cherry-pick-4369bd1258dc.patch
|
||||
cherry-pick-a047955845e5.patch
|
||||
cherry-pick-c61e9586156f.patch
|
||||
cherry-pick-395efd18d8ef.patch
|
||||
27
patches/libaom/cherry-pick-395efd18d8ef.patch
Normal file
27
patches/libaom/cherry-pick-395efd18d8ef.patch
Normal file
@@ -0,0 +1,27 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: James Zern <jzern@google.com>
|
||||
Date: Wed, 1 Apr 2026 20:56:24 -0700
|
||||
Subject: av1_nonrd_pick_inter_mode_sb: normalize ref frame check
|
||||
|
||||
Prefer `search_state.use_ref_frame_mask[]` over `cpi->ref_frame_flags`.
|
||||
These are equivalent and checking the former is more consistent with the
|
||||
rest of the function. This is a follow up to:
|
||||
4369bd1258 av1_nonrd_pick_inter_mode_sb: add missing ref_frame_flags check
|
||||
|
||||
Bug: 495477995, 495996858
|
||||
Change-Id: Ie4bd1f4c80c4182add35c7a9c1977c15ce97d3bd
|
||||
(cherry picked from commit 395efd18d8ef31d8452a0336e848c02072feffe7)
|
||||
|
||||
diff --git a/av1/encoder/nonrd_pickmode.c b/av1/encoder/nonrd_pickmode.c
|
||||
index 29539b18566c37d0e81975f54429f699907f619e..54d497af619cb7f59f3797c87dd3208c2b2448da 100644
|
||||
--- a/av1/encoder/nonrd_pickmode.c
|
||||
+++ b/av1/encoder/nonrd_pickmode.c
|
||||
@@ -3448,7 +3448,7 @@ void av1_nonrd_pick_inter_mode_sb(AV1_COMP *cpi, TileDataEnc *tile_data,
|
||||
!x->force_zeromv_skip_for_blk &&
|
||||
x->content_state_sb.source_sad_nonrd != kZeroSad &&
|
||||
x->source_variance == 0 && bsize < cm->seq_params->sb_size &&
|
||||
- (cpi->ref_frame_flags & AOM_LAST_FLAG) &&
|
||||
+ search_state.use_ref_frame_mask[LAST_FRAME] &&
|
||||
search_state.yv12_mb[LAST_FRAME][0].width == cm->width &&
|
||||
search_state.yv12_mb[LAST_FRAME][0].height == cm->height) {
|
||||
set_block_source_sad(cpi, x, bsize, &search_state.yv12_mb[LAST_FRAME][0]);
|
||||
24
patches/libaom/cherry-pick-4369bd1258dc.patch
Normal file
24
patches/libaom/cherry-pick-4369bd1258dc.patch
Normal file
@@ -0,0 +1,24 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: James Zern <jzern@google.com>
|
||||
Date: Fri, 27 Mar 2026 10:56:13 -0700
|
||||
Subject: av1_nonrd_pick_inter_mode_sb: add missing ref_frame_flags check
|
||||
|
||||
Before calling `set_block_source_sad()` ensure `LAST_FRAME` is
|
||||
available. Fixes a crash that may present as a use after free (UAF).
|
||||
|
||||
Bug: 495477995, 495996858
|
||||
Change-Id: I61452ce412fb9071c3370b4350ed8878013a8355
|
||||
(cherry picked from commit 4369bd1258dc99fa759916d9aba6509cdda9d877)
|
||||
|
||||
diff --git a/av1/encoder/nonrd_pickmode.c b/av1/encoder/nonrd_pickmode.c
|
||||
index f2010062323b0ff4a1236ef63516d9b2d8f3007a..0f2a1c780a56a51f69bba8893fea9d9ad98b85a3 100644
|
||||
--- a/av1/encoder/nonrd_pickmode.c
|
||||
+++ b/av1/encoder/nonrd_pickmode.c
|
||||
@@ -3440,6 +3440,7 @@ void av1_nonrd_pick_inter_mode_sb(AV1_COMP *cpi, TileDataEnc *tile_data,
|
||||
!x->force_zeromv_skip_for_blk &&
|
||||
x->content_state_sb.source_sad_nonrd != kZeroSad &&
|
||||
x->source_variance == 0 && bsize < cm->seq_params->sb_size &&
|
||||
+ (cpi->ref_frame_flags & AOM_LAST_FLAG) &&
|
||||
search_state.yv12_mb[LAST_FRAME][0].width == cm->width &&
|
||||
search_state.yv12_mb[LAST_FRAME][0].height == cm->height) {
|
||||
set_block_source_sad(cpi, x, bsize, &search_state.yv12_mb[LAST_FRAME][0]);
|
||||
187
patches/libaom/cherry-pick-a047955845e5.patch
Normal file
187
patches/libaom/cherry-pick-a047955845e5.patch
Normal file
@@ -0,0 +1,187 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Marco Paniconi <marpan@google.com>
|
||||
Date: Sun, 29 Mar 2026 20:27:20 -0700
|
||||
Subject: Set force_mv_inter_layer earlier in skip_inter_mode
|
||||
|
||||
For nonrd_pickmode: move the setting of
|
||||
force_mv_inter_layer earlier in the
|
||||
skip_inter_mode_nonrd(), to make sure it always
|
||||
get set (in case of false return in that function).
|
||||
|
||||
Thie prevents the usage of a scaled_ref in pickmode
|
||||
(combined_motion search) when it has actually not been
|
||||
set/scaled in av1_scale_references (before encoding).
|
||||
|
||||
Fixes a crash for use after free (UAF), reported
|
||||
in the issues below.
|
||||
|
||||
Added svc unittest to generate the issue. Also added
|
||||
assert check for scaled_ref in combined_motion_search.
|
||||
|
||||
Bug: 495477995, 495996858
|
||||
Change-Id: I578d19156d97a50546edc9422bc3581566f1236e
|
||||
(cherry picked from commit a047955845e50e43786d51cdefcfc9e87804ed61)
|
||||
|
||||
diff --git a/av1/encoder/nonrd_pickmode.c b/av1/encoder/nonrd_pickmode.c
|
||||
index 0f2a1c780a56a51f69bba8893fea9d9ad98b85a3..942b8ab23a2d448877c8801940fee4d0baae9aef 100644
|
||||
--- a/av1/encoder/nonrd_pickmode.c
|
||||
+++ b/av1/encoder/nonrd_pickmode.c
|
||||
@@ -192,7 +192,7 @@ static int combined_motion_search(AV1_COMP *cpi, MACROBLOCK *x,
|
||||
int *rate_mv, int64_t best_rd_sofar,
|
||||
int use_base_mv) {
|
||||
MACROBLOCKD *xd = &x->e_mbd;
|
||||
- const AV1_COMMON *cm = &cpi->common;
|
||||
+ AV1_COMMON *cm = &cpi->common;
|
||||
const SPEED_FEATURES *sf = &cpi->sf;
|
||||
MB_MODE_INFO *mi = xd->mi[0];
|
||||
int step_param = (sf->rt_sf.fullpel_search_step_param)
|
||||
@@ -207,6 +207,14 @@ static int combined_motion_search(AV1_COMP *cpi, MACROBLOCK *x,
|
||||
int cost_list[5];
|
||||
int search_subpel = 1;
|
||||
|
||||
+ if (av1_is_scaled(get_ref_scale_factors(cm, ref))) {
|
||||
+ const YV12_BUFFER_CONFIG *scaled_ref = av1_get_scaled_ref_frame(cpi, ref);
|
||||
+ (void)scaled_ref;
|
||||
+ assert(scaled_ref != NULL);
|
||||
+ assert(scaled_ref->y_crop_width == cm->width &&
|
||||
+ scaled_ref->y_crop_height == cm->height);
|
||||
+ }
|
||||
+
|
||||
start_mv = get_fullmv_from_mv(&ref_mv);
|
||||
|
||||
if (!use_base_mv)
|
||||
@@ -2490,6 +2498,23 @@ static AOM_FORCE_INLINE bool skip_inter_mode_nonrd(
|
||||
(*this_mode != GLOBALMV || *ref_frame != LAST_FRAME))
|
||||
return true;
|
||||
|
||||
+ *force_mv_inter_layer = 0;
|
||||
+ if (cpi->ppi->use_svc && svc->spatial_layer_id > 0 &&
|
||||
+ ((*ref_frame == LAST_FRAME && svc->skip_mvsearch_last) ||
|
||||
+ (*ref_frame == GOLDEN_FRAME && svc->skip_mvsearch_gf) ||
|
||||
+ (*ref_frame == ALTREF_FRAME && svc->skip_mvsearch_altref))) {
|
||||
+ // Only test mode if NEARESTMV/NEARMV is (svc_mv.mv.col, svc_mv.mv.row),
|
||||
+ // otherwise set NEWMV to (svc_mv.mv.col, svc_mv.mv.row).
|
||||
+ // Skip newmv and filter search.
|
||||
+ *force_mv_inter_layer = 1;
|
||||
+ if (*this_mode == NEWMV) {
|
||||
+ search_state->frame_mv[*this_mode][*ref_frame] = svc_mv;
|
||||
+ } else if (search_state->frame_mv[*this_mode][*ref_frame].as_int !=
|
||||
+ svc_mv.as_int) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// If the segment reference frame feature is enabled then do nothing if the
|
||||
// current ref frame is not allowed.
|
||||
if (segfeature_active(seg, segment_id, SEG_LVL_REF_FRAME)) {
|
||||
@@ -2565,23 +2590,6 @@ static AOM_FORCE_INLINE bool skip_inter_mode_nonrd(
|
||||
return true;
|
||||
}
|
||||
|
||||
- *force_mv_inter_layer = 0;
|
||||
- if (cpi->ppi->use_svc && svc->spatial_layer_id > 0 &&
|
||||
- ((*ref_frame == LAST_FRAME && svc->skip_mvsearch_last) ||
|
||||
- (*ref_frame == GOLDEN_FRAME && svc->skip_mvsearch_gf) ||
|
||||
- (*ref_frame == ALTREF_FRAME && svc->skip_mvsearch_altref))) {
|
||||
- // Only test mode if NEARESTMV/NEARMV is (svc_mv.mv.col, svc_mv.mv.row),
|
||||
- // otherwise set NEWMV to (svc_mv.mv.col, svc_mv.mv.row).
|
||||
- // Skip newmv and filter search.
|
||||
- *force_mv_inter_layer = 1;
|
||||
- if (*this_mode == NEWMV) {
|
||||
- search_state->frame_mv[*this_mode][*ref_frame] = svc_mv;
|
||||
- } else if (search_state->frame_mv[*this_mode][*ref_frame].as_int !=
|
||||
- svc_mv.as_int) {
|
||||
- return true;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
// For screen content: skip mode testing based on source_sad.
|
||||
if (cpi->oxcf.tune_cfg.content == AOM_CONTENT_SCREEN &&
|
||||
!x->force_zeromv_skip_for_blk) {
|
||||
diff --git a/test/svc_datarate_test.cc b/test/svc_datarate_test.cc
|
||||
index 0df678212acb0519aa4420ae57186840e12c682c..2f68ba7a214932b284a6eacbe1a9b5b474b6c659 100644
|
||||
--- a/test/svc_datarate_test.cc
|
||||
+++ b/test/svc_datarate_test.cc
|
||||
@@ -247,6 +247,7 @@ class DatarateTestSVC
|
||||
external_resize_pattern_ = 0;
|
||||
dynamic_tl_ = false;
|
||||
dynamic_scale_factors_ = false;
|
||||
+ disable_last_ref_ = false;
|
||||
}
|
||||
|
||||
void PreEncodeFrameHook(::libaom_test::VideoSource *video,
|
||||
@@ -302,7 +303,7 @@ class DatarateTestSVC
|
||||
spatial_layer_id, multi_ref_, comp_pred_,
|
||||
(video->frame() % cfg_.kf_max_dist) == 0, dynamic_enable_disable_mode_,
|
||||
rps_mode_, rps_recovery_frame_, simulcast_mode_, use_last_as_scaled_,
|
||||
- use_last_as_scaled_single_ref_);
|
||||
+ use_last_as_scaled_single_ref_, disable_last_ref_);
|
||||
if (intra_only_ == 1 && frame_sync_ > 0) {
|
||||
// Set an Intra-only frame on SL0 at frame_sync_.
|
||||
// In order to allow decoding to start on SL0 in mid-sequence we need to
|
||||
@@ -964,7 +965,7 @@ class DatarateTestSVC
|
||||
int multi_ref, int comp_pred, int is_key_frame,
|
||||
int dynamic_enable_disable_mode, int rps_mode, int rps_recovery_frame,
|
||||
int simulcast_mode, bool use_last_as_scaled,
|
||||
- bool use_last_as_scaled_single_ref) {
|
||||
+ bool use_last_as_scaled_single_ref, bool disable_last_ref) {
|
||||
int lag_index = 0;
|
||||
int base_count = frame_cnt >> 2;
|
||||
layer_id->spatial_layer_id = spatial_layer;
|
||||
@@ -1164,6 +1165,11 @@ class DatarateTestSVC
|
||||
if (dynamic_enable_disable_mode == 1 &&
|
||||
layer_id->spatial_layer_id == number_spatial_layers_ - 1)
|
||||
ref_frame_config->reference[0] = 0;
|
||||
+ // Always disable LAST reference under this flag. use GOLDEN reference.
|
||||
+ if (disable_last_ref) {
|
||||
+ ref_frame_config->reference[0] = 0;
|
||||
+ ref_frame_config->reference[3] = 1;
|
||||
+ }
|
||||
return layer_flags;
|
||||
}
|
||||
|
||||
@@ -1508,6 +1514,23 @@ class DatarateTestSVC
|
||||
CheckDatarate(0.80, 1.60);
|
||||
}
|
||||
|
||||
+ virtual void BasicRateTargetingSVC1TL2SLDisableLASTTest() {
|
||||
+ SetUpCbr();
|
||||
+ cfg_.g_error_resilient = 0;
|
||||
+
|
||||
+ ::libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352,
|
||||
+ 288, 30, 1, 0, 300);
|
||||
+ const int bitrate_array[2] = { 300, 600 };
|
||||
+ cfg_.rc_target_bitrate = bitrate_array[GET_PARAM(4)];
|
||||
+ ResetModel();
|
||||
+ disable_last_ref_ = true;
|
||||
+ screen_mode_ = true;
|
||||
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
|
||||
+#if CONFIG_AV1_DECODER
|
||||
+ EXPECT_EQ((int)GetMismatchFrames(), 0);
|
||||
+#endif
|
||||
+ }
|
||||
+
|
||||
virtual void BasicRateTargetingSVC3TL3SLIntraStartDecodeBaseMidSeq() {
|
||||
SetUpCbr();
|
||||
cfg_.rc_max_quantizer = 56;
|
||||
@@ -2380,6 +2403,7 @@ class DatarateTestSVC
|
||||
int external_resize_pattern_;
|
||||
bool dynamic_tl_;
|
||||
bool dynamic_scale_factors_;
|
||||
+ bool disable_last_ref_;
|
||||
};
|
||||
|
||||
// Check basic rate targeting for CBR, for 3 temporal layers, 1 spatial.
|
||||
@@ -2458,6 +2482,12 @@ TEST_P(DatarateTestSVC, BasicRateTargetingSVC1TL2SL) {
|
||||
BasicRateTargetingSVC1TL2SLTest();
|
||||
}
|
||||
|
||||
+// Check basic rate targeting for CBR, for 2 spatial layers, 1 temporal.
|
||||
+// Disable the usage of LAST referenc frame.
|
||||
+TEST_P(DatarateTestSVC, BasicRateTargetingSVC1TL2SLDisableLAST) {
|
||||
+ BasicRateTargetingSVC1TL2SLDisableLASTTest();
|
||||
+}
|
||||
+
|
||||
// Check basic rate targeting for CBR, for 3 spatial layers, 3 temporal,
|
||||
// with Intra-only frame inserted in the stream. Verify that we can start
|
||||
// decoding the SL0 stream at the intra_only frame in mid-sequence.
|
||||
37
patches/libaom/cherry-pick-c61e9586156f.patch
Normal file
37
patches/libaom/cherry-pick-c61e9586156f.patch
Normal file
@@ -0,0 +1,37 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Wan-Teh Chang <wtc@google.com>
|
||||
Date: Wed, 1 Apr 2026 18:57:32 -0700
|
||||
Subject: Change cm back to const in combined_motion_search
|
||||
|
||||
The local variable cm in combined_motion_search() was changed to a
|
||||
non-const pointer so that it could be passed to get_ref_scale_factors().
|
||||
There is a get_ref_scale_factors_const() function for this purpose.
|
||||
|
||||
A follow-up to commit a047955.
|
||||
|
||||
Bug: 495477995, 495996858
|
||||
Change-Id: Ic8b66f8060247a3487a7740fe5383c6e5455fa10
|
||||
(cherry picked from commit c61e9586156f0023ad31e8a6abb0dfdcfd820927)
|
||||
|
||||
diff --git a/av1/encoder/nonrd_pickmode.c b/av1/encoder/nonrd_pickmode.c
|
||||
index 942b8ab23a2d448877c8801940fee4d0baae9aef..29539b18566c37d0e81975f54429f699907f619e 100644
|
||||
--- a/av1/encoder/nonrd_pickmode.c
|
||||
+++ b/av1/encoder/nonrd_pickmode.c
|
||||
@@ -192,7 +192,7 @@ static int combined_motion_search(AV1_COMP *cpi, MACROBLOCK *x,
|
||||
int *rate_mv, int64_t best_rd_sofar,
|
||||
int use_base_mv) {
|
||||
MACROBLOCKD *xd = &x->e_mbd;
|
||||
- AV1_COMMON *cm = &cpi->common;
|
||||
+ const AV1_COMMON *cm = &cpi->common;
|
||||
const SPEED_FEATURES *sf = &cpi->sf;
|
||||
MB_MODE_INFO *mi = xd->mi[0];
|
||||
int step_param = (sf->rt_sf.fullpel_search_step_param)
|
||||
@@ -207,7 +207,7 @@ static int combined_motion_search(AV1_COMP *cpi, MACROBLOCK *x,
|
||||
int cost_list[5];
|
||||
int search_subpel = 1;
|
||||
|
||||
- if (av1_is_scaled(get_ref_scale_factors(cm, ref))) {
|
||||
+ if (av1_is_scaled(get_ref_scale_factors_const(cm, ref))) {
|
||||
const YV12_BUFFER_CONFIG *scaled_ref = av1_get_scaled_ref_frame(cpi, ref);
|
||||
(void)scaled_ref;
|
||||
assert(scaled_ref != NULL);
|
||||
2
patches/pdfium/.patches
Normal file
2
patches/pdfium/.patches
Normal file
@@ -0,0 +1,2 @@
|
||||
cherry-pick-ca8a943c247c.patch
|
||||
cherry-pick-bce2e6728279.patch
|
||||
36
patches/pdfium/cherry-pick-bce2e6728279.patch
Normal file
36
patches/pdfium/cherry-pick-bce2e6728279.patch
Normal file
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Tom Sepez <tsepez@google.com>
|
||||
Date: Tue, 7 Apr 2026 15:50:30 -0700
|
||||
Subject: Use safe arithmetic in CFX_PSRenderer::DrawDIBits()
|
||||
|
||||
Hardening suggestion from the AI bot.
|
||||
|
||||
Bug: 500036290
|
||||
Change-Id: Ie521629d06ba944f610b941a8c9e9505fa29aea7
|
||||
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/145731
|
||||
Reviewed-by: Lei Zhang <thestig@chromium.org>
|
||||
Commit-Queue: Tom Sepez <tsepez@chromium.org>
|
||||
|
||||
diff --git a/core/fxge/win32/cfx_psrenderer.cpp b/core/fxge/win32/cfx_psrenderer.cpp
|
||||
index b38f1a2b7c3271769e609763be2e183f2890ebb3..b8710e50ed01233b2aefbf1760e26e05964b315e 100644
|
||||
--- a/core/fxge/win32/cfx_psrenderer.cpp
|
||||
+++ b/core/fxge/win32/cfx_psrenderer.cpp
|
||||
@@ -620,8 +620,16 @@ bool CFX_PSRenderer::DrawDIBits(RetainPtr<const CFX_DIBBase> bitmap,
|
||||
encoder_iface_->pJpegEncodeFunc(bitmap, &output_buf, &output_size)) {
|
||||
filter = "/DCTDecode filter ";
|
||||
} else {
|
||||
- int src_pitch = width * bytes_per_pixel;
|
||||
- output_size = height * src_pitch;
|
||||
+ FX_SAFE_UINT32 safe_pitch = bytes_per_pixel;
|
||||
+ safe_pitch *= width;
|
||||
+ FX_SAFE_UINT32 safe_output_size = safe_pitch;
|
||||
+ safe_output_size *= height;
|
||||
+ if (!safe_output_size.IsValid()) {
|
||||
+ WriteString("\nQ\n");
|
||||
+ return false;
|
||||
+ }
|
||||
+ uint32_t src_pitch = safe_pitch.ValueOrDie();
|
||||
+ output_size = safe_output_size.ValueOrDie();
|
||||
output_buf = FX_Alloc(uint8_t, output_size);
|
||||
for (int row = 0; row < height; row++) {
|
||||
const uint8_t* src_scan = bitmap->GetScanline(row).data();
|
||||
70
patches/pdfium/cherry-pick-ca8a943c247c.patch
Normal file
70
patches/pdfium/cherry-pick-ca8a943c247c.patch
Normal file
@@ -0,0 +1,70 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Lei Zhang <thestig@chromium.org>
|
||||
Date: Fri, 27 Mar 2026 14:52:16 -0700
|
||||
Subject: Patch an overflow in libtiff
|
||||
|
||||
Apply fix [1] from upstream, which is not in the most recent versioned
|
||||
release.
|
||||
|
||||
[1] https://gitlab.com/libtiff/libtiff/-/commit/0f726d9
|
||||
|
||||
Bug: 496907110
|
||||
Change-Id: Ic8665879ebdd4445f473e9a1e156cfc42c294d51
|
||||
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/145550
|
||||
Reviewed-by: Andy Phan <andyphan@chromium.org>
|
||||
Commit-Queue: Lei Zhang <thestig@chromium.org>
|
||||
|
||||
diff --git a/third_party/libtiff/0034-tiff-jpeg-overflow.patch b/third_party/libtiff/0034-tiff-jpeg-overflow.patch
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..ba6086a38adfa0bd7726affda0f11381e04501e5
|
||||
--- /dev/null
|
||||
+++ b/third_party/libtiff/0034-tiff-jpeg-overflow.patch
|
||||
@@ -0,0 +1,25 @@
|
||||
+commit 0f726d9477a11e15eb67ca349c03907f6cfb82a9
|
||||
+Author: Mikhail Khachaiants <mkhachaiants@gmail.com>
|
||||
+Date: Mon Dec 1 22:26:34 2025 +0200
|
||||
+
|
||||
+ tif_jpeg: reject mismatched JPEG data precision to avoid write overflow
|
||||
+
|
||||
+ Ensure TIFF BitsPerSample matches both BITS_IN_JSAMPLE and the JPEG
|
||||
+ header data_precision for JPEG-compressed images. This prevents
|
||||
+ under-sized scanline buffers that can lead to write buffer overflows
|
||||
+ in jdcolor.c/null_convert when decoding malformed inputs.
|
||||
+
|
||||
+diff --git a/libtiff/tif_jpeg.c b/libtiff/tif_jpeg.c
|
||||
+index aba5f99b..4d6370b5 100644
|
||||
+--- a/libtiff/tif_jpeg.c
|
||||
++++ b/libtiff/tif_jpeg.c
|
||||
+@@ -1282,7 +1282,8 @@ int TIFFJPEGIsFullStripRequired(TIFF *tif)
|
||||
+ sp->cinfo.d.data_precision = td->td_bitspersample;
|
||||
+ sp->cinfo.d.bits_in_jsample = td->td_bitspersample;
|
||||
+ #else
|
||||
+- if (sp->cinfo.d.data_precision != td->td_bitspersample)
|
||||
++ if (td->td_bitspersample != BITS_IN_JSAMPLE ||
|
||||
++ sp->cinfo.d.data_precision != td->td_bitspersample)
|
||||
+ {
|
||||
+ TIFFErrorExtR(tif, module, "Improper JPEG data precision");
|
||||
+ return (0);
|
||||
diff --git a/third_party/libtiff/README.pdfium b/third_party/libtiff/README.pdfium
|
||||
index 9953e767853bcd30683cc24d0d1839c916659185..e3f352d747007641b5d0bd2256a5dbc8af7c20af 100644
|
||||
--- a/third_party/libtiff/README.pdfium
|
||||
+++ b/third_party/libtiff/README.pdfium
|
||||
@@ -19,3 +19,4 @@ Local Modifications:
|
||||
0028-nstrips-OOM.patch: return error for excess number of tiles/strips.
|
||||
0031-safe_size_ingtStripContig.patch: return error if the size to read overflow from int32.
|
||||
0033-avail-out-overflow.patch: signed comparison in PixarLogDecode().
|
||||
+0034-tiff-jpeg-overflow.patch: reject mismatched JPEG data precision.
|
||||
diff --git a/third_party/libtiff/tif_jpeg.c b/third_party/libtiff/tif_jpeg.c
|
||||
index 5281457d936a0dfa5f877c6a7efff6a65066f520..a9764f073db04d6e593e421105d0f59efbfbbeb2 100644
|
||||
--- a/third_party/libtiff/tif_jpeg.c
|
||||
+++ b/third_party/libtiff/tif_jpeg.c
|
||||
@@ -1287,7 +1287,8 @@ int TIFFJPEGIsFullStripRequired(TIFF *tif)
|
||||
sp->cinfo.d.data_precision = td->td_bitspersample;
|
||||
sp->cinfo.d.bits_in_jsample = td->td_bitspersample;
|
||||
#else
|
||||
- if (sp->cinfo.d.data_precision != td->td_bitspersample)
|
||||
+ if (td->td_bitspersample != BITS_IN_JSAMPLE ||
|
||||
+ sp->cinfo.d.data_precision != td->td_bitspersample)
|
||||
{
|
||||
TIFFErrorExtR(tif, module, "Improper JPEG data precision");
|
||||
return (0);
|
||||
2
patches/skia/.patches
Normal file
2
patches/skia/.patches
Normal file
@@ -0,0 +1,2 @@
|
||||
cherry-pick-0566b2f5f0d1.patch
|
||||
cherry-pick-3f9969421ad5.patch
|
||||
651
patches/skia/cherry-pick-0566b2f5f0d1.patch
Normal file
651
patches/skia/cherry-pick-0566b2f5f0d1.patch
Normal file
@@ -0,0 +1,651 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Michael Ludwig <michaelludwig@google.com>
|
||||
Date: Wed, 1 Apr 2026 09:48:48 -0400
|
||||
Subject: Use 16-bit size for ResourceKeys
|
||||
|
||||
Internally, ResourceKey required the size to fit into a uint16_t so this
|
||||
makes that explicit in the public API. It also changes how the size is
|
||||
stored to instead record the num32DataCount directly and then convert to
|
||||
bytes as needed, whereas previously it was requiring that the actual
|
||||
byte count fit into a uint16_t. This gives a bit more head room.
|
||||
|
||||
Call sites to the ResourceKey builders are updated to now have the
|
||||
responsibility of checking that their size can fit into a uint16_t. For
|
||||
the most part, these were fixed or trivially small variable key sizes.
|
||||
The two exceptions were Ganesh's style key (with dashes) and its
|
||||
inherited key system for shapes with applied styles and path effects.
|
||||
They now have reasonable limits to prevent the keys from growing bigger
|
||||
than about 1kb.
|
||||
|
||||
Bug: b/495700484
|
||||
Change-Id: I6ac4f17628b9a2e1a777c473b74e6d1f5c68b27d
|
||||
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1199497
|
||||
Reviewed-by: Robert Phillips <robertphillips@google.com>
|
||||
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
|
||||
|
||||
diff --git a/src/gpu/ResourceKey.h b/src/gpu/ResourceKey.h
|
||||
index f8dee7983036a95d2f5fd7404553916b5c616e83..19851a67653669058361570c615d9be45dc5153a 100644
|
||||
--- a/src/gpu/ResourceKey.h
|
||||
+++ b/src/gpu/ResourceKey.h
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
+#include <limits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
@@ -77,14 +78,10 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
- Builder(ResourceKey* key, uint32_t domain, int data32Count) : fKey(key) {
|
||||
- size_t count = SkToSizeT(data32Count);
|
||||
+ Builder(ResourceKey* key, uint16_t domain, uint16_t data32Count) : fKey(key) {
|
||||
SkASSERT(domain != kInvalidDomain);
|
||||
- key->fKey.reset(kMetaDataCnt + count);
|
||||
- size_t size = (count + kMetaDataCnt) * sizeof(uint32_t);
|
||||
- SkASSERT(SkToU16(size) == size);
|
||||
- SkASSERT(SkToU16(domain) == domain);
|
||||
- key->fKey[kDomainAndSize_MetaDataIdx] = SkToU32(domain | (size << 16));
|
||||
+ key->fKey.reset(kMetaDataCnt + data32Count);
|
||||
+ key->fKey[kDomainAndSize_MetaDataIdx] = domain | (data32Count << 16);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -92,7 +89,7 @@ public:
|
||||
};
|
||||
|
||||
protected:
|
||||
- static const uint32_t kInvalidDomain = 0;
|
||||
+ static const uint16_t kInvalidDomain = 0;
|
||||
|
||||
ResourceKey() { this->reset(); }
|
||||
|
||||
@@ -118,10 +115,10 @@ protected:
|
||||
return *this;
|
||||
}
|
||||
|
||||
- uint32_t domain() const { return fKey[kDomainAndSize_MetaDataIdx] & 0xffff; }
|
||||
+ uint16_t domain() const { return fKey[kDomainAndSize_MetaDataIdx] & 0xffff; }
|
||||
|
||||
/** size of the key data, excluding meta-data (hash, domain, etc). */
|
||||
- size_t dataSize() const { return this->size() - 4 * kMetaDataCnt; }
|
||||
+ size_t dataSize() const { return (fKey[kDomainAndSize_MetaDataIdx] >> 16) * sizeof(uint32_t); }
|
||||
|
||||
/** ptr to the key data, excluding meta-data (hash, domain, etc). */
|
||||
const uint32_t* data() const {
|
||||
@@ -149,14 +146,17 @@ protected:
|
||||
private:
|
||||
enum MetaDataIdx {
|
||||
kHash_MetaDataIdx,
|
||||
- // The key domain and size are packed into a single uint32_t.
|
||||
+ // The key domain and size are packed into a single uint32_t. The stored size is in units
|
||||
+ // of uint32_t and does not include the metadata, i.e. it stores the data32Count provided
|
||||
+ // to the original key builder.
|
||||
kDomainAndSize_MetaDataIdx,
|
||||
|
||||
kLastMetaDataIdx = kDomainAndSize_MetaDataIdx
|
||||
};
|
||||
static const uint32_t kMetaDataCnt = kLastMetaDataIdx + 1;
|
||||
|
||||
- size_t internalSize() const { return fKey[kDomainAndSize_MetaDataIdx] >> 16; }
|
||||
+ // Total size in bytes, including metadata
|
||||
+ size_t internalSize() const { return this->dataSize() + sizeof(uint32_t) * kMetaDataCnt; }
|
||||
|
||||
void validate() const {
|
||||
SkASSERT(this->isValid());
|
||||
@@ -197,7 +197,7 @@ private:
|
||||
class ScratchKey : public ResourceKey {
|
||||
public:
|
||||
/** Uniquely identifies the type of resource that is cached as scratch. */
|
||||
- typedef uint32_t ResourceType;
|
||||
+ typedef uint16_t ResourceType;
|
||||
|
||||
/** Generate a unique ResourceType. */
|
||||
static ResourceType GenerateResourceType();
|
||||
@@ -219,7 +219,7 @@ public:
|
||||
|
||||
class Builder : public ResourceKey::Builder {
|
||||
public:
|
||||
- Builder(ScratchKey* key, ResourceType type, int data32Count)
|
||||
+ Builder(ScratchKey* key, ResourceType type, uint16_t data32Count)
|
||||
: ResourceKey::Builder(key, type, data32Count) {}
|
||||
};
|
||||
};
|
||||
@@ -240,7 +240,7 @@ public:
|
||||
*/
|
||||
class UniqueKey : public ResourceKey {
|
||||
public:
|
||||
- typedef uint32_t Domain;
|
||||
+ typedef uint16_t Domain;
|
||||
/** Generate a Domain for unique keys. */
|
||||
static Domain GenerateDomain();
|
||||
|
||||
@@ -279,17 +279,17 @@ public:
|
||||
|
||||
class Builder : public ResourceKey::Builder {
|
||||
public:
|
||||
- Builder(UniqueKey* key, Domain type, int data32Count, const char* tag = nullptr)
|
||||
+ Builder(UniqueKey* key, Domain type, uint16_t data32Count, const char* tag = nullptr)
|
||||
: ResourceKey::Builder(key, type, data32Count) {
|
||||
key->fTag = tag;
|
||||
}
|
||||
|
||||
/** Used to build a key that wraps another key and adds additional data. */
|
||||
- Builder(UniqueKey* key, const UniqueKey& innerKey, Domain domain, int extraData32Cnt,
|
||||
+ Builder(UniqueKey* key, const UniqueKey& innerKey, Domain domain, uint16_t extraData32Cnt,
|
||||
const char* tag = nullptr)
|
||||
: ResourceKey::Builder(key,
|
||||
domain,
|
||||
- Data32CntForInnerKey(innerKey) + extraData32Cnt) {
|
||||
+ Data32CntForInnerKey(innerKey, extraData32Cnt)) {
|
||||
SkASSERT(&innerKey != key);
|
||||
// add the inner key to the end of the key so that op[] can be indexed normally.
|
||||
uint32_t* innerKeyData = &this->operator[](extraData32Cnt);
|
||||
@@ -300,9 +300,15 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
- static int Data32CntForInnerKey(const UniqueKey& innerKey) {
|
||||
- // key data + domain
|
||||
- return SkToInt((innerKey.dataSize() >> 2) + 1);
|
||||
+ static uint16_t Data32CntForInnerKey(const UniqueKey& innerKey, uint16_t extraData32Cnt) {
|
||||
+ // key data + domain + extraData32Cnt needs to fit into a uint16_t. This key builder is
|
||||
+ // only used in Ganesh for wrapping textures
|
||||
+ uint16_t innerData32Cnt = innerKey.dataSize() >> 2;
|
||||
+ // The Builder API doesn't have a way to return a failure, so if this is somehow
|
||||
+ // exceeded, then we have no way to recover.
|
||||
+ SkASSERT_RELEASE((uint32_t) extraData32Cnt + (uint32_t) innerData32Cnt + 1 <=
|
||||
+ (uint32_t) std::numeric_limits<uint16_t>::max());
|
||||
+ return innerData32Cnt + extraData32Cnt + 1;
|
||||
}
|
||||
};
|
||||
|
||||
diff --git a/src/gpu/ganesh/GrStyle.cpp b/src/gpu/ganesh/GrStyle.cpp
|
||||
index 5d7bc9c1d971bbcf3df0fa720f660f23dfdcbab5..d1bdcf5a117b61f3a8ac3010d6d3cc3702f82ced 100644
|
||||
--- a/src/gpu/ganesh/GrStyle.cpp
|
||||
+++ b/src/gpu/ganesh/GrStyle.cpp
|
||||
@@ -18,8 +18,18 @@
|
||||
|
||||
int GrStyle::KeySize(const GrStyle &style, Apply apply, uint32_t flags) {
|
||||
static_assert(sizeof(uint32_t) == sizeof(SkScalar));
|
||||
+
|
||||
+ // We embed the dash interval pattern into the key, and the key size must fit within 16-bits.
|
||||
+ // However, we put a more conservative upper limit on the dashes because we don't want to keep
|
||||
+ // key memory locked up in caches during pathological cases.
|
||||
+ static constexpr int kDashIntervalKeyLimit = 512;
|
||||
+
|
||||
int size = 0;
|
||||
if (style.isDashed()) {
|
||||
+ if (style.dashIntervalCnt() > kDashIntervalKeyLimit) {
|
||||
+ return -1; // Disable caching for pathologically large dash patterns
|
||||
+ }
|
||||
+
|
||||
// One scalar for scale, one for dash phase, and one for each dash value.
|
||||
size += 2 + style.dashIntervalCnt();
|
||||
} else if (style.pathEffect()) {
|
||||
diff --git a/src/gpu/ganesh/GrStyle.h b/src/gpu/ganesh/GrStyle.h
|
||||
index 41b0ce9db13e57db63cd1949dc87b9c20023fcc6..252b975e1e66dd654326449f3c9cbc317b8a2fda 100644
|
||||
--- a/src/gpu/ganesh/GrStyle.h
|
||||
+++ b/src/gpu/ganesh/GrStyle.h
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
* into a key. This occurs when there is a path effect that is not a dash. The key can
|
||||
* either reflect just the path effect (if one) or the path effect and the strokerec. Note
|
||||
* that a simple fill has a zero sized key.
|
||||
+ *
|
||||
+ * If a positive value is returned, it will fit in a uint16_t.
|
||||
*/
|
||||
static int KeySize(const GrStyle&, Apply, uint32_t flags = 0);
|
||||
|
||||
diff --git a/src/gpu/ganesh/geometry/GrStyledShape.cpp b/src/gpu/ganesh/geometry/GrStyledShape.cpp
|
||||
index 3c2b942aa6c614a6312e6695309cb9fc8dd6f5d5..6aa4daa3f8d76ab0dfe062dfb2f4b1503a8a5e1f 100644
|
||||
--- a/src/gpu/ganesh/geometry/GrStyledShape.cpp
|
||||
+++ b/src/gpu/ganesh/geometry/GrStyledShape.cpp
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
+#include <limits>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -141,12 +142,12 @@ static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
|
||||
SkASSERT(key - origKey == path_key_from_data_size(path));
|
||||
}
|
||||
|
||||
-int GrStyledShape::unstyledKeySize() const {
|
||||
+uint16_t GrStyledShape::unstyledKeySize() const {
|
||||
if (fInheritedKey.count()) {
|
||||
- return fInheritedKey.count();
|
||||
+ return SkTo<uint16_t>(fInheritedKey.count());
|
||||
}
|
||||
|
||||
- int count = 1; // Every key has the state flags from the GrShape
|
||||
+ uint16_t count = 1; // Every key has the state flags from the GrShape
|
||||
switch(fShape.type()) {
|
||||
case GrShape::Type::kPoint:
|
||||
static_assert(0 == sizeof(SkPoint) % sizeof(uint32_t));
|
||||
@@ -170,11 +171,13 @@ int GrStyledShape::unstyledKeySize() const {
|
||||
break;
|
||||
case GrShape::Type::kPath: {
|
||||
if (0 == fGenID) {
|
||||
- return -1; // volatile, so won't be keyed
|
||||
+ return 0; // volatile, so won't be keyed
|
||||
}
|
||||
+ // When >= 0, `dataKeySize` is a reasonably small number bounded by
|
||||
+ // kMaxKeyFromDataVerbCnt since point count is derived from verb count.
|
||||
int dataKeySize = path_key_from_data_size(fShape.path());
|
||||
if (dataKeySize >= 0) {
|
||||
- count += dataKeySize;
|
||||
+ count += SkTo<uint16_t>(dataKeySize);
|
||||
} else {
|
||||
count++; // Just adds the gen ID.
|
||||
}
|
||||
@@ -251,6 +254,7 @@ void GrStyledShape::writeUnstyledKey(uint32_t* key) const {
|
||||
|
||||
void GrStyledShape::setInheritedKey(const GrStyledShape &parent, GrStyle::Apply apply,
|
||||
SkScalar scale) {
|
||||
+ static constexpr int kInheritedKeyLimit = 1024;
|
||||
SkASSERT(!fInheritedKey.count());
|
||||
// If the output shape turns out to be simple, then we will just use its geometric key
|
||||
if (fShape.isPath()) {
|
||||
@@ -264,7 +268,7 @@ void GrStyledShape::setInheritedKey(const GrStyledShape &parent, GrStyle::Apply
|
||||
bool useParentGeoKey = !parentCnt;
|
||||
if (useParentGeoKey) {
|
||||
parentCnt = parent.unstyledKeySize();
|
||||
- if (parentCnt < 0) {
|
||||
+ if (!parentCnt) {
|
||||
// The parent's geometry has no key so we will have no key.
|
||||
fGenID = 0;
|
||||
return;
|
||||
@@ -283,7 +287,12 @@ void GrStyledShape::setInheritedKey(const GrStyledShape &parent, GrStyle::Apply
|
||||
// we try to get a key for the shape.
|
||||
fGenID = 0;
|
||||
return;
|
||||
+ } else if (parentCnt + styleCnt > kInheritedKeyLimit) {
|
||||
+ // Prevent chained path effects and styles from growing the key too large
|
||||
+ fGenID = 0;
|
||||
+ return;
|
||||
}
|
||||
+
|
||||
fInheritedKey.reset(parentCnt + styleCnt);
|
||||
if (useParentGeoKey) {
|
||||
// This will be the geo key.
|
||||
diff --git a/src/gpu/ganesh/geometry/GrStyledShape.h b/src/gpu/ganesh/geometry/GrStyledShape.h
|
||||
index 97db583a5cf8aa8c7fe8c0e054ef622ca6945744..ed4345355478121dfedb1a894e159b80c4fca304 100644
|
||||
--- a/src/gpu/ganesh/geometry/GrStyledShape.h
|
||||
+++ b/src/gpu/ganesh/geometry/GrStyledShape.h
|
||||
@@ -252,11 +252,11 @@ public:
|
||||
|
||||
/**
|
||||
* Gets the size of the key for the shape represented by this GrStyledShape (ignoring its
|
||||
- * styling). A negative value is returned if the shape has no key (shouldn't be cached).
|
||||
+ * styling). A zero value is returned if the shape has no key (shouldn't be cached).
|
||||
*/
|
||||
- int unstyledKeySize() const;
|
||||
+ uint16_t unstyledKeySize() const;
|
||||
|
||||
- bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
|
||||
+ bool hasUnstyledKey() const { return this->unstyledKeySize() > 0; }
|
||||
|
||||
/**
|
||||
* Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
|
||||
diff --git a/src/gpu/ganesh/image/GrImageUtils.cpp b/src/gpu/ganesh/image/GrImageUtils.cpp
|
||||
index a88ff15c0a59f1a5f417aa37f243385503a3dfd9..8fd9ea6a55e4a77c793c7af2361aab7e06c63f08 100644
|
||||
--- a/src/gpu/ganesh/image/GrImageUtils.cpp
|
||||
+++ b/src/gpu/ganesh/image/GrImageUtils.cpp
|
||||
@@ -683,8 +683,7 @@ GrSurfaceProxyView FindOrMakeCachedMipmappedView(GrRecordingContext* rContext,
|
||||
SkASSERT(baseKey.isValid());
|
||||
skgpu::UniqueKey mipmappedKey;
|
||||
static const skgpu::UniqueKey::Domain kMipmappedDomain = skgpu::UniqueKey::GenerateDomain();
|
||||
- { // No extra values beyond the domain are required. Must name the var to please
|
||||
- // clang-tidy.
|
||||
+ { // No extra values beyond the domain are required. Must name the var to please clang-tidy.
|
||||
skgpu::UniqueKey::Builder b(&mipmappedKey, baseKey, kMipmappedDomain, 0);
|
||||
}
|
||||
SkASSERT(mipmappedKey.isValid());
|
||||
diff --git a/src/gpu/ganesh/ops/TriangulatingPathRenderer.cpp b/src/gpu/ganesh/ops/TriangulatingPathRenderer.cpp
|
||||
index 124e51842eafbf7d3b010ca3232731d549515a9b..1bf3038847223c8167fa60c852c23216d186c5cc 100644
|
||||
--- a/src/gpu/ganesh/ops/TriangulatingPathRenderer.cpp
|
||||
+++ b/src/gpu/ganesh/ops/TriangulatingPathRenderer.cpp
|
||||
@@ -282,8 +282,7 @@ private:
|
||||
bool inverseFill = shape.inverseFilled();
|
||||
|
||||
static constexpr int kClipBoundsCnt = sizeof(devClipBounds) / sizeof(uint32_t);
|
||||
- int shapeKeyDataCnt = shape.unstyledKeySize();
|
||||
- SkASSERT(shapeKeyDataCnt >= 0);
|
||||
+ uint16_t shapeKeyDataCnt = shape.unstyledKeySize();
|
||||
skgpu::UniqueKey::Builder builder(key, kDomain, shapeKeyDataCnt + kClipBoundsCnt, "Path");
|
||||
shape.writeUnstyledKey(&builder[0]);
|
||||
// For inverse fills, the tessellation is dependent on clip bounds.
|
||||
diff --git a/src/gpu/graphite/GraphiteResourceKey.h b/src/gpu/graphite/GraphiteResourceKey.h
|
||||
index 12e72d1b24f45ac5885a125cb3d52cb648a55402..d52f0099a722aaf2a4b655abf6bf8c0ea26dcf57 100644
|
||||
--- a/src/gpu/graphite/GraphiteResourceKey.h
|
||||
+++ b/src/gpu/graphite/GraphiteResourceKey.h
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
|
||||
class Builder : public ResourceKey::Builder {
|
||||
public:
|
||||
- Builder(GraphiteResourceKey* key, ResourceType type, int data32Count)
|
||||
+ Builder(GraphiteResourceKey* key, ResourceType type, uint16_t data32Count)
|
||||
: ResourceKey::Builder(key, type, data32Count) {}
|
||||
};
|
||||
};
|
||||
diff --git a/src/gpu/graphite/RasterPathUtils.cpp b/src/gpu/graphite/RasterPathUtils.cpp
|
||||
index 1d8b5563e41bf8e3f515438c666760b228c3947c..8557d35bc4803cf71224457d103c3a9ce874012c 100644
|
||||
--- a/src/gpu/graphite/RasterPathUtils.cpp
|
||||
+++ b/src/gpu/graphite/RasterPathUtils.cpp
|
||||
@@ -140,7 +140,7 @@ skgpu::UniqueKey GeneratePathMaskKey(const Shape& shape,
|
||||
skgpu::UniqueKey maskKey;
|
||||
{
|
||||
static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
|
||||
- int styleKeySize = 7;
|
||||
+ uint16_t styleKeySize = 7;
|
||||
if (!strokeRec.isHairlineStyle() && !strokeRec.isFillStyle()) {
|
||||
// Add space for width and miter if needed
|
||||
styleKeySize += 2;
|
||||
@@ -185,66 +185,56 @@ skgpu::UniqueKey GenerateClipMaskKey(uint32_t stackRecordID,
|
||||
skgpu::UniqueKey maskKey;
|
||||
// if the element list is too large we just use the stackRecordID
|
||||
if (elementsForMask->size() <= kMaxShapeCountForKey) {
|
||||
- constexpr int kXformKeySize = 5;
|
||||
- int keySize = 0;
|
||||
- bool canCreateKey = true;
|
||||
- // Iterate through to get key size and see if we can create a key at all
|
||||
+ static constexpr int kXformKeySize = 5;
|
||||
+ uint16_t keySize = includeBounds ? 2 : 0;
|
||||
+ // Iterate through to get key size; given kMaxShapeCountForKey and Shape's own key size
|
||||
+ // limitations, this should always fit safely within a 16-bit number
|
||||
for (int i = 0; i < elementsForMask->size(); ++i) {
|
||||
- int shapeKeySize = (*elementsForMask)[i]->fShape.keySize();
|
||||
- if (shapeKeySize < 0) {
|
||||
- canCreateKey = false;
|
||||
- break;
|
||||
- }
|
||||
- keySize += kXformKeySize + shapeKeySize;
|
||||
+ keySize += kXformKeySize + (*elementsForMask)[i]->fShape.keySize();
|
||||
}
|
||||
- if (canCreateKey) {
|
||||
- if (includeBounds) {
|
||||
- keySize += 2;
|
||||
- }
|
||||
- skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize,
|
||||
- "Clip Path Mask");
|
||||
- int elementKeyIndex = 0;
|
||||
- Rect unclippedBounds = Rect::InfiniteInverted();
|
||||
- for (int i = 0; i < elementsForMask->size(); ++i) {
|
||||
- const ClipStack::Element* element = (*elementsForMask)[i];
|
||||
-
|
||||
- // Add transform key and get packed fractional translation bits
|
||||
- uint32_t fracBits = add_transform_key(&builder,
|
||||
- elementKeyIndex,
|
||||
- element->fLocalToDevice);
|
||||
- uint32_t opBits = static_cast<uint32_t>(element->fOp);
|
||||
- builder[elementKeyIndex + 4] = fracBits | (opBits << 16);
|
||||
-
|
||||
- const Shape& shape = element->fShape;
|
||||
- shape.writeKey(&builder[elementKeyIndex + kXformKeySize],
|
||||
- /*includeInverted=*/true);
|
||||
-
|
||||
- elementKeyIndex += kXformKeySize + shape.keySize();
|
||||
-
|
||||
- Rect transformedBounds = element->fLocalToDevice.mapRect(element->fShape.bounds());
|
||||
- unclippedBounds.join(transformedBounds);
|
||||
- }
|
||||
-
|
||||
- // The keyBounds are the maskDeviceBounds relative to the full transformed mask. We use
|
||||
- // this to ensure we capture the situation where the maskDeviceBounds are equal in two
|
||||
- // cases but actually enclose different regions of the full mask due to an integer
|
||||
- // translation (which is not captured in the key) in the element transforms.
|
||||
- *keyBounds = maskDeviceBounds.makeOffset(-unclippedBounds.left(),
|
||||
- -unclippedBounds.top());
|
||||
-
|
||||
- if (includeBounds) {
|
||||
- SkASSERT(SkTFitsIn<int16_t>(keyBounds->left()));
|
||||
- SkASSERT(SkTFitsIn<int16_t>(keyBounds->top()));
|
||||
- SkASSERT(SkTFitsIn<int16_t>(keyBounds->right()));
|
||||
- SkASSERT(SkTFitsIn<int16_t>(keyBounds->bottom()));
|
||||
-
|
||||
- builder[elementKeyIndex] = keyBounds->left() | (keyBounds->top() << 16);
|
||||
- builder[elementKeyIndex+1] = keyBounds->right() | (keyBounds->bottom() << 16);
|
||||
- }
|
||||
-
|
||||
- *usesPathKey = true;
|
||||
- return maskKey;
|
||||
+
|
||||
+ skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize, "Clip Path Mask");
|
||||
+ int elementKeyIndex = 0;
|
||||
+ Rect unclippedBounds = Rect::InfiniteInverted();
|
||||
+ for (int i = 0; i < elementsForMask->size(); ++i) {
|
||||
+ const ClipStack::Element* element = (*elementsForMask)[i];
|
||||
+
|
||||
+ // Add transform key and get packed fractional translation bits
|
||||
+ uint32_t fracBits = add_transform_key(&builder,
|
||||
+ elementKeyIndex,
|
||||
+ element->fLocalToDevice);
|
||||
+ uint32_t opBits = static_cast<uint32_t>(element->fOp);
|
||||
+ builder[elementKeyIndex + 4] = fracBits | (opBits << 16);
|
||||
+
|
||||
+ const Shape& shape = element->fShape;
|
||||
+ shape.writeKey(&builder[elementKeyIndex + kXformKeySize],
|
||||
+ /*includeInverted=*/true);
|
||||
+
|
||||
+ elementKeyIndex += kXformKeySize + shape.keySize();
|
||||
+
|
||||
+ Rect transformedBounds = element->fLocalToDevice.mapRect(element->fShape.bounds());
|
||||
+ unclippedBounds.join(transformedBounds);
|
||||
+ }
|
||||
+
|
||||
+ // The keyBounds are the maskDeviceBounds relative to the full transformed mask. We use
|
||||
+ // this to ensure we capture the situation where the maskDeviceBounds are equal in two
|
||||
+ // cases but actually enclose different regions of the full mask due to an integer
|
||||
+ // translation (which is not captured in the key) in the element transforms.
|
||||
+ *keyBounds = maskDeviceBounds.makeOffset(-unclippedBounds.left(),
|
||||
+ -unclippedBounds.top());
|
||||
+
|
||||
+ if (includeBounds) {
|
||||
+ SkASSERT(SkTFitsIn<int16_t>(keyBounds->left()));
|
||||
+ SkASSERT(SkTFitsIn<int16_t>(keyBounds->top()));
|
||||
+ SkASSERT(SkTFitsIn<int16_t>(keyBounds->right()));
|
||||
+ SkASSERT(SkTFitsIn<int16_t>(keyBounds->bottom()));
|
||||
+
|
||||
+ builder[elementKeyIndex] = keyBounds->left() | (keyBounds->top() << 16);
|
||||
+ builder[elementKeyIndex+1] = keyBounds->right() | (keyBounds->bottom() << 16);
|
||||
}
|
||||
+
|
||||
+ *usesPathKey = true;
|
||||
+ return maskKey;
|
||||
}
|
||||
|
||||
// Either we have too many elements or at least one shape can't create a key
|
||||
diff --git a/src/gpu/graphite/ResourceProvider.cpp b/src/gpu/graphite/ResourceProvider.cpp
|
||||
index cd67a8af6fe60c2239dc6cfc7adaa8604ef3743a..80ce839d942b96f6ba7eb0456881a986accfe145 100644
|
||||
--- a/src/gpu/graphite/ResourceProvider.cpp
|
||||
+++ b/src/gpu/graphite/ResourceProvider.cpp
|
||||
@@ -177,7 +177,7 @@ sk_sp<Sampler> ResourceProvider::findOrCreateCompatibleSampler(const SamplerDesc
|
||||
// immutable sampler details into the SamplerDesc, so there is no need to delegate to Caps
|
||||
// to create a specific key.
|
||||
const SkSpan<const uint32_t>& samplerData = samplerDesc.asSpan();
|
||||
- GraphiteResourceKey::Builder builder(&key, kType, samplerData.size());
|
||||
+ GraphiteResourceKey::Builder builder(&key, kType, SkTo<uint16_t>(samplerData.size()));
|
||||
|
||||
for (size_t i = 0; i < samplerData.size(); i++) {
|
||||
builder[i] = samplerData[i];
|
||||
@@ -231,8 +231,8 @@ sk_sp<Buffer> ResourceProvider::findOrCreateBuffer(
|
||||
// For the key we need ((sizeof(size_t) + (sizeof(uint32_t) - 1)) / (sizeof(uint32_t))
|
||||
// uint32_t's for the size and one uint32_t for the rest.
|
||||
static_assert(sizeof(uint32_t) == 4);
|
||||
- static const int kSizeKeyNum32DataCnt = (sizeof(size_t) + 3) / 4;
|
||||
- static const int kKeyNum32DataCnt = kSizeKeyNum32DataCnt + 1;
|
||||
+ static const uint16_t kSizeKeyNum32DataCnt = (sizeof(size_t) + 3) / 4;
|
||||
+ static const uint16_t kKeyNum32DataCnt = kSizeKeyNum32DataCnt + 1;
|
||||
|
||||
SkASSERT(static_cast<uint32_t>(type) < (1u << 4));
|
||||
SkASSERT(static_cast<uint32_t>(accessPattern) < (1u << 2));
|
||||
diff --git a/src/gpu/graphite/dawn/DawnCaps.cpp b/src/gpu/graphite/dawn/DawnCaps.cpp
|
||||
index 3717790e1413401c0fbf12ff4cebd31132153ffd..1c281e3e7fd0299fb14d7fa8c763690bf68bae6b 100644
|
||||
--- a/src/gpu/graphite/dawn/DawnCaps.cpp
|
||||
+++ b/src/gpu/graphite/dawn/DawnCaps.cpp
|
||||
@@ -1016,7 +1016,7 @@ uint32_t DawnCaps::getRenderPassDescKeyForPipeline(const RenderPassDesc& renderP
|
||||
loadResolveAttachmentKey;
|
||||
}
|
||||
|
||||
-static constexpr int kDawnGraphicsPipelineKeyData32Count = 4;
|
||||
+static constexpr uint16_t kDawnGraphicsPipelineKeyData32Count = 4;
|
||||
|
||||
UniqueKey DawnCaps::makeGraphicsPipelineKey(const GraphicsPipelineDesc& pipelineDesc,
|
||||
const RenderPassDesc& renderPassDesc) const {
|
||||
@@ -1234,7 +1234,7 @@ void DawnCaps::buildKeyForTexture(SkISize dimensions,
|
||||
SkASSERT(static_cast<uint32_t>(dawnInfo.fUsage) < (1u << 28)); // usage is remaining 28 bits
|
||||
|
||||
// We need two uint32_ts for dimensions, 1 for format, and 1 for the rest of the key;
|
||||
- int num32DataCnt = 2 + 1 + 1;
|
||||
+ uint16_t num32DataCnt = 2 + 1 + 1;
|
||||
bool hasYcbcrInfo = false;
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
// If we are using ycbcr texture/sampling, more key information is needed.
|
||||
diff --git a/src/gpu/graphite/geom/AnalyticBlurMask.cpp b/src/gpu/graphite/geom/AnalyticBlurMask.cpp
|
||||
index 97f38ba054f66249d70c739cea0318f4d3e30203..5a118bf8b1792be4400a5a43db2d936366157fd0 100644
|
||||
--- a/src/gpu/graphite/geom/AnalyticBlurMask.cpp
|
||||
+++ b/src/gpu/graphite/geom/AnalyticBlurMask.cpp
|
||||
@@ -375,7 +375,7 @@ std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRRect(Recorder* recorder,
|
||||
static const UniqueKey::Domain kRRectBlurDomain = UniqueKey::GenerateDomain();
|
||||
UniqueKey key;
|
||||
{
|
||||
- static constexpr int kKeySize = sizeof(DerivedParams) / sizeof(uint32_t);
|
||||
+ static constexpr uint16_t kKeySize = sizeof(DerivedParams) / sizeof(uint32_t);
|
||||
static_assert(SkIsAlign4(sizeof(DerivedParams)));
|
||||
// TODO: We should discretize the sigma to perceptibly meaningful changes to the table,
|
||||
// as well as the underlying the round rect geometry.
|
||||
diff --git a/src/gpu/graphite/geom/Shape.cpp b/src/gpu/graphite/geom/Shape.cpp
|
||||
index 29898fb00507aa64317ffd78354f36a18ca0a7b0..2465dcfb9fc92b678e67520e7b0e104d8514c147 100644
|
||||
--- a/src/gpu/graphite/geom/Shape.cpp
|
||||
+++ b/src/gpu/graphite/geom/Shape.cpp
|
||||
@@ -183,8 +183,8 @@ void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
-int Shape::keySize() const {
|
||||
- int count = 1; // Every key has the state flags from the Shape
|
||||
+uint16_t Shape::keySize() const {
|
||||
+ uint16_t count = 1; // Every key has the state flags from the Shape
|
||||
switch(this->type()) {
|
||||
case Type::kLine:
|
||||
static_assert(0 == sizeof(skvx::float4) % sizeof(uint32_t));
|
||||
@@ -207,7 +207,7 @@ int Shape::keySize() const {
|
||||
if (!this->path().isEmpty()) {
|
||||
int dataKeySize = path_key_from_data_size(this->path());
|
||||
if (dataKeySize >= 0) {
|
||||
- count += dataKeySize;
|
||||
+ count += SkTo<uint16_t>(dataKeySize);
|
||||
} else {
|
||||
count++; // Just adds the gen ID.
|
||||
}
|
||||
diff --git a/src/gpu/graphite/geom/Shape.h b/src/gpu/graphite/geom/Shape.h
|
||||
index 8c02945b7090779385a82aa4a8d257dad758e331..30dd8fffb797aee0c8a85093f5d5cdb97ef1a0fa 100644
|
||||
--- a/src/gpu/graphite/geom/Shape.h
|
||||
+++ b/src/gpu/graphite/geom/Shape.h
|
||||
@@ -184,7 +184,7 @@ public:
|
||||
/**
|
||||
* Gets the size of the key for the shape represented by this Shape.
|
||||
*/
|
||||
- int keySize() const;
|
||||
+ uint16_t keySize() const;
|
||||
|
||||
/**
|
||||
* Writes keySize() bytes into the provided pointer. Assumes that there is enough
|
||||
diff --git a/src/gpu/graphite/mtl/MtlCaps.mm b/src/gpu/graphite/mtl/MtlCaps.mm
|
||||
index 7816ca699def160c1aa56afb28463d3c9d77926f..e5939af8fef877c297e98195e111860c88b0e876 100644
|
||||
--- a/src/gpu/graphite/mtl/MtlCaps.mm
|
||||
+++ b/src/gpu/graphite/mtl/MtlCaps.mm
|
||||
@@ -949,7 +949,7 @@ MTLPixelFormat format_from_compression(SkTextureCompressionType compression) {
|
||||
return {formatInfo.fColorTypeInfos.get(), formatInfo.fColorTypeInfoCount};
|
||||
}
|
||||
|
||||
-static constexpr int kMtlGraphicsPipelineKeyData32Count = 4;
|
||||
+static constexpr uint16_t kMtlGraphicsPipelineKeyData32Count = 4;
|
||||
|
||||
UniqueKey MtlCaps::makeGraphicsPipelineKey(const GraphicsPipelineDesc& pipelineDesc,
|
||||
const RenderPassDesc& renderPassDesc) const {
|
||||
@@ -1193,7 +1193,7 @@ MTLPixelFormat format_from_compression(SkTextureCompressionType compression) {
|
||||
SkASSERT(static_cast<uint32_t>(isFBOnly) < (1u << 1));
|
||||
|
||||
// We need two uint32_ts for dimensions, 2 for format, and 1 for the rest of the key;
|
||||
- static int kNum32DataCnt = 2 + 2 + 1;
|
||||
+ static uint16_t kNum32DataCnt = 2 + 2 + 1;
|
||||
|
||||
GraphiteResourceKey::Builder builder(key, type, kNum32DataCnt);
|
||||
|
||||
diff --git a/src/gpu/graphite/vk/VulkanCaps.cpp b/src/gpu/graphite/vk/VulkanCaps.cpp
|
||||
index 799d90b03c54cee89b24281e25c46813adef5046..f5ae0b882af66050b3e90c121675ab1b3a4ec870 100644
|
||||
--- a/src/gpu/graphite/vk/VulkanCaps.cpp
|
||||
+++ b/src/gpu/graphite/vk/VulkanCaps.cpp
|
||||
@@ -2087,7 +2087,7 @@ bool VulkanCaps::msaaTextureRenderToSingleSampledSupport(const TextureInfo& info
|
||||
|
||||
// 4 uint32s for the render step id, paint id, compatible render pass description, and write
|
||||
// swizzle.
|
||||
-static constexpr int kPipelineKeyData32Count = 4;
|
||||
+static constexpr uint16_t kPipelineKeyData32Count = 4;
|
||||
|
||||
static constexpr int kPipelineKeyRenderStepIDIndex = 0;
|
||||
static constexpr int kPipelineKeyPaintParamsIDIndex = 1;
|
||||
@@ -2173,15 +2173,15 @@ void VulkanCaps::buildKeyForTexture(SkISize dimensions,
|
||||
SkASSERT(vkInfo.fAspectMask < (1u << 11)); // aspectMask is bits 8 - 19
|
||||
|
||||
// We need two uint32_ts for dimensions and 3 for miscellaneous information.
|
||||
- static constexpr int kNum32DimensionDataCnt = 2;
|
||||
- static constexpr int kNum32MiscDataCnt = 3;
|
||||
+ static constexpr uint16_t kNum32DimensionDataCnt = 2;
|
||||
+ static constexpr uint16_t kNum32MiscDataCnt = 3;
|
||||
// Non-YCbCr formats need 1 int for format.
|
||||
// YCbCr conversion needs 1 int for non-format flags, and a 64-bit format (external or regular).
|
||||
- static constexpr int kNum32FormatDataCntNoYcbcr = 1;
|
||||
- static constexpr int kNum32FormatDataCntYcbcr = 3;
|
||||
+ static constexpr uint16_t kNum32FormatDataCntNoYcbcr = 1;
|
||||
+ static constexpr uint16_t kNum32FormatDataCntYcbcr = 3;
|
||||
|
||||
const VulkanYcbcrConversionInfo& ycbcrInfo = vkInfo.fYcbcrConversionInfo;
|
||||
- const int num32DataCnt =
|
||||
+ const uint16_t num32DataCnt =
|
||||
kNum32DimensionDataCnt + kNum32MiscDataCnt +
|
||||
(ycbcrInfo.isValid() ? kNum32FormatDataCntYcbcr : kNum32FormatDataCntNoYcbcr);
|
||||
|
||||
diff --git a/src/gpu/graphite/vk/VulkanResourceProvider.cpp b/src/gpu/graphite/vk/VulkanResourceProvider.cpp
|
||||
index bb2f400250c569b73120f857133cafcca51c1c77..f66c6d0d2cf8c8bb4b34c7479445185c3a3c7cf4 100644
|
||||
--- a/src/gpu/graphite/vk/VulkanResourceProvider.cpp
|
||||
+++ b/src/gpu/graphite/vk/VulkanResourceProvider.cpp
|
||||
@@ -218,7 +218,7 @@ GraphiteResourceKey build_desc_set_key(const SkSpan<DescriptorData>& requestedDe
|
||||
}
|
||||
|
||||
GraphiteResourceKey key;
|
||||
- GraphiteResourceKey::Builder builder(&key, kType, keyData.size());
|
||||
+ GraphiteResourceKey::Builder builder(&key, kType, SkTo<uint16_t>(keyData.size()));
|
||||
|
||||
for (int i = 0; i < keyData.size(); i++) {
|
||||
builder[i] = keyData[i];
|
||||
@@ -548,7 +548,7 @@ sk_sp<VulkanYcbcrConversion> VulkanResourceProvider::findOrCreateCompatibleYcbcr
|
||||
GraphiteResourceKey key;
|
||||
{
|
||||
static const ResourceType kType = GraphiteResourceKey::GenerateResourceType();
|
||||
- static constexpr int kKeySize = 3;
|
||||
+ static constexpr uint16_t kKeySize = 3;
|
||||
|
||||
GraphiteResourceKey::Builder builder(&key, kType, kKeySize);
|
||||
ImmutableSamplerInfo packedInfo = VulkanYcbcrConversion::ToImmutableSamplerInfo(ycbcrInfo);
|
||||
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
|
||||
index 68da13ca9b048cd0d1dc9b62090c17793a11b3a1..9c3ef544ffda6ba72c2972f020ac2a06232484b6 100644
|
||||
--- a/src/utils/SkShadowUtils.cpp
|
||||
+++ b/src/utils/SkShadowUtils.cpp
|
||||
@@ -358,7 +358,10 @@ public:
|
||||
const SkMatrix& viewMatrix() const { return *fViewMatrix; }
|
||||
#if defined(SK_GANESH)
|
||||
/** Negative means the vertices should not be cached for this path. */
|
||||
- int keyBytes() const { return fShapeForKey.unstyledKeySize() * sizeof(uint32_t); }
|
||||
+ int keyBytes() const {
|
||||
+ return fShapeForKey.hasUnstyledKey() ? fShapeForKey.unstyledKeySize() * sizeof(uint32_t)
|
||||
+ : -1;
|
||||
+ }
|
||||
void writeKey(void* key) const {
|
||||
fShapeForKey.writeUnstyledKey(reinterpret_cast<uint32_t*>(key));
|
||||
}
|
||||
28
patches/skia/cherry-pick-3f9969421ad5.patch
Normal file
28
patches/skia/cherry-pick-3f9969421ad5.patch
Normal file
@@ -0,0 +1,28 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Kalvin Lee <kdlee@chromium.org>
|
||||
Date: Fri, 27 Mar 2026 16:37:30 +0900
|
||||
Subject: Terracotta-Phase-1: Reorder member destruction
|
||||
|
||||
This is a speculative patch. Please see the bug for details.
|
||||
|
||||
Bug: b/496393742
|
||||
Change-Id: Ib574a0086f92abda83715b36a0d1e7a99e9edd67
|
||||
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1196676
|
||||
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
|
||||
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
|
||||
Reviewed-by: Thomas Smith <thomsmit@google.com>
|
||||
|
||||
diff --git a/include/gpu/graphite/Context.h b/include/gpu/graphite/Context.h
|
||||
index d3a8ce563713d5a6866b4efa8dcdfa959c92ff09..9397220c2662798c69008343b36e925ad6e4a768 100644
|
||||
--- a/include/gpu/graphite/Context.h
|
||||
+++ b/include/gpu/graphite/Context.h
|
||||
@@ -403,8 +403,8 @@ private:
|
||||
|
||||
sk_sp<SharedContext> fSharedContext;
|
||||
std::unique_ptr<ResourceProvider> fResourceProvider;
|
||||
- std::unique_ptr<QueueManager> fQueueManager;
|
||||
std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager;
|
||||
+ std::unique_ptr<QueueManager> fQueueManager;
|
||||
std::unique_ptr<const skcpu::ContextImpl> fCPUContext;
|
||||
|
||||
PersistentPipelineStorage* fPersistentPipelineStorage;
|
||||
@@ -2,3 +2,6 @@ chore_allow_customizing_microtask_policy_per_context.patch
|
||||
build_warn_instead_of_abort_on_builtin_pgo_profile_mismatch.patch
|
||||
cherry-pick-b54c7841e2cd.patch
|
||||
cherry-pick-ba0258ba9609.patch
|
||||
cherry-pick-036e5e8f69be.patch
|
||||
cherry-pick-07398289d921.patch
|
||||
cherry-pick-a068030f5179.patch
|
||||
|
||||
65
patches/v8/cherry-pick-036e5e8f69be.patch
Normal file
65
patches/v8/cherry-pick-036e5e8f69be.patch
Normal file
@@ -0,0 +1,65 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Nico Hartmann <nicohartmann@chromium.org>
|
||||
Date: Wed, 25 Mar 2026 12:11:21 +0100
|
||||
Subject: [turbofan] Grow ContextAccess' depth field to 31 bits
|
||||
|
||||
Fixed: 495273999
|
||||
Change-Id: I1ce294051aad3f413744386223976cd9c8b24bca
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7698216
|
||||
Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
|
||||
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
|
||||
Auto-Submit: Nico Hartmann <nicohartmann@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#106031}
|
||||
|
||||
diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc
|
||||
index a598cbc13d4558a6623bf182b07f9c5368bab65d..46a4346720861c9262f8529d1afd02cdd015690c 100644
|
||||
--- a/src/compiler/js-operator.cc
|
||||
+++ b/src/compiler/js-operator.cc
|
||||
@@ -152,16 +152,14 @@ const CallRuntimeParameters& CallRuntimeParametersOf(const Operator* op) {
|
||||
return OpParameter<CallRuntimeParameters>(op);
|
||||
}
|
||||
|
||||
-
|
||||
ContextAccess::ContextAccess(size_t depth, size_t index, bool immutable)
|
||||
- : immutable_(immutable),
|
||||
- depth_(static_cast<uint16_t>(depth)),
|
||||
+ : immutable_and_depth_(ImmutableField::encode(immutable) |
|
||||
+ DepthField::encode(static_cast<uint32_t>(depth))),
|
||||
index_(static_cast<uint32_t>(index)) {
|
||||
- DCHECK(depth <= std::numeric_limits<uint16_t>::max());
|
||||
+ CHECK_EQ(depth, DepthField::decode(immutable_and_depth_));
|
||||
DCHECK(index <= std::numeric_limits<uint32_t>::max());
|
||||
}
|
||||
|
||||
-
|
||||
bool operator==(ContextAccess const& lhs, ContextAccess const& rhs) {
|
||||
return lhs.depth() == rhs.depth() && lhs.index() == rhs.index() &&
|
||||
lhs.immutable() == rhs.immutable();
|
||||
diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h
|
||||
index ac3b4c0534ce6a328cf212f8d603e521ca97eaef..a2c4fa47aecab627199d55087da07f041666857e 100644
|
||||
--- a/src/compiler/js-operator.h
|
||||
+++ b/src/compiler/js-operator.h
|
||||
@@ -354,15 +354,19 @@ class ContextAccess final {
|
||||
public:
|
||||
ContextAccess(size_t depth, size_t index, bool immutable);
|
||||
|
||||
- size_t depth() const { return depth_; }
|
||||
+ size_t depth() const { return DepthField::decode(immutable_and_depth_); }
|
||||
size_t index() const { return index_; }
|
||||
- bool immutable() const { return immutable_; }
|
||||
+ bool immutable() const {
|
||||
+ return ImmutableField::decode(immutable_and_depth_);
|
||||
+ }
|
||||
|
||||
private:
|
||||
+ using ImmutableField = base::BitField<bool, 0, 1>;
|
||||
+ using DepthField = ImmutableField::Next<uint32_t, 31>;
|
||||
+
|
||||
// For space reasons, we keep this tightly packed, otherwise we could just use
|
||||
// a simple int/int/bool POD.
|
||||
- const bool immutable_;
|
||||
- const uint16_t depth_;
|
||||
+ uint32_t immutable_and_depth_;
|
||||
const uint32_t index_;
|
||||
};
|
||||
|
||||
172
patches/v8/cherry-pick-07398289d921.patch
Normal file
172
patches/v8/cherry-pick-07398289d921.patch
Normal file
@@ -0,0 +1,172 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Paolo Severini <paolosev@microsoft.com>
|
||||
Date: Tue, 31 Mar 2026 13:05:12 +0200
|
||||
Subject: [compiler] Fix FrameStateFunctionInfo comparison for JS-to-Wasm
|
||||
frames
|
||||
|
||||
The equality operator for FrameStateFunctionInfo did not compare the
|
||||
wasm signature field in JSToWasmFrameStateFunctionInfo. This could
|
||||
cause CSE to incorrectly merge FrameState nodes with different wasm
|
||||
signatures, leading to the deoptimizer using the wrong return type
|
||||
when materializing a JS-to-Wasm builtin continuation frame.
|
||||
|
||||
Bug: 497404188
|
||||
Change-Id: I671cda5784089dd9875d90c5f48e8580cb5fa697
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7709449
|
||||
Reviewed-by: Matthias Liedtke <mliedtke@chromium.org>
|
||||
Reviewed-by: Daniel Lehmann <dlehmann@chromium.org>
|
||||
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#106175}
|
||||
|
||||
diff --git a/src/compiler/frame-states.cc b/src/compiler/frame-states.cc
|
||||
index 7c15107243dd233cf3a4f4fa0b4d61557201ce7e..5312f07b5feb7c356e907d43d3bbc6bd35525e90 100644
|
||||
--- a/src/compiler/frame-states.cc
|
||||
+++ b/src/compiler/frame-states.cc
|
||||
@@ -37,11 +37,12 @@ std::ostream& operator<<(std::ostream& os, OutputFrameStateCombine const& sc) {
|
||||
bool operator==(FrameStateFunctionInfo const& lhs,
|
||||
FrameStateFunctionInfo const& rhs) {
|
||||
#if V8_HOST_ARCH_X64
|
||||
-// If this static_assert fails, then you've probably added a new field to
|
||||
-// FrameStateFunctionInfo. Make sure to take it into account in this equality
|
||||
-// function, and update the static_assert.
|
||||
+// If these static_asserts fail, then you've probably added a new field to
|
||||
+// FrameStateFunctionInfo or JSToWasmFrameStateFunctionInfo. Make sure to
|
||||
+// take it into account in this function, and update the static_assert.
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
static_assert(sizeof(FrameStateFunctionInfo) == 40);
|
||||
+ static_assert(sizeof(JSToWasmFrameStateFunctionInfo) == 48);
|
||||
#else
|
||||
static_assert(sizeof(FrameStateFunctionInfo) == 32);
|
||||
#endif
|
||||
@@ -52,6 +53,18 @@ bool operator==(FrameStateFunctionInfo const& lhs,
|
||||
lhs.wasm_function_index() != rhs.wasm_function_index()) {
|
||||
return false;
|
||||
}
|
||||
+
|
||||
+ // JSToWasmFrameStateFunctionInfo has an additional signature_ field.
|
||||
+ // Two frame states with different wasm signatures must not compare equal,
|
||||
+ // otherwise CSE/GVN can merge them and the deoptimizer will use the wrong
|
||||
+ // signature to materialize the continuation frame.
|
||||
+ if (lhs.type() == FrameStateType::kJSToWasmBuiltinContinuation &&
|
||||
+ rhs.type() == FrameStateType::kJSToWasmBuiltinContinuation) {
|
||||
+ if (static_cast<const JSToWasmFrameStateFunctionInfo&>(lhs).signature() !=
|
||||
+ static_cast<const JSToWasmFrameStateFunctionInfo&>(rhs).signature()) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
#endif
|
||||
|
||||
return lhs.type() == rhs.type() &&
|
||||
diff --git a/src/compiler/frame-states.h b/src/compiler/frame-states.h
|
||||
index 1faaa599bd7d83ad372eec823ebc553a4ec0745a..e17798a1a8e3a127aa6aa7bb2466d2efda02e75a 100644
|
||||
--- a/src/compiler/frame-states.h
|
||||
+++ b/src/compiler/frame-states.h
|
||||
@@ -108,6 +108,10 @@ class FrameStateFunctionInfo {
|
||||
bytecode_array_(bytecode_array) {
|
||||
}
|
||||
|
||||
+ // Prevent slicing when copying through base-class references.
|
||||
+ FrameStateFunctionInfo(const FrameStateFunctionInfo&) = delete;
|
||||
+ FrameStateFunctionInfo& operator=(const FrameStateFunctionInfo&) = delete;
|
||||
+
|
||||
int local_count() const { return local_count_; }
|
||||
uint16_t parameter_count() const { return parameter_count_; }
|
||||
uint16_t parameter_count_without_receiver() const {
|
||||
diff --git a/test/mjsunit/regress/wasm/regress-497404188.js b/test/mjsunit/regress/wasm/regress-497404188.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..54992bd37cdeeb81d9d542462c075e8601dcec83
|
||||
--- /dev/null
|
||||
+++ b/test/mjsunit/regress/wasm/regress-497404188.js
|
||||
@@ -0,0 +1,92 @@
|
||||
+// Copyright 2026 the V8 project authors. All rights reserved.
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+// Flags: --allow-natives-syntax --turbofan --no-maglev
|
||||
+
|
||||
+// Regression test for a type confusion in the deoptimizer caused by missing
|
||||
+// signature comparison in FrameStateFunctionInfo::operator==.
|
||||
+// Two wasm functions with different return types (externref vs i64) but
|
||||
+// identical parameter signatures can have their JSToWasmBuiltinContinuation
|
||||
+// frame states merged by CSE. On lazy deopt, the deoptimizer then uses the
|
||||
+// wrong signature to materialize the result, reading a tagged reference as
|
||||
+// an untagged i64 or vice versa.
|
||||
+
|
||||
+d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
|
||||
+
|
||||
+function makeInstance(callback) {
|
||||
+ const builder = new WasmModuleBuilder();
|
||||
+ const callback_index = builder.addImport('env', 'callback', kSig_v_v);
|
||||
+
|
||||
+ // Mutable Wasm globals to hold the return values for the two functions. The
|
||||
+ // callback can modify these to trigger deopt at the right moment and test
|
||||
+ // that the correct type is materialized after deopt.
|
||||
+ const g_ref =
|
||||
+ builder.addGlobal(kWasmExternRef, true, false).exportAs('g_ref');
|
||||
+ const g_i64 = builder.addGlobal(kWasmI64, true, false).exportAs('g_i64');
|
||||
+
|
||||
+ // Returns an externref global after calling the callback (which may trigger
|
||||
+ // deopt).
|
||||
+ builder.addFunction('return_ref', kSig_r_v)
|
||||
+ .addBody([
|
||||
+ kExprCallFunction, callback_index,
|
||||
+ kExprGlobalGet, g_ref.index,
|
||||
+ ])
|
||||
+ .exportFunc();
|
||||
+
|
||||
+ // Returns an i64 global after calling the callback (which may trigger deopt).
|
||||
+ builder.addFunction('return_i64', kSig_l_v)
|
||||
+ .addBody([
|
||||
+ kExprCallFunction, callback_index,
|
||||
+ kExprGlobalGet, g_i64.index,
|
||||
+ ])
|
||||
+ .exportFunc();
|
||||
+
|
||||
+ return builder.instantiate({env: {callback}}).exports;
|
||||
+}
|
||||
+
|
||||
+(function testNoTypeConfusionOnLazyDeopt() {
|
||||
+ let arm_deopt = false;
|
||||
+
|
||||
+ function ProtoForI64() {}
|
||||
+ function ProtoForRef() {}
|
||||
+
|
||||
+ const exports_ = makeInstance(() => {
|
||||
+ // Trigger deopt by changing the prototype after optimization.
|
||||
+ if (arm_deopt) {
|
||||
+ ProtoForRef.prototype.deopt_marker = 1;
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ // Install wasm getters with different return types on different prototypes.
|
||||
+ Object.defineProperty(
|
||||
+ ProtoForI64.prototype, 'x', {get: exports_.return_i64});
|
||||
+ Object.defineProperty(
|
||||
+ ProtoForRef.prototype, 'x', {get: exports_.return_ref});
|
||||
+
|
||||
+ function foo(o) {
|
||||
+ return o.x;
|
||||
+ }
|
||||
+
|
||||
+ const obj_i64 = new ProtoForI64();
|
||||
+ const obj_ref = new ProtoForRef();
|
||||
+
|
||||
+ const sentinel = {tag: 'sentinel'};
|
||||
+ exports_.g_ref.value = sentinel;
|
||||
+ exports_.g_i64.value = 42n;
|
||||
+
|
||||
+ // Train the function with both receiver types.
|
||||
+ %PrepareFunctionForOptimization(foo);
|
||||
+ for (let i = 0; i < 20; ++i) {
|
||||
+ foo(obj_i64);
|
||||
+ foo(obj_ref);
|
||||
+ }
|
||||
+
|
||||
+ // Optimize and run once without deopt.
|
||||
+ %OptimizeFunctionOnNextCall(foo);
|
||||
+ assertEquals(42n, foo(obj_i64));
|
||||
+
|
||||
+ arm_deopt = true;
|
||||
+ const result = foo(obj_ref);
|
||||
+ assertEquals(sentinel, result);
|
||||
+})();
|
||||
194
patches/v8/cherry-pick-a068030f5179.patch
Normal file
194
patches/v8/cherry-pick-a068030f5179.patch
Normal file
@@ -0,0 +1,194 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Gomes <victorgomes@chromium.org>
|
||||
Date: Thu, 26 Mar 2026 10:39:16 +0100
|
||||
Subject: [maglev] Use transitive IsEscaping check for inlined allocations
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Check if a virtual object is escaping transitively.
|
||||
|
||||
Rename non-transitive function to HasEscapeUses instead.
|
||||
|
||||
Fixed: 495751197
|
||||
Change-Id: I1df519079515b522974708f5419a81b5cbee7ade
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7702700
|
||||
Reviewed-by: Marja Hölttä <marja@chromium.org>
|
||||
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#106064}
|
||||
|
||||
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
|
||||
index 6b754b4d8592f0214f731dc9b298b3aab59527f2..7d47edc6dfbe5daa5e89647ea38c4e8ec21a522c 100644
|
||||
--- a/src/maglev/maglev-graph-builder.cc
|
||||
+++ b/src/maglev/maglev-graph-builder.cc
|
||||
@@ -4646,19 +4646,20 @@ void MaglevGraphBuilder::BuildInitializeStore_TrustedPointer(
|
||||
CHECK(!result.IsDoneWithAbort());
|
||||
}
|
||||
|
||||
-namespace {
|
||||
-bool IsEscaping(Graph* graph, InlinedAllocation* alloc) {
|
||||
- if (alloc->IsEscaping()) return true;
|
||||
- auto it = graph->allocations_elide_map().find(alloc);
|
||||
- if (it == graph->allocations_elide_map().end()) return false;
|
||||
+bool MaglevGraphBuilder::IsEscaping(InlinedAllocation* alloc) {
|
||||
+ if (alloc->HasEscapingUses()) return true;
|
||||
+ auto it = graph_->allocations_elide_map().find(alloc);
|
||||
+ if (it == graph_->allocations_elide_map().end()) return false;
|
||||
for (InlinedAllocation* inner_alloc : it->second) {
|
||||
- if (IsEscaping(graph, inner_alloc)) {
|
||||
+ if (IsEscaping(inner_alloc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+namespace {
|
||||
+
|
||||
bool VerifyIsNotEscaping(VirtualObjectList vos, InlinedAllocation* alloc) {
|
||||
for (VirtualObject* vo : vos) {
|
||||
if (vo->allocation() == alloc) continue;
|
||||
@@ -4668,7 +4669,7 @@ bool VerifyIsNotEscaping(VirtualObjectList vos, InlinedAllocation* alloc) {
|
||||
if (!nested_value->Is<InlinedAllocation>()) return true;
|
||||
ValueNode* nested_alloc = nested_value->Cast<InlinedAllocation>();
|
||||
if (nested_alloc == alloc) {
|
||||
- if (vo->allocation()->IsEscaping() ||
|
||||
+ if (vo->allocation()->HasEscapingUses() ||
|
||||
!VerifyIsNotEscaping(vos, vo->allocation())) {
|
||||
escaped = true;
|
||||
}
|
||||
@@ -4687,6 +4688,7 @@ bool MaglevGraphBuilder::CanTrackObjectChanges(ValueNode* receiver,
|
||||
if (!v8_flags.maglev_object_tracking) return false;
|
||||
if (!receiver->Is<InlinedAllocation>()) return false;
|
||||
InlinedAllocation* alloc = receiver->Cast<InlinedAllocation>();
|
||||
+ if (IsEscaping(alloc)) return false;
|
||||
if (mode == TrackObjectMode::kStore) {
|
||||
// If we have two objects A and B, such that A points to B (it contains B in
|
||||
// one of its field), we cannot change B without also changing A, even if
|
||||
@@ -4695,7 +4697,6 @@ bool MaglevGraphBuilder::CanTrackObjectChanges(ValueNode* receiver,
|
||||
graph_->allocations_elide_map().end()) {
|
||||
return false;
|
||||
}
|
||||
- if (alloc->IsEscaping()) return false;
|
||||
// Ensure object is escaped if we are within a try-catch block. This is
|
||||
// crucial because a deoptimization point inside the catch handler could
|
||||
// re-materialize objects differently, depending on whether the throw
|
||||
@@ -4704,9 +4705,6 @@ bool MaglevGraphBuilder::CanTrackObjectChanges(ValueNode* receiver,
|
||||
// the try-block started, but for now, err on the side of caution and
|
||||
// always escape.
|
||||
if (IsInsideTryBlock()) return false;
|
||||
- } else {
|
||||
- DCHECK_EQ(mode, TrackObjectMode::kLoad);
|
||||
- if (IsEscaping(graph_, alloc)) return false;
|
||||
}
|
||||
// We don't support loop phis inside VirtualObjects, so any access inside a
|
||||
// loop should escape the object, except for objects that were created since
|
||||
@@ -9195,7 +9193,7 @@ MaybeReduceResult MaglevGraphBuilder::TryReduceArrayIteratorPrototypeNext(
|
||||
VirtualObject* array = iterated_object->Cast<InlinedAllocation>()->object();
|
||||
// TODO(victorgomes): Remove this once we track changes in the inlined
|
||||
// allocated object.
|
||||
- if (iterated_object->Cast<InlinedAllocation>()->IsEscaping()) {
|
||||
+ if (IsEscaping(iterated_object->Cast<InlinedAllocation>())) {
|
||||
FAIL("allocation is escaping, map could have been changed");
|
||||
}
|
||||
// TODO(victorgomes): This effectively disable the optimization for `for-of`
|
||||
@@ -12315,7 +12313,7 @@ MaglevGraphBuilder::TryGetNonEscapingArgumentsObject(ValueNode* value) {
|
||||
}
|
||||
// TODO(victorgomes): We can probably loosen the IsNotEscaping requirement if
|
||||
// we keep track of the arguments object changes so far.
|
||||
- if (alloc->IsEscaping()) return {};
|
||||
+ if (IsEscaping(alloc)) return {};
|
||||
VirtualObject* object = alloc->object();
|
||||
if (!object->has_static_map()) return {};
|
||||
// TODO(victorgomes): Support simple JSArray forwarding.
|
||||
diff --git a/src/maglev/maglev-graph-builder.h b/src/maglev/maglev-graph-builder.h
|
||||
index 6b162e22b09b008a470612b5689323749891e5fd..1bf3400df6da6d15c959d74a722c6ecfa08b3ac1 100644
|
||||
--- a/src/maglev/maglev-graph-builder.h
|
||||
+++ b/src/maglev/maglev-graph-builder.h
|
||||
@@ -1447,6 +1447,7 @@ class MaglevGraphBuilder {
|
||||
ValueNode* object, ValueNode* callable,
|
||||
compiler::FeedbackSource feedback_source);
|
||||
|
||||
+ bool IsEscaping(InlinedAllocation* allocation);
|
||||
VirtualObject* GetObjectFromAllocation(InlinedAllocation* allocation);
|
||||
VirtualObject* GetModifiableObjectFromAllocation(
|
||||
InlinedAllocation* allocation);
|
||||
diff --git a/src/maglev/maglev-ir.h b/src/maglev/maglev-ir.h
|
||||
index d6282a00ebb327be9ed431e764fbe6a92abf72d5..d224c28bf639e952c8b7eaff1f28ce650cf1f33e 100644
|
||||
--- a/src/maglev/maglev-ir.h
|
||||
+++ b/src/maglev/maglev-ir.h
|
||||
@@ -6119,7 +6119,7 @@ class InlinedAllocation : public FixedInputValueNodeT<1, InlinedAllocation> {
|
||||
DCHECK(!HasBeenAnalysed());
|
||||
non_escaping_use_count_ += n;
|
||||
}
|
||||
- bool IsEscaping() const {
|
||||
+ bool HasEscapingUses() const {
|
||||
DCHECK(!HasBeenAnalysed());
|
||||
return use_count_ > non_escaping_use_count_;
|
||||
}
|
||||
diff --git a/src/maglev/maglev-post-hoc-optimizations-processors.h b/src/maglev/maglev-post-hoc-optimizations-processors.h
|
||||
index a7740283358d90383f366d29fea9360f1fd8661b..715cef1c67f9fdca830530fd438b3b6334922413 100644
|
||||
--- a/src/maglev/maglev-post-hoc-optimizations-processors.h
|
||||
+++ b/src/maglev/maglev-post-hoc-optimizations-processors.h
|
||||
@@ -382,7 +382,7 @@ class AnyUseMarkingProcessor {
|
||||
auto* alloc = it.first;
|
||||
if (alloc->HasBeenAnalysed()) continue;
|
||||
// Check if all its uses are non escaping.
|
||||
- if (alloc->IsEscaping()) {
|
||||
+ if (alloc->HasEscapingUses()) {
|
||||
// Escape this allocation and all its dependencies.
|
||||
EscapeAllocation(graph, alloc, it.second);
|
||||
} else {
|
||||
diff --git a/test/mjsunit/maglev/regress-495751197.js b/test/mjsunit/maglev/regress-495751197.js
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..b6e5baefea2a5d626bff5716f23a2ceadab01bad
|
||||
--- /dev/null
|
||||
+++ b/test/mjsunit/maglev/regress-495751197.js
|
||||
@@ -0,0 +1,47 @@
|
||||
+// Copyright 2026 the V8 project authors. All rights reserved.
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+// Flags: --allow-natives-syntax
|
||||
+
|
||||
+let do_transition = false;
|
||||
+let marker = {x: 1.337};
|
||||
+
|
||||
+Object.defineProperty(Object.prototype, '_leak', {
|
||||
+ get() {
|
||||
+ if (do_transition) {
|
||||
+ this[1][0] = marker;
|
||||
+ }
|
||||
+ return this;
|
||||
+ },
|
||||
+ configurable: true
|
||||
+});
|
||||
+
|
||||
+function target(iter) {
|
||||
+ return iter.next().value;
|
||||
+}
|
||||
+
|
||||
+function inner() {
|
||||
+ var leaked;
|
||||
+ // Forces WithContext creation. _leak triggers the runtime call.
|
||||
+ with (arguments) { leaked = _leak; }
|
||||
+ return target.apply(null, arguments);
|
||||
+}
|
||||
+
|
||||
+function outer() {
|
||||
+ let a = [1.1, 2.2, 3.3];
|
||||
+ let iter = a.values();
|
||||
+ return inner(iter, a);
|
||||
+}
|
||||
+
|
||||
+%PrepareFunctionForOptimization(target);
|
||||
+%PrepareFunctionForOptimization(inner);
|
||||
+%PrepareFunctionForOptimization(outer);
|
||||
+
|
||||
+outer(); outer(); outer();
|
||||
+
|
||||
+%OptimizeMaglevOnNextCall(outer);
|
||||
+
|
||||
+do_transition = true;
|
||||
+let r = outer();
|
||||
+assertFalse(typeof r === "number");
|
||||
Reference in New Issue
Block a user