tor-browser

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

commit c03efb6f2ec6d68c6de2fd43394b3d65141381d0
parent f82c81855d62f18927cdf8ba05f78d4355f68724
Author: André Bargull <andre.bargull@gmail.com>
Date:   Mon, 13 Oct 2025 12:55:26 +0000

Bug 1990248 - Part 6: Add a runtime fuse for default casing. r=spidermonkey-reviewers,jandem

Add runtime-wide fuse to guard that the default locale uses the default casing
algorithm. This will allow to inline `String.prototype.to{Lower,Upper}Case` in
the next part.

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

Diffstat:
Mjs/src/builtin/String.cpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mjs/src/builtin/String.h | 4++++
Mjs/src/jit/CompilationDependencyTracker.h | 1+
Mjs/src/jit/WarpOracle.cpp | 6++++++
Mjs/src/vm/Runtime.cpp | 15+++++++++++++++
Mjs/src/vm/Runtime.h | 10+++++++++-
Mjs/src/vm/RuntimeFuses.cpp | 17+++++++++++++++++
Mjs/src/vm/RuntimeFuses.h | 20+++++++++++++++++---
8 files changed, 127 insertions(+), 8 deletions(-)

diff --git a/js/src/builtin/String.cpp b/js/src/builtin/String.cpp @@ -10,6 +10,7 @@ #include "mozilla/CheckedInt.h" #include "mozilla/Compiler.h" #if JS_HAS_INTL_API +# include "mozilla/intl/Locale.h" # include "mozilla/intl/String.h" #endif #include "mozilla/Likely.h" @@ -944,17 +945,70 @@ static bool str_toLowerCase(JSContext* cx, unsigned argc, Value* vp) { } #if JS_HAS_INTL_API +// Lithuanian, Turkish, and Azeri have language dependent case mappings. +static constexpr char LanguagesWithSpecialCasing[][3] = {"lt", "tr", "az"}; + +bool js::LocaleHasDefaultCaseMapping(const char* locale) { + MOZ_ASSERT(locale); + + size_t languageSubtagLength; + if (auto* sep = strchr(locale, '-')) { + languageSubtagLength = sep - locale; + } else { + languageSubtagLength = std::strlen(locale); + } + + // Invalid locale identifiers default to the last-ditch locale "en-GB", which + // has default case mapping. + mozilla::Span<const char> span{locale, languageSubtagLength}; + { + // Tell the analysis the |IsStructurallyValidLanguageTag| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + if (!mozilla::intl::IsStructurallyValidLanguageTag(span)) { + return true; + } + } + + mozilla::intl::LanguageSubtag subtag{span}; + + // Canonical case for the language subtag is lower-case + { + // Tell the analysis the |ToLowerCase| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + + subtag.ToLowerCase(); + } + + // Replace outdated language subtags. Skips complex language mappings, which + // is okay because none of the languages with special casing are affected by + // complex language mapping. + { + // Tell the analysis the |LanguageMapping| function can't GC. + JS::AutoSuppressGCAnalysis nogc; + + (void)mozilla::intl::Locale::LanguageMapping(subtag); + } + + // Check for languages which don't use the default case mapping algorithm. + for (const auto& language : LanguagesWithSpecialCasing) { + if (subtag.EqualTo(language)) { + return false; + } + } + + // Simple locale with default case mapping. (Or an invalid locale which + // defaults to the last-ditch locale "en-GB".) + return true; +} + static const char* CaseMappingLocale(JSLinearString* locale) { MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag"); - // Lithuanian, Turkish, and Azeri have language dependent case mappings. - static const char languagesWithSpecialCasing[][3] = {"lt", "tr", "az"}; - // All strings in |languagesWithSpecialCasing| are of length two, so we // only need to compare the first two characters to find a matching locale. // ES2017 Intl, §9.2.2 BestAvailableLocale if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') { - for (const auto& language : languagesWithSpecialCasing) { + for (const auto& language : LanguagesWithSpecialCasing) { if (locale->latin1OrTwoByteChar(0) == language[0] && locale->latin1OrTwoByteChar(1) == language[1]) { return language; diff --git a/js/src/builtin/String.h b/js/src/builtin/String.h @@ -90,6 +90,10 @@ extern JSLinearString* StringFromCharCode(JSContext* cx, int32_t charCode); extern JSLinearString* StringFromCodePoint(JSContext* cx, char32_t codePoint); +#if JS_HAS_INTL_API +bool LocaleHasDefaultCaseMapping(const char* locale); +#endif + } /* namespace js */ #endif /* builtin_String_h */ diff --git a/js/src/jit/CompilationDependencyTracker.h b/js/src/jit/CompilationDependencyTracker.h @@ -30,6 +30,7 @@ struct CompilationDependency : public TempObject { EmulatesUndefined, ArrayExceedsInt32Length, ObjectFuseProperty, + DefaultCaseMapping, Limit }; diff --git a/js/src/jit/WarpOracle.cpp b/js/src/jit/WarpOracle.cpp @@ -929,6 +929,12 @@ bool WarpOracle::addFuseDependency(RuntimeFuses::FuseIndex fuseIndex, CompilationDependency::Type::ArrayExceedsInt32Length>; return addIfStillValid(Dependency()); } + case RuntimeFuses::FuseIndex::DefaultLocaleHasDefaultCaseMappingFuse: { + using Dependency = RuntimeFuseDependency< + &RuntimeFuses::defaultLocaleHasDefaultCaseMappingFuse, + CompilationDependency::Type::DefaultCaseMapping>; + return addIfStillValid(Dependency()); + } case RuntimeFuses::FuseIndex::LastFuseIndex: break; } diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp @@ -20,6 +20,7 @@ #include "jsfriendapi.h" #include "jsmath.h" +#include "builtin/String.h" #include "frontend/CompilationStencil.h" #include "frontend/ParserAtom.h" // frontend::WellKnownParserAtoms #include "gc/GC.h" @@ -514,6 +515,13 @@ bool JSRuntime::setDefaultLocale(const char* locale) { return false; } +#if JS_HAS_INTL_API + if (!LocaleHasDefaultCaseMapping(newLocale.get())) { + runtimeFuses.ref().defaultLocaleHasDefaultCaseMappingFuse.popFuse( + mainContextFromOwnThread()); + } +#endif + defaultLocale.ref() = std::move(newLocale); return true; } @@ -551,6 +559,13 @@ const char* JSRuntime::getDefaultLocale() { *p = '-'; } +#if JS_HAS_INTL_API + if (!LocaleHasDefaultCaseMapping(lang.get())) { + runtimeFuses.ref().defaultLocaleHasDefaultCaseMappingFuse.popFuse( + mainContextFromOwnThread()); + } +#endif + defaultLocale.ref() = std::move(lang); return defaultLocale.ref().get(); } diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h @@ -794,9 +794,17 @@ struct JSRuntime { /* Reset the default locale to OS defaults. */ void resetDefaultLocale(); - /* Gets current default locale. String remains owned by context. */ + /* Gets current default locale. String remains owned by runtime. */ const char* getDefaultLocale(); + /* + * Gets current default locale or nullptr if not initialized. + * String remains owned by runtime. + */ + const char* getDefaultLocaleIfInitialized() const { + return defaultLocale.ref().get(); + } + /* Garbage collector state. */ js::gc::GCRuntime gc; diff --git a/js/src/vm/RuntimeFuses.cpp b/js/src/vm/RuntimeFuses.cpp @@ -9,6 +9,7 @@ #include <stddef.h> #include <stdint.h> +#include "builtin/String.h" #include "js/friend/UsageStatistics.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" @@ -60,3 +61,19 @@ void js::HasSeenObjectEmulateUndefinedFuse::popFuse(JSContext* cx) { void js::HasSeenArrayExceedsInt32LengthFuse::popFuse(JSContext* cx) { js::InvalidatingRuntimeFuse::popFuse(cx); } + +bool js::DefaultLocaleHasDefaultCaseMappingFuse::checkInvariant(JSContext* cx) { +#if JS_HAS_INTL_API + const char* locale = cx->runtime()->getDefaultLocaleIfInitialized(); + if (!locale) { + return true; + } + return LocaleHasDefaultCaseMapping(locale); +#else + return true; +#endif +} + +void js::DefaultLocaleHasDefaultCaseMappingFuse::popFuse(JSContext* cx) { + js::InvalidatingRuntimeFuse::popFuse(cx); +} diff --git a/js/src/vm/RuntimeFuses.h b/js/src/vm/RuntimeFuses.h @@ -38,9 +38,23 @@ class HasSeenArrayExceedsInt32LengthFuse final void popFuse(JSContext* cx) override; }; -#define FOR_EACH_RUNTIME_FUSE(FUSE) \ - FUSE(HasSeenObjectEmulateUndefinedFuse, hasSeenObjectEmulateUndefinedFuse) \ - FUSE(HasSeenArrayExceedsInt32LengthFuse, hasSeenArrayExceedsInt32LengthFuse) +class DefaultLocaleHasDefaultCaseMappingFuse final + : public InvalidatingRuntimeFuse { + const char* name() override { + return "DefaultLocaleHasDefaultCaseMappingFuse"; + } + + bool checkInvariant(JSContext* cx) override; + + public: + void popFuse(JSContext* cx) override; +}; + +#define FOR_EACH_RUNTIME_FUSE(FUSE) \ + FUSE(HasSeenObjectEmulateUndefinedFuse, hasSeenObjectEmulateUndefinedFuse) \ + FUSE(HasSeenArrayExceedsInt32LengthFuse, hasSeenArrayExceedsInt32LengthFuse) \ + FUSE(DefaultLocaleHasDefaultCaseMappingFuse, \ + defaultLocaleHasDefaultCaseMappingFuse) struct RuntimeFuses { RuntimeFuses() = default;