Bug 1421541 - Overhaul the prefs phase code. r=glandium

This code is used to detect too-early accesses of prefs in content processes.

The patch makes the following changes.

- New terminology: "early" prefs are those sent via the command line; "late"
  prefs are those sent via IPC. Previously the former were "init" prefs and the
  latter didn't have a clear name.

- The phase tracking and checking is now almost completely encapsulated within
  Preferences.cpp. The only exposure to outside code is via the
  AreAllPrefsSetInContentProcess() method, which has a single use.

- The number of states tracked drops from 5 to 3. There's no need to track the
  beginning of the pref-setting operations, because we only need to know if
  they've finished. (This also avoids the weirdness where we could transition
  from END_INIT_PREFS back to BEGIN_INIT_PREFS because of the way -intPrefs,
  -boolPrefs and -stringPrefs were parsed separately.)

MozReview-Commit-ID: IVJWiDxdsDV
This commit is contained in:
Nicholas Nethercote
2017-11-30 09:14:32 +11:00
parent 4f3e11acaf
commit e142bee515
8 changed files with 108 additions and 96 deletions

View File

@@ -730,7 +730,17 @@ pref_savePrefs()
#ifdef DEBUG
static pref_initPhase gPhase = START;
// For content processes, what prefs have been initialized?
enum class ContentProcessPhase
{
eNoPrefsSet,
eEarlyPrefsSet,
eEarlyAndLatePrefsSet,
};
// Note that this never changes in the parent process, and is only read in
// content processes.
static ContentProcessPhase gPhase = ContentProcessPhase::eNoPrefsSet;
struct StringComparator
{
@@ -748,11 +758,11 @@ struct StringComparator
};
static bool
InInitArray(const char* aPrefName)
IsEarlyPref(const char* aPrefName)
{
size_t prefsLen;
size_t found;
const char** list = mozilla::dom::ContentPrefs::GetContentPrefs(&prefsLen);
const char** list = mozilla::dom::ContentPrefs::GetEarlyPrefs(&prefsLen);
return BinarySearchIf(list, 0, prefsLen, StringComparator(aPrefName), &found);
}
@@ -774,29 +784,34 @@ public:
#endif // DEBUG
static Pref*
pref_HashTableLookup(const char* aKey)
pref_HashTableLookup(const char* aPrefName)
{
MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
MOZ_ASSERT((XRE_IsParentProcess() || gPhase != START),
"pref access before commandline prefs set");
// If you're hitting this assertion, you've added a pref access to start up.
// Consider moving it later or add it to the whitelist in ContentPrefs.cpp
// and get review from a DOM peer.
//
// Note that accesses of non-whitelisted prefs that happen while installing a
// callback (e.g. VarCache) are considered acceptable. These accesses will
// fail, but once the proper pref value is set the callback will be
// immediately called, so things should work out.
#ifdef DEBUG
if (!XRE_IsParentProcess() && gPhase <= END_INIT_PREFS &&
!gInstallingCallback && !InInitArray(aKey)) {
MOZ_CRASH_UNSAFE_PRINTF(
"accessing non-init pref %s before the rest of the prefs are sent", aKey);
if (!XRE_IsParentProcess()) {
if (gPhase == ContentProcessPhase::eNoPrefsSet) {
MOZ_CRASH_UNSAFE_PRINTF(
"accessing pref %s before early prefs are set", aPrefName);
}
if (gPhase == ContentProcessPhase::eEarlyPrefsSet && !gInstallingCallback &&
!IsEarlyPref(aPrefName)) {
// If you hit this crash, you have an early access of a non-early pref.
// Consider moving the access later or add the pref to the whitelist of
// early prefs in ContentPrefs.cpp and get review from a DOM peer.
//
// Note that accesses of non-early prefs that happen while
// installing a callback (e.g. VarCache) are considered acceptable. These
// accesses will fail, but once the proper pref value is set the callback
// will be immediately called, so things should work out.
MOZ_CRASH_UNSAFE_PRINTF(
"accessing non-early pref %s before late prefs are set", aPrefName);
}
}
#endif
return static_cast<Pref*>(gHashTable->Search(aKey));
return static_cast<Pref*>(gHashTable->Search(aPrefName));
}
static nsresult
@@ -3263,7 +3278,8 @@ public:
} // namespace
static InfallibleTArray<dom::Pref>* gInitDomPrefs;
// A list of prefs sent early from the parent, via the command line.
static InfallibleTArray<dom::Pref>* gEarlyDomPrefs;
/* static */ already_AddRefed<Preferences>
Preferences::GetInstanceForService()
@@ -3291,12 +3307,12 @@ Preferences::GetInstanceForService()
}
if (!XRE_IsParentProcess()) {
MOZ_ASSERT(gInitDomPrefs);
for (unsigned int i = 0; i < gInitDomPrefs->Length(); i++) {
Preferences::SetPreference(gInitDomPrefs->ElementAt(i));
MOZ_ASSERT(gEarlyDomPrefs);
for (unsigned int i = 0; i < gEarlyDomPrefs->Length(); i++) {
Preferences::SetPreference(gEarlyDomPrefs->ElementAt(i));
}
delete gInitDomPrefs;
gInitDomPrefs = nullptr;
delete gEarlyDomPrefs;
gEarlyDomPrefs = nullptr;
} else {
// Check if there is a deployment configuration file. If so, set up the
@@ -3422,9 +3438,29 @@ NS_INTERFACE_MAP_BEGIN(Preferences)
NS_INTERFACE_MAP_END
/* static */ void
Preferences::SetInitPreferences(nsTArray<dom::Pref>* aDomPrefs)
Preferences::SetEarlyPreferences(const nsTArray<dom::Pref>* aDomPrefs)
{
gInitDomPrefs = new InfallibleTArray<dom::Pref>(mozilla::Move(*aDomPrefs));
MOZ_ASSERT(!XRE_IsParentProcess());
#ifdef DEBUG
MOZ_ASSERT(gPhase == ContentProcessPhase::eNoPrefsSet);
gPhase = ContentProcessPhase::eEarlyPrefsSet;
#endif
gEarlyDomPrefs = new InfallibleTArray<dom::Pref>(mozilla::Move(*aDomPrefs));
}
/* static */ void
Preferences::SetLatePreferences(const nsTArray<dom::Pref>* aDomPrefs)
{
MOZ_ASSERT(!XRE_IsParentProcess());
#ifdef DEBUG
MOZ_ASSERT(gPhase == ContentProcessPhase::eEarlyPrefsSet);
gPhase = ContentProcessPhase::eEarlyAndLatePrefsSet;
#endif
for (unsigned int i = 0; i < aDomPrefs->Length(); i++) {
Preferences::SetPreference(aDomPrefs->ElementAt(i));
}
}
/* static */ void
@@ -3640,6 +3676,8 @@ Preferences::SetPreference(const dom::Pref& aDomPref)
/* static */ void
Preferences::GetPreference(dom::Pref* aDomPref)
{
MOZ_ASSERT(XRE_IsParentProcess());
Pref* pref = pref_HashTableLookup(aDomPref->name().get());
if (pref && pref->HasAdvisablySizedValues()) {
pref->ToDomPref(aDomPref);
@@ -3649,6 +3687,8 @@ Preferences::GetPreference(dom::Pref* aDomPref)
void
Preferences::GetPreferences(InfallibleTArray<dom::Pref>* aDomPrefs)
{
MOZ_ASSERT(XRE_IsParentProcess());
aDomPrefs->SetCapacity(gHashTable->EntryCount());
for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
auto pref = static_cast<Pref*>(iter.Get());
@@ -3661,16 +3701,11 @@ Preferences::GetPreferences(InfallibleTArray<dom::Pref>* aDomPrefs)
}
#ifdef DEBUG
void
Preferences::SetInitPhase(pref_initPhase aPhase)
bool
Preferences::AreAllPrefsSetInContentProcess()
{
gPhase = aPhase;
}
pref_initPhase
Preferences::InitPhase()
{
return gPhase;
MOZ_ASSERT(!XRE_IsParentProcess());
return gPhase == ContentProcessPhase::eEarlyAndLatePrefsSet;
}
#endif