LCOV - code coverage report
Current view: top level - xpcom/build - IOInterposer.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 141 258 54.7 %
Date: 2017-07-14 16:53:18 Functions: 24 41 58.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include <algorithm>
       8             : #include <vector>
       9             : 
      10             : #include "IOInterposer.h"
      11             : 
      12             : #include "IOInterposerPrivate.h"
      13             : #include "MainThreadIOLogger.h"
      14             : #include "mozilla/Atomics.h"
      15             : #include "mozilla/Mutex.h"
      16             : #include "mozilla/RefPtr.h"
      17             : #include "mozilla/StaticPtr.h"
      18             : #include "mozilla/ThreadLocal.h"
      19             : #include "nscore.h" // for NS_FREE_PERMANENT_DATA
      20             : #if !defined(XP_WIN)
      21             : #include "NSPRInterposer.h"
      22             : #endif // !defined(XP_WIN)
      23             : #include "nsXULAppAPI.h"
      24             : #include "PoisonIOInterposer.h"
      25             : 
      26             : using namespace mozilla;
      27             : 
      28             : namespace {
      29             : 
      30             : /** Find if a vector contains a specific element */
      31             : template<class T>
      32             : bool
      33           7 : VectorContains(const std::vector<T>& aVector, const T& aElement)
      34             : {
      35           7 :   return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end();
      36             : }
      37             : 
      38             : /** Remove element from a vector */
      39             : template<class T>
      40             : void
      41           0 : VectorRemove(std::vector<T>& aVector, const T& aElement)
      42             : {
      43             :   typename std::vector<T>::iterator newEnd =
      44           0 :     std::remove(aVector.begin(), aVector.end(), aElement);
      45           0 :   aVector.erase(newEnd, aVector.end());
      46           0 : }
      47             : 
      48             : /** Lists of Observers */
      49             : struct ObserverLists
      50             : {
      51             : private:
      52           0 :   ~ObserverLists() {}
      53             : 
      54             : public:
      55          17 :   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists)
      56             : 
      57           1 :   ObserverLists() {}
      58             : 
      59           0 :   ObserverLists(ObserverLists const& aOther)
      60           0 :     : mCreateObservers(aOther.mCreateObservers)
      61             :     , mReadObservers(aOther.mReadObservers)
      62             :     , mWriteObservers(aOther.mWriteObservers)
      63             :     , mFSyncObservers(aOther.mFSyncObservers)
      64             :     , mStatObservers(aOther.mStatObservers)
      65             :     , mCloseObservers(aOther.mCloseObservers)
      66           0 :     , mStageObservers(aOther.mStageObservers)
      67             :   {
      68           0 :   }
      69             :   // Lists of observers for I/O events.
      70             :   // These are implemented as vectors since they are allowed to survive gecko,
      71             :   // without reporting leaks. This is necessary for the IOInterposer to be used
      72             :   // for late-write checks.
      73             :   std::vector<IOInterposeObserver*>  mCreateObservers;
      74             :   std::vector<IOInterposeObserver*>  mReadObservers;
      75             :   std::vector<IOInterposeObserver*>  mWriteObservers;
      76             :   std::vector<IOInterposeObserver*>  mFSyncObservers;
      77             :   std::vector<IOInterposeObserver*>  mStatObservers;
      78             :   std::vector<IOInterposeObserver*>  mCloseObservers;
      79             :   std::vector<IOInterposeObserver*>  mStageObservers;
      80             : };
      81             : 
      82             : class PerThreadData
      83             : {
      84             : public:
      85          35 :   explicit PerThreadData(bool aIsMainThread = false)
      86          35 :     : mIsMainThread(aIsMainThread)
      87             :     , mIsHandlingObservation(false)
      88          35 :     , mCurrentGeneration(0)
      89             :   {
      90          35 :     MOZ_COUNT_CTOR(PerThreadData);
      91          35 :   }
      92             : 
      93           1 :   ~PerThreadData()
      94           1 :   {
      95           1 :     MOZ_COUNT_DTOR(PerThreadData);
      96           1 :   }
      97             : 
      98        1874 :   void CallObservers(IOInterposeObserver::Observation& aObservation)
      99             :   {
     100             :     // Prevent recursive reporting.
     101        1874 :     if (mIsHandlingObservation) {
     102           0 :       return;
     103             :     }
     104             : 
     105        1874 :     mIsHandlingObservation = true;
     106             :     // Decide which list of observers to inform
     107        1874 :     std::vector<IOInterposeObserver*>* observers = nullptr;
     108        1874 :     switch (aObservation.ObservedOperation()) {
     109             :       case IOInterposeObserver::OpCreateOrOpen:
     110          18 :         observers = &mObserverLists->mCreateObservers;
     111          18 :         break;
     112             :       case IOInterposeObserver::OpRead:
     113         691 :         observers = &mObserverLists->mReadObservers;
     114         691 :         break;
     115             :       case IOInterposeObserver::OpWrite:
     116         143 :         observers = &mObserverLists->mWriteObservers;
     117         143 :         break;
     118             :       case IOInterposeObserver::OpFSync:
     119           7 :         observers = &mObserverLists->mFSyncObservers;
     120           7 :         break;
     121             :       case IOInterposeObserver::OpStat:
     122         416 :         observers = &mObserverLists->mStatObservers;
     123         416 :         break;
     124             :       case IOInterposeObserver::OpClose:
     125         599 :         observers = &mObserverLists->mCloseObservers;
     126         599 :         break;
     127             :       case IOInterposeObserver::OpNextStage:
     128           0 :         observers = &mObserverLists->mStageObservers;
     129           0 :         break;
     130             :       default: {
     131             :         // Invalid IO operation, see documentation comment for
     132             :         // IOInterposer::Report()
     133           0 :         MOZ_ASSERT(false);
     134             :         // Just ignore it in non-debug builds.
     135             :         return;
     136             :       }
     137             :     }
     138        1874 :     MOZ_ASSERT(observers);
     139             : 
     140             :     // Inform observers
     141        3748 :     for (auto i = observers->begin(), e = observers->end(); i != e; ++i) {
     142        1874 :       (*i)->Observe(aObservation);
     143             :     }
     144        1874 :     mIsHandlingObservation = false;
     145             :   }
     146             : 
     147        1874 :   inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; }
     148             : 
     149        1874 :   inline bool IsMainThread() const { return mIsMainThread; }
     150             : 
     151          15 :   inline void SetObserverLists(uint32_t aNewGeneration,
     152             :                                RefPtr<ObserverLists>& aNewLists)
     153             :   {
     154          15 :     mCurrentGeneration = aNewGeneration;
     155          15 :     mObserverLists = aNewLists;
     156          15 :   }
     157             : 
     158           0 :   inline void ClearObserverLists()
     159             :   {
     160           0 :     if (mObserverLists) {
     161           0 :       mCurrentGeneration = 0;
     162           0 :       mObserverLists = nullptr;
     163             :     }
     164           0 :   }
     165             : 
     166             : private:
     167             :   bool                  mIsMainThread;
     168             :   bool                  mIsHandlingObservation;
     169             :   uint32_t              mCurrentGeneration;
     170             :   RefPtr<ObserverLists> mObserverLists;
     171             : };
     172             : 
     173             : class MasterList
     174             : {
     175             : public:
     176           1 :   MasterList()
     177           1 :     : mObservedOperations(IOInterposeObserver::OpNone)
     178           1 :     , mIsEnabled(true)
     179             :   {
     180           1 :     MOZ_COUNT_CTOR(MasterList);
     181           1 :   }
     182             : 
     183           0 :   ~MasterList()
     184           0 :   {
     185           0 :     MOZ_COUNT_DTOR(MasterList);
     186           0 :   }
     187             : 
     188           0 :   inline void Disable() { mIsEnabled = false; }
     189           0 :   inline void Enable() { mIsEnabled = true; }
     190             : 
     191           1 :   void Register(IOInterposeObserver::Operation aOp,
     192             :                 IOInterposeObserver* aObserver)
     193             :   {
     194           2 :     IOInterposer::AutoLock lock(mLock);
     195             : 
     196           1 :     ObserverLists* newLists = nullptr;
     197           1 :     if (mObserverLists) {
     198           0 :       newLists = new ObserverLists(*mObserverLists);
     199             :     } else {
     200           1 :       newLists = new ObserverLists();
     201             :     }
     202             :     // You can register to observe multiple types of observations
     203             :     // but you'll never be registered twice for the same observations.
     204           2 :     if (aOp & IOInterposeObserver::OpCreateOrOpen &&
     205           1 :         !VectorContains(newLists->mCreateObservers, aObserver)) {
     206           1 :       newLists->mCreateObservers.push_back(aObserver);
     207             :     }
     208           2 :     if (aOp & IOInterposeObserver::OpRead &&
     209           1 :         !VectorContains(newLists->mReadObservers, aObserver)) {
     210           1 :       newLists->mReadObservers.push_back(aObserver);
     211             :     }
     212           2 :     if (aOp & IOInterposeObserver::OpWrite &&
     213           1 :         !VectorContains(newLists->mWriteObservers, aObserver)) {
     214           1 :       newLists->mWriteObservers.push_back(aObserver);
     215             :     }
     216           2 :     if (aOp & IOInterposeObserver::OpFSync &&
     217           1 :         !VectorContains(newLists->mFSyncObservers, aObserver)) {
     218           1 :       newLists->mFSyncObservers.push_back(aObserver);
     219             :     }
     220           2 :     if (aOp & IOInterposeObserver::OpStat &&
     221           1 :         !VectorContains(newLists->mStatObservers, aObserver)) {
     222           1 :       newLists->mStatObservers.push_back(aObserver);
     223             :     }
     224           2 :     if (aOp & IOInterposeObserver::OpClose &&
     225           1 :         !VectorContains(newLists->mCloseObservers, aObserver)) {
     226           1 :       newLists->mCloseObservers.push_back(aObserver);
     227             :     }
     228           2 :     if (aOp & IOInterposeObserver::OpNextStage &&
     229           1 :         !VectorContains(newLists->mStageObservers, aObserver)) {
     230           1 :       newLists->mStageObservers.push_back(aObserver);
     231             :     }
     232           1 :     mObserverLists = newLists;
     233           1 :     mObservedOperations =
     234           1 :       (IOInterposeObserver::Operation)(mObservedOperations | aOp);
     235             : 
     236           1 :     mCurrentGeneration++;
     237           1 :   }
     238             : 
     239           0 :   void Unregister(IOInterposeObserver::Operation aOp,
     240             :                   IOInterposeObserver* aObserver)
     241             :   {
     242           0 :     IOInterposer::AutoLock lock(mLock);
     243             : 
     244           0 :     ObserverLists* newLists = nullptr;
     245           0 :     if (mObserverLists) {
     246           0 :       newLists = new ObserverLists(*mObserverLists);
     247             :     } else {
     248           0 :       newLists = new ObserverLists();
     249             :     }
     250             : 
     251           0 :     if (aOp & IOInterposeObserver::OpCreateOrOpen) {
     252           0 :       VectorRemove(newLists->mCreateObservers, aObserver);
     253           0 :       if (newLists->mCreateObservers.empty()) {
     254           0 :         mObservedOperations =
     255           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     256             :                                            ~IOInterposeObserver::OpCreateOrOpen);
     257             :       }
     258             :     }
     259           0 :     if (aOp & IOInterposeObserver::OpRead) {
     260           0 :       VectorRemove(newLists->mReadObservers, aObserver);
     261           0 :       if (newLists->mReadObservers.empty()) {
     262           0 :         mObservedOperations =
     263           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     264             :                                            ~IOInterposeObserver::OpRead);
     265             :       }
     266             :     }
     267           0 :     if (aOp & IOInterposeObserver::OpWrite) {
     268           0 :       VectorRemove(newLists->mWriteObservers, aObserver);
     269           0 :       if (newLists->mWriteObservers.empty()) {
     270           0 :         mObservedOperations =
     271           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     272             :                                            ~IOInterposeObserver::OpWrite);
     273             :       }
     274             :     }
     275           0 :     if (aOp & IOInterposeObserver::OpFSync) {
     276           0 :       VectorRemove(newLists->mFSyncObservers, aObserver);
     277           0 :       if (newLists->mFSyncObservers.empty()) {
     278           0 :         mObservedOperations =
     279           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     280             :                                            ~IOInterposeObserver::OpFSync);
     281             :       }
     282             :     }
     283           0 :     if (aOp & IOInterposeObserver::OpStat) {
     284           0 :       VectorRemove(newLists->mStatObservers, aObserver);
     285           0 :       if (newLists->mStatObservers.empty()) {
     286           0 :         mObservedOperations =
     287           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     288             :                                            ~IOInterposeObserver::OpStat);
     289             :       }
     290             :     }
     291           0 :     if (aOp & IOInterposeObserver::OpClose) {
     292           0 :       VectorRemove(newLists->mCloseObservers, aObserver);
     293           0 :       if (newLists->mCloseObservers.empty()) {
     294           0 :         mObservedOperations =
     295           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     296             :                                            ~IOInterposeObserver::OpClose);
     297             :       }
     298             :     }
     299           0 :     if (aOp & IOInterposeObserver::OpNextStage) {
     300           0 :       VectorRemove(newLists->mStageObservers, aObserver);
     301           0 :       if (newLists->mStageObservers.empty()) {
     302           0 :         mObservedOperations =
     303           0 :           (IOInterposeObserver::Operation)(mObservedOperations &
     304             :                                            ~IOInterposeObserver::OpNextStage);
     305             :       }
     306             :     }
     307           0 :     mObserverLists = newLists;
     308           0 :     mCurrentGeneration++;
     309           0 :   }
     310             : 
     311        1874 :   void Update(PerThreadData& aPtd)
     312             :   {
     313        1874 :     if (mCurrentGeneration == aPtd.GetCurrentGeneration()) {
     314        1859 :       return;
     315             :     }
     316             :     // If the generation counts don't match then we need to update the current
     317             :     // thread's observer list with the new master list.
     318          30 :     IOInterposer::AutoLock lock(mLock);
     319          15 :     aPtd.SetObserverLists(mCurrentGeneration, mObserverLists);
     320             :   }
     321             : 
     322        3752 :   inline bool IsObservedOperation(IOInterposeObserver::Operation aOp)
     323             :   {
     324             :     // The quick reader may observe that no locks are being employed here,
     325             :     // hence the result of the operations is truly undefined. However, most
     326             :     // computers will usually return either true or false, which is good enough.
     327             :     // It is not a problem if we occasionally report more or less IO than is
     328             :     // actually occurring.
     329        3752 :     return mIsEnabled && !!(mObservedOperations & aOp);
     330             :   }
     331             : 
     332             : private:
     333             :   RefPtr<ObserverLists>             mObserverLists;
     334             :   // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked
     335             :   // (We want to monitor IO during shutdown). Furthermore, as we may have to
     336             :   // unregister observers during shutdown an OffTheBooksMutex is not an option
     337             :   // either, as its base calls into sDeadlockDetector which may be nullptr
     338             :   // during shutdown.
     339             :   IOInterposer::Mutex               mLock;
     340             :   // Flags tracking which operations are being observed
     341             :   IOInterposeObserver::Operation    mObservedOperations;
     342             :   // Used for quickly disabling everything by IOInterposer::Disable()
     343             :   Atomic<bool>                      mIsEnabled;
     344             :   // Used to inform threads that the master observer list has changed
     345             :   Atomic<uint32_t>                  mCurrentGeneration;
     346             : };
     347             : 
     348             : // Special observation used by IOInterposer::EnteringNextStage()
     349           0 : class NextStageObservation : public IOInterposeObserver::Observation
     350             : {
     351             : public:
     352           0 :   NextStageObservation()
     353           0 :     : IOInterposeObserver::Observation(IOInterposeObserver::OpNextStage,
     354           0 :                                        "IOInterposer", false)
     355             :   {
     356           0 :     mStart = TimeStamp::Now();
     357           0 :     mEnd = mStart;
     358           0 :   }
     359             : };
     360             : 
     361             : // List of observers registered
     362           3 : static StaticAutoPtr<MasterList> sMasterList;
     363             : static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData;
     364             : static bool sThreadLocalDataInitialized;
     365             : } // namespace
     366             : 
     367        1695 : IOInterposeObserver::Observation::Observation(Operation aOperation,
     368             :                                               const char* aReference,
     369        1695 :                                               bool aShouldReport)
     370             :   : mOperation(aOperation)
     371             :   , mReference(aReference)
     372        1695 :   , mShouldReport(IOInterposer::IsObservedOperation(aOperation) &&
     373        3390 :                   aShouldReport)
     374             : {
     375        1695 :   if (mShouldReport) {
     376        1691 :     mStart = TimeStamp::Now();
     377             :   }
     378        1695 : }
     379             : 
     380         183 : IOInterposeObserver::Observation::Observation(Operation aOperation,
     381             :                                               const TimeStamp& aStart,
     382             :                                               const TimeStamp& aEnd,
     383         183 :                                               const char* aReference)
     384             :   : mOperation(aOperation)
     385             :   , mStart(aStart)
     386             :   , mEnd(aEnd)
     387             :   , mReference(aReference)
     388         183 :   , mShouldReport(false)
     389             : {
     390         183 : }
     391             : 
     392             : const char*
     393           0 : IOInterposeObserver::Observation::ObservedOperationString() const
     394             : {
     395           0 :   switch (mOperation) {
     396             :     case OpCreateOrOpen:
     397           0 :       return "create/open";
     398             :     case OpRead:
     399           0 :       return "read";
     400             :     case OpWrite:
     401           0 :       return "write";
     402             :     case OpFSync:
     403           0 :       return "fsync";
     404             :     case OpStat:
     405           0 :       return "stat";
     406             :     case OpClose:
     407           0 :       return "close";
     408             :     case OpNextStage:
     409           0 :       return "NextStage";
     410             :     default:
     411           0 :       return "unknown";
     412             :   }
     413             : }
     414             : 
     415             : void
     416        1695 : IOInterposeObserver::Observation::Report()
     417             : {
     418        1695 :   if (mShouldReport) {
     419        1691 :     mEnd = TimeStamp::Now();
     420        1691 :     IOInterposer::Report(*this);
     421             :   }
     422        1695 : }
     423             : 
     424             : bool
     425           1 : IOInterposer::Init()
     426             : {
     427             :   // Don't initialize twice...
     428           1 :   if (sMasterList) {
     429           0 :     return true;
     430             :   }
     431           1 :   if (!sThreadLocalData.init()) {
     432           0 :     return false;
     433             :   }
     434           1 :   sThreadLocalDataInitialized = true;
     435           1 :   bool isMainThread = true;
     436           1 :   RegisterCurrentThread(isMainThread);
     437           1 :   sMasterList = new MasterList();
     438             : 
     439           1 :   MainThreadIOLogger::Init();
     440             : 
     441             :   // Now we initialize the various interposers depending on platform
     442           1 :   InitPoisonIOInterposer();
     443             :   // We don't hook NSPR on Windows because PoisonIOInterposer captures a
     444             :   // superset of the former's events.
     445             : #if !defined(XP_WIN)
     446           1 :   InitNSPRIOInterposing();
     447             : #endif
     448           1 :   return true;
     449             : }
     450             : 
     451             : bool
     452        1874 : IOInterposeObserver::IsMainThread()
     453             : {
     454        1874 :   if (!sThreadLocalDataInitialized) {
     455           0 :     return false;
     456             :   }
     457        1874 :   PerThreadData* ptd = sThreadLocalData.get();
     458        1874 :   if (!ptd) {
     459           0 :     return false;
     460             :   }
     461        1874 :   return ptd->IsMainThread();
     462             : }
     463             : 
     464             : void
     465           0 : IOInterposer::Clear()
     466             : {
     467             :   /* Clear() is a no-op on release builds so that we may continue to trap I/O
     468             :      until process termination. In leak-checking builds, we need to shut down
     469             :      IOInterposer so that all references are properly released. */
     470             : #ifdef NS_FREE_PERMANENT_DATA
     471           0 :   UnregisterCurrentThread();
     472           0 :   sMasterList = nullptr;
     473             : #endif
     474           0 : }
     475             : 
     476             : void
     477           0 : IOInterposer::Disable()
     478             : {
     479           0 :   if (!sMasterList) {
     480           0 :     return;
     481             :   }
     482           0 :   sMasterList->Disable();
     483             : }
     484             : 
     485             : void
     486           0 : IOInterposer::Enable()
     487             : {
     488           0 :   if (!sMasterList) {
     489           0 :     return;
     490             :   }
     491           0 :   sMasterList->Enable();
     492             : }
     493             : 
     494             : void
     495        1874 : IOInterposer::Report(IOInterposeObserver::Observation& aObservation)
     496             : {
     497        1874 :   PerThreadData* ptd = sThreadLocalData.get();
     498        1874 :   if (!ptd) {
     499             :     // In this case the current thread is not registered with IOInterposer.
     500             :     // Alternatively we could take the slow path and just lock everything if
     501             :     // we're not registered. That could potentially perform poorly, though.
     502           0 :     return;
     503             :   }
     504             : 
     505        1874 :   if (!sMasterList) {
     506             :     // If there is no longer a master list then we should clear the local one.
     507           0 :     ptd->ClearObserverLists();
     508           0 :     return;
     509             :   }
     510             : 
     511        1874 :   sMasterList->Update(*ptd);
     512             : 
     513             :   // Don't try to report if there's nobody listening.
     514        1874 :   if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) {
     515           0 :     return;
     516             :   }
     517             : 
     518        1874 :   ptd->CallObservers(aObservation);
     519             : }
     520             : 
     521             : bool
     522        3752 : IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp)
     523             : {
     524        3752 :   return sMasterList && sMasterList->IsObservedOperation(aOp);
     525             : }
     526             : 
     527             : void
     528           1 : IOInterposer::Register(IOInterposeObserver::Operation aOp,
     529             :                        IOInterposeObserver* aObserver)
     530             : {
     531           1 :   MOZ_ASSERT(aObserver);
     532           1 :   if (!sMasterList || !aObserver) {
     533           0 :     return;
     534             :   }
     535             : 
     536           1 :   sMasterList->Register(aOp, aObserver);
     537             : }
     538             : 
     539             : void
     540           0 : IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
     541             :                          IOInterposeObserver* aObserver)
     542             : {
     543           0 :   if (!sMasterList) {
     544           0 :     return;
     545             :   }
     546             : 
     547           0 :   sMasterList->Unregister(aOp, aObserver);
     548             : }
     549             : 
     550             : void
     551          67 : IOInterposer::RegisterCurrentThread(bool aIsMainThread)
     552             : {
     553          67 :   if (!sThreadLocalDataInitialized) {
     554          32 :     return;
     555             :   }
     556          35 :   MOZ_ASSERT(!sThreadLocalData.get());
     557          35 :   PerThreadData* curThreadData = new PerThreadData(aIsMainThread);
     558          35 :   sThreadLocalData.set(curThreadData);
     559             : }
     560             : 
     561             : void
     562           1 : IOInterposer::UnregisterCurrentThread()
     563             : {
     564           1 :   if (!sThreadLocalDataInitialized) {
     565           0 :     return;
     566             :   }
     567           1 :   PerThreadData* curThreadData = sThreadLocalData.get();
     568           1 :   MOZ_ASSERT(curThreadData);
     569           1 :   sThreadLocalData.set(nullptr);
     570           1 :   delete curThreadData;
     571             : }
     572             : 
     573             : void
     574           0 : IOInterposer::EnteringNextStage()
     575             : {
     576           0 :   if (!sMasterList) {
     577           0 :     return;
     578             :   }
     579           0 :   NextStageObservation observation;
     580           0 :   Report(observation);
     581             : }
     582             : 

Generated by: LCOV version 1.13