diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index cc3c3b59d132..d245a0e693f2 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -88,6 +88,10 @@ #include "plstr.h" #include "prlink.h" +#ifdef DEBUG +# include +#endif + #ifdef MOZ_MEMORY # include "mozmemory.h" #endif @@ -1179,6 +1183,20 @@ using PrefsHashTable = mozilla::HashSet, PrefHasher>; static PrefsHashTable* gHashTable; +#ifdef DEBUG +// This defines the datatype used to store our `Once` StaticPrefs checker. +// We can't use HashMap for now due to alignment restrictions when dealing with +// std::function (see bug 1557617). +typedef std::function AntiFootgunCallback; +struct CompareStr { + bool operator()(char const* a, char const* b) const { + return std::strcmp(a, b) < 0; + } +}; +typedef std::map AntiFootgunMap; +static AntiFootgunMap* gOnceStaticPrefsAntiFootgun; +#endif + // The callback list contains all the priority callbacks followed by the // non-priority callbacks. gLastPriorityNode records where the first part ends. static CallbackNode* gFirstCallback = nullptr; @@ -1671,6 +1689,20 @@ static void NotifyCallbacks(const char* aPrefName, const PrefWrapper* aPref) { } gShouldCleanupDeadNodes = false; } + +#ifdef DEBUG + if (XRE_IsParentProcess() && StaticPrefs::preferences_check_once_policy()) { + // Check that we aren't modifying a `Once` pref using that prefName. + // We have about 100 `Once` StaticPrefs defined. std::map performs a search + // in O(log n), so this is fast enough for our case. + MOZ_ASSERT(gOnceStaticPrefsAntiFootgun); + auto search = gOnceStaticPrefsAntiFootgun->find(aPrefName); + if (search != gOnceStaticPrefsAntiFootgun->end()) { + // Run the callback. + (search->second)(); + } + } +#endif } //=========================================================================== @@ -3460,6 +3492,10 @@ already_AddRefed Preferences::GetInstanceForService() { gTelemetryLoadData = new nsDataHashtable(); +#ifdef DEBUG + gOnceStaticPrefsAntiFootgun = new AntiFootgunMap(); +#endif + #ifdef ACCESS_COUNTS MOZ_ASSERT(!gAccessCounts); gAccessCounts = new AccessCountsHashTable(); @@ -3595,6 +3631,11 @@ Preferences::~Preferences() { delete gTelemetryLoadData; gTelemetryLoadData = nullptr; +#ifdef DEBUG + delete gOnceStaticPrefsAntiFootgun; + gOnceStaticPrefsAntiFootgun = nullptr; +#endif + #ifdef ACCESS_COUNTS delete gAccessCounts; #endif @@ -5486,11 +5527,41 @@ void StaticPrefs::InitOncePrefs() { // // This is done to get the potentially updated Preference value as we didn't // register a callback method for the `Once` policy. + // + // On debug build, we also install a mechanism that allows to check if the + // original Preference is being modified once `Once` StaticPrefs have been + // initialized as this would indicate a likely misuse of `Once` StaticPrefs + // and that maybe instead they should have been made `Live`. #define PREF(name, cpp_type, value) -#define VARCACHE_PREF(policy, name, id, cpp_type, value) \ - if (UpdatePolicy::policy == UpdatePolicy::Once) { \ - StaticPrefs::sVarCache_##id = GetPref(name, StripAtomic(value)); \ - } +#ifdef DEBUG +# define VARCACHE_PREF(policy, name, id, cpp_type, value) \ + if (UpdatePolicy::policy == UpdatePolicy::Once) { \ + StaticPrefs::sVarCache_##id = \ + GetPref(name, StripAtomic(value)); \ + auto checkPref = [&]() { \ + if (!sOncePrefRead) { \ + return; \ + } \ + StripAtomic staticPrefValue = StaticPrefs::id(); \ + StripAtomic preferenceValue = \ + GetPref(Get##id##PrefName(), StripAtomic(value)); \ + MOZ_ASSERT( \ + staticPrefValue == preferenceValue, \ + "Preference '" name "' got modified since StaticPrefs::" #id \ + " got initialized. Consider using a `Live` StaticPrefs instead"); \ + }; \ + gOnceStaticPrefsAntiFootgun->insert( \ + std::pair(Get##id##PrefName(), \ + std::move(checkPref))); \ + } +#else +# define VARCACHE_PREF(policy, name, id, cpp_type, value) \ + if (UpdatePolicy::policy == UpdatePolicy::Once) { \ + StaticPrefs::sVarCache_##id = \ + GetPref(name, StripAtomic(value)); \ + } +#endif + #include "mozilla/StaticPrefList.h" #undef PREF #undef VARCACHE_PREF diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h index d4c72237510e..c327ac3755a3 100644 --- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -6029,6 +6029,18 @@ VARCACHE_PREF( PREF("preferences.allow.omt-write", bool, true) +#ifdef DEBUG +// If set to true, setting a Preference matched to a `Once` StaticPref will +// assert that the value matches. Such assertion being broken is a clear flag +// that the Once policy shouldn't be used. +VARCACHE_PREF( + Live, + "preferences.check.once.policy", + preferences_check_once_policy, + bool, false +) +#endif + //--------------------------------------------------------------------------- // Prefs starting with "print." //---------------------------------------------------------------------------