tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit dba4fc7e5995f501da0ff329806fc9a8031ee4f9
parent 34a4e384fc01a450ac011c7a66fc75f5aabc95b7
Author: Chun-Min Chang <chun.m.chang@gmail.com>
Date:   Mon, 15 Dec 2025 20:42:59 +0000

Bug 2005833 - SimpleMap: Enforce key uniqueness and optimize removal r=media-playback-reviewers,padenot

Previously, `SimpleMap` performed unconditional insertions without checking for
duplicate keys. This patch enforces key uniqueness by preventing the insertion
of duplicates.

Guaranteing uniqueness allows the use of `UnorderedRemoveElementAt` instead of
`RemoveElementAt`. This improves removal performance, as preserving relative
entry order is no longer required.

Additionally, this patch adds a `Count` method to retrieve the number of items
in the map and introduces gtests to verify these behavior changes.

Differential Revision: https://phabricator.services.mozilla.com/D276307

Diffstat:
Mdom/media/gmp/ChromiumCDMChild.cpp | 1+
Adom/media/gtest/TestSimpleMap.cpp | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/gtest/moz.build | 1+
Mdom/media/platforms/SimpleMap.h | 48++++++++++++++++++++++++++++++++++--------------
Mdom/media/platforms/android/RemoteDataDecoder.cpp | 2++
Mdom/media/platforms/ffmpeg/FFmpegVideoDecoder.h | 1+
Mdom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp | 2++
7 files changed, 152 insertions(+), 14 deletions(-)

diff --git a/dom/media/gmp/ChromiumCDMChild.cpp b/dom/media/gmp/ChromiumCDMChild.cpp @@ -743,6 +743,7 @@ mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecryptAndDecodeFrame( // CDM's decoder reorders to ensure frames output are in presentation order. // So we need to store the durations of the frames input, and retrieve them // on output. + MOZ_DIAGNOSTIC_ASSERT(!mFrameDurations.Contains(aBuffer.mTimestamp())); mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration()); cdm::InputBuffer_2 input = {}; diff --git a/dom/media/gtest/TestSimpleMap.cpp b/dom/media/gtest/TestSimpleMap.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleMap.h" +#include "gtest/gtest.h" +#include "nsString.h" + +namespace mozilla { + +TEST(SimpleMapTest, Insert) +{ + SimpleMap<int, nsCString> map; + + // Insert new key-value pair + ASSERT_TRUE(map.Insert(1, "first"_ns)); + ASSERT_EQ(map.Count(), size_t(1)); + + // Try to insert duplicate key - should fail + ASSERT_FALSE(map.Insert(1, "second"_ns)); + ASSERT_EQ(map.Count(), size_t(1)); // Count should still be 1 + + // Verify original value is preserved + Maybe<nsCString> taken = map.Take(1); + ASSERT_TRUE(taken.isSome()); + ASSERT_TRUE(taken.ref().EqualsLiteral("first")); + ASSERT_EQ(map.Count(), size_t(0)); + + // Verify key is removed + Maybe<nsCString> shouldBeNothing = map.Take(1); + ASSERT_TRUE(shouldBeNothing.isNothing()); +} + +TEST(SimpleMapTest, Find) +{ + SimpleMap<int, nsCString> map; + + ASSERT_FALSE(map.Contains(1)); + ASSERT_TRUE(map.Insert(1, "one"_ns)); + ASSERT_TRUE(map.Contains(1)); + + nsCString value; + ASSERT_TRUE(map.Find(1, value)); + ASSERT_TRUE(value.EqualsLiteral("one")); + ASSERT_FALSE(map.Contains(1)); // Find also removes the element. + ASSERT_FALSE(map.Find(2, value)); +} + +TEST(SimpleMapTest, Take) +{ + SimpleMap<int, nsCString> map; + + ASSERT_EQ(map.Count(), size_t(0)); + map.Insert(1, "one"_ns); + ASSERT_EQ(map.Count(), size_t(1)); + map.Insert(2, "two"_ns); + ASSERT_EQ(map.Count(), size_t(2)); + + Maybe<nsCString> taken = map.Take(1); + ASSERT_TRUE(taken.isSome()); + ASSERT_TRUE(taken.ref().EqualsLiteral("one")); + ASSERT_FALSE(map.Contains(1)); + ASSERT_TRUE(map.Contains(2)); + ASSERT_EQ(map.Count(), size_t(1)); + + Maybe<nsCString> notTaken = map.Take(3); + ASSERT_TRUE(notTaken.isNothing()); +} + +TEST(SimpleMapTest, Clear) +{ + SimpleMap<int, nsCString> map; + + ASSERT_EQ(map.Count(), size_t(0)); + + map.Insert(1, "one"_ns); + map.Insert(2, "two"_ns); + + ASSERT_EQ(map.Count(), size_t(2)); + ASSERT_TRUE(map.Contains(1)); + ASSERT_TRUE(map.Contains(2)); + + map.Clear(); + ASSERT_FALSE(map.Contains(1)); + ASSERT_FALSE(map.Contains(2)); + ASSERT_EQ(map.Count(), size_t(0)); +} + +TEST(SimpleMapTest, ForEach) +{ + SimpleMap<int, nsCString> map; + + map.Insert(1, "one"_ns); + map.Insert(2, "two"_ns); + map.Insert(3, "three"_ns); + Maybe<nsCString> taken = map.Take(1); + ASSERT_TRUE(taken.isSome()); + ASSERT_TRUE(taken.ref().EqualsLiteral("one")); + ASSERT_EQ(map.Count(), size_t(2)); + + nsTArray<int> keys; + map.ForEach( + [&](int key, const nsCString& value) { keys.AppendElement(key); }); + + // The order of iteration is not guaranteed. + ASSERT_EQ(keys.Length(), size_t(2)); + ASSERT_TRUE(keys.Contains(2) && keys.Contains(3)); +} + +} // namespace mozilla diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build @@ -64,6 +64,7 @@ UNIFIED_SOURCES += [ "TestOggWriter.cpp", "TestOpusParser.cpp", "TestRust.cpp", + "TestSimpleMap.cpp", "TestTimeUnit.cpp", "TestVideoFrameContainer.cpp", "TestVideoSegment.cpp", diff --git a/dom/media/platforms/SimpleMap.h b/dom/media/platforms/SimpleMap.h @@ -47,19 +47,22 @@ class SimpleMap { // Check if aKey is in the map. bool Contains(const K& aKey) { - struct Comparator { - bool Equals(const ElementType& aElement, const K& aKey) const { - return aElement.first == aKey; - } - }; Policy guard(mLock); - return mMap.Contains(aKey, Comparator()); + return FindIndex(aKey).isSome(); } + // Insert Key and Value pair at the end of our map. - void Insert(const K& aKey, const V& aValue) { + // Returns true if the insertion succeeded, or false if the key already + // exists. + bool Insert(const K& aKey, const V& aValue) { Policy guard(mLock); + if (FindIndex(aKey).isSome()) { + return false; + } mMap.AppendElement(std::make_pair(aKey, aValue)); + return true; } + // Sets aValue matching aKey and remove it from the map if found. // The element returned is the first one found. // Returns true if found, false otherwise. @@ -70,24 +73,24 @@ class SimpleMap { } return false; } + // Take the value matching aKey and remove it from the map if found. Maybe<V> Take(const K& aKey) { Policy guard(mLock); - for (uint32_t i = 0; i < mMap.Length(); i++) { - ElementType& element = mMap[i]; - if (element.first == aKey) { - Maybe<V> value = Some(element.second); - mMap.RemoveElementAt(i); - return value; - } + if (Maybe<size_t> index = FindIndex(aKey)) { + Maybe<V> value = Some(std::move(mMap[*index].second)); + mMap.UnorderedRemoveElementAt(*index); + return value; } return Nothing(); } + // Remove all elements of the map. void Clear() { Policy guard(mLock); mMap.Clear(); } + // Iterate through all elements of the map and call the function F. template <typename F> void ForEach(F&& aCallback) { @@ -97,7 +100,24 @@ class SimpleMap { } } + // Return the number of elements in the map. + size_t Count() { + Policy guard(mLock); + return mMap.Length(); + } + private: + // Return the index of the first element matching aKey, or Nothing() if not + // found. + Maybe<size_t> FindIndex(const K& aKey) const { + for (size_t i = 0; i < mMap.Length(); ++i) { + if (mMap[i].first == aKey) { + return Some(i); + } + } + return Nothing(); + } + typename Policy::PolicyLock mLock; MapType mMap; }; diff --git a/dom/media/platforms/android/RemoteDataDecoder.cpp b/dom/media/platforms/android/RemoteDataDecoder.cpp @@ -301,6 +301,8 @@ class RemoteVideoDecoder final : public RemoteDataDecoder { InputInfo info(aSample->mDuration.ToMicroseconds(), config->mImage, config->mDisplay); + MOZ_DIAGNOSTIC_ASSERT( + !mInputInfos.Contains(aSample->mTime.ToMicroseconds())); mInputInfos.Insert(aSample->mTime.ToMicroseconds(), info); return RemoteDataDecoder::Decode(aSample); } diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h @@ -336,6 +336,7 @@ class FFmpegVideoDecoder<LIBAV_VER> // after decoding. As such we instead use a map using the given ts as key // that we will retrieve later. The map will have a typical size of 16 // entry. + MOZ_DIAGNOSTIC_ASSERT(!mInputInfo.Contains(GetSampleInputKey(aSample))); mInputInfo.Insert(GetSampleInputKey(aSample), InputInfo(aSample)); } diff --git a/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp b/dom/media/platforms/ffmpeg/FFmpegVideoEncoder.cpp @@ -617,6 +617,7 @@ Result<MediaDataEncoder::EncodedData, MediaResult> FFmpegVideoEncoder< // Provide fake pts, see header file. if (mConfig.mCodec == CodecType::AV1) { mFrame->pts = mFakePts; + MOZ_DIAGNOSTIC_ASSERT(!mPtsMap.Contains(mFakePts)); mPtsMap.Insert(mFakePts, aSample->mTime.ToMicroseconds()); mFakePts += aSample->mDuration.ToMicroseconds(); mCurrentFramePts = aSample->mTime.ToMicroseconds(); @@ -627,6 +628,7 @@ Result<MediaDataEncoder::EncodedData, MediaResult> FFmpegVideoEncoder< mFrame->duration = aSample->mDuration.ToMicroseconds(); # else // Save duration in the time_base unit. + MOZ_DIAGNOSTIC_ASSERT(!mDurationMap.Contains(mFrame->pts)); mDurationMap.Insert(mFrame->pts, aSample->mDuration.ToMicroseconds()); # endif Duration(mFrame) = aSample->mDuration.ToMicroseconds();