forked from mirrors/gecko-dev
		
	 72c46c09e0
			
		
	
	
		72c46c09e0
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D54020 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* 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 "mozilla/Maybe.h"
 | |
| #include "mozilla/Mutex.h"
 | |
| #include "mozilla/Scoped.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| #include "PoisonIOInterposer.h"
 | |
| 
 | |
| #include "prlock.h"
 | |
| 
 | |
| #ifdef MOZ_REPLACE_MALLOC
 | |
| #  include "replace_malloc_bridge.h"
 | |
| #endif
 | |
| 
 | |
| // Auxiliary method to convert file descriptors to ids
 | |
| #if defined(XP_WIN)
 | |
| #  include <io.h>
 | |
| inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
 | |
|   intptr_t handle = _get_osfhandle(aFd);
 | |
|   if ((handle == -1) || (handle == -2)) {
 | |
|     // -1: Invalid handle. -2: stdin/out/err not associated with a stream.
 | |
|     return mozilla::Nothing();
 | |
|   }
 | |
|   return mozilla::Some(handle);
 | |
| }
 | |
| #else
 | |
| inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
 | |
|   return mozilla::Some<intptr_t>(aFd);
 | |
| }
 | |
| #endif /* if not XP_WIN */
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| struct DebugFilesAutoLockTraits {
 | |
|   typedef PRLock* type;
 | |
|   typedef const PRLock* const_type;
 | |
|   static const_type empty() { return nullptr; }
 | |
|   static void release(type aL) { PR_Unlock(aL); }
 | |
| };
 | |
| 
 | |
| class DebugFilesAutoLock : public mozilla::Scoped<DebugFilesAutoLockTraits> {
 | |
|   static PRLock* Lock;
 | |
| 
 | |
|  public:
 | |
|   static PRLock* getDebugFileIDsLock() {
 | |
|     // On windows this static is not thread safe, but we know that the first
 | |
|     // call is from
 | |
|     // * An early registration of a debug FD or
 | |
|     // * The call to InitWritePoisoning.
 | |
|     // Since the early debug FDs are logs created early in the main thread
 | |
|     // and no writes are trapped before InitWritePoisoning, we are safe.
 | |
|     if (!Lock) {
 | |
|       Lock = PR_NewLock();
 | |
|     }
 | |
| 
 | |
|     // We have to use something lower level than a mutex. If we don't, we
 | |
|     // can get recursive in here when called from logging a call to free.
 | |
|     return Lock;
 | |
|   }
 | |
| 
 | |
|   DebugFilesAutoLock()
 | |
|       : mozilla::Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
 | |
|     PR_Lock(get());
 | |
|   }
 | |
| };
 | |
| 
 | |
| PRLock* DebugFilesAutoLock::Lock;
 | |
| 
 | |
| // The ChunkedList<T> class implements, at the high level, a non-iterable
 | |
| // list of instances of T. Its goal is to be somehow minimalist for the
 | |
| // use case of storing the debug files handles here, with the property of
 | |
| // not requiring a lock to look up whether it contains a specific value.
 | |
| // It is also chunked in blocks of chunk_size bytes so that its
 | |
| // initialization doesn't require a memory allocation, while keeping the
 | |
| // possibility to increase its size as necessary. Note that chunks are
 | |
| // never deallocated (except in the destructor).
 | |
| // All operations are essentially O(N) but N is not expected to be large
 | |
| // enough to matter.
 | |
| template <typename T, size_t chunk_size = 64>
 | |
| class ChunkedList {
 | |
|   struct ListChunk {
 | |
|     static const size_t kLength =
 | |
|         (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic<T>);
 | |
| 
 | |
|     mozilla::Atomic<T> mElements[kLength];
 | |
|     mozilla::UniquePtr<ListChunk> mNext;
 | |
| 
 | |
|     ListChunk() : mNext(nullptr) {}
 | |
|   };
 | |
| 
 | |
|   ListChunk mList;
 | |
|   mozilla::Atomic<size_t> mLength;
 | |
| 
 | |
|  public:
 | |
|   ChunkedList() : mLength(0) {}
 | |
| 
 | |
|   ~ChunkedList() {
 | |
|     // There can be writes happening after this destructor runs, so keep
 | |
|     // the list contents and don't reset mLength. But if there are more
 | |
|     // elements left than the first chunk can hold, then all hell breaks
 | |
|     // loose for any write that would happen after that because any extra
 | |
|     // chunk would be deallocated, so just crash in that case.
 | |
|     MOZ_RELEASE_ASSERT(mLength <= ListChunk::kLength);
 | |
|   }
 | |
| 
 | |
|   // Add an element at the end of the last chunk of the list. Create a new
 | |
|   // chunk if there is not enough room.
 | |
|   // This is not thread-safe with another thread calling Add or Remove.
 | |
|   void Add(T aValue) {
 | |
|     ListChunk* list = &mList;
 | |
|     size_t position = mLength;
 | |
|     for (; position >= ListChunk::kLength; position -= ListChunk::kLength) {
 | |
|       if (!list->mNext) {
 | |
|         list->mNext.reset(new ListChunk());
 | |
|       }
 | |
|       list = list->mNext.get();
 | |
|     }
 | |
|     // Use an order of operations that ensures any racing Contains call
 | |
|     // can't be hurt.
 | |
|     list->mElements[position] = aValue;
 | |
|     mLength++;
 | |
|   }
 | |
| 
 | |
|   // Remove an element from the list by replacing it with the last element
 | |
|   // of the list, and then shrinking the list.
 | |
|   // This is not thread-safe with another thread calling Add or Remove.
 | |
|   void Remove(T aValue) {
 | |
|     if (!mLength) {
 | |
|       return;
 | |
|     }
 | |
|     ListChunk* list = &mList;
 | |
|     size_t last = mLength - 1;
 | |
|     do {
 | |
|       size_t position = 0;
 | |
|       // Look for an element matching the given value.
 | |
|       for (; position < ListChunk::kLength; position++) {
 | |
|         if (aValue == list->mElements[position]) {
 | |
|           ListChunk* last_list = list;
 | |
|           // Look for the last element in the list, starting from where we are
 | |
|           // instead of starting over.
 | |
|           for (; last >= ListChunk::kLength; last -= ListChunk::kLength) {
 | |
|             last_list = last_list->mNext.get();
 | |
|           }
 | |
|           // Use an order of operations that ensures any racing Contains call
 | |
|           // can't be hurt.
 | |
|           T value = last_list->mElements[last];
 | |
|           list->mElements[position] = value;
 | |
|           mLength--;
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|       last -= ListChunk::kLength;
 | |
|       list = list->mNext.get();
 | |
|     } while (list);
 | |
|   }
 | |
| 
 | |
|   // Returns whether the list contains the given value. It is meant to be safe
 | |
|   // to use without locking, with the tradeoff of being not entirely accurate
 | |
|   // if another thread adds or removes an element while this function runs.
 | |
|   bool Contains(T aValue) {
 | |
|     ListChunk* list = &mList;
 | |
|     // Fix the range of the lookup to whatever the list length is when the
 | |
|     // function is called.
 | |
|     size_t length = mLength;
 | |
|     do {
 | |
|       size_t list_length = ListChunk::kLength;
 | |
|       list_length = std::min(list_length, length);
 | |
|       for (size_t position = 0; position < list_length; position++) {
 | |
|         if (aValue == list->mElements[position]) {
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|       length -= ListChunk::kLength;
 | |
|       list = list->mNext.get();
 | |
|     } while (list);
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| };
 | |
| 
 | |
| typedef ChunkedList<intptr_t> FdList;
 | |
| 
 | |
| // Return a list used to hold the IDs of the current debug files. On unix
 | |
| // an ID is a file descriptor. On Windows it is a file HANDLE.
 | |
| FdList& getDebugFileIDs() {
 | |
|   static FdList DebugFileIDs;
 | |
|   return DebugFileIDs;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| // Auxiliary Method to test if a file descriptor is registered to be ignored
 | |
| // by the poisoning IO interposer
 | |
| bool IsDebugFile(intptr_t aFileID) {
 | |
|   return getDebugFileIDs().Contains(aFileID);
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | |
| 
 | |
| extern "C" {
 | |
| 
 | |
| void MozillaRegisterDebugHandle(intptr_t aHandle) {
 | |
|   DebugFilesAutoLock lockedScope;
 | |
|   FdList& DebugFileIDs = getDebugFileIDs();
 | |
|   MOZ_ASSERT(!DebugFileIDs.Contains(aHandle));
 | |
|   DebugFileIDs.Add(aHandle);
 | |
| }
 | |
| 
 | |
| void MozillaRegisterDebugFD(int aFd) {
 | |
|   mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
 | |
|   if (!handle.isSome()) {
 | |
|     return;
 | |
|   }
 | |
|   MozillaRegisterDebugHandle(handle.value());
 | |
| }
 | |
| 
 | |
| void MozillaRegisterDebugFILE(FILE* aFile) {
 | |
|   int fd = fileno(aFile);
 | |
|   if (fd == 1 || fd == 2) {
 | |
|     return;
 | |
|   }
 | |
|   MozillaRegisterDebugFD(fd);
 | |
| }
 | |
| 
 | |
| void MozillaUnRegisterDebugHandle(intptr_t aHandle) {
 | |
|   DebugFilesAutoLock lockedScope;
 | |
|   FdList& DebugFileIDs = getDebugFileIDs();
 | |
|   MOZ_ASSERT(DebugFileIDs.Contains(aHandle));
 | |
|   DebugFileIDs.Remove(aHandle);
 | |
| }
 | |
| 
 | |
| void MozillaUnRegisterDebugFD(int aFd) {
 | |
|   mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
 | |
|   if (!handle.isSome()) {
 | |
|     return;
 | |
|   }
 | |
|   MozillaUnRegisterDebugHandle(handle.value());
 | |
| }
 | |
| 
 | |
| void MozillaUnRegisterDebugFILE(FILE* aFile) {
 | |
|   int fd = fileno(aFile);
 | |
|   if (fd == 1 || fd == 2) {
 | |
|     return;
 | |
|   }
 | |
|   fflush(aFile);
 | |
|   MozillaUnRegisterDebugFD(fd);
 | |
| }
 | |
| 
 | |
| }  // extern "C"
 | |
| 
 | |
| #ifdef MOZ_REPLACE_MALLOC
 | |
| void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) {
 | |
|   MozillaRegisterDebugHandle(aHandle);
 | |
| }
 | |
| 
 | |
| void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) {
 | |
|   MozillaUnRegisterDebugHandle(aHandle);
 | |
| }
 | |
| #endif
 |